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