1 from ChannelSelection import ChannelSelection, BouquetSelector
3 from Components.ActionMap import ActionMap, HelpableActionMap
4 from Components.ActionMap import NumberActionMap
5 from Components.Harddisk import harddiskmanager
6 from Components.Input import Input
7 from Components.Label import Label
8 from Components.PluginComponent import plugins
9 from Components.ServiceEventTracker import ServiceEventTracker
10 from Components.Sources.Boolean import Boolean
11 from Components.config import config, ConfigBoolean, ConfigClock
12 from Components.SystemInfo import SystemInfo
13 from EpgSelection import EPGSelection
14 from Plugins.Plugin import PluginDescriptor
16 from Screen import Screen
17 from Screens.ChoiceBox import ChoiceBox
18 from Screens.Dish import Dish
19 from Screens.EventView import EventViewEPGSelect, EventViewSimple
20 from Screens.InputBox import InputBox
21 from Screens.MessageBox import MessageBox
22 from Screens.MinuteInput import MinuteInput
23 from Screens.TimerSelection import TimerSelection
24 from Screens.PictureInPicture import PictureInPicture
25 from Screens.SubtitleDisplay import SubtitleDisplay
26 from Screens.RdsDisplay import RdsInfoDisplay, RassInteractive
27 from Screens.TimeDateInput import TimeDateInput
28 from ServiceReference import ServiceReference
30 from Tools import Notifications
31 from Tools.Directories import SCOPE_HDD, resolveFilename, fileExists
33 from enigma import eTimer, eServiceCenter, eDVBServicePMTHandler, iServiceInformation, \
34 iPlayableService, eServiceReference, eEPGCache
36 from time import time, localtime, strftime
37 from os import stat as os_stat
38 from bisect import insort
40 from RecordTimer import RecordTimerEntry, RecordTimer
43 from Menu import MainMenu, mdom
47 self.dishDialog = self.session.instantiateDialog(Dish)
49 class InfoBarShowHide:
50 """ InfoBar show/hide control, accepts toggleShow and hide actions, might start
58 self["ShowHideActions"] = ActionMap( ["InfobarShowHideActions"] ,
60 "toggleShow": self.toggleShow,
62 }, 1) # lower prio to make it possible to override ok and cancel..
64 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
66 iPlayableService.evStart: self.serviceStarted,
69 self.__state = self.STATE_SHOWN
72 self.hideTimer = eTimer()
73 self.hideTimer.callback.append(self.doTimerHide)
74 self.hideTimer.start(5000, True)
76 self.onShow.append(self.__onShow)
77 self.onHide.append(self.__onHide)
79 def serviceStarted(self):
81 if config.usage.show_infobar_on_zap.value:
85 self.__state = self.STATE_SHOWN
88 def startHideTimer(self):
89 if self.__state == self.STATE_SHOWN and not self.__locked:
90 idx = config.usage.infobar_timeout.index
92 self.hideTimer.start(idx*1000, True)
95 self.__state = self.STATE_HIDDEN
101 def doTimerHide(self):
102 self.hideTimer.stop()
103 if self.__state == self.STATE_SHOWN:
106 def toggleShow(self):
107 if self.__state == self.STATE_SHOWN:
109 self.hideTimer.stop()
110 elif self.__state == self.STATE_HIDDEN:
114 self.__locked = self.__locked + 1
117 self.hideTimer.stop()
119 def unlockShow(self):
120 self.__locked = self.__locked - 1
122 self.startHideTimer()
124 # def startShow(self):
125 # self.instance.m_animation.startMoveAnimation(ePoint(0, 600), ePoint(0, 380), 100)
126 # self.__state = self.STATE_SHOWN
128 # def startHide(self):
129 # self.instance.m_animation.startMoveAnimation(ePoint(0, 380), ePoint(0, 600), 100)
130 # self.__state = self.STATE_HIDDEN
132 class NumberZap(Screen):
139 self.close(int(self["number"].getText()))
141 def keyNumberGlobal(self, number):
142 self.Timer.start(3000, True) #reset timer
143 self.field = self.field + str(number)
144 self["number"].setText(self.field)
145 if len(self.field) >= 4:
148 def __init__(self, session, number):
149 Screen.__init__(self, session)
150 self.field = str(number)
152 self["channel"] = Label(_("Channel:"))
154 self["number"] = Label(self.field)
156 self["actions"] = NumberActionMap( [ "SetupActions" ],
160 "1": self.keyNumberGlobal,
161 "2": self.keyNumberGlobal,
162 "3": self.keyNumberGlobal,
163 "4": self.keyNumberGlobal,
164 "5": self.keyNumberGlobal,
165 "6": self.keyNumberGlobal,
166 "7": self.keyNumberGlobal,
167 "8": self.keyNumberGlobal,
168 "9": self.keyNumberGlobal,
169 "0": self.keyNumberGlobal
172 self.Timer = eTimer()
173 self.Timer.callback.append(self.keyOK)
174 self.Timer.start(3000, True)
176 class InfoBarNumberZap:
177 """ Handles an initial number for NumberZapping """
179 self["NumberActions"] = NumberActionMap( [ "NumberActions"],
181 "1": self.keyNumberGlobal,
182 "2": self.keyNumberGlobal,
183 "3": self.keyNumberGlobal,
184 "4": self.keyNumberGlobal,
185 "5": self.keyNumberGlobal,
186 "6": self.keyNumberGlobal,
187 "7": self.keyNumberGlobal,
188 "8": self.keyNumberGlobal,
189 "9": self.keyNumberGlobal,
190 "0": self.keyNumberGlobal,
193 def keyNumberGlobal(self, number):
194 # print "You pressed number " + str(number)
196 if isinstance(self, InfoBarPiP) and self.pipHandles0Action():
197 self.pipDoHandle0Action()
199 self.servicelist.recallPrevService()
201 if self.has_key("TimeshiftActions") and not self.timeshift_enabled:
202 self.session.openWithCallback(self.numberEntered, NumberZap, number)
204 def numberEntered(self, retval):
205 # print self.servicelist
207 self.zapToNumber(retval)
209 def searchNumberHelper(self, serviceHandler, num, bouquet):
210 servicelist = serviceHandler.list(bouquet)
211 if not servicelist is None:
213 serviceIterator = servicelist.getNext()
214 if not serviceIterator.valid(): #check end of list
216 playable = not (serviceIterator.flags & (eServiceReference.isMarker|eServiceReference.isDirectory))
219 if not num: #found service with searched number ?
220 return serviceIterator, 0
223 def zapToNumber(self, number):
224 bouquet = self.servicelist.bouquet_root
226 serviceHandler = eServiceCenter.getInstance()
227 if not config.usage.multibouquet.value:
228 service, number = self.searchNumberHelper(serviceHandler, number, bouquet)
230 bouquetlist = serviceHandler.list(bouquet)
231 if not bouquetlist is None:
233 bouquet = bouquetlist.getNext()
234 if not bouquet.valid(): #check end of list
236 if bouquet.flags & eServiceReference.isDirectory:
237 service, number = self.searchNumberHelper(serviceHandler, number, bouquet)
238 if not service is None:
239 if self.servicelist.getRoot() != bouquet: #already in correct bouquet?
240 self.servicelist.clearPath()
241 if self.servicelist.bouquet_root != bouquet:
242 self.servicelist.enterPath(self.servicelist.bouquet_root)
243 self.servicelist.enterPath(bouquet)
244 self.servicelist.setCurrentSelection(service) #select the service in servicelist
245 self.servicelist.zap()
247 config.misc.initialchannelselection = ConfigBoolean(default = True)
249 class InfoBarChannelSelection:
250 """ ChannelSelection - handles the channelSelection dialog and the initial
251 channelChange actions which open the channelSelection dialog """
254 self.servicelist = self.session.instantiateDialog(ChannelSelection)
256 if config.misc.initialchannelselection.value:
257 self.onShown.append(self.firstRun)
259 self["ChannelSelectActions"] = HelpableActionMap(self, "InfobarChannelSelection",
261 "switchChannelUp": (self.switchChannelUp, _("open servicelist(up)")),
262 "switchChannelDown": (self.switchChannelDown, _("open servicelist(down)")),
263 "zapUp": (self.zapUp, _("previous channel")),
264 "zapDown": (self.zapDown, _("next channel")),
265 "historyBack": (self.historyBack, _("previous channel in history")),
266 "historyNext": (self.historyNext, _("next channel in history")),
267 "openServiceList": (self.openServiceList, _("open servicelist")),
270 def showTvChannelList(self, zap=False):
271 self.servicelist.setModeTv()
273 self.servicelist.zap()
274 self.session.execDialog(self.servicelist)
276 def showRadioChannelList(self, zap=False):
277 self.servicelist.setModeRadio()
279 self.servicelist.zap()
280 self.session.execDialog(self.servicelist)
283 self.onShown.remove(self.firstRun)
284 config.misc.initialchannelselection.value = False
285 config.misc.initialchannelselection.save()
286 self.switchChannelDown()
288 def historyBack(self):
289 self.servicelist.historyBack()
291 def historyNext(self):
292 self.servicelist.historyNext()
294 def switchChannelUp(self):
295 self.servicelist.moveUp()
296 self.session.execDialog(self.servicelist)
298 def switchChannelDown(self):
299 self.servicelist.moveDown()
300 self.session.execDialog(self.servicelist)
302 def openServiceList(self):
303 self.session.execDialog(self.servicelist)
306 if self.servicelist.inBouquet():
307 prev = self.servicelist.getCurrentSelection()
309 prev = prev.toString()
311 if config.usage.quickzap_bouquet_change.value:
312 if self.servicelist.atBegin():
313 self.servicelist.prevBouquet()
314 self.servicelist.moveUp()
315 cur = self.servicelist.getCurrentSelection()
316 if not cur or (not (cur.flags & 64)) or cur.toString() == prev:
319 self.servicelist.moveUp()
320 self.servicelist.zap()
323 if self.servicelist.inBouquet():
324 prev = self.servicelist.getCurrentSelection()
326 prev = prev.toString()
328 if config.usage.quickzap_bouquet_change.value and self.servicelist.atEnd():
329 self.servicelist.nextBouquet()
331 self.servicelist.moveDown()
332 cur = self.servicelist.getCurrentSelection()
333 if not cur or (not (cur.flags & 64)) or cur.toString() == prev:
336 self.servicelist.moveDown()
337 self.servicelist.zap()
340 """ Handles a menu action, to open the (main) menu """
342 self["MenuActions"] = HelpableActionMap(self, "InfobarMenuActions",
344 "mainMenu": (self.mainMenu, _("Enter main menu...")),
346 self.session.infobar = None
349 print "loading mainmenu XML..."
350 menu = mdom.getroot()
351 assert menu.tag == "menu", "root element in menu must be 'menu'!"
353 self.session.infobar = self
354 # so we can access the currently active infobar from screens opened from within the mainmenu
355 # at the moment used from the SubserviceSelection
357 self.session.openWithCallback(self.mainMenuClosed, MainMenu, menu)
359 def mainMenuClosed(self, *val):
360 self.session.infobar = None
362 class InfoBarSimpleEventView:
363 """ Opens the Eventview for now/next """
365 self["EPGActions"] = HelpableActionMap(self, "InfobarEPGActions",
367 "showEventInfo": (self.openEventView, _("show event details")),
368 "showInfobarOrEpgWhenInfobarAlreadyVisible": self.showEventInfoWhenNotVisible,
371 def showEventInfoWhenNotVisible(self):
378 def openEventView(self):
380 self.epglist = epglist
381 service = self.session.nav.getCurrentService()
382 ref = self.session.nav.getCurrentlyPlayingServiceReference()
383 info = service.info()
391 self.session.open(EventViewSimple, epglist[0], ServiceReference(ref), self.eventViewCallback)
393 def eventViewCallback(self, setEvent, setService, val): #used for now/next displaying
394 epglist = self.epglist
397 epglist[0] = epglist[1]
401 class SimpleServicelist:
402 def __init__(self, services):
403 self.services = services
404 self.length = len(services)
407 def selectService(self, service):
413 while self.services[self.current].ref != service:
415 if self.current >= self.length:
419 def nextService(self):
422 if self.current+1 < self.length:
427 def prevService(self):
430 if self.current-1 > -1:
433 self.current = self.length - 1
435 def currentService(self):
436 if not self.length or self.current >= self.length:
438 return self.services[self.current]
441 """ EPG - Opens an EPG list when the showEPGList action fires """
443 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
445 iPlayableService.evUpdatedEventInfo: self.__evEventInfoChanged,
448 self.is_now_next = False
450 self.bouquetSel = None
451 self.eventView = None
452 self["EPGActions"] = HelpableActionMap(self, "InfobarEPGActions",
454 "showEventInfo": (self.openEventView, _("show EPG...")),
455 "showEventInfoPlugin": (self.showEventInfoPlugins, _("show single service EPG...")),
456 "showInfobarOrEpgWhenInfobarAlreadyVisible": self.showEventInfoWhenNotVisible,
459 def showEventInfoWhenNotVisible(self):
466 def zapToService(self, service):
467 if not service is None:
468 if self.servicelist.getRoot() != self.epg_bouquet: #already in correct bouquet?
469 self.servicelist.clearPath()
470 if self.servicelist.bouquet_root != self.epg_bouquet:
471 self.servicelist.enterPath(self.servicelist.bouquet_root)
472 self.servicelist.enterPath(self.epg_bouquet)
473 self.servicelist.setCurrentSelection(service) #select the service in servicelist
474 self.servicelist.zap()
476 def getBouquetServices(self, bouquet):
478 servicelist = eServiceCenter.getInstance().list(bouquet)
479 if not servicelist is None:
481 service = servicelist.getNext()
482 if not service.valid(): #check if end of list
484 if service.flags & (eServiceReference.isDirectory | eServiceReference.isMarker): #ignore non playable services
486 services.append(ServiceReference(service))
489 def openBouquetEPG(self, bouquet, withCallback=True):
490 services = self.getBouquetServices(bouquet)
492 self.epg_bouquet = bouquet
494 self.dlg_stack.append(self.session.openWithCallback(self.closed, EPGSelection, services, self.zapToService, None, self.changeBouquetCB))
496 self.session.open(EPGSelection, services, self.zapToService, None, self.changeBouquetCB)
498 def changeBouquetCB(self, direction, epg):
501 self.bouquetSel.down()
504 bouquet = self.bouquetSel.getCurrent()
505 services = self.getBouquetServices(bouquet)
507 self.epg_bouquet = bouquet
508 epg.setServices(services)
510 def closed(self, ret=False):
511 closedScreen = self.dlg_stack.pop()
512 if self.bouquetSel and closedScreen == self.bouquetSel:
513 self.bouquetSel = None
514 elif self.eventView and closedScreen == self.eventView:
515 self.eventView = None
517 dlgs=len(self.dlg_stack)
519 self.dlg_stack[dlgs-1].close(dlgs > 1)
521 def openMultiServiceEPG(self, withCallback=True):
522 bouquets = self.servicelist.getBouquetList()
527 if cnt > 1: # show bouquet list
529 self.bouquetSel = self.session.openWithCallback(self.closed, BouquetSelector, bouquets, self.openBouquetEPG, enableWrapAround=True)
530 self.dlg_stack.append(self.bouquetSel)
532 self.bouquetSel = self.session.open(BouquetSelector, bouquets, self.openBouquetEPG, enableWrapAround=True)
534 self.openBouquetEPG(bouquets[0][1], withCallback)
536 def changeServiceCB(self, direction, epg):
539 self.serviceSel.nextService()
541 self.serviceSel.prevService()
542 epg.setService(self.serviceSel.currentService())
544 def SingleServiceEPGClosed(self, ret=False):
545 self.serviceSel = None
547 def openSingleServiceEPG(self):
548 ref=self.session.nav.getCurrentlyPlayingServiceReference()
550 if self.servicelist.getMutableList() is not None: # bouquet in channellist
551 current_path = self.servicelist.getRoot()
552 services = self.getBouquetServices(current_path)
553 self.serviceSel = SimpleServicelist(services)
554 if self.serviceSel.selectService(ref):
555 self.session.openWithCallback(self.SingleServiceEPGClosed, EPGSelection, ref, serviceChangeCB = self.changeServiceCB)
557 self.session.openWithCallback(self.SingleServiceEPGClosed, EPGSelection, ref)
559 self.session.open(EPGSelection, ref)
561 def showEventInfoPlugins(self):
562 list = [(p.name, boundFunction(self.runPlugin, p)) for p in plugins.getPlugins(where = PluginDescriptor.WHERE_EVENTINFO)]
565 list.append((_("show single service EPG..."), self.openSingleServiceEPG))
566 self.session.openWithCallback(self.EventInfoPluginChosen, ChoiceBox, title=_("Please choose an extension..."), list = list, skin_name = "EPGExtensionsList")
568 self.openSingleServiceEPG()
570 def runPlugin(self, plugin):
571 plugin(session = self.session, servicelist = self.servicelist)
573 def EventInfoPluginChosen(self, answer):
574 if answer is not None:
577 def openSimilarList(self, eventid, refstr):
578 self.session.open(EPGSelection, refstr, None, eventid)
580 def getNowNext(self):
582 service = self.session.nav.getCurrentService()
583 info = service and service.info()
584 ptr = info and info.getEvent(0)
587 ptr = info and info.getEvent(1)
590 self.epglist = epglist
592 def __evEventInfoChanged(self):
593 if self.is_now_next and len(self.dlg_stack) == 1:
595 assert self.eventView
597 self.eventView.setEvent(self.epglist[0])
599 def openEventView(self):
600 ref = self.session.nav.getCurrentlyPlayingServiceReference()
602 epglist = self.epglist
604 self.is_now_next = False
605 epg = eEPGCache.getInstance()
606 ptr = ref and ref.valid() and epg.lookupEventTime(ref, -1)
609 ptr = epg.lookupEventTime(ref, ptr.getBeginTime(), +1)
613 self.is_now_next = True
615 self.eventView = self.session.openWithCallback(self.closed, EventViewEPGSelect, self.epglist[0], ServiceReference(ref), self.eventViewCallback, self.openSingleServiceEPG, self.openMultiServiceEPG, self.openSimilarList)
616 self.dlg_stack.append(self.eventView)
618 print "no epg for the service avail.. so we show multiepg instead of eventinfo"
619 self.openMultiServiceEPG(False)
621 def eventViewCallback(self, setEvent, setService, val): #used for now/next displaying
622 epglist = self.epglist
625 epglist[0]=epglist[1]
629 class InfoBarRdsDecoder:
630 """provides RDS and Rass support/display"""
632 self.rds_display = self.session.instantiateDialog(RdsInfoDisplay)
633 self.rass_interactive = None
635 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
637 iPlayableService.evEnd: self.__serviceStopped,
638 iPlayableService.evUpdatedRassSlidePic: self.RassSlidePicChanged
641 self["RdsActions"] = ActionMap(["InfobarRdsActions"],
643 "startRassInteractive": self.startRassInteractive
646 self["RdsActions"].setEnabled(False)
648 self.onLayoutFinish.append(self.rds_display.show)
649 self.rds_display.onRassInteractivePossibilityChanged.append(self.RassInteractivePossibilityChanged)
651 def RassInteractivePossibilityChanged(self, state):
652 self["RdsActions"].setEnabled(state)
654 def RassSlidePicChanged(self):
655 if not self.rass_interactive:
656 service = self.session.nav.getCurrentService()
657 decoder = service and service.rdsDecoder()
659 decoder.showRassSlidePicture()
661 def __serviceStopped(self):
662 if self.rass_interactive is not None:
663 rass_interactive = self.rass_interactive
664 self.rass_interactive = None
665 rass_interactive.close()
667 def startRassInteractive(self):
668 self.rds_display.hide()
669 self.rass_interactive = self.session.openWithCallback(self.RassInteractiveClosed, RassInteractive)
671 def RassInteractiveClosed(self, *val):
672 if self.rass_interactive is not None:
673 self.rass_interactive = None
674 self.RassSlidePicChanged()
675 self.rds_display.show()
678 """handles actions like seeking, pause"""
680 SEEK_STATE_PLAY = (0, 0, 0, ">")
681 SEEK_STATE_PAUSE = (1, 0, 0, "||")
682 SEEK_STATE_EOF = (1, 0, 0, "END")
684 def __init__(self, actionmap = "InfobarSeekActions", useSeekBackHack=True):
685 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
687 iPlayableService.evSeekableStatusChanged: self.__seekableStatusChanged,
688 iPlayableService.evStart: self.__serviceStarted,
690 iPlayableService.evEOF: self.__evEOF,
691 iPlayableService.evSOF: self.__evSOF,
694 class InfoBarSeekActionMap(HelpableActionMap):
695 def __init__(self, screen, *args, **kwargs):
696 HelpableActionMap.__init__(self, screen, *args, **kwargs)
699 def action(self, contexts, action):
700 print "action:", action
701 if action[:5] == "seek:":
702 time = int(action[5:])
703 self.screen.doSeekRelative(time * 90000)
705 elif action[:8] == "seekdef:":
706 key = int(action[8:])
707 time = (-config.seek.selfdefined_13.value, False, config.seek.selfdefined_13.value,
708 -config.seek.selfdefined_46.value, False, config.seek.selfdefined_46.value,
709 -config.seek.selfdefined_79.value, False, config.seek.selfdefined_79.value)[key-1]
710 self.screen.doSeekRelative(time * 90000)
713 return HelpableActionMap.action(self, contexts, action)
715 self["SeekActions"] = InfoBarSeekActionMap(self, actionmap,
717 "playpauseService": self.playpauseService,
718 "pauseService": (self.pauseService, _("pause")),
719 "unPauseService": (self.unPauseService, _("continue")),
721 "seekFwd": (self.seekFwd, _("skip forward")),
722 "seekFwdManual": (self.seekFwdManual, _("skip forward (enter time)")),
723 "seekBack": (self.seekBack, _("skip backward")),
724 "seekBackManual": (self.seekBackManual, _("skip backward (enter time)"))
726 # give them a little more priority to win over color buttons
728 self["SeekActions"].setEnabled(False)
730 self.seekstate = self.SEEK_STATE_PLAY
731 self.lastseekstate = self.SEEK_STATE_PLAY
733 self.onPlayStateChanged = [ ]
735 self.lockedBecauseOfSkipping = False
737 self.__seekableStatusChanged()
739 def makeStateForward(self, n):
740 # minspeed = config.seek.stepwise_minspeed.value
741 # repeat = int(config.seek.stepwise_repeat.value)
742 # if minspeed != "Never" and n >= int(minspeed) and repeat > 1:
743 # return (0, n * repeat, repeat, ">> %dx" % n)
745 return (0, n, 0, ">> %dx" % n)
747 def makeStateBackward(self, n):
748 # minspeed = config.seek.stepwise_minspeed.value
749 # repeat = int(config.seek.stepwise_repeat.value)
750 # if minspeed != "Never" and n >= int(minspeed) and repeat > 1:
751 # return (0, -n * repeat, repeat, "<< %dx" % n)
753 return (0, -n, 0, "<< %dx" % n)
755 def makeStateSlowMotion(self, n):
756 return (0, 0, n, "/%d" % n)
758 def isStateForward(self, state):
761 def isStateBackward(self, state):
764 def isStateSlowMotion(self, state):
765 return state[1] == 0 and state[2] > 1
767 def getHigher(self, n, lst):
773 def getLower(self, n, lst):
781 def showAfterSeek(self):
782 if isinstance(self, InfoBarShowHide):
792 service = self.session.nav.getCurrentService()
796 seek = service.seek()
798 if seek is None or not seek.isCurrentlySeekable():
803 def isSeekable(self):
804 if self.getSeek() is None:
808 def __seekableStatusChanged(self):
809 # print "seekable status changed!"
810 if not self.isSeekable():
811 self["SeekActions"].setEnabled(False)
812 # print "not seekable, return to play"
813 self.setSeekState(self.SEEK_STATE_PLAY)
815 self["SeekActions"].setEnabled(True)
818 def __serviceStarted(self):
819 self.seekstate = self.SEEK_STATE_PLAY
820 self.__seekableStatusChanged()
822 def setSeekState(self, state):
823 service = self.session.nav.getCurrentService()
828 if not self.isSeekable():
829 if state not in (self.SEEK_STATE_PLAY, self.SEEK_STATE_PAUSE):
830 state = self.SEEK_STATE_PLAY
832 pauseable = service.pause()
834 if pauseable is None:
835 print "not pauseable."
836 state = self.SEEK_STATE_PLAY
838 self.seekstate = state
840 if pauseable is not None:
841 if self.seekstate[0]:
842 print "resolved to PAUSE"
844 elif self.seekstate[1]:
845 print "resolved to FAST FORWARD"
846 pauseable.setFastForward(self.seekstate[1])
847 elif self.seekstate[2]:
848 print "resolved to SLOW MOTION"
849 pauseable.setSlowMotion(self.seekstate[2])
851 print "resolved to PLAY"
854 for c in self.onPlayStateChanged:
857 self.checkSkipShowHideLock()
861 def playpauseService(self):
862 if self.seekstate != self.SEEK_STATE_PLAY:
863 self.unPauseService()
867 def pauseService(self):
868 if self.seekstate == self.SEEK_STATE_PAUSE:
869 if config.seek.on_pause.value == "play":
870 self.unPauseService()
871 elif config.seek.on_pause.value == "step":
872 self.doSeekRelative(1)
873 elif config.seek.on_pause.value == "last":
874 self.setSeekState(self.lastseekstate)
875 self.lastseekstate = self.SEEK_STATE_PLAY
877 if self.seekstate != self.SEEK_STATE_EOF:
878 self.lastseekstate = self.seekstate
879 self.setSeekState(self.SEEK_STATE_PAUSE);
881 def unPauseService(self):
883 if self.seekstate == self.SEEK_STATE_PLAY:
885 self.setSeekState(self.SEEK_STATE_PLAY)
887 def doSeek(self, pts):
888 seekable = self.getSeek()
893 def doSeekRelative(self, pts):
894 seekable = self.getSeek()
897 prevstate = self.seekstate
899 if self.seekstate == self.SEEK_STATE_EOF:
900 if prevstate == self.SEEK_STATE_PAUSE:
901 self.setSeekState(self.SEEK_STATE_PAUSE)
903 self.setSeekState(self.SEEK_STATE_PLAY)
904 seekable.seekRelative(pts<0 and -1 or 1, abs(pts))
905 if abs(pts) > 100 and config.usage.show_infobar_on_skip.value:
909 if self.seekstate == self.SEEK_STATE_PLAY:
910 self.setSeekState(self.makeStateForward(int(config.seek.enter_forward.value)))
911 elif self.seekstate == self.SEEK_STATE_PAUSE:
912 if len(config.seek.speeds_slowmotion.value):
913 self.setSeekState(self.makeStateSlowMotion(config.seek.speeds_slowmotion.value[-1]))
915 self.setSeekState(self.makeStateForward(int(config.seek.enter_forward.value)))
916 elif self.seekstate == self.SEEK_STATE_EOF:
918 elif self.isStateForward(self.seekstate):
919 speed = self.seekstate[1]
920 if self.seekstate[2]:
921 speed /= self.seekstate[2]
922 speed = self.getHigher(speed, config.seek.speeds_forward.value) or config.seek.speeds_forward.value[-1]
923 self.setSeekState(self.makeStateForward(speed))
924 elif self.isStateBackward(self.seekstate):
925 speed = -self.seekstate[1]
926 if self.seekstate[2]:
927 speed /= self.seekstate[2]
928 speed = self.getLower(speed, config.seek.speeds_backward.value)
930 self.setSeekState(self.makeStateBackward(speed))
932 self.setSeekState(self.SEEK_STATE_PLAY)
933 elif self.isStateSlowMotion(self.seekstate):
934 speed = self.getLower(self.seekstate[2], config.seek.speeds_slowmotion.value) or config.seek.speeds_slowmotion.value[0]
935 self.setSeekState(self.makeStateSlowMotion(speed))
938 seekstate = self.seekstate
939 if seekstate == self.SEEK_STATE_PLAY:
940 self.setSeekState(self.makeStateBackward(int(config.seek.enter_backward.value)))
941 elif seekstate == self.SEEK_STATE_EOF:
942 self.setSeekState(self.makeStateBackward(int(config.seek.enter_backward.value)))
943 self.doSeekRelative(-6)
944 elif seekstate == self.SEEK_STATE_PAUSE:
945 self.doSeekRelative(-1)
946 elif self.isStateForward(seekstate):
949 speed /= seekstate[2]
950 speed = self.getLower(speed, config.seek.speeds_forward.value)
952 self.setSeekState(self.makeStateForward(speed))
954 self.setSeekState(self.SEEK_STATE_PLAY)
955 elif self.isStateBackward(seekstate):
956 speed = -seekstate[1]
958 speed /= seekstate[2]
959 speed = self.getHigher(speed, config.seek.speeds_backward.value) or config.seek.speeds_backward.value[-1]
960 self.setSeekState(self.makeStateBackward(speed))
961 elif self.isStateSlowMotion(seekstate):
962 speed = self.getHigher(seekstate[2], config.seek.speeds_slowmotion.value)
964 self.setSeekState(self.makeStateSlowMotion(speed))
966 self.setSeekState(self.SEEK_STATE_PAUSE)
968 def seekFwdManual(self):
969 self.session.openWithCallback(self.fwdSeekTo, MinuteInput)
971 def fwdSeekTo(self, minutes):
972 print "Seek", minutes, "minutes forward"
973 self.doSeekRelative(minutes * 60 * 90000)
975 def seekBackManual(self):
976 self.session.openWithCallback(self.rwdSeekTo, MinuteInput)
978 def rwdSeekTo(self, minutes):
980 self.doSeekRelative(-minutes * 60 * 90000)
982 def checkSkipShowHideLock(self):
983 wantlock = self.seekstate != self.SEEK_STATE_PLAY
985 if config.usage.show_infobar_on_skip.value:
986 if self.lockedBecauseOfSkipping and not wantlock:
988 self.lockedBecauseOfSkipping = False
990 if wantlock and not self.lockedBecauseOfSkipping:
992 self.lockedBecauseOfSkipping = True
994 def calcRemainingTime(self):
995 seekable = self.getSeek()
996 if seekable is not None:
997 len = seekable.getLength()
999 tmp = self.cueGetEndCutPosition()
1004 pos = seekable.getPlayPosition()
1005 speednom = self.seekstate[1] or 1
1006 speedden = self.seekstate[2] or 1
1007 if not len[0] and not pos[0]:
1008 if len[1] <= pos[1]:
1010 time = (len[1] - pos[1])*speedden/(90*speednom)
1015 if self.seekstate == self.SEEK_STATE_EOF:
1018 # if we are seeking forward, we try to end up ~1s before the end, and pause there.
1019 seekstate = self.seekstate
1020 if self.seekstate != self.SEEK_STATE_PAUSE:
1021 self.setSeekState(self.SEEK_STATE_EOF)
1023 if seekstate not in (self.SEEK_STATE_PLAY, self.SEEK_STATE_PAUSE): # if we are seeking
1024 seekable = self.getSeek()
1025 if seekable is not None:
1027 if seekstate == self.SEEK_STATE_PLAY: # regular EOF
1028 self.doEofInternal(True)
1030 self.doEofInternal(False)
1032 def doEofInternal(self, playing):
1033 pass # Defined in subclasses
1036 self.setSeekState(self.SEEK_STATE_PLAY)
1039 from Screens.PVRState import PVRState, TimeshiftState
1041 class InfoBarPVRState:
1042 def __init__(self, screen=PVRState, force_show = False):
1043 self.onPlayStateChanged.append(self.__playStateChanged)
1044 self.pvrStateDialog = self.session.instantiateDialog(screen)
1045 self.onShow.append(self._mayShow)
1046 self.onHide.append(self.pvrStateDialog.hide)
1047 self.force_show = force_show
1050 if self.execing and self.seekstate != self.SEEK_STATE_PLAY:
1051 self.pvrStateDialog.show()
1053 def __playStateChanged(self, state):
1054 playstateString = state[3]
1055 self.pvrStateDialog["state"].setText(playstateString)
1057 # if we return into "PLAY" state, ensure that the dialog gets hidden if there will be no infobar displayed
1058 if not config.usage.show_infobar_on_skip.value and self.seekstate == self.SEEK_STATE_PLAY and not self.force_show:
1059 self.pvrStateDialog.hide()
1064 class InfoBarTimeshiftState(InfoBarPVRState):
1066 InfoBarPVRState.__init__(self, screen=TimeshiftState, force_show = True)
1069 if self.execing and self.timeshift_enabled and self.seekstate != self.SEEK_STATE_PLAY:
1070 self.pvrStateDialog.show()
1072 class InfoBarShowMovies:
1074 # i don't really like this class.
1075 # it calls a not further specified "movie list" on up/down/movieList,
1076 # so this is not more than an action map
1078 self["MovieListActions"] = HelpableActionMap(self, "InfobarMovieListActions",
1080 "movieList": (self.showMovies, _("movie list")),
1081 "up": (self.showMovies, _("movie list")),
1082 "down": (self.showMovies, _("movie list"))
1085 # InfoBarTimeshift requires InfoBarSeek, instantiated BEFORE!
1089 # Timeshift works the following way:
1090 # demux0 demux1 "TimeshiftActions" "TimeshiftActivateActions" "SeekActions"
1091 # - normal playback TUNER unused PLAY enable disable disable
1092 # - user presses "yellow" button. FILE record PAUSE enable disable enable
1093 # - user presess pause again FILE record PLAY enable disable enable
1094 # - user fast forwards FILE record FF enable disable enable
1095 # - end of timeshift buffer reached TUNER record PLAY enable enable disable
1096 # - user backwards FILE record BACK # !! enable disable enable
1100 # - when a service is playing, pressing the "timeshiftStart" button ("yellow") enables recording ("enables timeshift"),
1101 # freezes the picture (to indicate timeshift), sets timeshiftMode ("activates timeshift")
1102 # now, the service becomes seekable, so "SeekActions" are enabled, "TimeshiftEnableActions" are disabled.
1103 # - the user can now PVR around
1104 # - if it hits the end, the service goes into live mode ("deactivates timeshift", it's of course still "enabled")
1105 # the service looses it's "seekable" state. It can still be paused, but just to activate timeshift right
1107 # the seek actions will be disabled, but the timeshiftActivateActions will be enabled
1108 # - if the user rewinds, or press pause, timeshift will be activated again
1110 # note that a timeshift can be enabled ("recording") and
1111 # activated (currently time-shifting).
1113 class InfoBarTimeshift:
1115 self["TimeshiftActions"] = HelpableActionMap(self, "InfobarTimeshiftActions",
1117 "timeshiftStart": (self.startTimeshift, _("start timeshift")), # the "yellow key"
1118 "timeshiftStop": (self.stopTimeshift, _("stop timeshift")) # currently undefined :), probably 'TV'
1120 self["TimeshiftActivateActions"] = ActionMap(["InfobarTimeshiftActivateActions"],
1122 "timeshiftActivateEnd": self.activateTimeshiftEnd, # something like "rewind key"
1123 "timeshiftActivateEndAndPause": self.activateTimeshiftEndAndPause # something like "pause key"
1124 }, prio=-1) # priority over record
1126 self.timeshift_enabled = 0
1127 self.timeshift_state = 0
1128 self.ts_rewind_timer = eTimer()
1129 self.ts_rewind_timer.callback.append(self.rewindService)
1131 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1133 iPlayableService.evStart: self.__serviceStarted,
1134 iPlayableService.evSeekableStatusChanged: self.__seekableStatusChanged
1137 def getTimeshift(self):
1138 service = self.session.nav.getCurrentService()
1139 return service and service.timeshift()
1141 def startTimeshift(self):
1142 print "enable timeshift"
1143 ts = self.getTimeshift()
1145 self.session.open(MessageBox, _("Timeshift not possible!"), MessageBox.TYPE_ERROR)
1146 print "no ts interface"
1149 if self.timeshift_enabled:
1150 print "hu, timeshift already enabled?"
1152 if not ts.startTimeshift():
1153 self.timeshift_enabled = 1
1155 # we remove the "relative time" for now.
1156 #self.pvrStateDialog["timeshift"].setRelative(time.time())
1159 #self.setSeekState(self.SEEK_STATE_PAUSE)
1160 self.activateTimeshiftEnd(False)
1162 # enable the "TimeshiftEnableActions", which will override
1163 # the startTimeshift actions
1164 self.__seekableStatusChanged()
1166 print "timeshift failed"
1168 def stopTimeshift(self):
1169 if not self.timeshift_enabled:
1171 print "disable timeshift"
1172 ts = self.getTimeshift()
1175 self.session.openWithCallback(self.stopTimeshiftConfirmed, MessageBox, _("Stop Timeshift?"), MessageBox.TYPE_YESNO)
1177 def stopTimeshiftConfirmed(self, confirmed):
1181 ts = self.getTimeshift()
1186 self.timeshift_enabled = 0
1189 self.__seekableStatusChanged()
1191 # activates timeshift, and seeks to (almost) the end
1192 def activateTimeshiftEnd(self, back = True):
1193 ts = self.getTimeshift()
1194 print "activateTimeshiftEnd"
1199 if ts.isTimeshiftActive():
1200 print "!! activate timeshift called - but shouldn't this be a normal pause?"
1204 ts.activateTimeshift() # activate timeshift will automatically pause
1205 self.setSeekState(self.SEEK_STATE_PAUSE)
1208 self.ts_rewind_timer.start(200, 1)
1210 def rewindService(self):
1211 self.setSeekState(self.makeStateBackward(int(config.seek.enter_backward.value)))
1213 # same as activateTimeshiftEnd, but pauses afterwards.
1214 def activateTimeshiftEndAndPause(self):
1215 print "activateTimeshiftEndAndPause"
1216 #state = self.seekstate
1217 self.activateTimeshiftEnd(False)
1219 def __seekableStatusChanged(self):
1222 # print "self.isSeekable", self.isSeekable()
1223 # print "self.timeshift_enabled", self.timeshift_enabled
1225 # when this service is not seekable, but timeshift
1226 # is enabled, this means we can activate
1228 if not self.isSeekable() and self.timeshift_enabled:
1231 # print "timeshift activate:", enabled
1232 self["TimeshiftActivateActions"].setEnabled(enabled)
1234 def __serviceStarted(self):
1235 self.timeshift_enabled = False
1236 self.__seekableStatusChanged()
1238 from Screens.PiPSetup import PiPSetup
1240 class InfoBarExtensions:
1241 EXTENSION_SINGLE = 0
1247 self["InstantExtensionsActions"] = HelpableActionMap(self, "InfobarExtensions",
1249 "extensions": (self.showExtensionSelection, _("view extensions...")),
1250 }, 1) # lower priority
1252 def addExtension(self, extension, key = None, type = EXTENSION_SINGLE):
1253 self.list.append((type, extension, key))
1255 def updateExtension(self, extension, key = None):
1256 self.extensionsList.append(extension)
1258 if self.extensionKeys.has_key(key):
1262 for x in self.availableKeys:
1263 if not self.extensionKeys.has_key(x):
1268 self.extensionKeys[key] = len(self.extensionsList) - 1
1270 def updateExtensions(self):
1271 self.extensionsList = []
1272 self.availableKeys = [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "red", "green", "yellow", "blue" ]
1273 self.extensionKeys = {}
1275 if x[0] == self.EXTENSION_SINGLE:
1276 self.updateExtension(x[1], x[2])
1279 self.updateExtension(y[0], y[1])
1282 def showExtensionSelection(self):
1283 self.updateExtensions()
1284 extensionsList = self.extensionsList[:]
1287 for x in self.availableKeys:
1288 if self.extensionKeys.has_key(x):
1289 entry = self.extensionKeys[x]
1290 extension = self.extensionsList[entry]
1292 name = str(extension[0]())
1293 list.append((extension[0](), extension))
1295 extensionsList.remove(extension)
1297 extensionsList.remove(extension)
1298 list.extend([(x[0](), x) for x in extensionsList])
1300 keys += [""] * len(extensionsList)
1301 self.session.openWithCallback(self.extensionCallback, ChoiceBox, title=_("Please choose an extension..."), list = list, keys = keys, skin_name = "ExtensionsList")
1303 def extensionCallback(self, answer):
1304 if answer is not None:
1307 from Tools.BoundFunction import boundFunction
1309 # depends on InfoBarExtensions
1311 class InfoBarPlugins:
1313 self.addExtension(extension = self.getPluginList, type = InfoBarExtensions.EXTENSION_LIST)
1315 def getPluginName(self, name):
1318 def getPluginList(self):
1319 list = [((boundFunction(self.getPluginName, p.name), boundFunction(self.runPlugin, p), lambda: True), None, p.name) for p in plugins.getPlugins(where = PluginDescriptor.WHERE_EXTENSIONSMENU)]
1320 list.sort(key = lambda e: e[2]) # sort by name
1323 def runPlugin(self, plugin):
1324 if isinstance(self, InfoBarChannelSelection):
1325 plugin(session = self.session, servicelist = self.servicelist)
1327 plugin(session = self.session)
1329 from Components.Task import job_manager
1330 class InfoBarJobman:
1332 self.addExtension(extension = self.getJobList, type = InfoBarExtensions.EXTENSION_LIST)
1334 def getJobList(self):
1335 return [((boundFunction(self.getJobName, job), boundFunction(self.showJobView, job), lambda: True), None) for job in job_manager.getPendingJobs()]
1337 def getJobName(self, job):
1338 return "%s: %s (%d%%)" % (job.getStatustext(), job.name, int(100*job.progress/float(job.end)))
1340 def showJobView(self, job):
1341 from Screens.TaskView import JobView
1342 job_manager.in_background = False
1343 self.session.openWithCallback(self.JobViewCB, JobView, job)
1345 def JobViewCB(self, in_background):
1346 job_manager.in_background = in_background
1348 # depends on InfoBarExtensions
1352 self.session.pipshown
1354 self.session.pipshown = False
1355 if SystemInfo.get("NumVideoDecoders", 1) > 1:
1357 self.addExtension((self.getShowHideName, self.showPiP, lambda: True), "blue")
1358 self.addExtension((self.getMoveName, self.movePiP, self.pipShown), "green")
1359 self.addExtension((self.getSwapName, self.swapPiP, self.pipShown), "yellow")
1361 self.addExtension((self.getShowHideName, self.showPiP, self.pipShown), "blue")
1362 self.addExtension((self.getMoveName, self.movePiP, self.pipShown), "green")
1365 return self.session.pipshown
1367 def pipHandles0Action(self):
1368 return self.pipShown() and config.usage.pip_zero_button.value != "standard"
1370 def getShowHideName(self):
1371 if self.session.pipshown:
1372 return _("Disable Picture in Picture")
1374 return _("Activate Picture in Picture")
1376 def getSwapName(self):
1377 return _("Swap Services")
1379 def getMoveName(self):
1380 return _("Move Picture in Picture")
1383 if self.session.pipshown:
1384 del self.session.pip
1385 self.session.pipshown = False
1387 self.session.pip = self.session.instantiateDialog(PictureInPicture)
1388 self.session.pip.show()
1389 newservice = self.session.nav.getCurrentlyPlayingServiceReference()
1390 if self.session.pip.playService(newservice):
1391 self.session.pipshown = True
1392 self.session.pip.servicePath = self.servicelist.getCurrentServicePath()
1394 self.session.pipshown = False
1395 del self.session.pip
1396 self.session.nav.playService(newservice)
1399 swapservice = self.session.nav.getCurrentlyPlayingServiceReference()
1400 if self.session.pip.servicePath:
1401 servicepath = self.servicelist.getCurrentServicePath()
1402 ref=servicepath[len(servicepath)-1]
1403 pipref=self.session.pip.getCurrentService()
1404 self.session.pip.playService(swapservice)
1405 self.servicelist.setCurrentServicePath(self.session.pip.servicePath)
1406 if pipref.toString() != ref.toString(): # is a subservice ?
1407 self.session.nav.stopService() # stop portal
1408 self.session.nav.playService(pipref) # start subservice
1409 self.session.pip.servicePath=servicepath
1412 self.session.open(PiPSetup, pip = self.session.pip)
1414 def pipDoHandle0Action(self):
1415 use = config.usage.pip_zero_button.value
1418 elif "swapstop" == use:
1424 from RecordTimer import parseEvent, RecordTimerEntry
1426 class InfoBarInstantRecord:
1427 """Instant Record - handles the instantRecord action in order to
1428 start/stop instant records"""
1430 self["InstantRecordActions"] = HelpableActionMap(self, "InfobarInstantRecord",
1432 "instantRecord": (self.instantRecord, _("Instant Record...")),
1436 def stopCurrentRecording(self, entry = -1):
1437 if entry is not None and entry != -1:
1438 self.session.nav.RecordTimer.removeEntry(self.recording[entry])
1439 self.recording.remove(self.recording[entry])
1441 def startInstantRecording(self, limitEvent = False):
1442 serviceref = self.session.nav.getCurrentlyPlayingServiceReference()
1444 # try to get event info
1447 service = self.session.nav.getCurrentService()
1448 epg = eEPGCache.getInstance()
1449 event = epg.lookupEventTime(serviceref, -1, 0)
1451 info = service.info()
1452 ev = info.getEvent(0)
1458 end = begin + 3600 # dummy
1459 name = "instant record"
1463 if event is not None:
1464 curEvent = parseEvent(event)
1466 description = curEvent[3]
1467 eventid = curEvent[4]
1472 self.session.open(MessageBox, _("No event info found, recording indefinitely."), MessageBox.TYPE_INFO)
1474 if isinstance(serviceref, eServiceReference):
1475 serviceref = ServiceReference(serviceref)
1477 recording = RecordTimerEntry(serviceref, begin, end, name, description, eventid, dirname = config.movielist.last_videodir.value)
1478 recording.dontSave = True
1480 if event is None or limitEvent == False:
1481 recording.autoincrease = True
1482 if recording.setAutoincreaseEnd():
1483 self.session.nav.RecordTimer.record(recording)
1484 self.recording.append(recording)
1486 simulTimerList = self.session.nav.RecordTimer.record(recording)
1487 if simulTimerList is not None: # conflict with other recording
1488 name = simulTimerList[1].name
1489 name_date = ' '.join((name, strftime('%c', localtime(simulTimerList[1].begin))))
1490 print "[TIMER] conflicts with", name_date
1491 recording.autoincrease = True # start with max available length, then increment
1492 if recording.setAutoincreaseEnd():
1493 self.session.nav.RecordTimer.record(recording)
1494 self.recording.append(recording)
1495 self.session.open(MessageBox, _("Record time limited due to conflicting timer %s") % name_date, MessageBox.TYPE_INFO)
1497 self.session.open(MessageBox, _("Couldn't record due to conflicting timer %s") % name, MessageBox.TYPE_INFO)
1498 recording.autoincrease = False
1500 self.recording.append(recording)
1502 def isInstantRecordRunning(self):
1503 print "self.recording:", self.recording
1505 for x in self.recording:
1510 def recordQuestionCallback(self, answer):
1511 print "pre:\n", self.recording
1513 if answer is None or answer[1] == "no":
1516 recording = self.recording[:]
1518 if not x in self.session.nav.RecordTimer.timer_list:
1519 self.recording.remove(x)
1520 elif x.dontSave and x.isRunning():
1521 list.append((x, False))
1523 if answer[1] == "changeduration":
1524 if len(self.recording) == 1:
1525 self.changeDuration(0)
1527 self.session.openWithCallback(self.changeDuration, TimerSelection, list)
1528 elif answer[1] == "changeendtime":
1529 if len(self.recording) == 1:
1532 self.session.openWithCallback(self.setEndtime, TimerSelection, list)
1533 elif answer[1] == "stop":
1534 if len(self.recording) == 1:
1535 self.stopCurrentRecording(0)
1537 self.session.openWithCallback(self.stopCurrentRecording, TimerSelection, list)
1538 elif answer[1] in ( "indefinitely" , "manualduration", "manualendtime", "event"):
1539 self.startInstantRecording(limitEvent = answer[1] in ("event", "manualendtime") or False)
1540 if answer[1] == "manualduration":
1541 self.changeDuration(len(self.recording)-1)
1542 elif answer[1] == "manualendtime":
1543 self.setEndtime(len(self.recording)-1)
1544 print "after:\n", self.recording
1546 def setEndtime(self, entry):
1547 if entry is not None and entry >= 0:
1548 self.selectedEntry = entry
1549 self.endtime=ConfigClock(default = self.recording[self.selectedEntry].end)
1550 dlg = self.session.openWithCallback(self.TimeDateInputClosed, TimeDateInput, self.endtime)
1551 dlg.setTitle(_("Please change recording endtime"))
1553 def TimeDateInputClosed(self, ret):
1556 localendtime = localtime(ret[1])
1557 print "stopping recording at", strftime("%c", localendtime)
1558 if self.recording[self.selectedEntry].end != ret[1]:
1559 self.recording[self.selectedEntry].autoincrease = False
1560 self.recording[self.selectedEntry].end = ret[1]
1561 self.session.nav.RecordTimer.timeChanged(self.recording[self.selectedEntry])
1563 def changeDuration(self, entry):
1564 if entry is not None and entry >= 0:
1565 self.selectedEntry = entry
1566 self.session.openWithCallback(self.inputCallback, InputBox, title=_("How many minutes do you want to record?"), text="5", maxSize=False, type=Input.NUMBER)
1568 def inputCallback(self, value):
1569 if value is not None:
1570 print "stopping recording after", int(value), "minutes."
1571 entry = self.recording[self.selectedEntry]
1573 entry.autoincrease = False
1574 entry.end = int(time()) + 60 * int(value)
1575 self.session.nav.RecordTimer.timeChanged(entry)
1577 def instantRecord(self):
1578 dir = config.movielist.last_videodir.value
1579 if not fileExists(dir, 'w'):
1580 dir = resolveFilename(SCOPE_HDD)
1584 # XXX: this message is a little odd as we might be recording to a remote device
1585 self.session.open(MessageBox, _("No HDD found or HDD not initialized!"), MessageBox.TYPE_ERROR)
1588 if self.isInstantRecordRunning():
1589 self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1590 title=_("A recording is currently running.\nWhat do you want to do?"), \
1591 list=((_("stop recording"), "stop"), \
1592 (_("add recording (stop after current event)"), "event"), \
1593 (_("add recording (indefinitely)"), "indefinitely"), \
1594 (_("add recording (enter recording duration)"), "manualduration"), \
1595 (_("add recording (enter recording endtime)"), "manualendtime"), \
1596 (_("change recording (duration)"), "changeduration"), \
1597 (_("change recording (endtime)"), "changeendtime"), \
1598 (_("do nothing"), "no")))
1600 self.session.openWithCallback(self.recordQuestionCallback, ChoiceBox, \
1601 title=_("Start recording?"), \
1602 list=((_("add recording (stop after current event)"), "event"), \
1603 (_("add recording (indefinitely)"), "indefinitely"), \
1604 (_("add recording (enter recording duration)"), "manualduration"), \
1605 (_("add recording (enter recording endtime)"), "manualendtime"), \
1606 (_("don't record"), "no")))
1608 from Tools.ISO639 import LanguageCodes
1610 class InfoBarAudioSelection:
1612 self["AudioSelectionAction"] = HelpableActionMap(self, "InfobarAudioSelectionActions",
1614 "audioSelection": (self.audioSelection, _("Audio Options...")),
1617 def audioSelection(self):
1618 service = self.session.nav.getCurrentService()
1619 self.audioTracks = audio = service and service.audioTracks()
1620 n = audio and audio.getNumberOfTracks() or 0
1623 self.audioChannel = service.audioChannel()
1628 i = audio.getTrackInfo(idx)
1629 languages = i.getLanguage().split('/')
1630 description = i.getDescription()
1633 for lang in languages:
1636 if LanguageCodes.has_key(lang):
1637 language += LanguageCodes[lang][0]
1642 if len(description):
1643 description += " (" + language + ")"
1645 description = language
1647 tlist.append((description, idx))
1650 tlist.sort(key=lambda x: x[0])
1652 selectedAudio = self.audioTracks.getCurrentTrack()
1657 if x[1] != selectedAudio:
1665 if SystemInfo["CanDownmixAC3"]:
1666 flist = [(_("AC3 downmix") + " - " +(_("Off"), _("On"))[config.av.downmix_ac3.value and 1 or 0], "CALLFUNC", self.changeAC3Downmix),
1667 ((_("Left"), _("Stereo"), _("Right"))[self.audioChannel.getCurrentChannel()], "mode")]
1668 usedKeys.extend(["red", "green"])
1669 availableKeys.extend(["yellow", "blue"])
1672 flist = [((_("Left"), _("Stereo"), _("Right"))[self.audioChannel.getCurrentChannel()], "mode")]
1673 usedKeys.extend(["red"])
1674 availableKeys.extend(["green", "yellow", "blue"])
1677 if hasattr(self, "runPlugin"):
1679 def __init__(self, fnc, *args):
1682 def __call__(self, *args, **kwargs):
1683 self.fnc(*self.args)
1685 Plugins = [ (p.name, PluginCaller(self.runPlugin, p)) for p in plugins.getPlugins(where = PluginDescriptor.WHERE_AUDIOMENU) ]
1689 flist.append((p[0], "CALLFUNC", p[1]))
1691 usedKeys.append(availableKeys[0])
1692 del availableKeys[0]
1696 flist.append(("--", ""))
1700 keys = usedKeys + [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" ] + [""] * n
1701 self.session.openWithCallback(self.audioSelected, ChoiceBox, title=_("Select audio track"), list = flist + tlist, selection = selection, keys = keys, skin_name = "AudioTrackSelection")
1703 del self.audioTracks
1705 def changeAC3Downmix(self, arg):
1706 choicelist = self.session.current_dialog["list"]
1707 list = choicelist.list
1709 list[0][1]=(t[0], t[1], t[2], t[3], t[4], t[5], t[6],
1710 _("AC3 downmix") + " - " + (_("On"), _("Off"))[config.av.downmix_ac3.value and 1 or 0])
1711 choicelist.setList(list)
1712 if config.av.downmix_ac3.value:
1713 config.av.downmix_ac3.value = False
1715 config.av.downmix_ac3.value = True
1716 config.av.downmix_ac3.save()
1718 def audioSelected(self, audio):
1719 if audio is not None:
1720 if isinstance(audio[1], str):
1721 if audio[1] == "mode":
1722 keys = ["red", "green", "yellow"]
1723 selection = self.audioChannel.getCurrentChannel()
1724 tlist = ((_("left"), 0), (_("stereo"), 1), (_("right"), 2))
1725 self.session.openWithCallback(self.modeSelected, ChoiceBox, title=_("Select audio mode"), list = tlist, selection = selection, keys = keys, skin_name ="AudioModeSelection")
1727 del self.audioChannel
1728 if self.session.nav.getCurrentService().audioTracks().getNumberOfTracks() > audio[1]:
1729 self.audioTracks.selectTrack(audio[1])
1731 del self.audioChannel
1732 del self.audioTracks
1734 def modeSelected(self, mode):
1735 if mode is not None:
1736 self.audioChannel.selectChannel(mode[1])
1737 del self.audioChannel
1739 class InfoBarSubserviceSelection:
1741 self["SubserviceSelectionAction"] = HelpableActionMap(self, "InfobarSubserviceSelectionActions",
1743 "subserviceSelection": (self.subserviceSelection, _("Subservice list...")),
1746 self["SubserviceQuickzapAction"] = HelpableActionMap(self, "InfobarSubserviceQuickzapActions",
1748 "nextSubservice": (self.nextSubservice, _("Switch to next subservice")),
1749 "prevSubservice": (self.prevSubservice, _("Switch to previous subservice"))
1751 self["SubserviceQuickzapAction"].setEnabled(False)
1753 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1755 iPlayableService.evUpdatedEventInfo: self.checkSubservicesAvail
1760 def checkSubservicesAvail(self):
1761 service = self.session.nav.getCurrentService()
1762 subservices = service and service.subServices()
1763 if not subservices or subservices.getNumberOfSubservices() == 0:
1764 self["SubserviceQuickzapAction"].setEnabled(False)
1766 def nextSubservice(self):
1767 self.changeSubservice(+1)
1769 def prevSubservice(self):
1770 self.changeSubservice(-1)
1772 def changeSubservice(self, direction):
1773 service = self.session.nav.getCurrentService()
1774 subservices = service and service.subServices()
1775 n = subservices and subservices.getNumberOfSubservices()
1778 ref = self.session.nav.getCurrentlyPlayingServiceReference()
1781 if subservices.getSubservice(idx).toString() == ref.toString():
1786 selection += direction
1791 newservice = subservices.getSubservice(selection)
1792 if newservice.valid():
1795 self.session.nav.playService(newservice, False)
1797 def subserviceSelection(self):
1798 service = self.session.nav.getCurrentService()
1799 subservices = service and service.subServices()
1800 self.bouquets = self.servicelist.getBouquetList()
1801 n = subservices and subservices.getNumberOfSubservices()
1804 ref = self.session.nav.getCurrentlyPlayingServiceReference()
1808 i = subservices.getSubservice(idx)
1809 if i.toString() == ref.toString():
1811 tlist.append((i.getName(), i))
1814 if self.bouquets and len(self.bouquets):
1815 keys = ["red", "blue", "", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ] + [""] * n
1816 if config.usage.multibouquet.value:
1817 tlist = [(_("Quickzap"), "quickzap", service.subServices()), (_("Add to bouquet"), "CALLFUNC", self.addSubserviceToBouquetCallback), ("--", "")] + tlist
1819 tlist = [(_("Quickzap"), "quickzap", service.subServices()), (_("Add to favourites"), "CALLFUNC", self.addSubserviceToBouquetCallback), ("--", "")] + tlist
1822 tlist = [(_("Quickzap"), "quickzap", service.subServices()), ("--", "")] + tlist
1823 keys = ["red", "", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ] + [""] * n
1826 self.session.openWithCallback(self.subserviceSelected, ChoiceBox, title=_("Please select a subservice..."), list = tlist, selection = selection, keys = keys, skin_name = "SubserviceSelection")
1828 def subserviceSelected(self, service):
1830 if not service is None:
1831 if isinstance(service[1], str):
1832 if service[1] == "quickzap":
1833 from Screens.SubservicesQuickzap import SubservicesQuickzap
1834 self.session.open(SubservicesQuickzap, service[2])
1836 self["SubserviceQuickzapAction"].setEnabled(True)
1837 self.session.nav.playService(service[1], False)
1839 def addSubserviceToBouquetCallback(self, service):
1840 if len(service) > 1 and isinstance(service[1], eServiceReference):
1841 self.selectedSubservice = service
1842 if self.bouquets is None:
1845 cnt = len(self.bouquets)
1846 if cnt > 1: # show bouquet list
1847 self.bsel = self.session.openWithCallback(self.bouquetSelClosed, BouquetSelector, self.bouquets, self.addSubserviceToBouquet)
1848 elif cnt == 1: # add to only one existing bouquet
1849 self.addSubserviceToBouquet(self.bouquets[0][1])
1850 self.session.open(MessageBox, _("Service has been added to the favourites."), MessageBox.TYPE_INFO)
1852 def bouquetSelClosed(self, confirmed):
1854 del self.selectedSubservice
1856 self.session.open(MessageBox, _("Service has been added to the selected bouquet."), MessageBox.TYPE_INFO)
1858 def addSubserviceToBouquet(self, dest):
1859 self.servicelist.addServiceToBouquet(dest, self.selectedSubservice[1])
1861 self.bsel.close(True)
1863 del self.selectedSubservice
1865 class InfoBarAdditionalInfo:
1868 self["RecordingPossible"] = Boolean(fixed=harddiskmanager.HDDCount() > 0 and config.misc.rcused.value == 1)
1869 self["TimeshiftPossible"] = self["RecordingPossible"]
1870 self["ShowTimeshiftOnYellow"] = Boolean(fixed=(not config.misc.rcused.value == 0))
1871 self["ShowAudioOnYellow"] = Boolean(fixed=config.misc.rcused.value == 0)
1872 self["ShowRecordOnRed"] = Boolean(fixed=config.misc.rcused.value == 1)
1873 self["ExtensionsAvailable"] = Boolean(fixed=1)
1875 class InfoBarNotifications:
1877 self.onExecBegin.append(self.checkNotifications)
1878 Notifications.notificationAdded.append(self.checkNotificationsIfExecing)
1879 self.onClose.append(self.__removeNotification)
1881 def __removeNotification(self):
1882 Notifications.notificationAdded.remove(self.checkNotificationsIfExecing)
1884 def checkNotificationsIfExecing(self):
1886 self.checkNotifications()
1888 def checkNotifications(self):
1889 notifications = Notifications.notifications
1891 n = notifications[0]
1893 del notifications[0]
1896 if n[3].has_key("onSessionOpenCallback"):
1897 n[3]["onSessionOpenCallback"]()
1898 del n[3]["onSessionOpenCallback"]
1901 dlg = self.session.openWithCallback(cb, n[1], *n[2], **n[3])
1903 dlg = self.session.open(n[1], *n[2], **n[3])
1905 # remember that this notification is currently active
1907 Notifications.current_notifications.append(d)
1908 dlg.onClose.append(boundFunction(self.__notificationClosed, d))
1910 def __notificationClosed(self, d):
1911 Notifications.current_notifications.remove(d)
1913 class InfoBarServiceNotifications:
1915 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1917 iPlayableService.evEnd: self.serviceHasEnded
1920 def serviceHasEnded(self):
1921 print "service end!"
1924 self.setSeekState(self.SEEK_STATE_PLAY)
1928 class InfoBarCueSheetSupport:
1934 ENABLE_RESUME_SUPPORT = False
1936 def __init__(self, actionmap = "InfobarCueSheetActions"):
1937 self["CueSheetActions"] = HelpableActionMap(self, actionmap,
1939 "jumpPreviousMark": (self.jumpPreviousMark, _("jump to previous marked position")),
1940 "jumpNextMark": (self.jumpNextMark, _("jump to next marked position")),
1941 "toggleMark": (self.toggleMark, _("toggle a cut mark at the current position"))
1945 self.is_closing = False
1946 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
1948 iPlayableService.evStart: self.__serviceStarted,
1951 def __serviceStarted(self):
1954 print "new service started! trying to download cuts!"
1955 self.downloadCuesheet()
1957 if self.ENABLE_RESUME_SUPPORT:
1960 for (pts, what) in self.cut_list:
1961 if what == self.CUT_TYPE_LAST:
1964 if last is not None:
1965 self.resume_point = last
1966 if config.usage.on_movie_start.value == "ask":
1967 Notifications.AddNotificationWithCallback(self.playLastCB, MessageBox, _("Do you want to resume this playback?"), timeout=10)
1968 elif config.usage.on_movie_start.value == "resume":
1969 # TRANSLATORS: The string "Resuming playback" flashes for a moment
1970 # TRANSLATORS: at the start of a movie, when the user has selected
1971 # TRANSLATORS: "Resume from last position" as start behavior.
1972 # TRANSLATORS: The purpose is to notify the user that the movie starts
1973 # TRANSLATORS: in the middle somewhere and not from the beginning.
1974 # TRANSLATORS: (Some translators seem to have interpreted it as a
1975 # TRANSLATORS: question or a choice, but it is a statement.)
1976 Notifications.AddNotificationWithCallback(self.playLastCB, MessageBox, _("Resuming playback"), timeout=2, type=MessageBox.TYPE_INFO)
1978 def playLastCB(self, answer):
1980 self.doSeek(self.resume_point)
1981 self.hideAfterResume()
1983 def hideAfterResume(self):
1984 if isinstance(self, InfoBarShowHide):
1987 def __getSeekable(self):
1988 service = self.session.nav.getCurrentService()
1991 return service.seek()
1993 def cueGetCurrentPosition(self):
1994 seek = self.__getSeekable()
1997 r = seek.getPlayPosition()
2002 def cueGetEndCutPosition(self):
2005 for cp in self.cut_list:
2006 if cp[1] == self.CUT_TYPE_OUT:
2010 elif cp[1] == self.CUT_TYPE_IN:
2014 def jumpPreviousNextMark(self, cmp, start=False):
2015 current_pos = self.cueGetCurrentPosition()
2016 if current_pos is None:
2018 mark = self.getNearestCutPoint(current_pos, cmp=cmp, start=start)
2019 if mark is not None:
2027 def jumpPreviousMark(self):
2028 # we add 2 seconds, so if the play position is <2s after
2029 # the mark, the mark before will be used
2030 self.jumpPreviousNextMark(lambda x: -x-5*90000, start=True)
2032 def jumpNextMark(self):
2033 if not self.jumpPreviousNextMark(lambda x: x):
2036 def getNearestCutPoint(self, pts, cmp=abs, start=False):
2042 bestdiff = cmp(0 - pts)
2044 nearest = [0, False]
2045 for cp in self.cut_list:
2046 if beforecut and cp[1] in (self.CUT_TYPE_IN, self.CUT_TYPE_OUT):
2048 if cp[1] == self.CUT_TYPE_IN: # Start is here, disregard previous marks
2049 diff = cmp(cp[0] - pts)
2055 if cp[1] in (self.CUT_TYPE_MARK, self.CUT_TYPE_LAST):
2056 diff = cmp(cp[0] - pts)
2057 if diff >= 0 and (nearest is None or bestdiff > diff):
2062 def toggleMark(self, onlyremove=False, onlyadd=False, tolerance=5*90000, onlyreturn=False):
2063 current_pos = self.cueGetCurrentPosition()
2064 if current_pos is None:
2065 print "not seekable"
2068 nearest_cutpoint = self.getNearestCutPoint(current_pos)
2070 if nearest_cutpoint is not None and abs(nearest_cutpoint[0] - current_pos) < tolerance:
2072 return nearest_cutpoint
2074 self.removeMark(nearest_cutpoint)
2075 elif not onlyremove and not onlyreturn:
2076 self.addMark((current_pos, self.CUT_TYPE_MARK))
2081 def addMark(self, point):
2082 insort(self.cut_list, point)
2083 self.uploadCuesheet()
2084 self.showAfterCuesheetOperation()
2086 def removeMark(self, point):
2087 self.cut_list.remove(point)
2088 self.uploadCuesheet()
2089 self.showAfterCuesheetOperation()
2091 def showAfterCuesheetOperation(self):
2092 if isinstance(self, InfoBarShowHide):
2095 def __getCuesheet(self):
2096 service = self.session.nav.getCurrentService()
2099 return service.cueSheet()
2101 def uploadCuesheet(self):
2102 cue = self.__getCuesheet()
2105 print "upload failed, no cuesheet interface"
2107 cue.setCutList(self.cut_list)
2109 def downloadCuesheet(self):
2110 cue = self.__getCuesheet()
2113 print "download failed, no cuesheet interface"
2116 self.cut_list = cue.getCutList()
2118 class InfoBarSummary(Screen):
2120 <screen position="0,0" size="132,64">
2121 <widget source="global.CurrentTime" render="Label" position="62,46" size="82,18" font="Regular;16" >
2122 <convert type="ClockToText">WithSeconds</convert>
2124 <widget source="session.RecordState" render="FixedLabel" text=" " position="62,46" size="82,18" zPosition="1" >
2125 <convert type="ConfigEntryTest">config.usage.blinking_display_clock_during_recording,True,CheckSourceBoolean</convert>
2126 <convert type="ConditionalShowHide">Blink</convert>
2128 <widget source="session.CurrentService" render="Label" position="6,4" size="120,42" font="Regular;18" >
2129 <convert type="ServiceName">Name</convert>
2131 <widget source="session.Event_Now" render="Progress" position="6,46" size="46,18" borderWidth="1" >
2132 <convert type="EventTime">Progress</convert>
2136 # for picon: (path="piconlcd" will use LCD picons)
2137 # <widget source="session.CurrentService" render="Picon" position="6,0" size="120,64" path="piconlcd" >
2138 # <convert type="ServiceName">Reference</convert>
2141 class InfoBarSummarySupport:
2145 def createSummary(self):
2146 return InfoBarSummary
2148 class InfoBarMoviePlayerSummary(Screen):
2150 <screen position="0,0" size="132,64">
2151 <widget source="global.CurrentTime" render="Label" position="62,46" size="64,18" font="Regular;16" halign="right" >
2152 <convert type="ClockToText">WithSeconds</convert>
2154 <widget source="session.RecordState" render="FixedLabel" text=" " position="62,46" size="64,18" zPosition="1" >
2155 <convert type="ConfigEntryTest">config.usage.blinking_display_clock_during_recording,True,CheckSourceBoolean</convert>
2156 <convert type="ConditionalShowHide">Blink</convert>
2158 <widget source="session.CurrentService" render="Label" position="6,4" size="120,42" font="Regular;18" >
2159 <convert type="ServiceName">Name</convert>
2161 <widget source="session.CurrentService" render="Progress" position="6,46" size="56,18" borderWidth="1" >
2162 <convert type="ServicePosition">Position</convert>
2166 class InfoBarMoviePlayerSummarySupport:
2170 def createSummary(self):
2171 return InfoBarMoviePlayerSummary
2173 class InfoBarTeletextPlugin:
2175 self.teletext_plugin = None
2177 for p in plugins.getPlugins(PluginDescriptor.WHERE_TELETEXT):
2178 self.teletext_plugin = p
2180 if self.teletext_plugin is not None:
2181 self["TeletextActions"] = HelpableActionMap(self, "InfobarTeletextActions",
2183 "startTeletext": (self.startTeletext, _("View teletext..."))
2186 print "no teletext plugin found!"
2188 def startTeletext(self):
2189 self.teletext_plugin(session=self.session, service=self.session.nav.getCurrentService())
2191 class InfoBarSubtitleSupport(object):
2193 object.__init__(self)
2194 self.subtitle_window = self.session.instantiateDialog(SubtitleDisplay)
2195 self.__subtitles_enabled = False
2197 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
2199 iPlayableService.evEnd: self.__serviceStopped,
2200 iPlayableService.evUpdatedInfo: self.__updatedInfo
2202 self.cached_subtitle_checked = False
2203 self.__selected_subtitle = None
2205 def __serviceStopped(self):
2206 self.cached_subtitle_checked = False
2207 if self.__subtitles_enabled:
2208 self.subtitle_window.hide()
2209 self.__subtitles_enabled = False
2210 self.__selected_subtitle = None
2212 def __updatedInfo(self):
2213 if not self.cached_subtitle_checked:
2214 self.cached_subtitle_checked = True
2215 subtitle = self.getCurrentServiceSubtitle()
2216 self.setSelectedSubtitle(subtitle and subtitle.getCachedSubtitle())
2217 if self.__selected_subtitle:
2218 self.setSubtitlesEnable(True)
2220 def getCurrentServiceSubtitle(self):
2221 service = self.session.nav.getCurrentService()
2222 return service and service.subtitle()
2224 def setSubtitlesEnable(self, enable=True):
2225 subtitle = self.getCurrentServiceSubtitle()
2227 if self.__selected_subtitle:
2228 if subtitle and not self.__subtitles_enabled:
2229 subtitle.enableSubtitles(self.subtitle_window.instance, self.selected_subtitle)
2230 self.subtitle_window.show()
2231 self.__subtitles_enabled = True
2234 subtitle.disableSubtitles(self.subtitle_window.instance)
2235 self.__selected_subtitle = False
2236 self.__subtitles_enabled = False
2237 self.subtitle_window.hide()
2239 def setSelectedSubtitle(self, subtitle):
2240 self.__selected_subtitle = subtitle
2242 subtitles_enabled = property(lambda self: self.__subtitles_enabled, setSubtitlesEnable)
2243 selected_subtitle = property(lambda self: self.__selected_subtitle, setSelectedSubtitle)
2245 class InfoBarServiceErrorPopupSupport:
2247 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
2249 iPlayableService.evTuneFailed: self.__tuneFailed,
2250 iPlayableService.evStart: self.__serviceStarted
2252 self.__serviceStarted()
2254 def __serviceStarted(self):
2255 self.last_error = None
2256 Notifications.RemovePopup(id = "ZapError")
2258 def __tuneFailed(self):
2259 service = self.session.nav.getCurrentService()
2260 info = service and service.info()
2261 error = info and info.getInfo(iServiceInformation.sDVBState)
2263 if error == self.last_error:
2266 self.last_error = error
2269 eDVBServicePMTHandler.eventNoResources: _("No free tuner!"),
2270 eDVBServicePMTHandler.eventTuneFailed: _("Tune failed!"),
2271 eDVBServicePMTHandler.eventNoPAT: _("No data on transponder!\n(Timeout reading PAT)"),
2272 eDVBServicePMTHandler.eventNoPATEntry: _("Service not found!\n(SID not found in PAT)"),
2273 eDVBServicePMTHandler.eventNoPMT: _("Service invalid!\n(Timeout reading PMT)"),
2274 eDVBServicePMTHandler.eventNewProgramInfo: None,
2275 eDVBServicePMTHandler.eventTuned: None,
2276 eDVBServicePMTHandler.eventSOF: None,
2277 eDVBServicePMTHandler.eventEOF: None,
2278 eDVBServicePMTHandler.eventMisconfiguration: _("Service unavailable!\nCheck tuner configuration!"),
2279 }.get(error) #this returns None when the key not exist in the dict
2281 if error is not None:
2282 Notifications.AddPopup(text = error, type = MessageBox.TYPE_ERROR, timeout = 5, id = "ZapError")
2284 Notifications.RemovePopup(id = "ZapError")