osdserver-0.1.3/0000775000175000000500000000000011623537360011667 5ustar udosrcosdserver-0.1.3/README.PERL-MODULE0000664000175000000500000001355311071721222014267 0ustar udosrc OSDServer Perl Bindings The OSDServer Perl bindings allow OSDServer access for Perl scripts using an encapsulated object-oriented interface that hides the necessary network communication in a perl module. The examples folder contains the OSDServer.pm module and a ported version of the OSDServer demo as demo-pm.pl. Also, simple helloworld-*.pl samples are available. THIS IS A FIRST DRAFT VERSION, THERE MAY BE BUGS AND THE INTERFACE MAY CHANGE IN FUTURE VERSIONS! THE DOCUMENTATION IS NOT YET COMPLETE! Usage ----- use OSDServer; loads the library my $server = OSDServer->Open($host, $port); my $server = OSDServer->Open($host); my $server = OSDServer->Open(); Open connection. $host defaults to localhost, $port defaults to 2010. Returns server object or undef in case of error. my $server = OSDServer->new(); $server->ConnectServer($host,$port); $server->SendGreeting(); Same as Open(), but in separate steps. $server->Close(); Sends QUIT and closes connection Comparison to network protocol ------------------------------ - Server side objects are named automatically. - Server objects are represented by local Perl objects - Server commands translate to Perl object methods - String quoting happens auto-magically - _FOCUS variable can be accessed by $server->GetFocusMenu(). Global commands --------------- $server->Message($message, %options) Send MESSAGE command. Available %options that can be added: Info => 1 Warn => 1 Error => 1 Queue => 1 Timeout => $timeout Seconds => $seconds Status => 1 StatusClear => 1 Hint: $message must be present for StatusClear option, but will be ignored. Returns key code, "" for timeout and undef for error. See main readme for explanations. $object = $server->NewMenu(...) $object = $server->NewOsdItem(...) $object = $server->NewEditStrItem(...) $object = $server->NewEditIntItem(...) $object = $server->NewEditListItem(...) Object constructors. Return undef if error. See below for parameters. $server->Delete($object) Deleting objects $server->EnterLocal() $server->LeaveLocal() Provided for completeness, though not really necessary. Returns false if error. All objects ----------- $object->EnableEvent(\@events) Enable events, provided as refetrence to string list. Use like this: $object->EnableEvent(["keyOk", "keyRed"]); Returns false if error. $object->Delete() Deleting objects. Returns false if error. Menu objects ------------ $menu = $server->NewMenu($title) Create new menu. Returns undef if error. $menu->Add($object) Add menu item. Return false if error. $object = $menu->AddNewOsdItem(...) $object = $menu->AddNewEditStrItem(...) $object = $menu->AddNewEditIntItem(...) $object = $menu->AddNewEditListItem(...) Integrated 'adding' constructors. see below for parameters. Return object or undef if error. $menu->AddSubMenu($menu2) Add sub menu to menu. Returns false if error. $menu->SetColorKeyText(%options) Set color buttons. Use like this: $menu->SetColorKeyText(Red => "text", Green => "text"); Returns false if error. $menu->SetColumns(@cols) Set columns. Use like this with up to 5 columns: $menu->SetColumns(10, 20, 30) Returns false if error. $menu->SetCurrent($index) Select menu item by number Returns false if error. ($itemno, $item, $itemid) = $menu->GetCurrent() Get current item. $itemno is the number, $item is the Perl object or "" if not a Perl object, $itemid the server side object name. Returns undef if error. $menu->Show() Show menu. Returns false if error. $menu->SendState($state) Send state. Use like this: $menu->SendState("osEnd"); Returns false if error. ($item, $itemid, $event) = $menu->SleepEvent(%options) Sleep for event. $item is the Perl object or "" if not a Perl object, $itemid the server side object name, $event the occured event. Returns undef if error. Allowed options: Timeout => $timeout TimeoutMS => $timeoutms All menu items -------------- $menuitem->SetCurrent() Make this menu item the currently selected item of the menu. Returns false if error. OSD items --------- $osditem = $server->NewOsdItem($text, %options) $osditem = $menu->AddNewOsdItem($text, %options) Add OsdItem. Allowed options: UnSelectable => 1 Returns undef if error. $osditem => SetUnSelectable() Make this item un-selectable. Returns false if error. $osditem->SetSelectable() Make this item selectable again. Returns false if error. $osditem->SetText($text) Change the text of this item. Returns false if error. String edit items ----------------- $edititem = $server->NewEditStrItem($description, $value) $edititem = $menu->AddNewEditStrItem($description, $value) Create a new OSD menu edit item. Returns undef if error. $str = $edititem->GetValue() Return the current value of the edit item. Returns undef if error. Integer edit items ------------------ $intitem = $server->NewEditIntItem($description, $value, %options) Create a new OSD menu integer item. Allowed options: Min => $min Max => $max MinString => $minstring MaxString => $maxstring Returns undef if error. $int = $intitem->GetValue() Return the current value of the integer item, always as number. Returns undef if error. List edit items --------------- $listitem = $server->NewEditListItem($description, \@listitems, %options) $listitem = $menu->AddNewEditListItem($description, \@listitems, %options) Create a new OSD menu list item. One of the list of items can be picked. Allowed options: Select => $select SelectName => $selectname Use like this: $listitem = $server->NewEditListItem("description", ["item1", "item2"], SelectName => "item1"); Returns undef if error. $value = $listitem->GetValue(%options) Returns the currently selected list item, either as number (0..n-1), or as text, if Name => 1 is specified as option. Returns undef if error. osdserver-0.1.3/Makefile0000664000175000000500000000447111623535324013333 0ustar udosrc# # Makefile for a Video Disk Recorder plugin # # $Id$ # The official name of this plugin. # This name will be used in the '-P...' option of VDR to load the plugin. # By default the main source file also carries this name. # IMPORTANT: the presence of this macro is important for the Make.config # file. So it must be defined, even if it is not used here! # PLUGIN = osdserver ### The version number of this plugin (taken from the main source file): VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).c | awk '{ print $$6 }' | sed -e 's/[";]//g') ### The C++ compiler and options: CXX ?= g++ CXXFLAGS ?= -g -O3 -Wall -Woverloaded-virtual -Wno-parentheses ### The directory environment: VDRDIR = ../../.. LIBDIR = ../../lib TMPDIR = /tmp ### Make sure that necessary options are included: -include $(VDRDIR)/Make.global ### Allow user defined options to overwrite defaults: -include $(VDRDIR)/Make.config -include Make.config ### The version number of VDR's plugin API (taken from VDR's "config.h"): APIVERSION = $(shell sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDIR)/config.h) ### The name of the distribution archive: ARCHIVE = $(PLUGIN)-$(VERSION) PACKAGE = vdr-$(ARCHIVE) ### Includes and Defines (add further entries here): INCLUDES += -I$(VDRDIR)/include DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' -DPLUGINVERSION='"$(VERSION)"' ### The object files (add further files here): OBJS = $(PLUGIN).o server.o tools.o interpreter.o osdobjectsbase.o osdobjects.o connection.o ### Default _default: all ### Implicit rules: %.o: %.c $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $< ### Dependencies: MAKEDEP = $(CXX) -MM -MG DEPFILE = .dependencies $(DEPFILE): Makefile @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@ -include $(DEPFILE) ### Targets: all: libvdr-$(PLUGIN).so libvdr-$(PLUGIN).so: $(OBJS) $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $(OBJS) -o $@ @cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION) dist: $(I18Npo) clean @-rm -rf $(TMPDIR)/$(ARCHIVE) @mkdir $(TMPDIR)/$(ARCHIVE) @cp -a * $(TMPDIR)/$(ARCHIVE) @tar czf $(PACKAGE).tgz --exclude .svn --exclude CVS -C $(TMPDIR) $(ARCHIVE) @-rm -rf $(TMPDIR)/$(ARCHIVE) @echo Distribution package created as $(PACKAGE).tgz clean: @-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~ $(PODIR)/*.mo $(PODIR)/*.pot osdserver-0.1.3/osdobjects.c0000664000175000000500000004302110757604375014202 0ustar udosrc #include #include "osdobjects.h" #include "osdserver.h" // --------------------- // cOsdServerOsdMenu // --------------------- cOsdServerMenu::cOsdServerMenu(const char *Title) { title=strdup(Title); ColorKeyRed = NULL; ColorKeyGreen = NULL; ColorKeyYellow = NULL; ColorKeyBlue = NULL; for (int i=0; iSubMenu = NULL; if (SubMenu) SubMenu->SuperMenu = NULL; for (cOsdServerMenuItem *item = menuitems.First(); item; item=menuitems.Next(item)) { item->menu = NULL; } if (title) free(title); if (ColorKeyRed) free(ColorKeyRed); if (ColorKeyGreen) free(ColorKeyGreen); if (ColorKeyYellow) free(ColorKeyYellow); if (ColorKeyBlue) free(ColorKeyBlue); } cOsdServerMenu::cPrivateMenu::cPrivateMenu(cOsdServerMenu *parent) : cPrivate(parent) { SetCols(parent->Columns[0], parent->Columns[1], parent->Columns[2], parent->Columns[3], parent->Columns[4]); ColorKeyRed = NULL; ColorKeyGreen = NULL; ColorKeyYellow = NULL; ColorKeyBlue = NULL; SubMenu_Dup = NULL; SetColorKeyText(parent->ColorKeyRed, parent->ColorKeyGreen, parent->ColorKeyYellow, parent->ColorKeyBlue); if (parent->SubMenu) { parent->SubMenu->Update(); AddSubMenu(parent->SubMenu->PrivateMenu()); } } cOsdServerMenu::cPrivateMenu::~cPrivateMenu() { cMutexLock Lock(&LockShared); if (ParentMenu()) ParentMenu()->DetachClose(); if (ColorKeyRed) free(ColorKeyRed); if (ColorKeyGreen) free(ColorKeyGreen); if (ColorKeyYellow) free(ColorKeyYellow); if (ColorKeyBlue) free(ColorKeyBlue); } eOSState cOsdServerMenu::cPrivateMenu::ProcessKey(eKeys Key) { if (!Parent) return osEnd; // We're detached, close this menu eOSState state = cOsdMenu::ProcessKey(Key); if (HasSubMenu()) return state; cMutexLock Lock(&LockShared); if (!ParentMenu() || ParentMenu()->Dirty) return state; if (ParentMenu()->SubMenu) { // We're on !HasSubMenu() - the submenu must have been closed ParentMenu()->SetMenuFocus(true); ParentMenu()->SubMenu->SuperMenu = NULL; ParentMenu()->SubMenu = NULL; } if (ParentMenu()->current != Current()) { ParentMenu()->TriggerItemEvent(cEvent::BlurEvent); ParentMenu()->current = Current(); ParentMenu()->TriggerItemEvent(cEvent::FocusEvent); } if (ParentMenu()->SendState != osUnknown) { state=ParentMenu()->SendState; ParentMenu()->SendState=osUnknown; return state; } if (state==osUnknown) { ParentMenu()->TriggerEvent(Key); } return state; } void cOsdServerMenu::cPrivateMenu::SetColorKeyText(const char *Red, const char *Green, const char *Yellow, const char *Blue) { if (ColorKeyRed) free(ColorKeyRed); if (ColorKeyGreen) free(ColorKeyGreen); if (ColorKeyYellow) free(ColorKeyYellow); if (ColorKeyBlue) free(ColorKeyBlue); ColorKeyRed = Red ? strdup(Red) : NULL; ColorKeyGreen = Green ? strdup(Green) : NULL; ColorKeyYellow = Yellow ? strdup(Yellow) : NULL; ColorKeyBlue = Blue ? strdup(Blue) : NULL; RestoreButtonBar(); } void cOsdServerMenu::cPrivateMenu::RestoreButtonBar() { SetHelp(ColorKeyRed, ColorKeyGreen, ColorKeyYellow, ColorKeyBlue); } void cOsdServerMenu::Update() { if (!Private) { Private=new cPrivateMenu(this); } else if (Dirty) { PrivateMenu()->SetTitle(title); PrivateMenu()->SetColorKeyText(ColorKeyRed, ColorKeyGreen, ColorKeyYellow, ColorKeyBlue); PrivateMenu()->SetCols(Columns[0], Columns[1], Columns[2], Columns[3], Columns[4]); if (SubMenu) { SubMenu->Update(); if (SubMenu->PrivateMenu() != PrivateMenu()->GetSubMenu()) PrivateMenu()->AddSubMenu(SubMenu->PrivateMenu()); } else { if (PrivateMenu()->HasSubMenu()) PrivateMenu()->CloseSubMenu(); } } else { if (SubMenu) SubMenu->Update(); } // Now call Update for all menu items: cOsdItem *last=NULL; for (cOsdServerMenuItem *item = menuitems.First(); item; item=menuitems.Next(item)) { item->ItemUpdate(); // Check ordering of VDR menu items cOsdItem *osditem=item->GetItem(); if (osditem->Prev() == NULL && osditem != PrivateMenu()->First()) { // Not yet listed, probably new PrivateMenu()->Add(osditem,false,last); } else if (osditem->Prev() != last) { // Not listed correctly, move it cOsdMenu *menu=PrivateMenu(); menu->cList::Del(osditem,false); PrivateMenu()->Add(osditem,false,last); } last=osditem; } if (last) last=PrivateMenu()->Next(last); while (last) { // All remaining objects now are stray cOsdItem *next = PrivateMenu()->Next(last); PrivateMenu()->Del(last->Index()); last = next; } if (Dirty) { cOsdServerMenuItem *cur=menuitems.Get(current); if (cur) PrivateMenu()->SetCurrent(cur->GetItem()); current=PrivateMenu()->Current(); // should be the same, just to make sure } SetMenuFocus(SubMenu == NULL); Dirty=false; } void cOsdServerMenu::Detach() { // Call Detach for all menu items too cMutexLock Lock(&LockShared); if (SubMenu) SubMenu->Detach(); for (cOsdServerMenuItem *item = menuitems.First(); item; item=menuitems.Next(item)) { item->ItemDetach(); } cShadowTemplate::Detach(); SetMenuFocus(false); } void cOsdServerMenu::DetachClose() { cMutexLock Lock(&LockShared); if (SubMenu) SubMenu->DetachClose(); if (!Dirty) { SetMenuFocus(false); TriggerEvent(cEvent::CloseEvent); } Detach(); } void cOsdServerMenu::Add(cOsdServerMenuItem *Item, bool Current, cOsdServerMenuItem *After) { cMutexLock Lock(&LockShared); Item->menu=this; menuitems.Add(Item,After); if (Current) current=Item->Index(); Dirty=true; } void cOsdServerMenu::Ins(cOsdServerMenuItem *Item, bool Current, cOsdServerMenuItem *Before) { cMutexLock Lock(&LockShared); Item->menu=this; menuitems.Ins(Item,Before); if (Current) current=Item->Index(); Dirty=true; } void cOsdServerMenu::Del(cOsdServerMenuItem *Item, bool Delete) { cMutexLock Lock(&LockShared); menuitems.Del(Item,Delete); Dirty=true; } cOsdObject* cOsdServerMenu::OpenOsd() { Update(); return PrivateMenu(); } void cOsdServerMenu::Show() { if (SuperMenu) { // Redirect to parent SuperMenu->Show(); return; } cMainThreadLock Lock; Lock.LockMainThread(); if (PrivateMenu()) { // There's already a VDR side object, just call show Update(); PrivateMenu()->Show(); Lock.UnlockMainThread(); return; } Lock.UnlockMainThread(); // Need to open the menu first. Call callback to do it. cPluginOsdServer::Plugin()->CallMainMenuAction(this,5000); } void cOsdServerMenu::GetCurrent(int &cur, cString &curs) { cur = current; cOsdServerMenuItem *o = menuitems.Get(cur); curs = o ? o->Name() : cString(); } void cOsdServerMenu::SetCurrent(int cur) { cMutexLock Lock(&LockShared); current=cur; Dirty=true; } void cOsdServerMenu::TriggerItemEvent(cEvent ev) { cOsdServerMenuItem *item = menuitems.Get(current); if (item) TriggerEvent(item, ev); } void cOsdServerMenu::SetColorKeyRed(const char *Red) { cMutexLock Lock(&LockShared); if (ColorKeyRed) free(ColorKeyRed); ColorKeyRed = Red ? strdup(Red) : NULL; Dirty=true; } void cOsdServerMenu::SetColorKeyGreen(const char *Green) { cMutexLock Lock(&LockShared); if (ColorKeyGreen) free(ColorKeyGreen); ColorKeyGreen = Green ? strdup(Green) : NULL; Dirty=true; } void cOsdServerMenu::SetColorKeyYellow(const char *Yellow) { cMutexLock Lock(&LockShared); if (ColorKeyYellow) free(ColorKeyYellow); ColorKeyYellow = Yellow ? strdup(Yellow) : NULL; Dirty=true; } void cOsdServerMenu::SetColorKeyBlue(const char *Blue) { cMutexLock Lock(&LockShared); if (ColorKeyBlue) free(ColorKeyBlue); ColorKeyBlue = Blue ? strdup(Blue) : NULL; Dirty=true; } void cOsdServerMenu::AddSubMenu(cOsdServerMenu *Menu) { cMutexLock Lock(&LockShared); SubMenu = Menu; SubMenu->SuperMenu = this; SetMenuFocus(false); Dirty = true; } void cOsdServerMenu::SetColumn(int number, int value) { cMutexLock Lock(&LockShared); if (number>=0 && numberPollEvent(Event)) return true; } if (!eventQueue.empty()) { Event = eventQueue.front(); eventQueue.pop(); return true; } return false; } // --------------------- // cOsdServerOsdItem // --------------------- cOsdServerOsdItem::cOsdServerOsdItem(const char *Text, bool Selectable) { text=strdup(Text); selectable=Selectable; } cOsdServerOsdItem::~cOsdServerOsdItem() { cMutexLock Lock(&LockShared); Detach(); if (GetMenu()) GetMenu()->Del(this,false); if (text) free(text); } cOsdServerOsdItem::cPrivateOsdItem::cPrivateOsdItem(cOsdServerOsdItem *parent) : cPrivate(parent) { cOsdItem::SetText(parent->text); cOsdItem::SetSelectable(parent->selectable); } eOSState cOsdServerOsdItem::cPrivateOsdItem::ProcessKey(eKeys Key) { eOSState state = cOsdItem::ProcessKey(Key); cMutexLock Lock(&LockShared); if (state==osUnknown && ParentOsdItem() && !ParentOsdItem()->Dirty && ParentOsdItem()->GetMenu()) { ParentOsdItem()->GetMenu()->TriggerEvent(ParentOsdItem(),Key); } return state; } void cOsdServerOsdItem::Update() { if (!Private) { Private=new cPrivateOsdItem(this); } else if (Dirty) { PrivateOsdItem()->SetText(text); PrivateOsdItem()->SetSelectable(selectable); } Dirty=false; } void cOsdServerOsdItem::SetSelectable(bool Selectable) { cMutexLock Lock(&LockShared); selectable = Selectable; Dirty = true; } void cOsdServerOsdItem::SetText(const char *NewText) { cMutexLock Lock(&LockShared); free(text); text = strdup(NewText); Dirty = true; } // --------------------------- // cOsdServerMenuEditStrItem // --------------------------- cOsdServerMenuEditStrItem::cOsdServerMenuEditStrItem(const char *Name, const char *Value, int Length) { name = strdup(Name); value = (char*)malloc(Length+1); length = Length; strn0cpy(value, Value, length); } cOsdServerMenuEditStrItem::~cOsdServerMenuEditStrItem() { cMutexLock Lock(&LockShared); Detach(); if (GetMenu()) GetMenu()->Del(this,false); if (name) free(name); if (value) free(value); } cOsdServerMenuEditStrItem::cPrivateMenuEditStrItem::cPrivateMenuEditStrItem(cOsdServerMenuEditStrItem *parent) : cPrivate(parent) { // ParentMenuEditStrItem()->value now belongs to this object, see explicit template specialization value = ParentMenuEditStrItem()->value; ParentMenuEditStrItem()->value = (char*)malloc(ParentMenuEditStrItem()->length+1); strn0cpy(ParentMenuEditStrItem()->value, value, ParentMenuEditStrItem()->length); InEdit = false; } cOsdServerMenuEditStrItem::cPrivateMenuEditStrItem::~cPrivateMenuEditStrItem() { if (Parent) Parent->Detach(); if (value) free(value); } eOSState cOsdServerMenuEditStrItem::cPrivateMenuEditStrItem::ProcessKey(eKeys Key) { eOSState state = cMenuEditStrItem::ProcessKey(Key); cMutexLock Lock(&LockShared); if (InEdit && !InEditMode() && ParentMenuEditStrItem() && !ParentMenuEditStrItem()->Dirty) { // Left edit mode. Copy value strn0cpy(ParentMenuEditStrItem()->value, value, ParentMenuEditStrItem()->length); // Re-set button bar ParentMenuEditStrItem()->RestoreButtonBar(); // Notify ParentMenuEditStrItem()->TriggerEvent(cEvent::EditEvent); InEdit = false; } if (InEditMode()) InEdit = true; if (state==osUnknown && ParentMenuEditStrItem() && !ParentMenuEditStrItem()->Dirty && ParentMenuEditStrItem()->GetMenu()) { ParentMenuEditStrItem()->GetMenu()->TriggerEvent(ParentMenuEditStrItem(),Key); } return state; } void cOsdServerMenuEditStrItem::Update() { if (!Private) { Private=new cPrivateMenuEditStrItem(this); } else if (Dirty) { } Dirty=false; } char* cOsdServerMenuEditStrItem::GetValue() { cMutexLock Lock(&LockShared); return strdup(value); } // --------------------------- // cOsdServerMenuEditIntItem // --------------------------- cOsdServerMenuEditIntItem::cOsdServerMenuEditIntItem(const char *Name, int Value, int Min, int Max, const char *MinString, const char *MaxString) { name = strdup(Name); value = Value; min = Min; max = Max; minString = MinString ? strdup(MinString) : NULL; maxString = MaxString ? strdup(MaxString) : NULL; } cOsdServerMenuEditIntItem::~cOsdServerMenuEditIntItem() { cMutexLock Lock(&LockShared); Detach(); if (GetMenu()) GetMenu()->Del(this,false); if (name) free(name); if (minString) free(minString); if (maxString) free(maxString); } cOsdServerMenuEditIntItem::cPrivateMenuEditIntItem::cPrivateMenuEditIntItem(cOsdServerMenuEditIntItem *parent) : cPrivate(parent) { // Replace temporary values of explicit template specialization constructor value = ParentMenuEditIntItem()->value; cMenuEditIntItem::value = &value; minString = ParentMenuEditIntItem()->minString ? strdup(ParentMenuEditIntItem()->minString) : NULL; maxString = ParentMenuEditIntItem()->maxString ? strdup(ParentMenuEditIntItem()->maxString) : NULL; } eOSState cOsdServerMenuEditIntItem::cPrivateMenuEditIntItem::ProcessKey(eKeys Key) { eOSState state = cMenuEditIntItem::ProcessKey(Key); cMutexLock Lock(&LockShared); if (ParentMenuEditIntItem() && value != ParentMenuEditIntItem()->value && !ParentMenuEditIntItem()->Dirty) { // Change detected. ParentMenuEditIntItem()->value = value; // Notify ParentMenuEditIntItem()->TriggerEvent(cEvent::EditEvent); } if (state==osUnknown && ParentMenuEditIntItem() && !ParentMenuEditIntItem()->Dirty && ParentMenuEditIntItem()->GetMenu()) { ParentMenuEditIntItem()->GetMenu()->TriggerEvent(ParentMenuEditIntItem(),Key); } return state; } void cOsdServerMenuEditIntItem::Update() { if (!Private) { Private=new cPrivateMenuEditIntItem(this); } else if (Dirty) { } Dirty=false; } int cOsdServerMenuEditIntItem::GetValue() { return value; } // ---------------------------- // cOsdServerMenuEditListItem // ---------------------------- cOsdServerMenuEditListItem::cOsdServerMenuEditListItem(const char *Name, int Value, int Max, const char * const *Strings) { name = strdup(Name); value = Value; max = Max; strings = new char*[max]; for (int i=0; iDel(this,false); if (name) free(name); for (int i=0; imax - 1 // Replace temporary values of explicit template specialization constructor value = ParentMenuEditListItem()->value; cMenuEditStraItem::value = &value; strings = ParentMenuEditListItem()->tmp_strings; ParentMenuEditListItem()->tmp_strings = NULL; } cOsdServerMenuEditListItem::cPrivateMenuEditListItem::~cPrivateMenuEditListItem() { if (Parent) Parent->Detach(); for (int i=0; ivalue && !ParentMenuEditListItem()->Dirty) { // Change detected. ParentMenuEditListItem()->value = value; // Notify ParentMenuEditListItem()->TriggerEvent(cEvent::EditEvent); } if (state==osUnknown && ParentMenuEditListItem() && !ParentMenuEditListItem()->Dirty && ParentMenuEditListItem()->GetMenu()) { ParentMenuEditListItem()->GetMenu()->TriggerEvent(ParentMenuEditListItem(),Key); } return state; } void cOsdServerMenuEditListItem::Update() { if (!Private) { tmp_strings = new char*[max]; for (int i=0; i #include #include #include #include #include #include #include "tools.h" #include "osdobjectsbase.h" class cOsdServerMenu : public cShadowObjectTemplate, public cOpenOsd::cCallback { // Shadow of cOsdMenu // Shadow variables of cOsdMenu char *title; char *ColorKeyRed, *ColorKeyGreen, *ColorKeyYellow, *ColorKeyBlue; static const int MaxColumns = 5; int Columns[MaxColumns]; cList menuitems; int current; cOsdServerMenu *SubMenu; cOsdServerMenu *SuperMenu; eOSState SendState; class cPrivateMenu : public cPrivate { char *ColorKeyRed, *ColorKeyGreen, *ColorKeyYellow, *ColorKeyBlue; cPrivateMenu *SubMenu_Dup; public: cPrivateMenu(cOsdServerMenu *parent); virtual ~cPrivateMenu(); cOsdServerMenu* ParentMenu() { return (cOsdServerMenu*)Parent; } virtual eOSState ProcessKey(eKeys Key); // Closes menu if it was detached void SetColorKeyText(const char *Red, const char *Green, const char *Yellow, const char *Blue); void RestoreButtonBar(); eOSState AddSubMenu(cPrivateMenu *SubMenu) { SubMenu_Dup = SubMenu; return cOsdMenu::AddSubMenu(SubMenu); } cPrivateMenu* GetSubMenu() { if (!HasSubMenu()) SubMenu_Dup = NULL; return SubMenu_Dup; } // Make some protected stuff public cOsdMenu::SetTitle; cOsdMenu::SetCurrent; cOsdMenu::SetCols; cOsdMenu::Del; cOsdMenu::HasSubMenu; cOsdMenu::CloseSubMenu; }; friend class cPrivateMenu; friend class cPrivate; cPrivateMenu* PrivateMenu() { return (cPrivateMenu*)Private; } void TriggerItemEvent(cEvent ev); bool isFocusedMenu; void SetMenuFocus(bool focus) { if (!isFocusedMenu && focus) { isFocusedMenu = focus; TriggerEvent(cEvent::FocusEvent); SetFocusObject(); } if (isFocusedMenu && !focus) { isFocusedMenu = focus; TriggerEvent(cEvent::BlurEvent); UnsetFocusObject(); } } public: cOsdServerMenu(const char *Title); virtual ~cOsdServerMenu(); virtual void Update(); virtual void Detach(); void DetachClose(); // Detach and post close events void Add(cOsdServerMenuItem *Item, bool Current = false, cOsdServerMenuItem *After = NULL); void Ins(cOsdServerMenuItem *Item, bool Current = false, cOsdServerMenuItem *Before = NULL); void Del(cOsdServerMenuItem *Item, bool Delete = true); virtual cOsdObject* OpenOsd(); // Callback for opening the OSD from main thread void Show(); // Calls cOsdMenu::Show() or creates the cOsdMenu object and displays it. Does the necessary thread syncing. // For a sub menu, redirects to the parent menu void GetCurrent(int &cur, cString &curs); void SetCurrent(int cur); void SetColorKeyRed(const char *Red); void SetColorKeyGreen(const char *Green); void SetColorKeyYellow(const char *Yellow); void SetColorKeyBlue(const char *Blue); void RestoreButtonBar() { if (!IsDetached()) PrivateMenu()->RestoreButtonBar(); } void AddSubMenu(cOsdServerMenu *Menu); void SetColumn(int number, int value); void SendOSState(eOSState State); virtual bool PollEvent(cTriggeredEvent &Event); static const enumClassId classid=clsidMenu; virtual enumClassId ClassId() { return classid; } }; // Specialize construction of cOsdMenu, because the default cPrivate constructor would expect // a cOsdMenu() default constructor. (see explicit template specialization) template<> inline cShadowTemplate::cPrivate::cPrivate(cShadowTemplate *parent) : cOsdMenu(((cOsdServerMenu*)parent)->title), cShadowBase::cPrivateBase(parent) { // nothing } class cOsdServerOsdItem : public cShadowMenuItemTemplate { // Shadow of cOsdItem // Shadow variables of cOsdItem char *text; bool selectable; // These are protected by LockShared mutex, // unless both threads are synchronized anyway. class cPrivateOsdItem : public cPrivate { public: cPrivateOsdItem(cOsdServerOsdItem *parent); virtual ~cPrivateOsdItem() { if (Parent) Parent->Detach(); } virtual eOSState ProcessKey(eKeys Key); cOsdServerOsdItem* ParentOsdItem() { return (cOsdServerOsdItem*)Parent; } }; friend class cPrivateOsdItem; cPrivateOsdItem* PrivateOsdItem() { return (cPrivateOsdItem*)Private; } public: cOsdServerOsdItem(const char *Text, bool Selectable = true); virtual ~cOsdServerOsdItem(); virtual void Update(); void SetSelectable(bool Selectable); void SetText(const char *text); static const enumClassId classid=clsidOsdItem; virtual enumClassId ClassId() { return classid; } }; class cOsdServerMenuEditStrItem : public cShadowMenuItemTemplate { // Shadow of cMenuEditStrItem // Shadow variables of cMenuEditStrItem char *name; char *value; int length; // These are protected by LockShared mutex, // unless both threads are synchronized anyway. class cPrivateMenuEditStrItem : public cPrivate { char *value; bool InEdit; public: cPrivateMenuEditStrItem(cOsdServerMenuEditStrItem *parent); virtual ~cPrivateMenuEditStrItem(); virtual eOSState ProcessKey(eKeys Key); cOsdServerMenuEditStrItem* ParentMenuEditStrItem() { return (cOsdServerMenuEditStrItem*)Parent; } }; friend class cPrivateMenuEditStrItem; cPrivateMenuEditStrItem* PrivateMenuEditStrItem() { return (cPrivateMenuEditStrItem*)Private; } void RestoreButtonBar() { if (GetMenu()) GetMenu()->RestoreButtonBar(); } public: cOsdServerMenuEditStrItem(const char *Name, const char *Value, int length); virtual ~cOsdServerMenuEditStrItem(); virtual void Update(); char* GetValue(); // allocated copy, free()! static const enumClassId classid=clsidMenuEditStrItem; virtual enumClassId ClassId() { return classid; } friend class cPrivate; }; // Specialize construction of cMenuEditStrItem, because the default cPrivate constructor would expect // a cMenuEditStrItem() default constructor. (see explicit template specialization) template<> inline cShadowTemplate::cPrivate::cPrivate(cShadowTemplate *parent) : cMenuEditStrItem( ((cOsdServerMenuEditStrItem*)parent)->name, ((cOsdServerMenuEditStrItem*)parent)->value, ((cOsdServerMenuEditStrItem*)parent)->length, #if VDRVERSNUM < 10511 FileNameChars #else NULL #endif ), cShadowBase::cPrivateBase(parent) { // nothing } class cOsdServerMenuEditIntItem : public cShadowMenuItemTemplate { // Shadow of cMenuEditIntItem // Shadow variables of cMenuEditIntItem char *name; int value; int min,max; char *minString, *maxString; // These are protected by LockShared mutex, // unless both threads are synchronized anyway. class cPrivateMenuEditIntItem : public cPrivate { int value; public: cPrivateMenuEditIntItem(cOsdServerMenuEditIntItem *parent); virtual ~cPrivateMenuEditIntItem() { if (Parent) Parent->Detach(); } virtual eOSState ProcessKey(eKeys Key); cOsdServerMenuEditIntItem* ParentMenuEditIntItem() { return (cOsdServerMenuEditIntItem*)Parent; } }; friend class cPrivateMenuEditIntItem; cPrivateMenuEditIntItem* PrivateMenuEditIntItem() { return (cPrivateMenuEditIntItem*)Private; } public: cOsdServerMenuEditIntItem(const char *Name, int Value, int Min = 0, int Max = INT_MAX, const char *MinString = NULL, const char *MaxString = NULL); virtual ~cOsdServerMenuEditIntItem(); virtual void Update(); int GetValue(); static const enumClassId classid=clsidMenuEditIntItem; virtual enumClassId ClassId() { return classid; } friend class cPrivate; }; // Specialize construction of cMenuEditIntItem, because the default cPrivate constructor would expect // a cMenuEditIntItem() default constructor. (see explicit template specialization) template<> inline cShadowTemplate::cPrivate::cPrivate(cShadowTemplate *parent) : cMenuEditIntItem( ((cOsdServerMenuEditIntItem*)parent)->name, &((cOsdServerMenuEditIntItem*)parent)->value, // temporary, will be replaced by higher constructor ((cOsdServerMenuEditIntItem*)parent)->min, ((cOsdServerMenuEditIntItem*)parent)->max, ((cOsdServerMenuEditIntItem*)parent)->minString, // temporary, will be replaced by higher constructor ((cOsdServerMenuEditIntItem*)parent)->maxString // temporary, will be replaced by higher constructor ), cShadowBase::cPrivateBase(parent) { // nothing } class cOsdServerMenuEditListItem : public cShadowMenuItemTemplate { // Shadow of cMenuEditListItem // Shadow variables of cMenuEditListItem char *name; int value; int max; char **strings; char **tmp_strings; // temporary for object construction only // These are protected by LockShared mutex, // unless both threads are synchronized anyway. class cPrivateMenuEditListItem : public cPrivate { int value; char **strings; public: cPrivateMenuEditListItem(cOsdServerMenuEditListItem *parent); virtual ~cPrivateMenuEditListItem(); virtual eOSState ProcessKey(eKeys Key); cOsdServerMenuEditListItem* ParentMenuEditListItem() { return (cOsdServerMenuEditListItem*)Parent; } }; friend class cPrivateMenuEditListItem; cPrivateMenuEditListItem* PrivateMenuEditListItem() { return (cPrivateMenuEditListItem*)Private; } public: cOsdServerMenuEditListItem(const char *Name, int Value, int Max, const char * const *Strings); virtual ~cOsdServerMenuEditListItem(); virtual void Update(); int GetValue(); char* GetValueName(); // allocated copy, free()! static const enumClassId classid=clsidMenuEditListItem; virtual enumClassId ClassId() { return classid; } friend class cPrivate; }; // Specialize construction of cMenuEditStraItem, because the default cPrivate constructor would expect // a cMenuEditStraItem() default constructor. (see explicit template specialization) template<> inline cShadowTemplate::cPrivate::cPrivate(cShadowTemplate *parent) : cMenuEditStraItem( ((cOsdServerMenuEditListItem*)parent)->name, &((cOsdServerMenuEditListItem*)parent)->value, // temporary, will be replaced by higher constructor ((cOsdServerMenuEditListItem*)parent)->max, ((cOsdServerMenuEditListItem*)parent)->tmp_strings ), cShadowBase::cPrivateBase(parent) { // nothing } #endif osdserver-0.1.3/README0000664000175000000500000003254610701700430012543 0ustar udosrc The OSDServer Plugin Written by: Udo Richter Project's homepage: http://www.udo-richter.de/vdr/osdserver.html http://www.udo-richter.de/vdr/osdserver.en.html See the file COPYING for license information. OSDServer provides VDR OSD access to external programs and scripts through a TCP/IP socket connection, just like an X server does. The connection protocol is designed for easy interpretation by script languages and shell scripts. The examples folder contains a hello world sample in perl and in shell script using netcat. OSDServer by default listens for commands on port 2010 Installing ---------- Just as every other plugin. If you're compiling VDR yourself, check the VDR documentation for details. If you're using a VDR distribution, check the distribution documentation on how to build plugin packages. You can put an osdserverhosts.conf file into your /video/plugins folder (or equivalent), to specify IP addresses that are allowed to connect to osdserver. The format of the file is the same as for VDR's svdrphosts.conf. If the osdserverhosts.conf file is not present, the content of the svdrphosts.conf is automatically used instead. Starting -------- Just as any plugin. See VDR documentation or distribution documentation. Osdserver accepts one command line option, the -p or --port option, to set a listening port for osdserver. The default port number is 2010. The language ------------ OSDServer accepts commands on TCP/IP socket 2010. Each command is one line of text. Command format looks like this: [Varname = ] [Object.] Command [Parameters] Everything is case-insensitive. Some commands return objects, these can be assigned to variable names for later use. Some commands are global, some are object-local. Parameters are evaluated shell-like, separated by whitespace. Whitespace except newlines can be preserved by ''-quotes or ""-quotes. Also, the respective other quote char will be ignored. The parser understands the following escape sequences everywhere: \\ \" \' \r \n \t In other words, the following strings are the same single parameter: "An 29\" display is rockin' cool" 'An 29" display is rockin\' cool' "An 29\" display is rockin\' cool" 'An 29\" display is rockin\' cool' Flags start with -. Flags and parameters can be mixed in any order. Note that "-flag" is the same as -flag. If you need to set a non-flag parameter that matches a known flag, you can use -- as end-of-flags marker. If you want to use locally inserted strings as parameter, you will have to quote the characters ' " \ TAB CR LF, and put either '' or "" around it. And you may want to prepend "--" just for safety. Variables --------- When creating objects, the resulting object can be 'assigned' to a variable name like this: varname = NEW object-type There's no need to declare a variable name, just use it. Valid variable names start with a character from a-z and can continue with a-z,0-9,_ to any length. Each object has exactly one variable name. If no name is assigned, or if the name gets re-used with a different object, the object will automatically have a name _xxx with an unique number xxx. With the ENTERLOCAL and LEAVELOCAL commands, the state of the variable names can be preserved and restored. LEAVELOCAL will delete all local variables, and restores locally overwritten variable names to its original object. Variables starting with _ are special variables. _xxx with a number xxx are automatically named objects (see above), and _FOCUS always refers to the currently visible OSD object (menu or similar, not menu item). This allows for example to call _FOCUS.ADDSUBMENU without knowing the currently visible menu. However this will fail if no menu is visible! Return codes ------------ All reply lines start with a 3-digit decimal number and a whitespace. The first digit is the category: 1xx Debugging messages 2xx End-of-reply messages 3xx Simple data return 4xx Error messages 5xx Quoted text return, single line 6xx Unquoted text return, multiline Each server reply can be identified by its code. Several mixed reply lines are possible. The reply always contains exactly one 2xx line at the end, after which the server will start listening for new commands, except after 202 Good Bye. 3xx lines are usually several space-separated fields of names or numbers. Quoted text mode returns the characters ' " \ TAB CR LF as quoted strings like \' \" \\ \t \r \n. This is very convenient to feed the output back into new commands: Just remove the leading 5xx code, put '' or "" around it, and you can use it as parameter. For safety, you may want to put -- before it. Unquoted text returns everything as it is, just with 6xx before it. If there are line breaks in the text, there will be several 6xx lines in the output. After connecting ---------------- After connecting, OSDServer sends a welcome message like this: 201 Welcome to OSDServer version x.x.x, VDR version x.x.x. The first expected command is "VERSION x.y". This identifies the protocol version, for future changes. Currently, only 0.1 is supported. Later OSDServer versions may decide to not accept the client due to incompatibility or enable some compatibility mode matching your protocol version. After that, any command is accepted. Global commands --------------- These commands are not related to an object. QUIT Guess what. IDLE does nothing. MESSAGE [-info|-warn|-error] -queue [-timeout #] [-seconds #] message MESSAGE [-info|-warn|-error] [-seconds #] message MESSAGE -status message MESSAGE -statusclear Issue OSD message. Defaults to -info messages. With -queue, use VDR message queue system with specified queue timeout. Without -queue, show message immediately. The message is shown the specified number of seconds. With -status, show the message until -statusclear is used again. -info, -warn and -error return a key event, "300 Message keyOk" for example. With -queue, a "301 timeout" is also possible. See also: List of available keys object = NEW object parameters... Create a new object variable. See individual objects for details. See also: menu.ADD object See also: menu.ADDNEW object See also: menu.ADDSUBMENU menu DELETE variable Destroy an previously allocated object. Note: Re-assigning a new object to an old variable does NOT delete the old object! All objects are deleted on disconnect. Deleting a menu also deletes all menu items of that menu. See also: variable.DELETE ENTERLOCAL Enter a new local variable context. All existing variables will be available in the new context, but the old context will be preserved as it is. This allows to re-use variable names and clean up all remains with LEAVELOCAL. See also: LEAVELOCAL LEAVELOCAL Leave the local variable context. All objects that were created in this context will be deleted. Any objects that were overwritten by objects with same name will be restored to their old name. Basically, the variables are just as they were at the ENTERLOCAL command. Exception: Deleting an object that was allocated before ENTERLOCAL will not restore it on LEAVELOCAL. See also: ENTERLOCAL All objects ----------- The following commands can be applied to all object types: object.ENABLEEVENT events... Enables an event on the given OSD menu or menu item. The events can be: close: On menus: Menu got closed edit: On menu items: Item got edited. focus: On menus: Menu got visible, or submenu got closed On menu items: Item got selected blur: On menus: Menu lost focus, or submenu got opened On menu items: Item got de-selected keyXXXXX: On menus: Key got pressed On menu items: Key got pressed on this item Key events will only work for certain keys, like keyOk, keyRed, keyGreen, keyYellow, keyBlue. Keys that are handled by VDR will not cause an event. See also: list of available keys See also: menu.SLEEPEVENT object.DELETE Deletes the object. See also: DELETE object Menu objects ------------ These represent OSD menus and sub-menus. Menus and menu items are not instantly displayed after creation. Instead, all changes are collected until a menu.SHOW command 'pushes' any changes to the VDR OSD, to increase performance and improve visual appearance. After menu.SHOW, the menus and menu items are 'live' until the menu or the menu items are edited by commands again. Editing returns menu and items into 'dirty' state just like new menus and items. Menu.SHOW will make all changes visible again and will bring menu and items back into 'live' mode. Important: 'dirty' objects will forget all actions by the user. If you pick a new current menu item, the old item stays selected until menu.SHOW is called. If however the user moves the cursor just in that moment, the action will be lost. So make sure taht you call menu.SHOW soon after editing anything. menu = NEW MENU Title Create a new OSD menu. (Doesnt display yet) menu.ADD menuitem Add a menu item to the end of the menu. menuitem = menu.ADDNEW object parameters... Convenient shortcut for menuitem = NEW object, followed by menu.ADD menuitem. menu.ADDSUBMENU othermenu Open the specified othermenu as sub-menu of menu, so that the back button will return to menu. menu.SETCOLORKEYTEXT [-red x] [-green x] [-yellow x] [-blue x] menu.SETCOLORKEYTEXT red green yellow blue Set the color button messages of the menu. If set to an empty string, the color button may be hidden by the skin. menu.SETCOLUMNS columns... Set (up to 5) tab-stop columns for the menu. For simple osditem objects, these columns can be reached by inserting \t into the text string. Edit items use the first column to separate the description from the value. menu.SETCURRENT number Set menu item number as currently selected item. 0 is the first item. menu.GETCURRENT Get currently selected menu item. Returns something like "302 4 myitem" if the menu item "myitem" in line 5 is currently selected. If nothing is selected, "302 -1" is returned. menu.SHOW Actually push menu to display. All changes to menus are hold back until SHOW is called. menu.SENDSTATE state Let menu return VDR eOSState. Currently supported: osEnd Close the whole menu tree osBack Return back one menu level osContinue Continue to be open, and do not time-out close the menu. menu.SLEEPEVENT [-timeout #|-timeoutms #] Wait for the next enabled event to happen, for specified time in seconds or miliseconds. Also reports events of the menu items. Menu must be visible for this. Returns "300 object event" or "301 timeout". The returned object is either the menu or the menu item. See also: object.ENABLEEVENT All menu items -------------- menuitem.SETCURRENT Make this menu item the currently selected item of the menu See also: menu.SETCURRENT OSD items --------- OSD items can be used as separators, as text display, and as selectable menu item. menuitem = NEW OSDITEM [-unselectable] text Create a new OSD menu item with text. If -unselectable, the item can't be focused. osditem.SETUNSELECTABLE Make this item un-selectable. osditem.SETSELECTABLE Make this item selectable again. osditem.SETTEXT Change the text of this item. String edit items ----------------- String edit items allow single line text input. edititem = NEW EDITSTRITEM description value Create a new OSD menu edit item edititem.GETVALUE [-quoted] Return the current value of the edit item. Returns either "500 value" with -quoted, or "600 value" without. See also: Return codes Integer edit items ------------------ Integer items allow editable number input. intitem = NEW EDITINTITEM [-min x] [-max x] [-minstring x] [-maxstring x] description value Create a new OSD menu integer item. If -min or -max is specified, limit the range respectively. If -minstring or -maxstring is specified, show these strings instead of the minimum or maximum value. intitem.GETVALUE Return the current value of the integer item, always as number. Returns "500 value". List edit items --------------- List edit items allow to pick one entry in a list of entries. listitem = NEW EDITLISTITEM [-select x | -selectname x] description listitems.. Create a new OSD menu list item. One of the list of items can be picked. The default item can be picked numerically (0..n-1) with -select, or by item text with -selectname. listitem.GETVALUE [-name] [-quoted] Returns the currently selected list item, either as number (0..n-1), or as text, if -name is specified. Returns either "500 value" with -quoted, or "600 value" without. See also: Return codes Available key events -------------------- keyUp keyDown keyMenu keyOk keyBack keyLeft keyRight keyRed keyGreen keyYellow keyBlue key0 key1 key2 key3 key4 key5 key6 key7 key8 key9 keyInfo keyPlay keyPause keyStop keyRecord keyFastFwd keyFastRew keyNext keyPrev keyPower keyChannel+ keyChannel- keyPrevChannel keyVolume+ keyVolume- keyMute keyAudio keySchedule keyChannels keyTimers keyRecordings keySetup keyCommands keyUser1 keyUser2 keyUser3 keyUser4 keyUser5 keyUser6 keyUser7 keyUser8 keyUser9 keyNone For all of the above keys, there are also keyXXXX|Repeat, keyXXXX|Release and keyXXXX|Repeat|Release. Messages may return any of these keys. The event system will only support a few of them, most are handled by VDR automatically. osdserver-0.1.3/osdserver.c0000664000175000000500000000740411623537333014054 0ustar udosrc/* * osdserver.c: A plugin for the Video Disk Recorder * * See the README file for copyright information and how to reach the author. * * $Id$ */ #include #include #include #include "osdserver.h" static const char *VERSION = "0.1.3"; static const char *DESCRIPTION = "Server for remote OSD clients"; cPluginOsdServer::cPluginOsdServer() : OpenOsd(this) { // Initialize any member variables here. // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT! Server=NULL; plugin=this; Port = 2010; } cPluginOsdServer::~cPluginOsdServer() { // Clean up after yourself! if (Server) delete Server; } const char *cPluginOsdServer::Version() { return VERSION; } const char *cPluginOsdServer::Description() { return DESCRIPTION; } const char *cPluginOsdServer::CommandLineHelp() { // Return a string that describes all known command line options. return " -p PORT, --port=PORT Listen on TCP/IP port number\n"; } bool cPluginOsdServer::ProcessArgs(int argc, char *argv[]) { // Read command line static struct option getopt_array[] = { {"port",1,0,'p'}, {0,0,0,0} }; while (1) { int optindex; int c=getopt_long(argc,argv,"p:",getopt_array,&optindex); if (c==-1) break; switch (c) { case 'p': Port = atoi(optarg); break; } } if (optind < argc) { fprintf(stderr, "osdserver: unknown parameter %s\n", argv[optind]); return false; } return true; } bool cPluginOsdServer::Initialize() { // Initialize any background activities the plugin shall perform. cString OsdServerHosts = AddDirectory(cPlugin::ConfigDirectory(), "osdserverhosts.conf"); if (access(OsdServerHosts, F_OK) == 0) Server = new cServer(Port, OsdServerHosts); else Server = new cServer(Port, NULL); return true; } bool cPluginOsdServer::Start() { // Start any background activities the plugin shall perform. Server->Start(); return true; } void cPluginOsdServer::Stop() { Server->Cancel(2); // Stop any background activities the plugin shall perform. } void cPluginOsdServer::Housekeeping() { // Perform any cleanup or other regular tasks. } void cPluginOsdServer::MainThreadHook() { // Perform actions in the context of the main program thread. // WARNING: Use with great care - see PLUGINS.html! OpenOsd.MainThreadHook(); cMainThreadLock::MainThreadHook(); } cString cPluginOsdServer::Active() { // Return a message string if shutdown should be postponed return NULL; } cOsdObject *cPluginOsdServer::MainMenuAction() { // Perform the action when selected from the main VDR menu. cOsdObject *obj=OpenOsd.MainMenuAction(); if (obj) return obj; esyslog("Strange MainMenuAction call"); return NULL; } cMenuSetupPage *cPluginOsdServer::SetupMenu() { // Return a setup menu in case the plugin supports one. return NULL; } bool cPluginOsdServer::SetupParse(const char *Name, const char *Value) { // Parse your own setup parameters and store their values. return false; } bool cPluginOsdServer::Service(const char *Id, void *Data) { // Handle custom service requests from other plugins return false; } const char **cPluginOsdServer::SVDRPHelpPages() { // Return help text for SVDRP commands this plugin implements return NULL; } cString cPluginOsdServer::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode) { // Process SVDRP commands this plugin implements return NULL; } cPluginOsdServer *cPluginOsdServer::plugin=NULL; VDRPLUGINCREATOR(cPluginOsdServer); // Don't touch this! osdserver-0.1.3/osdserver.h0000664000175000000500000000274710700707305014057 0ustar udosrc/* * osdserver.h: A plugin for the Video Disk Recorder * * See the README file for copyright information and how to reach the author. * * $Id$ */ #ifndef __OSDSERVER_OSDSERVER_H #define __OSDSERVER_OSDSERVER_H #include #include "server.h" #include "tools.h" class cPluginOsdServer : public cPlugin { private: // Add any member variables or functions you may need here. cServer *Server; cOpenOsd OpenOsd; static cPluginOsdServer *plugin; int Port; public: cPluginOsdServer(); virtual ~cPluginOsdServer(); virtual const char *Version(); virtual const char *Description(); virtual const char *CommandLineHelp(); virtual bool ProcessArgs(int argc, char *argv[]); virtual bool Initialize(); virtual bool Start(); virtual void Stop(); virtual void Housekeeping(); virtual void MainThreadHook(); virtual cString Active(); virtual const char *MainMenuEntry() { return NULL; } virtual cOsdObject *MainMenuAction(); virtual cMenuSetupPage *SetupMenu(); virtual bool SetupParse(const char *Name, const char *Value); virtual bool Service(const char *Id, void *Data = NULL); virtual const char **SVDRPHelpPages(); virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode); static cPluginOsdServer* Plugin() { return plugin; } bool CallMainMenuAction(cOpenOsd::cCallback *Callback, int timeout=-1) { return OpenOsd.CallMainMenuAction(Callback,timeout); } }; #endif osdserver-0.1.3/osdobjectsbase.c0000664000175000000500000000732210757604302015027 0ustar udosrc #include #include "osdobjectsbase.h" #include "osdserver.h" // -------------------- // cOsdServerObject // -------------------- cMutex cShadowBase::LockShared; int cOsdServerObject::nextId = 0; cOsdServerObject::cOsdServerObject() { context = NULL; id = nextId++; } cOsdServerObject::~cOsdServerObject() { if (context) context->Remove(this); } // --------------------- // cOsdServerContext // --------------------- cOsdServerObject* cServerContext::GetName(const char *Name) { if (!Name) return NULL; if (Name[0] == '_' && (Name[1] < '0' || Name[1] > '9')) { // Reserved name, but not _[0-9]* if (0 == strcasecmp(Name, "_FOCUS")) { return GetFocusObject(); } } ObjectList::iterator i = objects.find(Name); if (i != objects.end()) return i->second; return NULL; } void cServerContext::Add(cOsdServerObject *Object, cString Name) { if (localContext) localContext->Add(Object, Name); if (!(const char*)Name) Name = cString::sprintf("_%i", Object->id); ObjectList::iterator i = objects.find(Name); if (i != objects.end()) { // Already other object of same name. Rename the other. cOsdServerObject *prev = i->second; cString pname = cString::sprintf("_%i", prev->id); objects.erase(i); objects.insert(std::make_pair(pname, prev)); IdList::iterator id = idList.find(prev->id); if (id != idList.end()) idList.erase(id); idList.insert(std::make_pair(prev->id, pname)); } objects.insert(std::make_pair(Name, Object)); idList.insert(std::make_pair(Object->id, Name)); Object->context = this; } void cServerContext::Remove(cOsdServerObject *Object) { if (Object == focusObject) focusObject = NULL; if (localContext) localContext->Remove(Object); IdList::iterator id = idList.find(Object->id); if (id == idList.end()) return; ObjectList::iterator i = objects.find(id->second); // search by name if (i != objects.end() && i->second == Object) { objects.erase(i); } idList.erase(id); } cServerContext* cServerContext::MakeLocalContext() { // Create local context as alias of this one if (localContext) return localContext; localContext = new cServerContext; localContext->objects = objects; localContext->idList = idList; localContext->parentContext = this; return localContext; } void cServerContext::DestroyLocalContext() { if (localContext) { delete localContext; localContext = NULL; } } cOsdServerObject* cServerContext::GetFocusObject() { if (focusObject) return focusObject; if (parentContext) return parentContext->GetFocusObject(); return NULL; } cServerContext::cServerContext() { localContext = NULL; parentContext = NULL; focusObject = NULL; } cServerContext::~cServerContext() { // Avoid the default clear destructor - objects removes itself from list DestroyLocalContext(); while (!objects.empty()) { cOsdServerObject *obj = objects.begin()->second; objects.erase(objects.begin()); if (obj->context == this) delete obj; } if (parentContext) parentContext->localContext = NULL; } // --------------- // cShadowBase // --------------- cShadowBase::cShadowBase() { Dirty=true; } cShadowBase::cPrivateBase::cPrivateBase(cShadowBase *parent) { Parent=parent; } cShadowBase::cPrivateBase::~cPrivateBase() { if (!Parent) return; esyslog("osdserver: hard detaching of OSD VDR object"); Parent->Detach(); } osdserver-0.1.3/osdobjectsbase.h0000664000175000000500000002341510757603764015050 0ustar udosrc#ifndef __OSDSERVER_OSDOBJECTSBASE_H #define __OSDSERVER_OSDOBJECTSBASE_H #include #include #include #include #include #include #include #include "tools.h" class cServerContext; class cOsdServerObject { // Generic base class for named and unnamed objects of the network interface. // These objects live in the network thread // These objects may 'own' a VDR main thread object. // The OsdServerObject holds a copy of the state of the VDR object, and // can push its state to the VDR object, or create it if it doesnt exist yet. // The VDR object can detach if its lifetime ends. // Before deleting this object, always call Detach(). private: int id; // Unique ID identifying any object static int nextId; // Common ID counter cServerContext *context; // The 'owning' context. Deleting this context deletes the object. // The object can be present in more contexts. public: cOsdServerObject(); virtual ~cOsdServerObject(); // IDs to identify certain object instances from base class enum enumClassId { clsidMenu, clsidOsdItem, clsidMenuEditStrItem, clsidMenuEditIntItem, clsidMenuEditListItem }; virtual enumClassId ClassId()=0; virtual bool IsMenuItem() { return false; } // Generic cast function that checks the id. // Use as baseobject->Cast() template T* Cast() { if (ClassId() != T::classid) return NULL; return dynamic_cast(this); } // Context object name or NULL if not named inline cString Name(); class cEvent { public: enum eventType { NullEvent, KeyEvent, CloseEvent, FocusEvent, BlurEvent, EditEvent } type; eKeys key; cEvent() { type = NullEvent; key = kNone; } cEvent(eventType t) { type = t; key = kNone; } cEvent(eKeys k) { type = KeyEvent; key = k; } bool operator<(const cEvent &ev) const { if (type == KeyEvent && ev.type == KeyEvent) return key < ev.key; return type < ev.type; } }; class cTriggeredEvent : public cEvent { public: cOsdServerObject *source; cTriggeredEvent(cOsdServerObject *src, cEvent ev) : cEvent(ev) { source=src; } cTriggeredEvent(cOsdServerObject *src, eKeys k) : cEvent(k) { source=src; } cTriggeredEvent(cOsdServerObject *src, eventType t) : cEvent(t) { source=src; } cTriggeredEvent() { source=NULL; } }; protected: std::set enabledEvents; std::queue eventQueue; // These are protected by LockShared mutex, // unless both threads are synchronized anyway. bool IsEventEnabled(cEvent event) { std::set::iterator i = enabledEvents.find(event); return i != enabledEvents.end(); } public: void TriggerEvent(cOsdServerObject *src, cEvent ev) { if (src->IsEventEnabled(ev)) eventQueue.push(cTriggeredEvent(src,ev)); } void TriggerEvent(cEvent ev) { TriggerEvent(this,ev); } virtual bool EnableEvent(cEvent event) { enabledEvents.insert(event); return true; } virtual bool PollEvent(cTriggeredEvent &Event) { return false; } // Primitive event handling inline void SetFocusObject(); // Set object as focused inline void UnsetFocusObject(); // Set object as focused inline bool IsFocusObject(); // Check whether object is focused friend class cServerContext; }; struct ltcstrcase { bool operator()(const cString &s1, const cString &s2) const { return strcasecmp(s1, s2) < 0; } }; class cServerContext { // Manage a collection of named OsdServerObjects. typedef std::map ObjectList; ObjectList objects; // Maps name to object typedef std::map IdList; IdList idList; // Maps id to name cServerContext *localContext; cServerContext *parentContext; cOsdServerObject *focusObject; public: cOsdServerObject* GetName(const char *Name); // Get object by name cString GetNameById(int id) { // Get object name by id IdList::iterator i = idList.find(id); return (i != idList.end()) ? i->second : cString::sprintf("_%i", id); } void Add(cOsdServerObject *Object, cString Name); // Also sets the name of the object void Remove(cOsdServerObject *Object); // Removes an object from the context cServerContext* MakeLocalContext(); // Create local context as alias of this one cServerContext* GetParentContext() { return parentContext; } // Get parent of this context, or NULL for topmost context void DestroyLocalContext(); // Delete local contexts of this one cOsdServerObject* GetFocusObject(); // Get the currently focused object (not menu items, just menus) cServerContext(); virtual ~cServerContext(); friend class cOsdServerObject; }; // Forwarded inlines of cOsdServerObject inline cString cOsdServerObject::Name() { return context ? context->GetNameById(id) : cString(); } inline void cOsdServerObject::SetFocusObject() { if (context) context->focusObject = this; } inline void cOsdServerObject::UnsetFocusObject() { if (context && context->focusObject == this) context->focusObject = NULL; } inline bool cOsdServerObject::IsFocusObject() { return context ? (this == context->focusObject) : false; } class cOsdServerMenu; class cOsdServerMenuItem : public cListObject, public cOsdServerObject { // Base class for menu items cOsdServerMenu *menu; protected: virtual cOsdItem* GetItem()=0; cOsdServerMenuItem() { menu=NULL; } public: cOsdServerMenu* GetMenu() { return menu; } virtual bool IsMenuItem() { return true; } virtual void ItemUpdate()=0; // Forwarder to cShadowBase if of that class virtual void ItemDetach()=0; // Forwarder to cShadowBase if of that class friend class cOsdServerMenu; // to set menu }; class cShadowBase { // Abstract base class for shadowed objects. Acts as base class for cShadowTemplate protected: bool Dirty; // =true means that this object has changes that are not yet Update()d to // the VDR object. Also means that the VDR object will not upload // local changes, they will be discarded on Update(). // Protected by LockShared. static cMutex LockShared; // Common lock to access certain members from the VDR main thread class cPrivateBase { public: cShadowBase *Parent; // Parent is a pointer to the shadow object, or NULL if detached. // This member is protected by LockShared mutex. cPrivateBase(cShadowBase *parent); // Constructors are always called with synced threads virtual ~cPrivateBase(); // Warning: Higher level destructors need to call Parent->Detach(). // Foreground thread always calls destructor on its own }; public: virtual void Update()=0; // Push object status to VDR object or create VDR object. // Both threads MUST be synchronized before calling this virtual void Detach()=0; // Detach VDR object from this object. Can be called // from any context. virtual bool IsDetached()=0; // Check if object is 'live' cShadowBase(); virtual ~cShadowBase() {}; }; template class cShadowTemplate : public cShadowBase { // VDR object shadow class // This template encapsulates a VDR object as cOsdServerShadow::cPrivate // and provides the shadow cOsdServerObject for it. // cPrivate lives in the VDR main thread, while cOsdServerShadow lives in the network thread. // T needs to have a parameter-less constructor. Make one if you need one. protected: class cPrivate : public T, public cShadowBase::cPrivateBase { public: inline cPrivate(cShadowTemplate *parent) : cShadowBase::cPrivateBase(parent) { }; friend class cShadowTemplate; }; cPrivate *Private; // Pointer to the shadowed VDR object, or NULL if none attached (yet). // Call Upade() to attach one. public: // Constructor and destructor are called from network thread. // Warning: Higher level destructors need to call Detach(). cShadowTemplate() { Private=NULL; } virtual ~cShadowTemplate() { cMutexLock Lock(&LockShared); if (Private) { esyslog("osdserver: hard detaching of OSD shadow object"); Detach(); } } virtual void Detach() { // This Detach() removes the cross-links between cOsdServerShadow and cPrivate. // Detach private VDR object, make it independent cMutexLock Lock(&LockShared); if (!Private) return; // already detached Private->Parent=NULL; Private=NULL; } virtual bool IsDetached() { return Private==NULL; } }; template class cShadowObjectTemplate : public cOsdServerObject, public cShadowTemplate { virtual bool EnableEvent(cEvent event) { cMutexLock Lock(&cShadowBase::LockShared); return cOsdServerObject::EnableEvent(event); } }; template class cShadowMenuItemTemplate : public cOsdServerMenuItem, public cShadowTemplate { public: using cShadowTemplate::Update; using cShadowTemplate::Detach; using cShadowTemplate::Private; virtual void ItemUpdate() { Update(); } virtual void ItemDetach() { Detach(); } virtual cOsdItem* GetItem() { return Private; } virtual bool EnableEvent(cEvent event) { cMutexLock Lock(&cShadowBase::LockShared); return cOsdServerObject::EnableEvent(event); } }; #endif osdserver-0.1.3/tools.c0000664000175000000500000002117611623535550013201 0ustar udosrc #include #include #include #include #include #include #include #include "server.h" // --- cSocket --------------------------------------------------------------- // based on cSocket from VDR svdrp.c cOsdServerSocket::cOsdServerSocket(int Port, int Queue) { port = Port; sock = -1; queue = Queue; } cOsdServerSocket::~cOsdServerSocket() { Close(); } void cOsdServerSocket::Close(void) { if (sock >= 0) { close(sock); sock = -1; } } bool cOsdServerSocket::Open(void) { if (sock < 0) { // create socket: sock = socket(PF_INET, SOCK_STREAM, 0); if (sock < 0) { LOG_ERROR; port = 0; return false; } // allow it to always reuse the same port: int ReUseAddr = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &ReUseAddr, sizeof(ReUseAddr)); // struct sockaddr_in name; name.sin_family = AF_INET; name.sin_port = htons(port); name.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sock, (struct sockaddr *)&name, sizeof(name)) < 0) { LOG_ERROR; Close(); return false; } // make it non-blocking: int oldflags = fcntl(sock, F_GETFL, 0); if (oldflags < 0) { LOG_ERROR; return false; } oldflags |= O_NONBLOCK; if (fcntl(sock, F_SETFL, oldflags) < 0) { LOG_ERROR; return false; } // listen to the socket: if (listen(sock, queue) < 0) { LOG_ERROR; return false; } } return true; } bool cOsdServerSocket::Accepted(in_addr_t Address) { return false; // return SVDRPhosts.Acceptable(Address); } int cOsdServerSocket::Accept(void) { if (Open()) { struct sockaddr_in clientname; uint size = sizeof(clientname); int newsock = accept(sock, (struct sockaddr *)&clientname, &size); if (newsock > 0) { bool accepted = Accepted(clientname.sin_addr.s_addr); if (!accepted) { const char *s = "400 Access denied!\r\n202 Good Bye.\r\n"; if (write(newsock, s, strlen(s)) < 0) LOG_ERROR; close(newsock); newsock = -1; } isyslog("connect from %s, port %hu - %s", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port), accepted ? "accepted" : "DENIED"); } else if (errno != EINTR && errno != EAGAIN) { LOG_ERROR; } return newsock; } return -1; } int cMainThreadLock::Waiters=0; bool cMainThreadLock::WakeupSent=false; cMutex cMainThreadLock::mutex; cCondVar cMainThreadLock::MainThreadLocked; cCondVar cMainThreadLock::MainThreadContinue; cMainThreadLock::cCallbackList cMainThreadLock::Callbacks; //#define DEBUG_LOCKING #ifdef DEBUG_LOCKING tThreadId MainThreadId=cThread::ThreadId(); int LockCount=1; inline bool ThreadHasLock() { return (MainThreadId==cThread::ThreadId()); } inline void CheckThreads(const char *file, unsigned int line) { if (!ThreadHasLock()) printf("ERROR Thread not main (%s,%d)\n", file, line); if (LockCount<=0) printf("ERROR Thread lock %i (%s,%d)\n",LockCount, file, line); } inline void LockMain(const char *file, unsigned int line) { if (MainThreadId==0) MainThreadId=cThread::ThreadId(); LockCount++; CheckThreads(file,line); } inline void UnlockMain(const char *file, unsigned int line) { CheckThreads(file,line); LockCount--; if (!LockCount) MainThreadId=0; } #define CHECK_THREADS CheckThreads(__FILE__,__LINE__) #define THREAD_HAS_LOCK ThreadHasLock() #define LOCK_MAIN LockMain(__FILE__,__LINE__) #define UNLOCK_MAIN UnlockMain(__FILE__,__LINE__) #else // ifdef DEBUG_LOCKING #define CHECK_THREADS #define THREAD_HAS_LOCK true #define LOCK_MAIN #define UNLOCK_MAIN #endif cMainThreadLock::cMainThreadLock() { HasLock=false; } cMainThreadLock::~cMainThreadLock() { UnlockMainThread(); } cMainThreadLock::cCallbackList::~cCallbackList() { while (cCallback *c=First()) Del(c,false); } int cMainThreadLock::CallCallback(cCallback *Callback) { // Request calling the callback from main thread if (cThread::IsMainThread()) { // Identity crisis... // Just short-cut. return Callback->Callback(); } // Get lock for static vars mutex.Lock(); // Add to list of scheduled callbacks Callbacks.Add(Callback); // Let VDR main loop spin SendWakeup(); // Maybe we're in the callback handler, so send a wakeup MainThreadContinue.Broadcast(); // ... wait for the main thread to process the callback // (releases the lock) if (THREAD_HAS_LOCK) { UNLOCK_MAIN; Callback->cond.Wait(mutex); LOCK_MAIN; } else { Callback->cond.Wait(mutex); } // We're done mutex.Unlock(); return Callback->result; } void cMainThreadLock::MainThreadHook() { // Callback function for the VDR main loop // Get lock for static vars CHECK_THREADS; mutex.Lock(); CHECK_THREADS; // Wait() will temporarily release the lock. for (;;) { // Process listed callback requests while (cCallback *c = Callbacks.First()) { Callbacks.Del(c,false); // Call this callback object CHECK_THREADS; c->result=c->Callback(); CHECK_THREADS; // and wake up the initiating thread c->cond.Broadcast(); } // All waiting threads done? if (Waiters<=0) break; // Awake sleeping processes CHECK_THREADS; MainThreadLocked.Broadcast(); // and wait for the last thread to send the wakeup signal // or for a waiting thread that requests a callback UNLOCK_MAIN; MainThreadContinue.Wait(mutex); LOCK_MAIN; // Either new callbacks have been added, or all threads are done. } // exit with break only, so Waiters<=0 - and it will stay that way, since we have lock. WakeupSent=false; CHECK_THREADS; mutex.Unlock(); CHECK_THREADS; } void cMainThreadLock::LockMainThread() { // Lock the main thread sleeping if (cThread::IsMainThread()) { // Identity crisis... // Just short-cut. return; } // Only lock once if (HasLock) return; // Get lock for static vars mutex.Lock(); // Add to the list of waiting threads Waiters++; // Let VDR main loop spin SendWakeup(); // Wait for the notify of the main thread. MainThreadLocked.Wait(mutex); LOCK_MAIN; // Now we have main thread like exclusivity. HasLock=true; } void cMainThreadLock::UnlockMainThread() { // Release the lock of the main thread if (!HasLock) return; HasLock=false; // This thread does not wait any more Waiters--; if (Waiters==0) { // If we're the last, wake up main thread CHECK_THREADS; MainThreadContinue.Broadcast(); CHECK_THREADS; // Note: In worst case, another thread is appended before // the main thread can continue. The main thread handles this. } else { // Other threads are waiting. // Most probably have received a broadcast, and wait for a lock. // But some may be added lately, and are still waiting, so send a wakeup. CHECK_THREADS; MainThreadLocked.Broadcast(); CHECK_THREADS; } // We're done. Next please. UNLOCK_MAIN; mutex.Unlock(); } cOpenOsd::cOpenOsd(cPlugin *plugin) { Plugin=plugin; Callback=NULL; CallTime=0; } bool cOpenOsd::CallMainMenuAction(cCallback *callback, int timeout) { cMutexLock Lock(this); if (Callback) return false; if (!cRemote::CallPlugin(Plugin->Name())) return false; Callback=callback; CallTime=time(NULL); if (timeout>0) { if (!Signal.TimedWait(*this,timeout)) { Callback=NULL; return false; } } else if (timeout==0) { Signal.Wait(*this); } return true; } cOsdObject* cOpenOsd::MainMenuAction() { cMutexLock Lock(this); if (!Callback) return NULL; cOsdObject *obj=Callback->OpenOsd(); Callback=NULL; Signal.Broadcast(); return obj; } void cOpenOsd::MainThreadHook() { if (!Callback) return; // quick exit cMutexLock Lock(this); if (!Callback) return; if (time(NULL)-CallTime <= 3) return; if (cRemote::CallPlugin(Plugin->Name())) { esyslog("Timeout on calling MainMenuAction"); CallTime=time(NULL); } } osdserver-0.1.3/tools.h0000664000175000000500000001240710700705152013173 0ustar udosrc#ifndef __OSDSERVER_TOOLS_H #define __OSDSERVER_TOOLS_H #include #include #include #include class cOsdServerSocket { // based on cSocket from VDR svdrp.c private: int port; int sock; int queue; public: cOsdServerSocket(int Port, int Queue = 1); virtual ~cOsdServerSocket(); bool Open(void); int Accept(void); virtual bool Accepted(in_addr_t Address); void Close(void); operator int() { return sock; } }; class cSelect { int fdmax; fd_set readfds,writefds,exceptfds; timeval timeout; public: void Reset() { fdmax=-1; FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds); timeout.tv_sec=LONG_MAX; timeout.tv_usec=0; } void SetRead(int fd) { if (fd >= 0 && fd < FD_SETSIZE) { FD_SET(fd,&readfds); fdmax=max(fd,fdmax); } } void SetWrite(int fd) { if (fd >= 0 && fd < FD_SETSIZE) { FD_SET(fd,&writefds); fdmax=max(fd,fdmax); } } void SetExcept(int fd) { if (fd >= 0 && fd < FD_SETSIZE) { FD_SET(fd,&exceptfds); fdmax=max(fd,fdmax); } } void ClearRead(int fd) { if (fd >= 0 && fd < FD_SETSIZE) FD_CLR(fd,&readfds); } void ClearWrite(int fd) { if (fd >= 0 && fd < FD_SETSIZE) FD_CLR(fd,&writefds); } void ClearExcept(int fd) { if (fd >= 0 && fd < FD_SETSIZE) FD_CLR(fd,&exceptfds); } bool GetRead(int fd) { return (fd >= 0 && fd < FD_SETSIZE) ? FD_ISSET(fd,&readfds) : false; } bool GetWrite(int fd) { return (fd >= 0 && fd < FD_SETSIZE) ? FD_ISSET(fd,&writefds) : false; } bool GetExcept(int fd) { return (fd >= 0 && fd < FD_SETSIZE) ? FD_ISSET(fd,&exceptfds) : false; } void SetTimeout(long s, long us) { timeout.tv_sec=s; timeout.tv_usec=us; } void SetMinTimeout(long s, long us) { if (s < timeout.tv_sec || (s == timeout.tv_sec && us < timeout.tv_usec)) { timeout.tv_sec=s; timeout.tv_usec=us; } } void SetTimeoutMs(long ms) { SetTimeout(ms/1000,(ms%1000)*1000); } void SetMinTimeoutMs(int ms) { SetMinTimeout(ms/1000,(ms%1000)*1000); } int Select() { return select(fdmax+1,&readfds,&writefds,&exceptfds,(timeout.tv_sec!=LONG_MAX)?(&timeout):(NULL)); } cSelect() { Reset(); } }; class cMainThreadLock { public: class cCallback : public cListObject { cCondVar cond; int result; protected: virtual int Callback()=0; public: int CallCallback() { return cMainThreadLock::CallCallback(this); } friend class cMainThreadLock; }; private: static int Waiters; // # of waiting threads static bool WakeupSent; // Have we sent a keypress to the VDR main loop? static void SendWakeup() { if (WakeupSent) return; cRemote::Put(eKeys(kNone | k_Release)); WakeupSent=true; } static cMutex mutex; // Master mutex for locking all static members // Signals static cCondVar MainThreadLocked; // This signal wakes sleeping threads while the main loop // is locked safely. Thread will be exclusive until it releases the lock. // Main thread won't continue until all threads are done. static cCondVar MainThreadContinue; // This signal wakes the main thread. The main thread will then process // callbacks (under lock) and will also check for new waiting threads // List of callbacks to-do class cCallbackList : public cList { public: virtual ~cCallbackList(); }; static cCallbackList Callbacks; // Non-static: bool HasLock; // Local thread has the main lock static int CallCallback(cCallback *Callback); // implements cCallback::CallCallback(), runs in thread. public: cMainThreadLock(); ~cMainThreadLock(); static void MainThreadHook(); void LockMainThread(); void UnlockMainThread(); friend class cCallback; }; class cOpenOsd : protected cMutex { public: class cCallback { protected: virtual cOsdObject* OpenOsd()=0; public: virtual ~cCallback() {} friend class cOpenOsd; }; private: cPlugin *Plugin; cCallback *Callback; time_t CallTime; cCondVar Signal; public: cOpenOsd(cPlugin *Plugin); bool CallMainMenuAction(cCallback *Callback, int timeout=-1); // timeout<0: Call asynchronously // timeout=0: Wait indefinitely // timeout>0: Wait timeout ms. cOsdObject* MainMenuAction(); void MainThreadHook(); }; static inline char* skipspace(char *p) { if (!p) return NULL; while (*p && isspace(*p)) p++; return p; } static inline bool isword(char c, bool first=false) { if (('a'<=c && c<='z') || ('A'<=c && c<='Z') || c=='_') return true; if ('0'<=c && c<='9') return !first; return false; } static inline char* skipword(char *p) { if (!p || *p==0 || !isword(*p,true)) return NULL; p++; while (*p && isword(*p)) p++; return p; } static inline void despace(char *p) { while (*p && !isspace(*p)) p++; char *q=p; while (*p!=0) { if (!isspace(*p)) *q++=*p; p++; } *q=0; } #endif osdserver-0.1.3/HISTORY0000664000175000000500000000244611623537124012757 0ustar udosrcVDR Plugin 'osdserver' Revision History --------------------------------------- 2011-08-19: Version 0.1.3 - Update Makefile to current defaults - Fix Message command breakage on VDR 1.7.20 - Fix warnings - Fix: EditIntItem GetValue returns 600 code, not 500, thx to e-tobi for the hint 2008-10-04: Version 0.1.2 - Fix tr()-issue with FileNameChars, VDR 1.5.11+ - New: experimental OSDServer perl module bindings - New: Some more samples for the perl module 2008-06-08: Version 0.1.1a - Fix issue with VDR 1.5.15+, previously avalilable as patch 2007-10-07: Version 0.1.1 - Fix stupid bug with menu.GetCurrent 2007-10-06: Version 0.1.0 - EnterLocal and LeaveLocal for local variables - _Focus pseudo variable - Configurable listening port - osdserverhosts.conf - Bug fixes 2007-07-21: Version 0.0.2 - Event system extended to support all keys - Event system extended to events on all OSD objects - Events: blur, focus, edit, close - Quote chars are \r \n \t \" \' \\ - Support single and double quotes - OsdItem: SetSelectable / SetUnSelectable, -unselectable, SetText - OsdMenu: SetColorKeyText, SetColumns - OsdEditStrItem - OsdEditIntItem - OsdEditListItem - Message command returns proper "301 timeout" instead of "300 Message timeout" 2006-11-22: Version 0.0.1 - Milestone 1 - very basic menus and messages osdserver-0.1.3/server.c0000664000175000000500000001015110700705777013343 0ustar udosrc #include #include #include #include #include #include #include #include #include "server.h" cTCPConnection::cTCPConnection() { fd=0; Server=NULL; buffer=NULL; bufferLength=0; bufferFill=0; parsed=0; HasLine=false; } cTCPConnection::~cTCPConnection() { Close(); if (buffer) free(buffer); } bool cTCPConnection::Open(int filedesc, cServer *server) { if (IsConnected()) return false; fd=filedesc; Server=server; // make fd non-blocking: int oldflags = fcntl(fd, F_GETFL, 0); if (oldflags < 0) { LOG_ERROR; Close(); return false; } oldflags |= O_NONBLOCK; if (fcntl(fd, F_SETFL, oldflags) < 0) { LOG_ERROR; Close(); return false; } buffer=(char*)malloc(InitialBufferLength); if (!buffer) { Close(); return false; } bufferLength=InitialBufferLength; return true; } void cTCPConnection::Close() { if (fd>0) close(fd); fd=0; } bool cTCPConnection::Send(const char *s, int length) { if (!IsConnected()) return false; if (length < 0) length = strlen(s); if (safe_write(fd, s, length) < 0) { LOG_ERROR; return false; } return true; } /* Reply-Codes: 1xx - Debugging 2xx - End of reply 3xx - Status messages 4xx - Error codes 5xx - Data return, quoted 6xx - Data return, multi-line */ char* cTCPConnection::ReadLine() { if (HasLine) return buffer; while (parsed0) bufferFill+=len; while (parsedHasOSDServerHosts) return server->OSDServerHosts.Acceptable(Address); else return SVDRPhosts.Acceptable(Address); } void cServer::Action() { if (!socket.Open()) return; while (Running()) { cSelect Selector; Selector.SetTimeoutMs(1000); Selector.SetRead(socket); int fd=socket.Accept(); if (fd>0) { cTCPConnection *c=new cTCPConnection(); if (c && !c->Open(fd,this)) { delete c; c=NULL; } if (c) { cCommandProcessor *p=new cCommandProcessor(c); if (p) { connections.Add(p); } else { delete c; } } } for (cCommandProcessor *p = connections.First(); p; ) { cCommandProcessor *next=connections.Next(p); if (!p->Action(Selector)) connections.Del(p); p=next; } int cnt=Selector.Select(); if (cnt < 0) { if (errno!=EINTR) LOG_ERROR; } } socket.Close(); } cServer::~cServer() { } osdserver-0.1.3/server.h0000664000175000000500000000257610700705265013354 0ustar udosrc#ifndef __OSDSERVER_SERVER_H #define __OSDSERVER_SERVER_H #include #include #include "tools.h" #include "interpreter.h" class cServer; class cTCPConnection : public cConnection { int fd; cServer *Server; static const int InitialBufferLength=256; char *buffer; int bufferLength; int bufferFill; int parsed; bool HasLine; protected: virtual bool Send(const char *s, int length=-1); virtual char* ReadLine(); virtual void DelLine(); virtual void AddSelect(cSelect &Selector); public: cTCPConnection(); virtual ~cTCPConnection(); bool Open(int filedesc, cServer *Server); void Close(); virtual bool IsConnected() { return fd>0; } }; class cServer : public cThread { class cServerSocket : public cOsdServerSocket { cServer *server; public: cServerSocket(cServer *Server, int Port, int Queue = 1) : cOsdServerSocket(Port, Queue), server(Server) { } virtual bool Accepted(in_addr_t Address); }; friend class cServerSocket; cServerSocket socket; cList connections; bool HasOSDServerHosts; cSVDRPhosts OSDServerHosts; protected: virtual void Action(void); public: cServer(int Port, const char *Hosts); void Cancel(int WaitSeconds = 0) { cThread::Cancel(WaitSeconds); } virtual ~cServer(); }; #endif osdserver-0.1.3/examples/0002775000175000000500000000000011623537124013505 5ustar udosrcosdserver-0.1.3/examples/demo.pl0000775000175000000500000001673611071705014014774 0ustar udosrc#!/usr/bin/perl use strict; use warnings; use Socket; # Convenient return values my $true = 1; my $false = 0; # Some generic stuff for handling a connection sub NetError(*) { # Network error my ($msg)=@_; print STDERR "$msg\n"; exit 1; } sub error() { # Fatal error. Send quit command, close socket and terminate. SendCmd("Quit"); close SOCK; exit 1; } sub ConnectServer(**) { # Connect to the OSDServer my ($host,$port)=@_; my $iaddr = inet_aton($host) || NetError("no host: $host"); my $paddr = sockaddr_in($port, $iaddr); my $proto = getprotobyname('tcp'); socket(SOCK, PF_INET, SOCK_STREAM, $proto) || NetError("socket: $!"); connect(SOCK, $paddr) || NetError("connect: $!"); select(SOCK); $| = 1; select(STDOUT); } my @reply2xx; my @reply3xx; my @reply4xx; my @reply5xx; my @reply6xx; sub ReadReply() { # Read a complete server reply until 2xx return code, # and store replies in each category by number @reply2xx=undef; @reply3xx=undef; @reply4xx=undef; @reply5xx=undef; @reply6xx=undef; while () { chomp; $_ =~ s/\r$//; print "< $_\n"; # screen echo if ($_ =~ /^2/) { @reply2xx=split(/\s+/,$_,2); last; } if ($_ =~ /^3/) { @reply3xx=split(/\s+/,$_); } if ($_ =~ /^4/) { @reply4xx=split(/\s+/,$_,2); } if ($_ =~ /^5/) { @reply5xx=split(/\s+/,$_,2); } if ($_ =~ /^6/) { my @tmpreply6xx=split(/\s+/,$_,2); if (defined $reply6xx[0] && $reply6xx[0] == $tmpreply6xx[0]) { $tmpreply6xx[1] = "$tmpreply6xx[1]\n$reply6xx[1]"; } @reply6xx = @tmpreply6xx; } } return $false if defined $reply4xx[0]; return $true; } sub SendCmd(*) { # Send a command and read the reply my ($cmd)=@_; print "> $cmd\n"; # screen echo print SOCK "$cmd\n"; # network send return ReadReply; } sub IsEvent(**) { # Helper to check reply for a certain event my ($obj,$ev) = @_; return $false if !defined $reply3xx[0]; return $false if $reply3xx[0] != 300; return $false if $reply3xx[1] ne $obj; return $false if $reply3xx[2] ne $ev; return $true; } sub QuoteString(*) { # Quote arbitrary string for use in '' and "" my ($str)=@_; $str =~ s/\\/\\\\/g; $str =~ s/'/\\'/g; $str =~ s/"/\\"/g; $str =~ s/\r/\\r/g; $str =~ s/\n/\\n/g; $str =~ s/\t/\\t/g; return $str; } sub UnquoteString(*) { # Unquote string my ($str)=@_; $str =~ s/\\'/'/g; $str =~ s/\\"/"/g; $str =~ s/\\r/\r/g; $str =~ s/\\n/\n/g; $str =~ s/\\t/\t/g; $str =~ s/\\\\/\\/g; return $str; } # Enough generic stuff, now for some hello world my $who="OSDServer"; my $what="cool"; sub EditUserComment() { SendCmd "enterlocal" or return $false; # Preserve global variable space, so we can re-use 'menu' SendCmd "menu=New Menu 'Edit User comment'" or return $false; SendCmd "menu.SetColumns 15" or return $false; SendCmd "menu.EnableEvent keyOk close" or return $false; SendCmd "menu.AddNew OsdItem -unselectable '--- Who do you want to comment? --------------------'" or return $false; SendCmd "who=menu.AddNew EditListItem Who OSDServer VDR Linux Windows Klaus Bill -SelectName '$who'" or return $false; SendCmd "who.SetCurrent" or return $false; SendCmd "menu.AddNew OsdItem -unselectable '--- And how do you like it? --------------------'" or return $false; SendCmd "what=menu.AddNew EditStrItem 'It is' '$what'" or return $false; SendCmd "_focus.addsubmenu menu" or return $false; SendCmd "menu.show" or return $false; while (1) { SendCmd "menu.SleepEvent" or return $false; if (IsEvent("menu","keyOk")) { SendCmd "who.GetValue -name -quoted" or return $false; if (!defined $reply5xx[0] || $reply5xx[0] != 500) { return $false; } $who = $reply5xx[1]; SendCmd "what.GetValue -quoted" or return $false; if (!defined $reply5xx[0] || $reply5xx[0] != 500) { return $false; } $what = $reply5xx[1]; SendCmd "menu.SendState osBack" or return $false; SendCmd "delete menu" or return $false; SendCmd "leavelocal" or return $false; return $true; } if (IsEvent("menu","close")) { SendCmd "delete menu" or return $false; SendCmd "leavelocal" or return $false; return $true; } } } sub HelloWorld() { SendCmd "menu=New Menu 'Hello world'" or return $false; SendCmd "menu.SetColorKeyText -blue 'Help'" or return $false; SendCmd "menu.SetColumns 15" or return $false; SendCmd "menu.EnableEvent keyBlue close" or return $false; SendCmd "menu.AddNew OsdItem -unselectable '--- What do you think? --------------------'" or return $false; SendCmd "opt1=menu.AddNew OsdItem 'This is awesome!'" or return $false; SendCmd "opt1.SetCurrent" or return $false; SendCmd "opt1.EnableEvent keyOk" or return $false; SendCmd "opt2=menu.AddNew OsdItem 'You ain\\'t seen nothing yet'" or return $false; SendCmd "opt2.EnableEvent keyOk" or return $false; SendCmd "opt3=menu.AddNew OsdItem 'All your OSD are belong to us'" or return $false; SendCmd "opt3.EnableEvent keyOk" or return $false; SendCmd "opt4=menu.AddNew OsdItem 'User comment: $who is $what'" or return $false; SendCmd "opt4.EnableEvent keyOk keyRed focus blur" or return $false; SendCmd "menu.Show" or return $false; while (1) { SendCmd "menu.SleepEvent" or return $false; if (IsEvent("menu","close")) { SendCmd "Message 'You did not pick anything.'" or return $false; return $true; } if (IsEvent("opt1","keyOk")) { SendCmd "Message 'It is!'" or return $false; SendCmd "menu.SendState osEnd" or return $false; return $true; } elsif (IsEvent("opt2","keyOk")) { SendCmd "Message 'B-B-B-Baybee!'" or return $false; SendCmd "menu.SendState osEnd" or return $false; return $true; } elsif (IsEvent("opt3","keyOk")) { SendCmd "Message 'You are on the way to display!'" or return $false; SendCmd "menu.SendState osEnd" or return $false; return $true; } elsif (IsEvent("opt4","keyOk")) { SendCmd "Message '$who is $what'" or return $false; SendCmd "menu.SendState osEnd" or return $false; return $true; } elsif (IsEvent("opt4","focus")) { SendCmd "menu.SetColorKeyText -red edit" or return $false; SendCmd "menu.Show" or return $false; } elsif (IsEvent("opt4","blur")) { SendCmd "menu.SetColorKeyText -red ''" or return $false; SendCmd "menu.Show" or return $false; } elsif (IsEvent("opt4","keyRed")) { EditUserComment or return $false; SendCmd "opt4.SetText 'User comment: $who is $what'" or return $false; SendCmd "menu.Show" or return $false; } if (IsEvent("menu","keyBlue")) { SendCmd "Message 'Pick one or leave with back'" or return $false; } } } ############### # Main Code # ############### ConnectServer localhost,2010; # Connect to the server process ReadReply; # Read server welcome SendCmd "Version 0.1" or error; # Identify to server with protocol version# HelloWorld or error; # Do helloworld SendCmd "Quit"; # ... and good bye close(SOCK); # close socket osdserver-0.1.3/examples/demo.sh0000775000175000000500000001717611071705014014772 0ustar udosrc#!/bin/bash # # HelloWorld for OSDServer on localhost # # convenient return values true=0 false=1 # Some generic stuff for handling a connection function error() { # Fatal error. Send quit command, close FIFO and terminate netcat [ "${reply2xx[0]}" != 202 ] && SendCmd Quit exec 3>&- exec 4>&- kill $pid exit 1 } function ConnectServer() { # Connect to the OSDServer # Set up temporary fifo and open as file descriptor 3 and 4 mkfifo --mode=700 /tmp/pipe-in$$ /tmp/pipe-out$$ exec 3<> /tmp/pipe-in$$ exec 4<> /tmp/pipe-out$$ rm /tmp/pipe-in$$ /tmp/pipe-out$$ # Connect to server using the fifo { netcat $1 $2 # In case of connection loss: echo 499 disconnected echo 202 Good Bye. } <&3 >&4 & pid=$! # Sending to the server: use >&3 # Receive from the server: use <&4 } function ReadReply() { # Read a complete server reply until 2xx return code, # and store replies in each category by number reply2xx=() reply3xx=() reply4xx=() reply5xx=() reply6xx=() while read -r code line <&4 ; do echo "< $code $line" # screen echo case $code in 2*) IFS=$' \t\n\r' reply2xx=($code "$line") ;; 3*) IFS=$' \t\n\r' reply3xx=($code $line) ;; 4*) IFS=$' \t\n\r' reply4xx=($code "$line") ;; 5*) IFS=$' \t\n\r' reply5xx=($code "$line") ;; 6*) IFS=$' \t\n\r' reply6xx=($code "$line") ;; esac [ -n "${reply2xx[0]}" ] && break done [ -n "${reply4xx[0]}" ] && return $false return $true } function SendCmd() { # Send a command and read the reply echo "> $*" # screen echo echo "$*" >&3 # network send ReadReply } function IsEvent() { # Helper to check reply for a certain event [ "${reply3xx[0]}" != 300 ] && return $false [ "${reply3xx[1]}" != "$1" ] && return $false [ "${reply3xx[2]}" != "$2" ] && return $false return $true } function QuoteString() { # Quote arbitrary string for use in '' and "" local str="${!1}" str="${str//'\'/\\\\}" str="${str//'\\'/\\\\}" # work around bash bug: double quoted '\' str="${str//\'/$'\\\''}" # This is bogus, anyone knows something better to replace ' by \' ? str="${str//\"/\\\"}" str="${str//$'\r'/\\r}" str="${str//$'\n'/\\n}" str="${str//$'\t'/\\t}" eval "$1=\$str" } function UnquoteString() { # Unquote string local str="${!1}" str="${str//\\r/$'\r'}" str="${str//\\n/$'\n'}" str="${str//\\t/$'\t'}" str="${str//\\\"/\"}" str="${str//\\\'/\'}" str="${str//\\\\/\\}" eval "$1=\$str" } # Enough generic stuff, now for some hello world who="OSDServer" what="cool" function EditUserComment() { SendCmd "enterlocal" || return $false # Preserve global variable space, so we can re-use 'menu' SendCmd "menu=New Menu 'Edit User comment'" || return $false SendCmd "menu.SetColumns 15" || return $false SendCmd "menu.EnableEvent keyOk close" || return $false SendCmd "menu.AddNew OsdItem -unselectable '--- Who do you want to comment? --------------------'" || return $false SendCmd "who=menu.AddNew EditListItem Who OSDServer VDR Linux Windows Klaus Bill -SelectName '$who'" || return $false SendCmd "who.SetCurrent" || return $false SendCmd "menu.AddNew OsdItem -unselectable '--- And how do you like it? --------------------'" || return $false SendCmd "what=menu.AddNew EditStrItem 'It is' '$what'" || return $false SendCmd "_focus.addsubmenu menu" || return $false SendCmd "menu.show" || return $false while true; do SendCmd "menu.SleepEvent" || return $false if IsEvent menu keyOk ; then SendCmd "who.GetValue -name -quoted" || return $false [ "${reply5xx[0]}" != 500 ] && return $false who="${reply5xx[1]}" SendCmd "what.GetValue -quoted" || return $false [ "${reply5xx[0]}" != 500 ] && return $false what="${reply5xx[1]}" SendCmd "menu.SendState osBack" || return $false SendCmd "delete menu" || return $false SendCmd "leavelocal" || return $false return $true fi if IsEvent menu close ; then SendCmd "delete menu" || return $false SendCmd "leavelocal" || return $false return $true fi done } function HelloWorld() { SendCmd "menu=New Menu 'Hello world'" || return $false SendCmd "menu.SetColorKeyText -blue 'Help'" || return $false SendCmd "menu.SetColumns 15" || return $false SendCmd "menu.EnableEvent keyBlue close" || return $false SendCmd "lead=menu.AddNew OsdItem -unselectable '--- What do you think? --------------------'" || return $false SendCmd "opt1=menu.AddNew OsdItem 'This is awesome!'" || return $false SendCmd "opt1.SetCurrent" || return $false SendCmd "opt1.EnableEvent keyOk" || return $false SendCmd "opt2=menu.AddNew OsdItem 'You ain\\'t seen nothing yet'" || return $false SendCmd "opt2.EnableEvent keyOk" || return $false SendCmd "opt3=menu.AddNew OsdItem 'All your OSD are belong to us'" || return $false SendCmd "opt3.EnableEvent keyOk" || return $false SendCmd "opt4=menu.AddNew OsdItem 'User comment: $who is $what'" || return $false SendCmd "opt4.EnableEvent keyOk keyRed focus blur" || return $false SendCmd "menu.Show" || return $false while true ; do SendCmd "menu.SleepEvent" || return $false if IsEvent menu close ; then SendCmd "Message 'You did not pick anything.'" || return $false return $true fi if IsEvent opt1 keyOk ; then SendCmd "Message 'It is!'" || return $false SendCmd "menu.SendState osEnd" || return $false return $true fi if IsEvent opt2 keyOk ; then SendCmd "Message 'B-B-B-Baybee!'" || return $false SendCmd "menu.SendState osEnd" || return $false return $true fi if IsEvent opt3 keyOk ; then SendCmd "Message 'You are on the way to display!'" || return $false SendCmd "menu.SendState osEnd" || return $false return $true fi if IsEvent opt4 keyOk ; then SendCmd "Message '$who is $what'" || return $false SendCmd "menu.SendState osEnd" || return $false return $true fi if IsEvent opt4 focus ; then SendCmd "menu.SetColorKeyText -red edit" || return $false SendCmd "menu.Show" || return $false fi if IsEvent opt4 blur ; then SendCmd "menu.SetColorKeyText -red ''" || return $false SendCmd "menu.Show" || return $false fi if IsEvent opt4 keyRed ; then EditUserComment || return $false SendCmd "opt4.SetText 'User comment: $who is $what'" || return $false SendCmd "menu.Show" || return $false fi if IsEvent menu keyBlue ; then SendCmd "Message 'Pick one or leave with back'" || return $false fi done } ############### # Main Code # ############### ConnectServer localhost 2010 # Connect to the server process ReadReply || error # Read server welcome SendCmd "Version 0.1" || error # Identify to server with protocol version HelloWorld || error # Do helloworld SendCmd Quit # ... and good bye exec 3>&- exec 4>&- # close FIFOs osdserver-0.1.3/examples/OSDServer.pm0000664000175000000500000004065311623537124015665 0ustar udosrc ################## package OSDServer; ################## use strict; use warnings; use Socket; # Convenient return values use constant true => 1; use constant false => 0; sub new($) { my ($class)=@_; my $self = { SOCK => undef, nextid => 0, ids => {}, reply2xx => [], reply3xx => [], reply4xx => [], reply5xx => [], reply6xx => [], NetError => "", Welcome => "", Debug => 0, FocusMenu => undef, }; bless($self, $class); return $self; } # Some generic stuff for handling a connection sub Close($) { my ($self)=@_; $self->SendCmd("Quit"); close $self->{SOCK}; $self->{SOCK} = undef; } sub error($) { my ($self)=@_; $self->Close(); exit 1; } sub nextid($) { my ($self)=@_; return "id".++$self->{nextid}; } sub SetDebug($$) { my ($self,$debug)=@_; $self->{Debug} = $debug; } sub ConnectServer($;$$) { # Connect to the OSDServer my ($self,$host,$port)=@_; if (!defined $host) { $host = "localhost"; } if (!defined $port) { $port = 2010; } my $iaddr = inet_aton($host); if (!$iaddr) { $self->{NetError} = "no host: $host"; return false; } my $paddr = sockaddr_in($port, $iaddr); my $proto = getprotobyname('tcp'); if (!socket($self->{SOCK}, PF_INET, SOCK_STREAM, $proto)) { $self->{NetError} = "socket: $!"; return false; } if (!connect($self->{SOCK}, $paddr)) { $self->{NetError} = "connect: $!"; return false; } my $oldhandle = select($self->{SOCK}); $| = 1; select($oldhandle); return true; } sub SendGreeting($) { my ($self)=@_; # Read server welcome if (!$self->ReadReply) { return false; } return false if !defined $self->{reply2xx}[0]; return false if $self->{reply2xx}[0] != 201; $self->{Welcome} = $self->{reply3xx}[1]; # Identify to server with protocol version# return false if !$self->SendCmd("Version 0.1"); return true; } sub Open($;$$) { my ($self,$host,$port)=@_; if (!defined $host) { $host = "localhost"; } if (!defined $port) { $port = 2010; } if (!ref($self)) { $self = OSDServer->new(); } return undef if !$self->ConnectServer($host,$port); return undef if !$self->SendGreeting(); return $self; } sub ReadReply($) { # Read a complete server reply until 2xx return code, # and store replies in each category by number my ($self)=@_; $self->{reply2xx}=[]; $self->{reply3xx}=[]; $self->{reply4xx}=[]; $self->{reply5xx}=[]; $self->{reply6xx}=[]; my $sock = $self->{SOCK}; while (<$sock>) { chomp; $_ =~ s/\r$//; print "< $_\n" if $self->{Debug}; # screen echo if ($_ =~ /^2/) { $self->{reply2xx}=[split(/\s+/,$_,2)]; last; } if ($_ =~ /^3/) { $self->{reply3xx}=[split(/\s+/,$_)]; } if ($_ =~ /^4/) { $self->{reply4xx}=[split(/\s+/,$_,2)]; } if ($_ =~ /^5/) { $self->{reply5xx}=[split(/\s+/,$_,2)]; } if ($_ =~ /^6/) { my @tmpreply6xx=split(/\s+/,$_,2); if (defined $self->{reply6xx}[0] && $self->{reply6xx}[0] == $tmpreply6xx[0]) { $tmpreply6xx[1] = "$tmpreply6xx[1]\n$self->{reply6xx}[1]"; } $self->{reply6xx} = [@tmpreply6xx]; } } return false if defined $self->{reply4xx}[0]; return true; } sub GetCode2xx($) { my ($self)=@_; return false if !defined $self->{reply2xx}[0]; return $self->{reply2xx}[0]; } sub GetReply2xx($) { my ($self)=@_; return [@{$self->{reply2xx}}]; } sub GetCode3xx($) { my ($self)=@_; return false if !defined $self->{reply3xx}[0]; return $self->{reply3xx}[0]; } sub GetReply3xx($) { my ($self)=@_; return [@{$self->{reply3xx}}]; } sub GetCode4xx($) { my ($self)=@_; return false if !defined $self->{reply4xx}[0]; return $self->{reply4xx}[0]; } sub GetReply4xx($) { my ($self)=@_; return [@{$self->{reply4xx}}]; } sub GetCode5xx($) { my ($self)=@_; return false if !defined $self->{reply5xx}[0]; return $self->{reply5xx}[0]; } sub GetReply5xx($) { my ($self)=@_; return [@{$self->{reply5xx}}]; } sub GetCode6xx($) { my ($self)=@_; return false if !defined $self->{reply6xx}[0]; return $self->{reply6xx}[0]; } sub GetReply6xx($) { my ($self)=@_; return [@{$self->{reply6xx}}]; } sub SendCmd($$) { # Send a command and read the reply my ($self,$cmd)=@_; print "> $cmd\n" if $self->{Debug}; # screen echo my $sock = $self->{SOCK}; print $sock "$cmd\n"; # network send return $self->ReadReply(); } sub QuoteString($) { # Quote arbitrary string for use in '' and "" my ($str)=@_; $str =~ s/\\/\\\\/g; $str =~ s/'/\\'/g; $str =~ s/"/\\"/g; $str =~ s/\r/\\r/g; $str =~ s/\n/\\n/g; $str =~ s/\t/\\t/g; return $str; } sub UnquoteString($) { # Unquote string my ($str)=@_; $str =~ s/\\'/'/g; $str =~ s/\\"/"/g; $str =~ s/\\r/\r/g; $str =~ s/\\n/\n/g; $str =~ s/\\t/\t/g; $str =~ s/\\\\/\\/g; return $str; } sub GetFocusMenu($) { my ($self) = @_; if (!defined $self->{FocusMenu}) { $self->{FocusMenu} = OSDServer::Menu->attach($self, "_FOCUS"); } return $self->{FocusMenu}; } sub EnterLocal($) { my ($self) = @_; return $self->SendCmd("ENTERLOCAL"); } sub LeaveLocal($) { my ($self) = @_; return $self->SendCmd("LEAVELOCAL"); } sub Message($$%) { my ($self, $msg, %opts) = @_; if (defined $opts{StatusClear} && $opts{StatusClear}) { return $self->SendCmd('MESSAGE -statusclear'); } elsif (defined $opts{Status} && $opts{Status}) { return $self->SendCmd('MESSAGE -status -- "'.QuoteString($msg).'"'); } else { my $cmd = "MESSAGE"; if (defined $opts{Info} && $opts{Info}) { $cmd = "$cmd -info"; } elsif (defined $opts{Warn} && $opts{Warn}) { $cmd = "$cmd -warn"; } elsif (defined $opts{Error} && $opts{Error}) { $cmd = "$cmd -error"; } if (defined $opts{Queue} && $opts{Queue}) { $cmd = "$cmd -queue"; if (defined $opts{Timeout}) { $cmd = sprintf("$cmd -timeout %i", int($opts{Timeout})); } } if (defined $opts{Seconds}) { $cmd = sprintf("$cmd -seconds %i", int($opts{Seconds})); } $cmd = $cmd.' -- "'.QuoteString($msg).'"'; return undef if !$self->SendCmd($cmd); # error return "" if $self->GetCode3xx() == 301; # timeout return undef if $self->GetCode3xx() != 300; # unknown code return $self->GetReply3xx()->[2]; } } sub NewMenu($$) { my $self = shift; return OSDServer::Menu->new($self, $self->nextid(), @_); } sub NewOsdItem($$%) { my $self = shift; return OSDServer::OsdItem->new($self, $self->nextid(), undef, @_); } sub NewEditStrItem($$$) { my $self = shift; return OSDServer::EditStrItem->new($self, $self->nextid(), undef, @_); } sub NewEditIntItem($$$%) { my $self = shift; return OSDServer::EditIntItem->new($self, $self->nextid(), undef, @_); } sub NewEditListItem($$\@%) { my $self = shift; return OSDServer::EditIntItem->new($self, $self->nextid(), undef, @_); } sub Delete($$) { my ($self, $id)=@_; $id->Delete(); } ########################## package OSDServer::Object; ########################## use constant true => 1; use constant false => 0; sub new($$$) { my ($class, $server, $id)=@_; my $self = { server => $server, id => $id, }; $server->{ids}->{$id} = $self; bless($self, $class); return $self; } sub EnableEvent($\@) { my ($self, $events) = @_; my @events2 = map( { '"'.OSDServer::QuoteString($_).'"' } @{$events}); return $self->SendCmd("ENABLEEVENT -- ".join(' ', @events2)); } sub id($) { return $_[0]->{id}; } sub SendCmd($$;$) { my $self = shift; my $assign = (@_ > 1) ? shift()." = " : ""; my $cmd = shift; return $self->{server}->SendCmd($assign.$self->id().'.'.$cmd); } sub Delete($) { my ($self) = @_; return false if !$self->SendCmd("DELETE"); delete $self->{server}->{ids}->{$self->id()}; $self->{id}=undef; $self->{server}=undef; return true; } ######################## package OSDServer::Menu; ######################## use constant true => 1; use constant false => 0; @OSDServer::Menu::ISA = "OSDServer::Object"; sub new($$$$) { my ($class, $server, $name, $title) = @_; my $self = OSDServer::Object->new($server, $name); $self->{items} = {}; bless($self, $class); $title = OSDServer::QuoteString($title); return undef if !$self->{server}->SendCmd("$self->{id} = NEW MENU -- \"$title\""); return $self; } sub attach($$$) { my ($class, $server, $name) = @_; my $self = OSDServer::Object->new($server, $name); $self->{items} = {}; bless($self, $class); return $self; } sub Delete($) { my ($self) = @_; # for garbage collector: break cyclic refs foreach my $item (values %{$self->{items}}) { delete $item->{menu}; } $self->{items} = undef; return $self->SUPER::Delete(); } sub Add($$) { my ($self, $item) = @_; return false if !defined $item; return false if !$self->SendCmd("ADD ".$item->id()); $self->{items}->{$item->id()} = $item; $item->{menu} = $self; return true; } sub AddNewOsdItem($$%) { my $self = shift; my $item = OSDServer::OsdItem->new($self->{server}, $self->{server}->nextid(), $self, @_); if (defined $item) { $self->{items}->{$item->id()} = $item; $item->{menu} = $self; } return $item; } sub AddNewEditStrItem($$$) { my $self = shift; my $item = OSDServer::EditStrItem->new($self->{server}, $self->{server}->nextid(), $self, @_); if (defined $item) { $self->{items}->{$item->id()} = $item; $item->{menu} = $self; } return $item; } sub AddNewEditIntItem($$$%) { my $self = shift; my $item = OSDServer::EditIntItem->new($self->{server}, $self->{server}->nextid(), $self, @_); if (defined $item) { $self->{items}->{$item->id()} = $item; $item->{menu} = $self; } return $item; } sub AddNewEditListItem($$\@%) { my $self = shift; my $item = OSDServer::EditListItem->new($self->{server}, $self->{server}->nextid(), $self, @_); if (defined $item) { $self->{items}->{$item->id()} = $item; $item->{menu} = $self; } return $item; } sub AddSubMenu($$) { my ($self, $menu) = @_; return $self->SendCmd("ADDSUBMENU ".$menu->id()); } sub SetColorKeyText($%) { my ($self, %opts) = @_; my $cmd = "SETCOLORKEYTEXT"; if (defined $opts{Red}) { $cmd = $cmd.' -red "'.OSDServer::QuoteString($opts{Red}).'"'; } if (defined $opts{Green}) { $cmd = $cmd.' -green "'.OSDServer::QuoteString($opts{Green}).'"'; } if (defined $opts{Yellow}) { $cmd = $cmd.' -yellow "'.OSDServer::QuoteString($opts{Yellow}).'"'; } if (defined $opts{Blue}) { $cmd = $cmd.' -blue "'.OSDServer::QuoteString($opts{Blue}).'"'; } return $self->SendCmd($cmd); } sub SetColumns($$;$$$$) { my ($self, @cols) = @_; @cols=map(int, @cols); return $self->SendCmd("SETCOLUMNS ".join(' ', @cols)); } sub SetCurrent($$) { my ($self, $newcurrent) = @_; return $self->SendCmd("SETCURRENT ".int($newcurrent)); } sub GetCurrent($) { my ($self) = @_; return false if !$self->SendCmd("GETCURRENT"); return undef if $self->{server}->GetCode3xx() != 302; # unknown code my $itemno = $self->{server}->GetReply3xx()->[1]; my $itemid = $self->{server}->GetReply3xx()->[2]; my $item = $self->{server}->{ids}->{$itemid}; $item = "" if !defined $item; return ($itemno, $item, $itemid); } sub Show($) { my ($self) = @_; return $self->SendCmd("SHOW"); } sub SendState($$) { my ($self, $state) = @_; return $self->SendCmd('SENDSTATE -- "'.OSDServer::QuoteString($state).'"'); } sub SleepEvent($%) { my ($self, %opts) = @_; my $cmd = "SLEEPEVENT"; if (defined $opts{Timeout}) { $cmd = $cmd.'-timeout '.int($opts{Timeout}); } elsif (defined $opts{TimeoutMS}) { $cmd = $cmd.'-timeoutms '.int($opts{TimeoutMS}); } return undef if !$self->SendCmd($cmd); return ("", "", "") if $self->{server}->GetCode3xx() == 302; # timeout return undef if $self->{server}->GetCode3xx() != 300; # unknown code my $itemid = $self->{server}->GetReply3xx()->[1]; my $item = $self->{server}->{ids}->{$itemid}; $item = "" if !defined $item; my $event = $self->{server}->GetReply3xx()->[2]; return ($item, $itemid, $event); } ############################ package OSDServer::MenuItem; ############################ @OSDServer::MenuItem::ISA = "OSDServer::Object"; sub SetCurrent($) { my ($self) = @_; return $self->SendCmd("SETCURRENT"); } sub Delete($) { my ($self) = @_; if (defined $self->{menu}) { delete $self->{menu}->{items}->{$self->id()}; } return $self->SUPER::Delete(); } ########################### package OSDServer::OsdItem; ########################### @OSDServer::OsdItem::ISA = "OSDServer::MenuItem"; sub new($$$$$%) { my ($class, $server, $name, $menu, $title, %opts) = @_; my $self = OSDServer::Object->new($server, $name); bless($self, $class); my $par = ""; if (defined $opts{UnSelectable} && $opts{UnSelectable}) { $par = " -unselectable"; } $par = $par.' -- "'.OSDServer::QuoteString($title).'"'; if (defined $menu) { return undef if !$menu->SendCmd($self->id(), "ADDNEW OSDITEM".$par) } else { return undef if !$self->{server}->SendCmd($self->id()." = NEW OSDITEM".$par); } return $self; } sub SetUnSelectable($) { my ($self) = @_; return $self->SendCmd("SETUNSELECTABLE"); } sub SetSelectable($) { my ($self) = @_; return $self->SendCmd("SETSELECTABLE"); } sub SetText($$) { my ($self, $text) = @_; return $self->SendCmd('SETTEXT -- "'.OSDServer::QuoteString($text).'"'); } ############################### package OSDServer::EditStrItem; ############################### @OSDServer::EditStrItem::ISA = "OSDServer::MenuItem"; sub new($$$$$$) { my ($class, $server, $name, $menu, $desc, $value) = @_; my $self = OSDServer::Object->new($server, $name); bless($self, $class); my $par = ' -- "'.OSDServer::QuoteString($desc).'" "'.OSDServer::QuoteString($value).'"'; if (defined $menu) { return undef if !$menu->SendCmd($self->id(), "ADDNEW EDITSTRITEM".$par) } else { return undef if !$self->{server}->SendCmd($self->id()." = NEW EDITSTRITEM".$par); } return $self; } sub GetValue($) { my ($self) = @_; return undef if !$self->SendCmd("GETVALUE -quoted"); return undef if $self->{server}->GetCode5xx() != 500; # unknown code return OSDServer::UnquoteString($self->{server}->GetReply5xx()->[1]); } ############################### package OSDServer::EditIntItem; ############################### @OSDServer::EditIntItem::ISA = "OSDServer::MenuItem"; sub new($$$$$$%) { my ($class, $server, $name, $menu, $desc, $value, %opts) = @_; my $self = OSDServer::Object->new($server, $name); bless($self, $class); my $par = ""; if (defined $opts{Min}) { $par = $par.' -min '.int($opts{Min}); } if (defined $opts{Max}) { $par = $par.' -max '.int($opts{Max}); } if (defined $opts{MinString}) { $par = $par.' -minstring "'.OSDServer::QuoteString($opts{MinString}).'"'; } if (defined $opts{MaxString}) { $par = $par.' -maxstring "'.OSDServer::QuoteString($opts{MaxString}).'"'; } $par = $par.' -- "'.OSDServer::QuoteString($desc).'" '.int($value); if (defined $menu) { return undef if !$menu->SendCmd($self->id(), "ADDNEW EDITINTITEM".$par) } else { return undef if !$self->{server}->SendCmd($self->id()." = NEW EDITINTITEM".$par); } return $self; } sub GetValue($) { my ($self) = @_; return undef if !$self->SendCmd("GETVALUE"); return undef if $self->{server}->GetCode5xx() != 500; # unknown code return int($self->{server}->GetReply5xx()->[1]); } ############################### package OSDServer::EditListItem; ############################### @OSDServer::EditListItem::ISA = "OSDServer::MenuItem"; sub new($$$$$\@%) { my ($class, $server, $name, $menu, $desc, $values, %opts) = @_; my $self = OSDServer::Object->new($server, $name); bless($self, $class); my $par = ""; if (defined $opts{Select}) { $par = $par.' -select '.int($opts{Select}); } elsif (defined $opts{SelectName}) { $par = $par.' -selectname "'.OSDServer::QuoteString($opts{SelectName}).'"'; } $par = $par.' -- "'.OSDServer::QuoteString($desc).'"'; for my $value (@{$values}) { $par = $par.' "'.OSDServer::QuoteString($value).'"'; } if (defined $menu) { return undef if !$menu->SendCmd($self->id(), "ADDNEW EDITLISTITEM".$par) } else { return undef if !$self->{server}->SendCmd($self->id()." = NEW EDITLISTITEM".$par); } return $self; } sub GetValue($%) { my ($self,%opts) = @_; my $par = ' -quoted'; if (defined $opts{Name} && $opts{Name}) { $par = $par.' -name'; } return undef if !$self->SendCmd("GETVALUE".$par); return undef if $self->{server}->GetCode5xx() != 500; # unknown code my $result = $self->{server}->GetReply5xx()->[1]; if (defined $opts{Name} && $opts{Name}) { return OSDServer::UnquoteString($result); } else { return int($result); } } 1;osdserver-0.1.3/examples/helloworld-1.pl0000775000175000000500000000020211071721222016335 0ustar udosrc#!/usr/bin/perl use OSDServer; my $server = OSDServer->Open() or die "open"; $server->Message("Hello World!"); $server->Close(); osdserver-0.1.3/examples/helloworld-2.pl0000775000175000000500000000026011071721222016342 0ustar udosrc#!/usr/bin/perl use OSDServer; my $server = OSDServer->Open() or die "open"; if ($server->Message("Is this easy?") eq "keyOk") { print "This is easy!\n"; } $server->Close(); osdserver-0.1.3/examples/helloworld-3.pl0000775000175000000500000000144211623537124016357 0ustar udosrc#!/usr/bin/perl use OSDServer; my $server = OSDServer->Open() or die "open"; my $menu = $server->NewMenu("Hello World selecting"); $menu->EnableEvent(["keyOk", "close"]); my $one = $menu->AddNewOsdItem("Select one thing"); my $another = $menu->AddNewOsdItem("Select another thing"); my $different = $menu->AddNewOsdItem("Select something different"); $menu->Show(); my (undef,undef,$event) = $menu->SleepEvent(); my (undef, $item, undef) = $menu->GetCurrent(); if ($item eq $one and $event eq "keyOk") { print "One thing selected.\n"; } elsif ($item eq $another and $event eq "keyOk") { print "Another thing selected.\n"; } elsif ($item eq $different and $event eq "keyOk") { print "Something different selected.\n"; } elsif ($event eq "close") { print "Nothing selected.\n"; } $server->Close(); osdserver-0.1.3/examples/helloworld-4.pl0000775000175000000500000000062411623537124016361 0ustar udosrc#!/usr/bin/perl use OSDServer; my $server = OSDServer->Open() or die "open"; my $menu = $server->NewMenu("Hello World editing"); $menu->SetColumns(10); $menu->EnableEvent(["keyOk"]); my $input = $menu->AddNewEditStrItem("Edit Text", "Hello World"); $menu->Show(); my (undef,undef,$event) = $menu->SleepEvent(); if ($event eq "keyOk") { $text = $input->GetValue(); print "$text\n"; } $server->Close(); osdserver-0.1.3/examples/demo-pm.pl0000664000175000000500000001111611071721222015365 0ustar udosrc#!/usr/bin/perl use OSDServer; use strict; use warnings; use constant true => 1; use constant false => 0; my $who="OSDServer"; my $what="cool"; sub EditUserComment($) { my ($server)=@_; $server->EnterLocal() or return false; my $menu = $server->NewMenu("Edit User comment"); defined $menu and $menu->SetColumns(15) and $menu->EnableEvent(["keyOk", "close"]) or return false; $menu->AddNewOsdItem("--- Who do you want to comment? --------------------", UnSelectable => 1) or return false; my $whoedit = $menu->AddNewEditListItem("Who", ["OSDServer", "VDR", "Linux", "Windows", "Klaus", "Bill"], SelectName => $who) or return false; $whoedit->SetCurrent(); $menu->AddNewOsdItem("--- And how do you like it? --------------------", UnSelectable => 1) or return false; my $whatedit = $menu->AddNewEditStrItem("It is", $what) or return false; $server->GetFocusMenu()->AddSubMenu($menu) or return false; $menu->Show() or return false; while (1) { my ($id, $idt, $event) = $menu->SleepEvent(); if ($id eq $menu && $event eq "keyOk") { $who = $whoedit->GetValue(Name => 1); return false if !defined $who; $what = $whatedit->GetValue(); return false if !defined $what; $menu->SendState("osBack") or return false; last; } if ($id eq $menu && $event eq "close") { last; } } $menu->Delete() and $server->LeaveLocal() or return false; return true; } sub HelloWorld($) { my ($server)=@_; my $menu = $server->NewMenu("Hello world"); defined $menu and $menu->SetColorKeyText(Blue => "Help") and $menu->SetColumns(15) and $menu->EnableEvent(["keyBlue", "close"]) or return false; defined $menu->AddNewOsdItem("--- What do you think? --------------------", UnSelectable => 1) or return false; my $opt1 = $menu->AddNewOsdItem("This is awesome!"); defined $opt1 and $opt1->SetCurrent() and $opt1->EnableEvent(["keyOk"]) or return false; my $opt2 = $menu->AddNewOsdItem("You ain't seen nothing yet"); defined $opt2 and $opt2->EnableEvent(["keyOk"]) or return false; my $opt3 = $menu->AddNewOsdItem("All your OSD are belong to us"); defined $opt3 and $opt3->EnableEvent(["keyOk"]) or return false; my $opt4 = $menu->AddNewOsdItem("User comment: $who is $what"); defined $opt4 and $opt4->EnableEvent(["keyOk", "keyRed", "focus", "blur"]) or return false; $menu->Show() or return false; while (1) { my ($id, $idt, $event) = $menu->SleepEvent(); return false if !defined $event; if ($id eq $menu && $event eq "close") { $server->Message("You did not pick anything.") or return false; last; } if ($id eq $opt1 && $event eq "keyOk") { $server->Message("It is!") and $menu->SendState("osEnd") or return false; last; } elsif ($id eq $opt2 && $event eq "keyOk") { $server->Message("B-B-B-Baybee!") and $menu->SendState("osEnd") or return false; last; } elsif ($id eq $opt3 && $event eq "keyOk") { $server->Message("You are on the way to display!") and $menu->SendState("osEnd") or return false; last; } elsif ($id eq $opt4 && $event eq "keyOk") { $server->Message("$who is $what") and $menu->SendState("osEnd") or return false; last; } elsif ($id eq $opt4 && $event eq "focus") { $menu->SetColorKeyText(Red => "edit") and $menu->Show() or return false; } elsif ($id eq $opt4 && $event eq "blur") { $menu->SetColorKeyText(Red => "") and $menu->Show() or return false; } elsif ($id eq $opt4 && $event eq "keyRed") { EditUserComment($server) and $opt4->SetText("User comment: $who is $what") and $menu->Show() or return false; } if ($id eq $menu && $event eq "keyBlue") { $server->Message("Pick one or leave with back") or return false; } } $menu->Delete(); } ############### # Main Code # ############### my $server = OSDServer->Open(); $server->SetDebug(1); if (!defined $server) { print STDERR "$server->{NetError}\n"; exit 1; } HelloWorld($server); # Do helloworld $server->Close(); # ... and good bye osdserver-0.1.3/connection.c0000664000175000000500000000401310646403052014162 0ustar udosrc #include #include "connection.h" void cConnectionBase::ReplyPlain(int Code, char *buffer) { if (!IsConnected()) return; char *buffer1=NULL,*buffer2; if (500<=Code && Code<600) { // quote string int chars=0; char *p=buffer; while (*p!=0) { if (*p=='\r' || *p=='\n' || *p=='\t' || *p=='"' || *p=='\\' || *p=='\'') chars++; chars++; p++; } buffer1=(char*)malloc(chars+1); p=buffer; char *q=buffer1; while (*p!=0) { if (*p=='\r') { *q++='\\'; *q++='r'; } else if (*p=='\n') { *q++='\\'; *q++='n'; } else if (*p=='\t') { *q++='\\'; *q++='t'; } else if (*p=='"') { *q++='\\'; *q++='"'; } else if (*p=='\\') { *q++='\\'; *q++='\\'; } else if (*p=='\'') { *q++='\\'; *q++='\''; } else { *q++=*p; } p++; } *q=0; buffer=buffer1; } char *p = buffer; while (*p) { char *n = strchr(p, '\n'); if (n) *n=0; int len=asprintf(&buffer2,"%03d %s\r\n",Code,p); if (len<0) { LOG_ERROR; break; } Send(buffer2,len); free(buffer2); if (!n) break; p=n+1; } free(buffer1); } void cConnectionBase::Reply(int Code, const char *fmt, ...) { char *buffer; va_list ap; va_start(ap, fmt); int len=vasprintf(&buffer, fmt, ap); va_end(ap); if (len<0) { LOG_ERROR; return; } ReplyPlain(Code,buffer); free(buffer); } void cConnectionBase::ReplyDebug(int index, const char *fmt, ...) { char *buffer; va_list ap; index = (index%100)+100; va_start(ap, fmt); int len=vasprintf(&buffer, fmt, ap); va_end(ap); if (len<0) { LOG_ERROR; return; } ReplyPlain(index,buffer); free(buffer); } osdserver-0.1.3/connection.h0000664000175000000500000000117110646403052014171 0ustar udosrc#ifndef __OSDSERVER_CONNECTION_H #define __OSDSERVER_CONNECTION_H #include "tools.h" class cConnectionBase { public: cConnectionBase() { } virtual ~cConnectionBase() {} virtual bool Send(const char *s, int length=-1)=0; virtual char* ReadLine()=0; virtual void DelLine()=0; virtual void AddSelect(cSelect &Selector)=0; virtual bool IsConnected()=0; protected: void ReplyPlain(int Code, char *buffer); void Reply(int Code, const char *fmt, ...) __attribute__((format(printf,3,4))); public: void ReplyDebug(int index, const char *fmt, ...) __attribute__((format(printf,3,4))); }; #endif osdserver-0.1.3/COPYING0000664000175000000500000004310610505247356012727 0ustar udosrc GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. osdserver-0.1.3/interpreter.c0000664000175000000500000011373611623537124014407 0ustar udosrc #include #include #include "interpreter.h" // --------------- // cConnection // --------------- cConnection::cConnection() { } char* cConnection::KeyString(char *buffer, int bufferlen, eKeys key) { const char *k; if ((key&~k_Flags)==kNone) { k="None"; } else { k=cKey::ToString((eKeys)(key & ~k_Flags)); if (!k) k="Unknown"; } switch (key&k_Flags) { case 0: snprintf(buffer,bufferlen,"key%s",k); break; case k_Repeat: snprintf(buffer,bufferlen,"key%s|Repeat",k); break; case k_Release: snprintf(buffer,bufferlen,"key%s|Release",k); break; case k_Repeat|k_Release: snprintf(buffer,bufferlen,"key%s|Repeat|Release",k); break; } return buffer; } int cConnection::ParseKey(const char *str) { eKeys Key; char keyName[20]; unsigned int i; if (strncmp(str, "key",3) != 0) return -1; str+=3; i=0; while (i < sizeof(keyName) && *str != 0 && *str != '|') keyName[i++] = *str++; if (i>=sizeof(keyName)) return -1; keyName[i] = 0; Key = cKey::FromString(keyName); if (Key == kNone && strcmp(keyName, "None") != 0) return -1; if (*str == '|') { str++; if (strcmp(str, "Repeat") == 0) Key = eKeys(Key|k_Repeat); else if (strcmp(str, "Release") == 0) Key = eKeys(Key|k_Release); else if (strcmp(str, "Repeat|Release") == 0) Key = eKeys(Key|k_Repeat|k_Release); else return -1; } return Key; } // ------------------------------- // cCommandProcessor::cCommand // ------------------------------- cCommandProcessor::cCommand::cCommand() { Command=NULL; Context=NULL; AssignVar=NULL; } bool cCommandProcessor::cCommand::ParseLine(cConnection &Connection, char *line) { char *p,*ps; // Reset parameters Command=NULL; Context=NULL; AssignVar=NULL; args.clear(); // Parse one identifier line=skipspace(line); if (*line==0 || *line=='#') return false; p=skipspace(ps=skipword(line)); if (!p) { Connection.ReplyErrorIdentifier(line); return false; } if (*p=='=') { // Assignment found *ps=0; AssignVar=line; if (*line=='_') { Connection.ReplyErrorIdentifierReserved(line); return false; } // parse next word line=skipspace(p+1); p=skipspace(ps=skipword(line)); if (!p) { Connection.ReplyErrorIdentifier(line); return false; } } if (*p=='.') { // Found member operator, parse member sequence and command char *lastdot; while (*p=='.') { // Loop a sequence of identifer.identifier.identifier.command lastdot=p; p=skipspace(ps=skipword(skipspace(p+1))); if (!p) { Connection.ReplyErrorIdentifier(line); return false; } } // Remember whole sequence up to before last dot *lastdot=0; Context=line; despace(Context); // Parse next word line=skipspace(lastdot+1); p=skipspace(ps=skipword(line)); if (!p) { Connection.ReplyErrorIdentifier(line); return false; } } // Now parsed word is a command if (!isspace(*ps) && *ps!=0) { Connection.ReplyErrorIdentifier(line); return false; } *ps=0; Command=line; bool lastflag=false; while (*p!=0) { char *q=p; bool singlequote=false; bool doublequote=false; line=p; while (*p!=0 && (!isspace(*p) || singlequote || doublequote)) { switch (*p) { case '\\': p++; if (*p=='r') *q++='\r'; if (*p=='n') *q++='\n'; if (*p=='t') *q++='\t'; if (*p=='"') *q++='"'; if (*p=='\'') *q++='\''; if (*p=='\\') *q++='\\'; break; case '"': if (singlequote) *q++='"'; else doublequote=!doublequote; break; case '\'': if (doublequote) *q++='\''; else singlequote=!singlequote; break; default: *q++=*p; } p++; } p=skipspace(p); *q=0; if (!lastflag && 0==strcmp(line,"--")) { // Marker for end-of-flags reached lastflag=true; // ignore this parameter and dont increase argflagc from now on continue; } bool IsFlag = !lastflag && line[0]=='-'; args.push_back(cArg(line,IsFlag)); } return true; } void cCommandProcessor::cCommand::DropArgs(int start, int count) { args.erase(args.begin()+start,args.begin()+start+count); } bool cCommandProcessor::cCommand::ArgScanFlag(const char *flag) { int pos=0; args_iterator i; for (i=args.begin(); i!=args.end(); i++) { if (i->IsFlag() && 0==strcasecmp(flag,i->Text()+1)) break; pos++; } if (i==args.end()) return false; args.erase(i); return true; } const char* cCommandProcessor::cCommand::ArgScanFlagParam(const char *flag) { int pos=0; args_iterator i; for (i=args.begin(); i!=args.end(); i++) { if (i->IsFlag() && 0==strcasecmp(flag,i->Text()+1)) break; pos++; } if (i==args.end()) return NULL; const char *result=(i+1 != args.end()) ? ((i+1)->Text()) : (NULL); args.erase(i,i+2); return result; } // --------------------- // cCommandProcessor // --------------------- cCommandProcessor::cCommandProcessor(cConnection *connection) { Connection=connection; State=stateNew; SleepEventObj=NULL; SleepEventTimeout.tv_sec=0; SleepEventTimeout.tv_usec=0; LocalContext = &BaseContext; } cCommandProcessor::~cCommandProcessor() { if (Connection) delete Connection; } cOsdServerMenu* cCommandProcessor::NewMenu(cCommand &cmd) { if (!cmd.HasMaxArgument(1)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(1)); return NULL; } if (!cmd.HasMinArgument(1)) { Connection->ReplyErrorMissingParameter(); return NULL; } cOsdServerMenu *menu = new cOsdServerMenu(cmd.GetArg(0)); LocalContext->Add(menu, cmd.GetAssignVar()); return menu; } cOsdServerOsdItem* cCommandProcessor::NewOsdItem(cCommand &cmd) { bool Unselectable = cmd.ArgScanFlag("unselectable"); if (!cmd.HasMaxArgument(1)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(1)); return NULL; } if (!cmd.HasMinArgument(1)) { Connection->ReplyErrorMissingParameter(); return NULL; } cOsdServerOsdItem *item=new cOsdServerOsdItem(cmd.GetArg(0)); if (Unselectable) item->SetSelectable(false); LocalContext->Add(item, cmd.GetAssignVar()); return item; } cOsdServerMenuEditStrItem* cCommandProcessor::NewEditStrItem(cCommand &cmd) { if (!cmd.HasMaxArgument(2)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(2)); return NULL; } if (!cmd.HasMinArgument(2)) { Connection->ReplyErrorMissingParameter(); return NULL; } cOsdServerMenuEditStrItem *item=new cOsdServerMenuEditStrItem(cmd.GetArg(0), cmd.GetArg(1), 255); LocalContext->Add(item, cmd.GetAssignVar()); return item; } cOsdServerMenuEditIntItem* cCommandProcessor::NewEditIntItem(cCommand &cmd) { const char *MinString = cmd.ArgScanFlagParam("MinString"); const char *MaxString = cmd.ArgScanFlagParam("MaxString"); int max; int min; const char *Max = cmd.ArgScanFlagParam("Max"); const char *Min = cmd.ArgScanFlagParam("Min"); if (!Max) { max = INT_MAX; } else if (strcasecmp(Max,"none")==0) { max = INT_MAX; } else { char *p; max = strtol(Max, &p, 10); if (*p != 0) { Connection->ReplyErrorParameterInt(Max); return NULL; } } if (!Min) { min = 0; } else if (strcasecmp(Min,"none")==0) { min = INT_MIN; } else { char *p; min = strtol(Min, &p, 10); if (*p != 0) { Connection->ReplyErrorParameterInt(Min); return NULL; } } if (!cmd.HasMaxArgument(2)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(2)); return NULL; } if (!cmd.HasMinArgument(2)) { Connection->ReplyErrorMissingParameter(); return NULL; } char *p; int value = strtol(cmd.GetArg(1), &p, 10); if (*p != 0) { Connection->ReplyErrorParameterInt(cmd.GetArg(1)); return NULL; } cOsdServerMenuEditIntItem *item=new cOsdServerMenuEditIntItem(cmd.GetArg(0), value, min, max, MinString, MaxString); LocalContext->Add(item, cmd.GetAssignVar()); return item; } cOsdServerMenuEditListItem* cCommandProcessor::NewEditListItem(cCommand &cmd) { const char *Name = cmd.ArgScanFlagParam("SelectName"); const char *Number = cmd.ArgScanFlagParam("Select"); int value = 0; if (Name) { if (Number) { Connection->ReplyErrorConflictingParameters("SelectName","Select"); return NULL; } value = -1; } else if (Number) { char *p; value = strtol(Number, &p, 10) - 1; if (*p != 0) { Connection->ReplyErrorParameterInt(Number); return NULL; } } if (!cmd.HasMinArgument(2)) { Connection->ReplyErrorMissingParameter(); return NULL; } int max = cmd.GetArgCount()-1; const char *(strings[max]); for (int i=0; iReplyErrorParameterInvalid(Name); return NULL; } if (Number && (value>=max ||value<0)) { Connection->ReplyErrorParameterRange(Number); return NULL; } cOsdServerMenuEditListItem *item=new cOsdServerMenuEditListItem(cmd.GetArg(0), value, max, strings); LocalContext->Add(item, cmd.GetAssignVar()); return item; } cOsdServerObject* cCommandProcessor::NewObject(cCommand &cmd) { if (!cmd.HasMinArgument(1)) { Connection->ReplyErrorMissingParameter(); return NULL; } if (0 == strcasecmp(cmd.GetArg(0), "MENU")) { cmd.DropArgs(0,1); return NewMenu(cmd); } else if (0 == strcasecmp(cmd.GetArg(0), "OSDITEM")) { cmd.DropArgs(0,1); return NewOsdItem(cmd); } else if (0 == strcasecmp(cmd.GetArg(0), "EditStrItem")) { cmd.DropArgs(0,1); return NewEditStrItem(cmd); } else if (0 == strcasecmp(cmd.GetArg(0), "EditIntItem")) { cmd.DropArgs(0,1); return NewEditIntItem(cmd); } else if (0 == strcasecmp(cmd.GetArg(0), "EditListItem")) { cmd.DropArgs(0,1); return NewEditListItem(cmd); } Connection->ReplyErrorUnknownType(cmd.GetArg(0)); return NULL; } bool cCommandProcessor::CmdNew(cCommand &cmd) { cOsdServerObject *obj = NewObject(cmd); return (obj != NULL); } bool cCommandProcessor::CmdDelete(cCommand &cmd) { if (!cmd.HasMaxArgument(1)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(1)); return false; } if (!cmd.HasMinArgument(1)) { Connection->ReplyErrorMissingParameter(); return false; } cOsdServerObject *obj = LocalContext->GetName(cmd.GetArg(0)); if (!obj) { Connection->ReplyErrorUnknownObject(cmd.GetArg(0)); return false; } delete obj; return true; } bool cCommandProcessor::CmdObjectDelete(cCommand &cmd, cOsdServerObject *osdobj) { if (!cmd.HasMaxArgument(0)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(1)); return false; } delete osdobj; return true; } bool cCommandProcessor::CmdVersion(cCommand &cmd) { if (cmd.GetContext()) { Connection->ReplyErrorUnknownCommand(cmd.GetContext()); return false; } if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } if (!cmd.HasMaxArgument(1)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(1)); return false; } if (!cmd.HasMinArgument(1)) { Connection->ReplyErrorMissingParameter(); return false; } const char *verstr = cmd.GetArg(0); unsigned int major,minor; int len=0; if (sscanf(verstr, "%u.%u%n", &major, &minor, &len) < 2) { // Not matched both numbers Connection->ReplyErrorUnknownVersion(verstr); return false; } if (verstr[len] != 0) { // Not parsed to end of string Connection->ReplyErrorUnknownVersion(verstr); return false; } if (major == 0 && minor <= 1) { return true; } Connection->ReplyErrorUnknownVersion(verstr); return false; } bool cCommandProcessor::CmdQuit(cCommand &cmd) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } if (!cmd.HasMaxArgument(0)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(0)); return false; } Connection->ReplyGoodBye(); return true; } bool cCommandProcessor::CmdIdle(cCommand &cmd) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } if (!cmd.HasMaxArgument(0)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(0)); return false; } return true; } class cMessageCallback : public cMainThreadLock::cCallback { eMessageType Type; const char *s; int Seconds; virtual int Callback(); public: eKeys Message(eMessageType Type, const char *s, int Seconds = 0); }; int cMessageCallback::Callback() { return Skins.Message(Type, s, Seconds); } eKeys cMessageCallback::Message(eMessageType _Type, const char *_s, int _Seconds) { Type = _Type; s = _s; Seconds = _Seconds; return (eKeys)CallCallback(); } bool cCommandProcessor::CmdMessage(cCommand &cmd) { /* MESSAGE [-info|-warn|-error] -queue [-timeout #] [-seconds #] message MESSAGE [-info|-warn|-error] [-seconds #] message MESSAGE -status message MESSAGE -statusclear */ if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } enum eMode { status, info, warn, error, statusclear } mode=info; int seconds=0; int timeout=0; bool queue=cmd.ArgScanFlag("queue"); const char *cseconds=cmd.ArgScanFlagParam("seconds"); if (cseconds) seconds=atoi(cseconds); const char *ctimeout=cmd.ArgScanFlagParam("timeout"); if (ctimeout) timeout=atoi(ctimeout); if (ctimeout && !queue) { Connection->ReplyErrorParameter("-timeout requires -queue"); return false; } int ArgCount=1; if (cmd.ArgScanFlag("status")) { mode=status; if (cseconds || queue) { Connection->ReplyErrorParameter("-status doesnt accept -queue or -seconds"); return false; } } else if (cmd.ArgScanFlag("info")) { mode=info; } else if (cmd.ArgScanFlag("warn")) { mode=warn; } else if (cmd.ArgScanFlag("error")) { mode=error; } else if (cmd.ArgScanFlag("statusclear")) { mode=statusclear; if (cseconds || queue) { Connection->ReplyErrorParameter("-statusclear doesnt accept -queue or -seconds"); return false; } ArgCount=0; } if (!cmd.HasMaxArgument(ArgCount)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(ArgCount)); return false; } if (!cmd.HasMinArgument(ArgCount)) { Connection->ReplyErrorMissingParameter(); return false; } if (queue) { int k=kNone; switch (mode) { case info: k=Skins.QueueMessage(mtInfo,cmd.GetArg(0),seconds,timeout); break; case warn: k=Skins.QueueMessage(mtWarning,cmd.GetArg(0),seconds,timeout); break; case error: k=Skins.QueueMessage(mtError,cmd.GetArg(0),seconds,timeout); break; default:; } if (timeout!=0) { Connection->ReplyMessageKeyEvent("Message", (eKeys)k); } else { Connection->ReplyMessageEventTimeout(); } } else { int k=kNone; cMessageCallback Callback; switch (mode) { case status: k=Callback.Message(mtStatus,cmd.GetArg(0),0); break; case statusclear: k=Callback.Message(mtStatus,NULL,0); break; case info: k=Callback.Message(mtInfo,cmd.GetArg(0),seconds); break; case warn: k=Callback.Message(mtWarning,cmd.GetArg(0),seconds); break; case error: k=Callback.Message(mtError,cmd.GetArg(0),seconds); break; } if (mode!=status && mode!=statusclear) { if (k != -1) Connection->ReplyMessageKeyEvent("Message",(eKeys)k); else Connection->ReplyMessageEventTimeout(); } } return true; } bool cCommandProcessor::CmdEnterLocal(cCommand &cmd) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } if (!cmd.HasMaxArgument(0)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(0)); return false; } LocalContext = LocalContext->MakeLocalContext(); return true; } bool cCommandProcessor::CmdLeaveLocal(cCommand &cmd) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } if (!cmd.HasMaxArgument(0)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(0)); return false; } if (!LocalContext->GetParentContext()) { Connection->ReplyErrorNoParentContext(); return false; } LocalContext = LocalContext->GetParentContext(); LocalContext->DestroyLocalContext(); return true; } bool cCommandProcessor::CmdMenuAdd(cCommand &cmd, cOsdServerMenu *menu) { if (!cmd.HasMaxArgument(1)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(1)); return false; } if (!cmd.HasMinArgument(1)) { Connection->ReplyErrorMissingParameter(); return false; } cOsdServerObject *obj = LocalContext->GetName(cmd.GetArg(0)); if (!obj) { Connection->ReplyErrorUnknownObject(cmd.GetArg(0)); return false; } if (!obj->IsMenuItem()) { Connection->ReplyErrorIncompatibleType(cmd.GetArg(0)); return false; } menu->Add((cOsdServerMenuItem*)obj); return true; } bool cCommandProcessor::CmdMenuAddNew(cCommand &cmd, cOsdServerMenu *menu) { cOsdServerObject *obj = NewObject(cmd); if (!obj) return false; if (!obj->IsMenuItem()) { Connection->ReplyErrorIncompatibleType(cmd.GetArg(0)); return false; } menu->Add((cOsdServerMenuItem*)obj); return true; } bool cCommandProcessor::CmdMenuAddSubMenu(cCommand &cmd, cOsdServerMenu *menu) { if (!cmd.HasMaxArgument(1)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(1)); return false; } if (!cmd.HasMinArgument(1)) { Connection->ReplyErrorMissingParameter(); return false; } cOsdServerObject *obj = LocalContext->GetName(cmd.GetArg(0)); if (!obj) { Connection->ReplyErrorUnknownObject(cmd.GetArg(0)); return false; } if (obj->ClassId() != cOsdServerObject::clsidMenu) { Connection->ReplyErrorIncompatibleType(cmd.GetArg(0)); return false; } menu->AddSubMenu(obj->Cast()); return true; } bool cCommandProcessor::CmdMenuShow(cCommand &cmd, cOsdServerMenu *menu) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } if (!cmd.HasMaxArgument(0)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(0)); return false; } menu->Show(); return true; } bool cCommandProcessor::CmdMenuSendState(cCommand &cmd, cOsdServerMenu *menu) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } if (!cmd.HasMaxArgument(1)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(1)); return false; } if (!cmd.HasMinArgument(1)) { Connection->ReplyErrorMissingParameter(); return false; } if (strcmp(cmd.GetArg(0),"osEnd") == 0) { menu->SendOSState(osEnd); } else if (strcmp(cmd.GetArg(0),"osBack") == 0) { menu->SendOSState(osBack); } else if (strcmp(cmd.GetArg(0),"osContinue") == 0) { menu->SendOSState(osContinue); } else { Connection->ReplyErrorUnknownState(cmd.GetArg(0)); return false; } return true; } bool cCommandProcessor::CmdMenuSleepEvent(cCommand &cmd, int &timeout, cOsdServerMenu *menu) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } SleepEventTimeout.tv_sec=0; // means infinite const char *par=cmd.ArgScanFlagParam("timeout"); if (par) { gettimeofday(&SleepEventTimeout,NULL); SleepEventTimeout.tv_sec+=atoi(par); } else { const char *par=cmd.ArgScanFlagParam("timeoutms"); if (par) { gettimeofday(&SleepEventTimeout,NULL); int t=atoi(par); SleepEventTimeout.tv_sec+=t/1000; SleepEventTimeout.tv_usec+=1000*(t%1000); if (SleepEventTimeout.tv_usec>=1000000) { SleepEventTimeout.tv_usec-=1000000; SleepEventTimeout.tv_sec++; } } } if (!cmd.HasMaxArgument(0)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(0)); return false; } SleepEventObj=menu; if (HandleSleepEvent(timeout)) { return false; } State=stateSleepEvent; return true; } bool cCommandProcessor::CmdMenuGetCurrent(cCommand &cmd, cOsdServerMenu *menu) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } if (!cmd.HasMaxArgument(0)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(0)); return false; } int current; cString name; menu->GetCurrent(current,name); Connection->ReplyMessageMenuCurrent(current,name); return true; } bool cCommandProcessor::CmdMenuSetCurrent(cCommand &cmd, cOsdServerMenu *menu) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } if (!cmd.HasMaxArgument(1)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(1)); return false; } if (!cmd.HasMinArgument(1)) { Connection->ReplyErrorMissingParameter(); return false; } int NewCurrent=atoi(cmd.GetArg(0)); menu->SetCurrent(NewCurrent); return true; } bool cCommandProcessor::CmdMenuSetColorKeyText(cCommand &cmd, cOsdServerMenu *menu) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } if (!cmd.HasMinArgument(1)) { Connection->ReplyErrorMissingParameter(); return false; } const char *Red =cmd.ArgScanFlagParam("red"); const char *Green =cmd.ArgScanFlagParam("green"); const char *Yellow=cmd.ArgScanFlagParam("yellow"); const char *Blue =cmd.ArgScanFlagParam("blue"); for (int i=0;cmd.GetArg(i);i++) { if (!Red) { Red = cmd.GetArg(i); } else if (!Green) { Green = cmd.GetArg(i); } else if (!Yellow) { Yellow = cmd.GetArg(i); } else if (!Blue) { Blue = cmd.GetArg(i); } else { Connection->ReplyErrorColorKeyAlreadySet(); return false; } } if (Red ) menu->SetColorKeyRed (*Red !=0 ? Red : NULL); if (Green ) menu->SetColorKeyGreen (*Green !=0 ? Green : NULL); if (Yellow) menu->SetColorKeyYellow(*Yellow!=0 ? Yellow : NULL); if (Blue ) menu->SetColorKeyBlue (*Blue !=0 ? Blue : NULL); return true; } bool cCommandProcessor::CmdMenuSetColumns(cCommand &cmd, cOsdServerMenu *menu) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } if (!cmd.HasMaxArgument(5)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(5)); return false; } int i; for (i=0;cmd.GetArg(i);i++) { int res; if (!cmd.GetArgInt(i,res)) { Connection->ReplyErrorParameterInt(cmd.GetArg(i)); return false; } menu->SetColumn(i,res); } for (;i<5;i++) { menu->SetColumn(i,0); } return true; } bool cCommandProcessor::CmdItemSetCurrent(cCommand &cmd, cOsdServerMenuItem *menuitem) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } if (!cmd.HasMaxArgument(0)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(0)); return false; } cOsdServerMenu *menu=menuitem->GetMenu(); int NewCurrent=menuitem->Index(); menu->SetCurrent(NewCurrent); return true; } bool cCommandProcessor::CmdItemSetSelectable(cCommand &cmd, cOsdServerOsdItem *osditem, bool Selectable) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } if (!cmd.HasMaxArgument(0)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(0)); return false; } osditem->SetSelectable(Selectable); return true; } bool cCommandProcessor::CmdItemSetText(cCommand &cmd, cOsdServerOsdItem *osditem) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } if (!cmd.HasMinArgument(1)) { Connection->ReplyErrorMissingParameter(); return false; } if (!cmd.HasMaxArgument(1)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(1)); return false; } osditem->SetText(cmd.GetArg(0)); return true; } bool cCommandProcessor::CmdEditStrItemGetValue(cCommand &cmd, cOsdServerMenuEditStrItem *editobj) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } bool quoted=cmd.ArgScanFlag("Quoted"); if (!cmd.HasMaxArgument(0)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(0)); return false; } char *value = editobj->GetValue(); Connection->ReplyTextGetValue(quoted, value); free(value); return true; } bool cCommandProcessor::CmdEditIntItemGetValue(cCommand &cmd, cOsdServerMenuEditIntItem *editobj) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } if (!cmd.HasMaxArgument(0)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(0)); return false; } int value = editobj->GetValue(); char str[20]; sprintf(str, "%i", value); Connection->ReplyTextGetValue(true, str); return true; } bool cCommandProcessor::CmdEditListItemGetValue(cCommand &cmd, cOsdServerMenuEditListItem *editobj) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } bool Name = cmd.ArgScanFlag("Name"); bool quoted=cmd.ArgScanFlag("Quoted"); if (!cmd.HasMaxArgument(0)) { Connection->ReplyErrorUnknownParameter(cmd.GetArg(0)); return false; } if (Name) { char *value = editobj->GetValueName(); Connection->ReplyTextGetValue(quoted, value); free(value); } else { int value = editobj->GetValue(); char str[20]; sprintf(str, "%i", value+1); Connection->ReplyTextGetValue(quoted, str); } return true; } bool cCommandProcessor::CmdObjectEnableEvent(cCommand &cmd, cOsdServerObject *obj) { if (cmd.IsAssignment()) { Connection->ReplyErrorAssign(); return false; } for (int i=0;cmd.GetArg(i);i++) { int key = cConnection::ParseKey(cmd.GetArg(i)); if (key >= 0) { obj->EnableEvent((eKeys)key); } else if (0==strcmp(cmd.GetArg(i),"close")) { obj->EnableEvent(cOsdServerObject::cEvent::CloseEvent); } else if (0==strcmp(cmd.GetArg(i),"focus")) { obj->EnableEvent(cOsdServerObject::cEvent::FocusEvent); } else if (0==strcmp(cmd.GetArg(i),"blur")) { obj->EnableEvent(cOsdServerObject::cEvent::BlurEvent); } else if (0==strcmp(cmd.GetArg(i),"edit")) { obj->EnableEvent(cOsdServerObject::cEvent::EditEvent); } else { Connection->ReplyErrorUnknownEvent(cmd.GetArg(i)); } } return true; } // Context based command dispatchers bool cCommandProcessor::ProcessGlobalCommand(cCommand &cmd, int &timeout) { if (cmd.IsCommand("QUIT")) { if (CmdQuit(cmd)) { State=stateTerminated; return true; } } else if (cmd.IsCommand("IDLE")) { CmdIdle(cmd); } else if (cmd.IsCommand("MESSAGE")) { CmdMessage(cmd); } else if (cmd.IsCommand("NEW")) { CmdNew(cmd); } else if (cmd.IsCommand("DELETE")) { CmdDelete(cmd); } else if (cmd.IsCommand("ENTERLOCAL")) { CmdEnterLocal(cmd); } else if (cmd.IsCommand("LEAVELOCAL")) { CmdLeaveLocal(cmd); } else { return false; } Connection->ReplyReady(); return true; } bool cCommandProcessor::ProcessMenuCommand(cCommand &cmd, int &timeout, cOsdServerMenu *menu) { if (cmd.IsCommand("GETCURRENT")) { CmdMenuGetCurrent(cmd,menu); } else if (cmd.IsCommand("SETCURRENT")) { CmdMenuSetCurrent(cmd,menu); } else if (cmd.IsCommand("ADD")) { CmdMenuAdd(cmd,menu); } else if (cmd.IsCommand("ADDNEW")) { CmdMenuAddNew(cmd,menu); } else if (cmd.IsCommand("ADDSUBMENU")) { CmdMenuAddSubMenu(cmd,menu); } else if (cmd.IsCommand("SHOW")) { CmdMenuShow(cmd,menu); } else if (cmd.IsCommand("SENDSTATE")) { CmdMenuSendState(cmd,menu); } else if (cmd.IsCommand("SETCOLORKEYTEXT")) { CmdMenuSetColorKeyText(cmd,menu); } else if (cmd.IsCommand("SETCOLUMNS")) { CmdMenuSetColumns(cmd,menu); } else if (cmd.IsCommand("SLEEPEVENT")) { if (CmdMenuSleepEvent(cmd,timeout,menu)) { return true; } } else { return false; } Connection->ReplyReady(); return true; } bool cCommandProcessor::ProcessMenuItemCommand(cCommand &cmd, int &timeout, cOsdServerMenuItem *menuitem) { if (cmd.IsCommand("SETCURRENT")) { CmdItemSetCurrent(cmd,menuitem); } else { return false; } Connection->ReplyReady(); return true; } bool cCommandProcessor::ProcessOsdItemCommand(cCommand &cmd, int &timeout, cOsdServerOsdItem *osditem) { if (cmd.IsCommand("SETSELECTABLE")) { CmdItemSetSelectable(cmd,osditem,true); } else if (cmd.IsCommand("SETUNSELECTABLE")) { CmdItemSetSelectable(cmd,osditem,false); } else if (cmd.IsCommand("SETTEXT")) { CmdItemSetText(cmd,osditem); } else { return false; } Connection->ReplyReady(); return true; } bool cCommandProcessor::ProcessEditStrItemCommand(cCommand &cmd, int &timeout, cOsdServerMenuEditStrItem *edititem) { if (cmd.IsCommand("GETVALUE")) { CmdEditStrItemGetValue(cmd,edititem); } else { return false; } Connection->ReplyReady(); return true; } bool cCommandProcessor::ProcessEditIntItemCommand(cCommand &cmd, int &timeout, cOsdServerMenuEditIntItem *edititem) { if (cmd.IsCommand("GETVALUE")) { CmdEditIntItemGetValue(cmd,edititem); } else { return false; } Connection->ReplyReady(); return true; } bool cCommandProcessor::ProcessEditListItemCommand(cCommand &cmd, int &timeout, cOsdServerMenuEditListItem *edititem) { if (cmd.IsCommand("GETVALUE")) { CmdEditListItemGetValue(cmd,edititem); } else { return false; } Connection->ReplyReady(); return true; } bool cCommandProcessor::ProcessOsdObjectCommand(cCommand &cmd, int &timeout, cOsdServerObject *osdobj) { if (cmd.IsCommand("ENABLEEVENT")) { CmdObjectEnableEvent(cmd,osdobj); } else if (cmd.IsCommand("DELETE")) { CmdObjectDelete(cmd,osdobj); } else { return false; } Connection->ReplyReady(); return true; } bool cCommandProcessor::ProcessCommand(cCommand &cmd, int &timeout) { if (!cmd.GetContext()) { return ProcessGlobalCommand(cmd,timeout); } cOsdServerObject *ContextObj=LocalContext->GetName(cmd.GetContext()); if (!ContextObj) { Connection->ReplyErrorUnknownObject(cmd.GetContext()); Connection->ReplyReady(); return true; } if (ProcessOsdObjectCommand(cmd,timeout,ContextObj)) return true; if (ContextObj->IsMenuItem()) { if (ProcessMenuItemCommand(cmd,timeout,(cOsdServerMenuItem*)ContextObj)) return true; } switch (ContextObj->ClassId()) { case cOsdServerObject::clsidMenu: if (ProcessMenuCommand(cmd,timeout,ContextObj->Cast())) return true; break; case cOsdServerObject::clsidOsdItem: if (ProcessOsdItemCommand(cmd,timeout,ContextObj->Cast())) return true; break; case cOsdServerObject::clsidMenuEditStrItem: if (ProcessEditStrItemCommand(cmd,timeout,ContextObj->Cast())) return true; break; case cOsdServerObject::clsidMenuEditIntItem: if (ProcessEditIntItemCommand(cmd,timeout,ContextObj->Cast())) return true; break; case cOsdServerObject::clsidMenuEditListItem: if (ProcessEditListItemCommand(cmd,timeout,ContextObj->Cast())) return true; break; } return false; } void cCommandProcessor::ProcessWelcome(cCommand &cmd, int &timeout) { if (cmd.IsCommand("QUIT")) { if (CmdQuit(cmd)) { State=stateTerminated; return; } } else if (cmd.IsCommand("VERSION")) { if (CmdVersion(cmd)) { State=stateReady; Connection->ReplyReady(); return; } } else { Connection->ReplyErrorMissingVersion(); } Connection->ReplyWelcome(); } void cCommandProcessor::ProcessUnknown(cCommand &cmd) { if (cmd.GetCommand()) Connection->ReplyDebug(100, "Unknown Command: \"%s\"",cmd.GetCommand()); if (cmd.GetContext()) Connection->ReplyDebug(100, "Context: \"%s\"",cmd.GetContext()); if (cmd.GetAssignVar()) Connection->ReplyDebug(100, "Assignment to: \"%s\"",cmd.GetAssignVar()); for (int i=0;cmd.GetArg(i);i++) { Connection->ReplyDebug(100, "%s %i: \"%s\"",cmd.IsArgFlag(i)?"Arg/Flag":"Argument",i,cmd.GetArg(i)); } Connection->ReplyErrorUnknownCommand(cmd.GetCommand()?cmd.GetCommand():""); Connection->ReplyReady(); } bool cCommandProcessor::HandleSleepEvent(int &timeout) { cOsdServerObject::cTriggeredEvent event; if (SleepEventObj->PollEvent(event)) { switch (event.type) { case cOsdServerObject::cEvent::KeyEvent: Connection->ReplyMessageKeyEvent(event.source->Name(), event.key); return true; case cOsdServerObject::cEvent::CloseEvent: Connection->ReplyMessageEvent(event.source->Name(), "close"); return true; case cOsdServerObject::cEvent::EditEvent: Connection->ReplyMessageEvent(event.source->Name(), "edit"); return true; case cOsdServerObject::cEvent::FocusEvent: Connection->ReplyMessageEvent(event.source->Name(), "focus"); return true; case cOsdServerObject::cEvent::BlurEvent: Connection->ReplyMessageEvent(event.source->Name(), "blur"); return true; case cOsdServerObject::cEvent::NullEvent: ; } } if (SleepEventObj->IsDetached()) { Connection->ReplyErrorObjectNotActive(); return true; } if (SleepEventTimeout.tv_sec==0) { timeout=100; return false; } timeval t; gettimeofday(&t,NULL); int ms=1000*(SleepEventTimeout.tv_sec-t.tv_sec) +(SleepEventTimeout.tv_usec-t.tv_usec)/1000; if (ms<=0) { Connection->ReplyMessageEventTimeout(); return true; } timeout=min(ms,100); return false; } // Main command handler bool cCommandProcessor::Action(cSelect &Selector) { for (;;) { int timeout=0; char *line; cCommand cmd; if (!Connection->IsConnected()) { State=stateTerminated; } switch (State) { case stateNew: Connection->ReplyWelcome(); State=stateWelcome; break; case stateTerminated: return false; case stateSleepEvent: if (HandleSleepEvent(timeout)) { State=stateReady; Connection->ReplyReady(); } break; case stateReady: line=Connection->ReadLine(); if (!line) { Connection->AddSelect(Selector); return true; } if (cmd.ParseLine(*Connection,line)) { if (!ProcessCommand(cmd,timeout)) ProcessUnknown(cmd); } Connection->DelLine(); break; case stateWelcome: line=Connection->ReadLine(); if (!line) { Connection->AddSelect(Selector); return true; } if (cmd.ParseLine(*Connection,line)) { ProcessWelcome(cmd,timeout); } Connection->DelLine(); break; } // end switch (State) if (timeout) { Selector.SetMinTimeoutMs(timeout); return true; } } // end for (;;) return false; // never reached } osdserver-0.1.3/interpreter.h0000664000175000000500000002041310677754252014414 0ustar udosrc#ifndef __OSDSERVER_INTERPRETER_H #define __OSDSERVER_INTERPRETER_H #include #include #include #include #include "tools.h" #include "connection.h" #include "osdobjects.h" class cConnection : public cConnectionBase { public: cConnection(); public: void ReplyReady() { Reply(200,"Ready."); } void ReplyWelcome() { Reply(201,"Welcome to OSDServer version %s, VDR version %s.",PLUGINVERSION,VDRVERSION); } void ReplyGoodBye() { Reply(202,"Good Bye."); } // error 400: Access Denied. See tools.c. void ReplyErrorIdentifier(const char *id) { Reply(401,"Error: Expected an identifier: %s",id); } void ReplyErrorIdentifierReserved(const char *id) { Reply(402,"Error: Reserved identifier: %s",id); } void ReplyErrorUnknownObject(const char *c) { Reply(403,"Error: Unknown object %s.",c); } void ReplyErrorUnknownCommand(const char *s) { Reply(404,"Error: Unknown command %s",s); } void ReplyErrorMissingVersion() { Reply(405,"Error: Need to specify VERSION first"); } void ReplyErrorUnknownVersion(const char *s) { Reply(406,"Error: Protocol version %s not supported.",s); } void ReplyErrorMissingParameter() { Reply(407,"Error: Missing Parameter."); } void ReplyErrorUnknownParameter(const char *par) { Reply(408,"Error: Unknown Parameter: %s",par); } void ReplyErrorAssign() { Reply(409,"Error: Command does not return an object to assign."); } void ReplyErrorParameter(const char *s) { Reply(410,"Error: Parameter Error: %s",s); } void ReplyErrorUnknownEvent(const char *s) { Reply(411,"Error: Unknown Event %s.",s); } void ReplyErrorObjectNotActive() { Reply(412,"Error: Object is not active."); } void ReplyErrorUnknownState(const char *s) { Reply(413,"Error: State is not known: %s.",s); } void ReplyErrorColorKeyAlreadySet() { Reply(414,"Error: All color keys already set."); } void ReplyErrorParameterInt(const char *s) { Reply(414,"Error: Expected numeric parameter: %s",s); } void ReplyErrorParameterRange(const char *s) { Reply(415,"Error: Numeric parameter out of range: %s",s); } void ReplyErrorConflictingParameters(const char *s1, const char *s2) { Reply(416,"Error: Conflicting parameters: %s %s",s1,s2); } void ReplyErrorParameterInvalid(const char *s) { Reply(417,"Error: Invalid parameter: %s",s); } void ReplyErrorUnknownType(const char *s) { Reply(418,"Error: Unknown object type: %s",s); } void ReplyErrorIncompatibleType(const char *s) { Reply(419,"Error: Incompatible object type: %s",s); } void ReplyErrorNoParentContext() { Reply(420, "Error: No parent context"); } void ReplyMessageEvent(const char *who, const char *event) { Reply(300,"%s %s",who,event); } void ReplyMessageKeyEvent(const char *who, eKeys k) { char buffer[32]; ReplyMessageEvent(who,KeyString(buffer,sizeof buffer,k)); } void ReplyMessageEventTimeout() { Reply(301,"timeout"); } void ReplyMessageMenuCurrent(int i, const char *s) { if (s) Reply(302,"%i %s",i,s); else Reply(302,"%i",i); } void ReplyTextGetValue(bool quoted, char *value) { ReplyPlain(quoted?500:600, value); } static char* KeyString(char *buffer, int bufferlen, eKeys key); static int ParseKey(const char *str); }; class cCommandProcessor : public cListObject { public: cCommandProcessor(cConnection *Connection); ~cCommandProcessor(); bool Action(cSelect &Selector); class cCommand { private: char *Command; char *Context; char *AssignVar; class cArg { const char *text; bool isFlag; public: const char* Text() { return text; } bool IsFlag() { return isFlag; } bool GetInt(int &res) { char *p; res = strtol(text, &p, 10); return (*p==0); } cArg(const char *Text, bool IsFlag) { text = Text; isFlag = IsFlag; } }; std::vector args; typedef std::vector::iterator args_iterator; public: cCommand(); bool ParseLine(cConnection &Connection, char *line); const char* GetCommand() { return Command; } const char* GetContext() { return Context; } const char* GetAssignVar() { return AssignVar; } bool IsCommand(const char *cmd) { return strcasecmp(Command,cmd)==0; } bool IsAssignment() { return AssignVar!=NULL; } bool HasMaxArgument(unsigned int max) { return args.size()<=max; } bool HasMinArgument(unsigned int min) { return args.size()>=min; } int GetArgCount() { return args.size(); } const char* GetArg(unsigned int i) { if (i>=0 && i=0 && i=0 && i