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