pipzap: don't look for "activate picture in picture" if it isn't there
[enigma2-plugins.git] / pipzap / src / plugin.py
1 # Plugin definition
2 from Plugins.Plugin import PluginDescriptor
3
4 from Components.ActionMap import HelpableActionMap
5 from Components.ChoiceList import ChoiceEntryComponent
6 from Components.config import config, ConfigSubsection, ConfigEnableDisable
7 from Components.SystemInfo import SystemInfo
8 from Components.ParentalControl import parentalControl
9 from enigma import ePoint, eServiceReference, getDesktop
10 from Screens.ChannelSelection import ChannelContextMenu, ChannelSelection, ChannelSelectionBase
11 from Screens.InfoBar import InfoBar, MoviePlayer
12 from Screens.InfoBarGenerics import InfoBarNumberZap, InfoBarEPG, InfoBarChannelSelection, InfoBarPiP, InfoBarShowMovies, InfoBarTimeshift, InfoBarSeek, InfoBarPlugins
13 from Screens.PictureInPicture import PictureInPicture
14 from Screens.Screen import Screen
15 from PipzapSetup import PipzapSetup
16 from Components.PluginComponent import plugins
17
18 class baseMethods:
19         pass
20
21 #pragma mark -
22 #pragma mark ChannelSelection
23 #pragma mark -
24
25 # ChannelContextMenu: switch "Activate Picture in Picture" for pip/mainpicture
26 def ChannelContextMenu___init__(self, session, csel, *args, **kwargs):
27         baseMethods.ChannelContextMenu__init__(self, session, csel, *args, **kwargs)
28
29         if self.pipAvailable:
30                 list = self["menu"].list
31                 x = 0
32                 searchText = _("Activate Picture in Picture")
33                 for entry in list:
34                         if entry[0][0] == searchText:
35                                 if csel.dopipzap:
36                                         entry = ChoiceEntryComponent("", (_("play in mainwindow"), self.playMain))
37                                 else:
38                                         entry = ChoiceEntryComponent("blue", (_("play as picture in picture"), self.showServiceInPiP))
39                                 list[x] = entry
40                                 break
41                         x += 1
42                 self["menu"].setList(list)
43
44 def ChannelContextMenu_playMain(self):
45         # XXX: we want to keep the current selection
46         sel = self.csel.getCurrentSelection()
47         self.csel.zap()
48         self.csel.setCurrentSelection(sel)
49         self.close()
50
51 # do not hide existing pip
52 def ChannelContextMenu_showServiceInPiP(self):
53         if not self.pipAvailable:
54                 return
55
56         if not self.session.pipshown:
57                 self.session.pip = self.session.instantiateDialog(PictureInPicture)
58                 self.session.pip.show()
59
60         newservice = self.csel.servicelist.getCurrent()
61         if self.session.pip.playService(newservice):
62                 self.session.pipshown = True
63                 self.session.pip.servicePath = self.csel.getCurrentServicePath()
64                 self.close(True)
65         else:
66                 self.session.pipshown = False
67                 del self.session.pip
68                 self.session.openWithCallback(self.close, MessageBox, _("Could not open Picture in Picture"), MessageBox.TYPE_ERROR)
69
70 def ChannelSelectionBase__init__(self, *args, **kwargs):
71         baseMethods.ChannelSelectionBase__init__(self, *args, **kwargs)
72         self.dopipzap = False
73         self.enable_pipzap = False
74
75 def ChannelSelectionBase_setCurrentSelection(self, service, *args, **kwargs):
76         if service:
77                 baseMethods.ChannelSelectionBase_setCurrentSelection(self, service, *args, **kwargs)
78
79 def ChannelSelection_channelSelected(self, *args, **kwargs):
80         self.enable_pipzap = True
81         baseMethods.ChannelSelection_channelSelected(self, *args, **kwargs)
82         self.enable_pipzap = False
83
84 def ChannelSelection_togglePipzap(self):
85         assert(self.session.pip)
86         title = self.instance.getTitle()
87         pos = title.find(" (")
88         if pos != -1:
89                 title = title[:pos]
90         if self.dopipzap:
91                 # Mark PiP as inactive and effectively deactivate pipzap
92                 self.session.pip.inactive()
93                 self.dopipzap = False
94
95                 # Disable PiP if not playing a service
96                 if self.session.pip.pipservice is None:
97                         self.session.pipshown = False
98                         del self.session.pip
99
100                 # Move to playing service
101                 lastservice = eServiceReference(self.lastservice.value)
102                 if lastservice.valid() and self.getCurrentSelection() != lastservice:
103                         self.setCurrentSelection(lastservice)
104
105                 title += " (TV)"
106         else:
107                 # Mark PiP as active and effectively active pipzap
108                 self.session.pip.active()
109                 self.dopipzap = True
110
111                 # Move to service playing in pip (will not work with subservices)
112                 self.setCurrentSelection(self.session.pip.getCurrentService())
113
114                 title += " (PiP)"
115         self.setTitle(title)
116         self.buildTitleString()
117
118 def ChannelSelection_zap(self, *args, **kwargs):
119         if self.enable_pipzap and self.dopipzap:
120                 self.revertMode=None
121                 ref = self.session.pip.getCurrentService()
122                 nref = self.getCurrentSelection()
123                 if ref is None or ref != nref:
124                         if not config.ParentalControl.configured.value or parentalControl.getProtectionLevel(nref.toCompareString()) == -1:
125                                 if not self.session.pip.playService(nref):
126                                         # XXX: Make sure we set an invalid ref
127                                         self.session.pip.playService(None)
128         else:
129                 baseMethods.ChannelSelection_zap(self, *args, **kwargs)
130
131                 # Yes, we might double-check this, but we need to re-select pipservice if pipzap is active
132                 # and we just wanted to zap in mainwindow once
133                 # XXX: do we really want this? this also resets the service when zapping from context menu
134                 #      which is irritating
135                 if self.dopipzap:
136                         # This unfortunately won't work with subservices
137                         self.setCurrentSelection(self.session.pip.getCurrentService())
138
139 def ChannelSelection_setHistoryPath(self, *args, **kwargs):
140         baseMethods.ChannelSelection_setHistoryPath(self, *args, **kwargs)
141         if self.dopipzap:
142                 self.setCurrentSelection(self.session.pip.getCurrentService())
143
144 def ChannelSelection_cancel(self, *args, **kwargs):
145         if self.revertMode is None and self.dopipzap:
146                 # This unfortunately won't work with subservices
147                 self.setCurrentSelection(self.session.pip.getCurrentService())
148                 self.revertMode = 1337 # not in (None, MODE_TV, MODE_RADIO)
149         baseMethods.ChannelSelection_cancel(self, *args, **kwargs)
150
151 #pragma mark -
152 #pragma mark MoviePlayer
153 #pragma mark -
154
155 def MoviePlayer__init__(self, *args, **kwargs):
156         baseMethods.MoviePlayer__init__(self, *args, **kwargs)
157         self.servicelist = InfoBar.instance.servicelist
158
159         # WARNING: GROSS HACK INBOUND
160         del self.list[:]
161         self.allowPiP = True
162         InfoBarPlugins.__init__(self)
163         InfoBarPiP.__init__(self)
164
165         self["DirectionActions"] = HelpableActionMap(self, "DirectionActions",
166                 {
167                         "left": self.left,
168                         "right": self.right
169                 }, prio = -2)
170
171 def MoviePlayer_up(self):
172         slist = self.servicelist
173         if slist and slist.dopipzap:
174                 slist.moveUp()
175                 self.session.execDialog(slist)
176         else:
177                 self.showMovies()
178
179 def MoviePlayer_down(self):
180         slist = self.servicelist
181         if slist and slist.dopipzap:
182                 slist.moveDown()
183                 self.session.execDialog(slist)
184         else:
185                 self.showMovies()
186
187 def MoviePlayer_right(self):
188         # XXX: gross hack, we do not really seek if changing channel in pip :-)
189         slist = self.servicelist
190         if slist and slist.dopipzap:
191                 # XXX: We replicate InfoBarChannelSelection.zapDown here - we shouldn't do that
192                 if slist.inBouquet():
193                         prev = slist.getCurrentSelection()
194                         if prev:
195                                 prev = prev.toString()
196                                 while True:
197                                         if config.usage.quickzap_bouquet_change.value and slist.atEnd():
198                                                 slist.nextBouquet()
199                                         else:
200                                                 slist.moveDown()
201                                         cur = slist.getCurrentSelection()
202                                         if not cur or (not (cur.flags & 64)) or cur.toString() == prev:
203                                                 break
204                 else:
205                         slist.moveDown()
206                 slist.enable_pipzap = True
207                 slist.zap()
208                 slist.enable_pipzap = False
209         else:
210                 InfoBarSeek.seekFwd(self)
211
212 def MoviePlayer_left(self):
213         slist = self.servicelist
214         if slist and slist.dopipzap:
215                 # XXX: We replicate InfoBarChannelSelection.zapUp here - we shouldn't do that
216                 if slist.inBouquet():
217                         prev = slist.getCurrentSelection()
218                         if prev:
219                                 prev = prev.toString()
220                                 while True:
221                                         if config.usage.quickzap_bouquet_change.value:
222                                                 if slist.atBegin():
223                                                         slist.prevBouquet()
224                                         slist.moveUp()
225                                         cur = slist.getCurrentSelection()
226                                         if not cur or (not (cur.flags & 64)) or cur.toString() == prev:
227                                                 break
228                 else:
229                         slist.moveUp()
230                 slist.enable_pipzap = True
231                 slist.zap()
232                 slist.enable_pipzap = False
233         else:
234                 InfoBarSeek.seekBack(self)
235
236 def MoviePlayer_swapPiP(self):
237         pass
238
239 #pragma mark -
240 #pragma mark InfoBarGenerics
241 #pragma mark -
242
243 def InfoBarNumberZap_zapToNumber(self, *args, **kwargs):
244         self.servicelist.enable_pipzap = True
245         baseMethods.InfoBarNumberZap_zapToNumber(self, *args, **kwargs)
246         self.servicelist.enable_pipzap = False
247
248 def InfoBarChannelSelection_zapUp(self, *args, **kwargs):
249         self.servicelist.enable_pipzap = True
250         baseMethods.InfoBarChannelSelection_zapUp(self, *args, **kwargs)
251         self.servicelist.enable_pipzap = False
252
253 def InfoBarChannelSelection_zapDown(self, *args, **kwargs):
254         self.servicelist.enable_pipzap = True
255         baseMethods.InfoBarChannelSelection_zapDown(self, *args, **kwargs)
256         self.servicelist.enable_pipzap = False
257
258 def InfoBarEPG_zapToService(self, *args, **kwargs):
259         self.servicelist.enable_pipzap = True
260         baseMethods.InfoBarEPG_zapToService(self, *args, **kwargs)
261         self.servicelist.enable_pipzap = False
262
263 def InfoBarShowMovies__init__(self):
264         baseMethods.InfoBarShowMovies__init__(self)
265         self["MovieListActions"] = HelpableActionMap(self, "InfobarMovieListActions",
266                 {
267                         "movieList": (self.showMovies, _("movie list")),
268                         "up": (self.up, _("movie list")),
269                         "down": (self.down, _("movie list"))
270                 })
271
272 def InfoBarPiP__init__(self):
273         baseMethods.InfoBarPiP__init__(self)
274         if SystemInfo.get("NumVideoDecoders", 1) > 1 and self.allowPiP:
275                 self.addExtension((self.getTogglePipzapName, self.togglePipzap, self.pipShown), "red")
276                 if config.plugins.pipzap.enable_hotkey.value:
277                         self["pipzapActions"] = HelpableActionMap(self, "pipzapActions",
278                                 {
279                                         "switchPiP": (self.togglePipzap, _("zap in pip window...")),
280                                 })
281
282 def InfoBarPiP_getTogglePipzapName(self):
283         slist = self.servicelist
284         if slist and slist.dopipzap:
285                 return _("Zap focus to main screen")
286         return _("Zap focus to Picture in Picture")
287
288 def InfoBarPiP_togglePipzap(self):
289         # supposed to fix some problems with permanent timeshift patch
290         if isinstance(self, InfoBarTimeshift) and isinstance(self, InfoBarSeek) and \
291                 self.timeshift_enabled and self.isSeekable():
292                         return 0
293
294         if not self.session.pipshown:
295                 self.showPiP()
296         slist = self.servicelist
297         if slist:
298                 slist.togglePipzap()
299
300 def InfoBarPiP_showPiP(self, *args, **kwargs):
301         slist = self.servicelist
302         if self.session.pipshown and slist and slist.dopipzap:
303                 slist.togglePipzap()
304         elif not self.session.pipshown and isinstance(self, InfoBarShowMovies):
305                 self.session.pip = self.session.instantiateDialog(PictureInPicture)
306                 self.session.pip.show()
307                 self.session.pipshown = True
308                 if slist:
309                         self.session.pip.playService(slist.getCurrentSelection())
310                 return
311         baseMethods.InfoBarPiP_showPiP(self, *args, **kwargs)
312
313 # Using the base implementation would cause nasty bugs, so ignore it here
314 def InfoBarPiP_swapPiP(self):
315         swapservice = self.session.nav.getCurrentlyPlayingServiceReference()
316         pipref = self.session.pip.getCurrentService()
317         if pipref and swapservice and pipref.toString() != swapservice.toString():
318                         self.session.pip.playService(swapservice)
319
320                         slist = self.servicelist
321                         if slist:
322                                 # TODO: this behaves real bad on subservices
323                                 if slist.dopipzap:
324                                         slist.servicelist.setCurrent(swapservice)
325                                 else:
326                                         slist.servicelist.setCurrent(pipref)
327
328                                 slist.addToHistory(pipref) # add service to history
329                                 slist.lastservice.value = pipref.toString() # save service as last playing one
330                         self.session.nav.stopService() # stop portal
331                         self.session.nav.playService(pipref) # start subservice
332
333 #pragma mark -
334 #pragma mark Picture in Picture
335 #pragma mark -
336
337 class PictureInPictureZapping(Screen):
338         skin = """<screen name="PictureInPictureZapping" flags="wfNoBorder" position="50,50" size="90,26" title="PiPZap" zPosition="-1">
339                 <eLabel text="PiP-Zap" position="0,0" size="90,26" foregroundColor="#00ff66" font="Regular;26" />
340         </screen>"""
341         def refreshPosition(self):
342                 x = config.av.pip.value[0]
343                 y = config.av.pip.value[1]
344                 width = getDesktop(0).size().width()
345                 height = getDesktop(0).size().height()
346                 if x > width / 2:
347                         x = 40
348                 else:
349                         x = width - 120
350                 if y > height / 2:
351                         y = 40
352                 else:
353                         y = height - 55
354                 self.instance.move(ePoint(x, y))
355
356 def PictureInPicture__init__(self, session, *args, **kwargs):
357         baseMethods.PictureInPicture__init__(self, session, *args, **kwargs)
358         self.pipActive = session.instantiateDialog(PictureInPictureZapping)
359
360 def PictureInPicture_active(self):
361         self.pipActive.show()
362
363 def PictureInPicture_inactive(self):
364         self.pipActive.hide()
365
366 def PictureInPicture_move(self, *args, **kwargs):
367         baseMethods.PictureInPicture_move(self, *args, **kwargs)
368         self.pipActive.refreshPosition()
369
370 #pragma mark -
371 #pragma mark Plugin
372 #pragma mark -
373
374 def overwriteFunctions():
375         """Overwrite existing functions here to increase system stability a bit."""
376         baseMethods.ChannelContextMenu__init__ = ChannelContextMenu.__init__
377         ChannelContextMenu.__init__ = ChannelContextMenu___init__
378
379         ChannelContextMenu.playMain = ChannelContextMenu_playMain
380         ChannelContextMenu.showServiceInPiP = ChannelContextMenu_showServiceInPiP
381
382         baseMethods.ChannelSelectionBase__init__ = ChannelSelectionBase.__init__
383         ChannelSelectionBase.__init__ = ChannelSelectionBase__init__
384
385         baseMethods.ChannelSelectionBase_setCurrentSelection = ChannelSelectionBase.setCurrentSelection
386         ChannelSelectionBase.setCurrentSelection = ChannelSelectionBase_setCurrentSelection
387
388         baseMethods.ChannelSelection_channelSelected = ChannelSelection.channelSelected
389         ChannelSelection.channelSelected = ChannelSelection_channelSelected
390
391         ChannelSelection.togglePipzap = ChannelSelection_togglePipzap 
392
393         baseMethods.ChannelSelection_zap = ChannelSelection.zap
394         ChannelSelection.zap = ChannelSelection_zap
395
396         baseMethods.ChannelSelection_setHistoryPath = ChannelSelection.setHistoryPath
397         ChannelSelection.setHistoryPath = ChannelSelection_setHistoryPath
398
399         baseMethods.ChannelSelection_cancel = ChannelSelection.cancel
400         ChannelSelection.cancel = ChannelSelection_cancel
401
402         baseMethods.MoviePlayer__init__ = MoviePlayer.__init__
403         MoviePlayer.__init__ = MoviePlayer__init__
404
405         MoviePlayer.up = MoviePlayer_up
406         MoviePlayer.down = MoviePlayer_down
407         MoviePlayer.right = MoviePlayer_right
408         MoviePlayer.left = MoviePlayer_left
409         MoviePlayer.swapPiP = MoviePlayer_swapPiP
410
411         baseMethods.InfoBarNumberZap_zapToNumber = InfoBarNumberZap.zapToNumber
412         InfoBarNumberZap.zapToNumber = InfoBarNumberZap_zapToNumber
413
414         baseMethods.InfoBarChannelSelection_zapUp = InfoBarChannelSelection.zapUp
415         InfoBarChannelSelection.zapUp = InfoBarChannelSelection_zapUp
416
417         baseMethods.InfoBarChannelSelection_zapDown = InfoBarChannelSelection.zapDown
418         InfoBarChannelSelection.zapDown = InfoBarChannelSelection_zapDown
419
420         baseMethods.InfoBarEPG_zapToService = InfoBarEPG.zapToService
421         InfoBarEPG.zapToService = InfoBarEPG_zapToService
422
423         baseMethods.InfoBarShowMovies__init__ = InfoBarShowMovies.__init__
424         InfoBarShowMovies.__init__ = InfoBarShowMovies__init__
425
426         baseMethods.InfoBarPiP__init__ = InfoBarPiP.__init__
427         InfoBarPiP.__init__ = InfoBarPiP__init__
428
429         InfoBarPiP.getTogglePipzapName = InfoBarPiP_getTogglePipzapName
430         InfoBarPiP.togglePipzap = InfoBarPiP_togglePipzap
431         InfoBarPiP.swapPiP = InfoBarPiP_swapPiP
432
433         baseMethods.InfoBarPiP_showPiP = InfoBarPiP.showPiP
434         InfoBarPiP.showPiP = InfoBarPiP_showPiP
435
436         baseMethods.PictureInPicture__init__ = PictureInPicture.__init__
437         PictureInPicture.__init__ = PictureInPicture__init__
438
439         PictureInPicture.active = PictureInPicture_active
440         PictureInPicture.inactive = PictureInPicture_inactive
441
442         baseMethods.PictureInPicture_move = PictureInPicture.move
443         PictureInPicture.move = PictureInPicture_move
444
445 config.plugins.pipzap = ConfigSubsection()
446 config.plugins.pipzap.enable_hotkey = ConfigEnableDisable(default = True)
447 config.plugins.pipzap.show_in_plugins = ConfigEnableDisable(default = False)
448
449 def autostart(reason, **kwargs):
450         if reason == 0:
451                 overwriteFunctions()
452
453 def activate(session, *args, **kwargs):
454         InfoBar.instance.togglePipzap()
455
456 def main(session, *args, **kwargs):
457         session.open(PipzapSetup)
458
459 def menu(menuid):
460         if menuid != "system":
461                 return []
462         return [(_("pipzap"), main, "pipzap_setup", None)]
463
464 def housekeepingPluginmenu(el):
465         if el.value:
466                 plugins.addPlugin(activateDescriptor)
467         else:
468                 plugins.removePlugin(activateDescriptor)
469
470 config.plugins.pipzap.show_in_plugins.addNotifier(housekeepingPluginmenu, initial_call=False, immediate_feedback=True)
471 activateDescriptor = PluginDescriptor(name="pipzap", description=_("Toggle pipzap status"), where=PluginDescriptor.WHERE_PLUGINMENU, fnc=activate, needsRestart=False)
472
473 def Plugins(**kwargs):
474         # do not add any entry if only one (or less :P) video decoders present
475         if SystemInfo.get("NumVideoDecoders", 1) < 2:
476                 return []
477
478         l = [
479                 PluginDescriptor(
480                         where=PluginDescriptor.WHERE_AUTOSTART,
481                         fnc=autostart,
482                         needsRestart=True, # XXX: force restart for now as I don't think the plugin will work properly without one
483                 ),
484                 PluginDescriptor(
485                         where=PluginDescriptor.WHERE_MENU,
486                         fnc=menu,
487                         needsRestart=False,
488                 ),
489         ]
490         if config.plugins.pipzap.show_in_plugins.value:
491                 l.append(activateDescriptor)
492         return l