1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mosaic/plugin.py Mon Jan 16 22:56:52 2012 +0100 1.3 @@ -0,0 +1,383 @@ 1.4 +# -*- coding: UTF-8 -*- 1.5 +# Mosaic by AliAbdul 1.6 +from Components.ActionMap import NumberActionMap 1.7 +from Components.config import config, ConfigSubsection, ConfigInteger 1.8 +from Components.Console import Console 1.9 +from Components.Label import Label 1.10 +from Components.Language import language 1.11 +from Components.Pixmap import Pixmap 1.12 +from Components.VideoWindow import VideoWindow 1.13 +from enigma import eServiceCenter, eServiceReference, eTimer, getDesktop, loadPNG 1.14 +from os import environ 1.15 +from Plugins.Plugin import PluginDescriptor 1.16 +from Screens.ChannelSelection import BouquetSelector 1.17 +from Screens.MessageBox import MessageBox 1.18 +from Screens.Screen import Screen 1.19 +from Tools.Directories import resolveFilename, SCOPE_SKIN_IMAGE, SCOPE_LANGUAGE, SCOPE_PLUGINS 1.20 +from Tools.LoadPixmap import LoadPixmap 1.21 +import gettext 1.22 + 1.23 +################################################ 1.24 + 1.25 +grab_binary = "/usr/bin/grab" 1.26 +grab_picture = "/tmp/mosaic.jpg" 1.27 +grab_errorlog = "/tmp/mosaic.log" 1.28 + 1.29 +config_limits = (3, 30) 1.30 +config.plugins.Mosaic = ConfigSubsection() 1.31 +config.plugins.Mosaic.countdown = ConfigInteger(default=5, limits=config_limits) 1.32 + 1.33 +playingIcon = loadPNG(resolveFilename(SCOPE_SKIN_IMAGE, 'skin_default/icons/ico_mp_play.png')) 1.34 +pausedIcon = loadPNG(resolveFilename(SCOPE_SKIN_IMAGE, 'skin_default/icons/ico_mp_pause.png')) 1.35 + 1.36 +################################################ 1.37 + 1.38 +lang = language.getLanguage() 1.39 +environ["LANGUAGE"] = lang[:2] 1.40 +gettext.bindtextdomain("enigma2", resolveFilename(SCOPE_LANGUAGE)) 1.41 +gettext.textdomain("enigma2") 1.42 +gettext.bindtextdomain("Mosaic", "%s%s" % (resolveFilename(SCOPE_PLUGINS), "Extensions/Mosaic/locale/")) 1.43 + 1.44 +def _(txt): 1.45 + t = gettext.dgettext("Mosaic", txt) 1.46 + if t == txt: 1.47 + t = gettext.gettext(txt) 1.48 + return t 1.49 + 1.50 +################################################ 1.51 + 1.52 +class Mosaic(Screen): 1.53 + PLAY = 0 1.54 + PAUSE = 1 1.55 + 1.56 + desktop = getDesktop(0) 1.57 + size = desktop.size() 1.58 + width = size.width() 1.59 + height = size.height() 1.60 + windowWidth = width / 4 1.61 + windowHeight = height / 4 1.62 + 1.63 + positions = [] 1.64 + x = 80 1.65 + y = 50 1.66 + for i in range(1, 10): 1.67 + positions.append([x, y]) 1.68 + x += windowWidth 1.69 + x += ((width - 160) - (windowWidth * 3)) / 2 1.70 + if (i == 3) or (i == 6): 1.71 + y = y + windowHeight + 20 1.72 + x = 80 1.73 + 1.74 + skin = "" 1.75 + skin += """<screen position="0,0" size="%d,%d" title="Mosaic" flags="wfNoBorder" backgroundColor="#ffffff" >""" % (width, height) 1.76 + skin += """<widget name="playState" position="55,55" size="16,16" alphatest="on" />""" 1.77 + skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[0][0]-2, positions[0][1]-1, windowWidth, windowHeight) 1.78 + skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[1][0]-2, positions[1][1]-1, windowWidth, windowHeight) 1.79 + skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[2][0]-2, positions[2][1]-1, windowWidth, windowHeight) 1.80 + skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[3][0]-2, positions[3][1]-1, windowWidth, windowHeight) 1.81 + skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[4][0]-2, positions[4][1]-1, windowWidth, windowHeight) 1.82 + skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[5][0]-2, positions[5][1]-1, windowWidth, windowHeight) 1.83 + skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[6][0]-2, positions[6][1]-1, windowWidth, windowHeight) 1.84 + skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[7][0]-2, positions[7][1]-1, windowWidth, windowHeight) 1.85 + skin += """<eLabel position="%d,%d" size="%d,%d" />""" % (positions[8][0]-2, positions[8][1]-1, windowWidth, windowHeight) 1.86 + skin += """<widget name="channel1" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[0][0], positions[0][1]-18, windowWidth-4) 1.87 + skin += """<widget name="channel2" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[1][0], positions[1][1]-18, windowWidth-4) 1.88 + skin += """<widget name="channel3" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[2][0], positions[2][1]-18, windowWidth-4) 1.89 + skin += """<widget name="channel4" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[3][0], positions[3][1]-18, windowWidth-4) 1.90 + skin += """<widget name="channel5" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[4][0], positions[4][1]-18, windowWidth-4) 1.91 + skin += """<widget name="channel6" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[5][0], positions[5][1]-18, windowWidth-4) 1.92 + skin += """<widget name="channel7" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[6][0], positions[6][1]-18, windowWidth-4) 1.93 + skin += """<widget name="channel8" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[7][0], positions[7][1]-18, windowWidth-4) 1.94 + skin += """<widget name="channel9" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (positions[8][0], positions[8][1]-18, windowWidth-4) 1.95 + skin += """<widget name="window1" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[0][0]-2, positions[0][1]-1, windowWidth, windowHeight) 1.96 + skin += """<widget name="window2" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[1][0]-2, positions[1][1]-1, windowWidth, windowHeight) 1.97 + skin += """<widget name="window3" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[2][0]-2, positions[2][1]-1, windowWidth, windowHeight) 1.98 + skin += """<widget name="window4" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[3][0]-2, positions[3][1]-1, windowWidth, windowHeight) 1.99 + skin += """<widget name="window5" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[4][0]-2, positions[4][1]-1, windowWidth, windowHeight) 1.100 + skin += """<widget name="window6" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[5][0]-2, positions[5][1]-1, windowWidth, windowHeight) 1.101 + skin += """<widget name="window7" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[6][0]-2, positions[6][1]-1, windowWidth, windowHeight) 1.102 + skin += """<widget name="window8" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[7][0]-2, positions[7][1]-1, windowWidth, windowHeight) 1.103 + skin += """<widget name="window9" position="%d,%d" zPosition="1" size="%d,%d" />""" % (positions[8][0]-2, positions[8][1]-1, windowWidth, windowHeight) 1.104 + skin += """<widget name="video1" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[0][0]-2, positions[0][1]-1, windowWidth, windowHeight) 1.105 + skin += """<widget name="video2" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[1][0]-2, positions[1][1]-1, windowWidth, windowHeight) 1.106 + skin += """<widget name="video3" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[2][0]-2, positions[2][1]-1, windowWidth, windowHeight) 1.107 + skin += """<widget name="video4" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[3][0]-2, positions[3][1]-1, windowWidth, windowHeight) 1.108 + skin += """<widget name="video5" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[4][0]-2, positions[4][1]-1, windowWidth, windowHeight) 1.109 + skin += """<widget name="video6" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[5][0]-2, positions[5][1]-1, windowWidth, windowHeight) 1.110 + skin += """<widget name="video7" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[6][0]-2, positions[6][1]-1, windowWidth, windowHeight) 1.111 + skin += """<widget name="video8" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[7][0]-2, positions[7][1]-1, windowWidth, windowHeight) 1.112 + skin += """<widget name="video9" position="%d,%d" zPosition="2" size="%d,%d" backgroundColor="#ffffffff" />""" % (positions[8][0]-2, positions[8][1]-1, windowWidth, windowHeight) 1.113 + skin += """<widget name="event1" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[0][0]-2, positions[0][1]-1, windowWidth) 1.114 + skin += """<widget name="event2" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[1][0]-2, positions[1][1]-1, windowWidth) 1.115 + skin += """<widget name="event3" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[2][0]-2, positions[2][1]-1, windowWidth) 1.116 + skin += """<widget name="event4" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[3][0]-2, positions[3][1]-1, windowWidth) 1.117 + skin += """<widget name="event5" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[4][0]-2, positions[4][1]-1, windowWidth) 1.118 + skin += """<widget name="event6" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[5][0]-2, positions[5][1]-1, windowWidth) 1.119 + skin += """<widget name="event7" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[6][0]-2, positions[6][1]-1, windowWidth) 1.120 + skin += """<widget name="event8" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[7][0]-2, positions[7][1]-1, windowWidth) 1.121 + skin += """<widget name="event9" position="%d,%d" size="%d,20" zPosition="3" font="Regular;18" backgroundColor="#000000" foregroundColor="#ffffff" />""" % (positions[8][0]-2, positions[8][1]-1, windowWidth) 1.122 + 1.123 + skin += """<widget name="countdown" position="80,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" />""" % (height-50, windowWidth) 1.124 + skin += """<widget name="count" position="%d,%d" size="%d,20" font="Regular;18" backgroundColor="#ffffff" foregroundColor="#000000" halign="right" /> 1.125 + </screen>""" % (positions[2][0] ,height-50, windowWidth) 1.126 + 1.127 + def __init__(self, session, services): 1.128 + Screen.__init__(self, session) 1.129 + 1.130 + self.session = session 1.131 + self.oldService = self.session.nav.getCurrentlyPlayingServiceReference() 1.132 + self.consoleCmd = "" 1.133 + self.Console = Console() 1.134 + self.serviceHandler = eServiceCenter.getInstance() 1.135 + self.ref_list = services 1.136 + self.window_refs = [None, None, None, None, None, None, None, None, None] 1.137 + self.current_refidx = 0 1.138 + self.current_window = 1 1.139 + self.countdown = config.plugins.Mosaic.countdown.value 1.140 + self.working = False 1.141 + self.state = self.PLAY 1.142 + 1.143 + self["playState"] = Pixmap() 1.144 + for i in range(1, 10): 1.145 + self["window" + str(i)] = Pixmap() 1.146 + self["video" + str(i)] = VideoWindow(decoder=0, fb_width=self.width, fb_height=self.height) 1.147 + self["video" + str(i)].hide() 1.148 + self["channel" + str(i)] = Label("") 1.149 + self["event" + str(i)] = Label("") 1.150 + self["event" + str(i)].hide() 1.151 + self["video1"].decoder = 0 1.152 + self["video1"].show() 1.153 + self["countdown"] = Label() 1.154 + self.updateCountdownLabel() 1.155 + self["count"] = Label() 1.156 + 1.157 + self["actions"] = NumberActionMap(["MosaicActions"], 1.158 + { 1.159 + "ok": self.exit, 1.160 + "cancel": self.closeWithOldService, 1.161 + "green": self.play, 1.162 + "yellow": self.pause, 1.163 + "channelup": self.countdownPlus, 1.164 + "channeldown": self.countdownMinus, 1.165 + "1": self.numberPressed, 1.166 + "2": self.numberPressed, 1.167 + "3": self.numberPressed, 1.168 + "4": self.numberPressed, 1.169 + "5": self.numberPressed, 1.170 + "6": self.numberPressed, 1.171 + "7": self.numberPressed, 1.172 + "8": self.numberPressed, 1.173 + "9": self.numberPressed 1.174 + }, prio=-1) 1.175 + 1.176 + self.updateTimer = eTimer() 1.177 + self.updateTimer.callback.append(self.updateCountdown) 1.178 + self.checkTimer = eTimer() 1.179 + self.checkTimer.callback.append(self.checkGrab) 1.180 + self.checkTimer.start(500, 1) 1.181 + 1.182 + def checkGrab(self): 1.183 + # Start the first service in the bouquet and show the service-name 1.184 + ref = self.ref_list[0] 1.185 + self.window_refs[0] = ref 1.186 + info = self.serviceHandler.info(ref) 1.187 + name = info.getName(ref).replace('\xc2\x86', '').replace('\xc2\x87', '') 1.188 + event_name = self.getEventName(info, ref) 1.189 + self["channel1"].setText(name) 1.190 + self["event1"].setText(event_name) 1.191 + self.session.nav.playService(ref) 1.192 + self["count"].setText(_("Channel: ") + "1 / " + str(len(self.ref_list))) 1.193 + self["playState"].instance.setPixmap(playingIcon) 1.194 + # Start updating the video-screenshots 1.195 + self.updateTimer.start(1, 1) 1.196 + 1.197 + def exit(self, callback=None): 1.198 + self.deleteConsoleCallbacks() 1.199 + self.close() 1.200 + 1.201 + def deleteConsoleCallbacks(self): 1.202 + if self.Console.appContainers.has_key(self.consoleCmd): 1.203 + del self.Console.appContainers[self.consoleCmd].dataAvail[:] 1.204 + del self.Console.appContainers[self.consoleCmd].appClosed[:] 1.205 + del self.Console.appContainers[self.consoleCmd] 1.206 + del self.Console.extra_args[self.consoleCmd] 1.207 + del self.Console.callbacks[self.consoleCmd] 1.208 + 1.209 + def closeWithOldService(self): 1.210 + self.session.nav.playService(self.oldService) 1.211 + self.deleteConsoleCallbacks() 1.212 + self.close() 1.213 + 1.214 + def numberPressed(self, number): 1.215 + ref = self.window_refs[number-1] 1.216 + if ref is not None: 1.217 + self.session.nav.playService(ref) 1.218 + self.deleteConsoleCallbacks() 1.219 + self.close() 1.220 + 1.221 + def play(self): 1.222 + if self.working == False and self.state == self.PAUSE: 1.223 + self.state = self.PLAY 1.224 + self.updateTimer.start(1000, 1) 1.225 + self["playState"].instance.setPixmap(playingIcon) 1.226 + 1.227 + def pause(self): 1.228 + if self.working == False and self.state == self.PLAY: 1.229 + self.state = self.PAUSE 1.230 + self.updateTimer.stop() 1.231 + self["playState"].instance.setPixmap(pausedIcon) 1.232 + 1.233 + def countdownPlus(self): 1.234 + self.changeCountdown(1) 1.235 + 1.236 + def countdownMinus(self): 1.237 + self.changeCountdown(-1) 1.238 + 1.239 + def changeCountdown(self, direction): 1.240 + if self.working == False: 1.241 + configNow = config.plugins.Mosaic.countdown.value 1.242 + configNow += direction 1.243 + 1.244 + if configNow < config_limits[0]: 1.245 + configNow = config_limits[0] 1.246 + elif configNow > config_limits[1]: 1.247 + configNow = config_limits[1] 1.248 + 1.249 + config.plugins.Mosaic.countdown.value = configNow 1.250 + config.plugins.Mosaic.countdown.save() 1.251 + 1.252 + self.updateCountdownLabel() 1.253 + 1.254 + def makeNextScreenshot(self): 1.255 + # Grab video 1.256 + if not self.Console: 1.257 + self.Console = Console() 1.258 + self.consoleCmd = "%s -v -r %d -l -j 100 %s" % (grab_binary, self.windowWidth, grab_picture) 1.259 + self.Console.ePopen(self.consoleCmd, self.showNextScreenshot) 1.260 + 1.261 + def showNextScreenshot(self, result, retval, extra_args): 1.262 + if retval == 0: 1.263 + # Show screenshot in the current window 1.264 + pic = LoadPixmap(grab_picture) 1.265 + self["window" + str(self.current_window)].instance.setPixmap(pic) 1.266 + 1.267 + # Hide current video-window and show the running event-name 1.268 + self["video" + str(self.current_window)].hide() 1.269 + self["event" + str(self.current_window)].show() 1.270 + 1.271 + # Get next ref 1.272 + self.current_refidx += 1 1.273 + if self.current_refidx > (len(self.ref_list) -1): 1.274 + self.current_refidx = 0 1.275 + 1.276 + # Play next ref 1.277 + ref = self.ref_list[self.current_refidx] 1.278 + info = self.serviceHandler.info(ref) 1.279 + name = info.getName(ref).replace('\xc2\x86', '').replace('\xc2\x87', '') 1.280 + event_name = self.getEventName(info, ref) 1.281 + self.session.nav.playService(ref) 1.282 + 1.283 + # Get next window index 1.284 + self.current_window += 1 1.285 + if self.current_window > 9: 1.286 + self.current_window = 1 1.287 + 1.288 + # Save the ref 1.289 + self.window_refs[self.current_window-1] = ref 1.290 + 1.291 + # Save the event-name and hide the label 1.292 + self["event" + str(self.current_window)].hide() 1.293 + self["event" + str(self.current_window)].setText(event_name) 1.294 + 1.295 + # Show the new video-window 1.296 + self["video" + str(self.current_window)].show() 1.297 + self["video" + str(self.current_window)].decoder = 0 1.298 + 1.299 + # Show the servicename 1.300 + self["channel" + str(self.current_window)].setText(name) 1.301 + self["count"].setText(_("Channel: ") + str(self.current_refidx + 1) + " / " + str(len(self.ref_list))) 1.302 + 1.303 + # Restart timer 1.304 + self.working = False 1.305 + self.updateTimer.start(1, 1) 1.306 + else: 1.307 + print "[Mosaic] retval: %d result: %s" % (retval, result) 1.308 + 1.309 + try: 1.310 + f = open(grab_errorlog, "w") 1.311 + f.write("retval: %d\nresult: %s" % (retval, result)) 1.312 + f.close() 1.313 + except: 1.314 + pass 1.315 + 1.316 + self.session.openWithCallback(self.exit, MessageBox, _("Error while creating screenshot. You need grab version 0.8 or higher!"), MessageBox.TYPE_ERROR, timeout=5) 1.317 + 1.318 + def updateCountdown(self, callback=None): 1.319 + self.countdown -= 1 1.320 + self.updateCountdownLabel() 1.321 + if self.countdown == 0: 1.322 + self.countdown = config.plugins.Mosaic.countdown.value 1.323 + self.working = True 1.324 + self.makeNextScreenshot() 1.325 + else: 1.326 + self.updateTimer.start(1000, 1) 1.327 + 1.328 + def updateCountdownLabel(self): 1.329 + self["countdown"].setText("%s %s / %s" % (_("Countdown:"), str(self.countdown), str(config.plugins.Mosaic.countdown.value))) 1.330 + 1.331 + def getEventName(self, info, ref): 1.332 + event = info.getEvent(ref) 1.333 + if event is not None: 1.334 + eventName = event.getEventName() 1.335 + if eventName is None: 1.336 + eventName = "" 1.337 + else: 1.338 + eventName = "" 1.339 + return eventName 1.340 + 1.341 +################################################ 1.342 +# Most stuff stolen from the GraphMultiEPG 1.343 + 1.344 +Session = None 1.345 +Servicelist = None 1.346 +BouquetSelectorScreen = None 1.347 + 1.348 +def getBouquetServices(bouquet): 1.349 + services = [] 1.350 + Servicelist = eServiceCenter.getInstance().list(bouquet) 1.351 + if Servicelist is not None: 1.352 + while True: 1.353 + service = Servicelist.getNext() 1.354 + if not service.valid(): 1.355 + break 1.356 + if service.flags & (eServiceReference.isDirectory | eServiceReference.isMarker): 1.357 + continue 1.358 + services.append(service) 1.359 + return services 1.360 + 1.361 +def closeBouquetSelectorScreen(ret=None): 1.362 + if BouquetSelectorScreen is not None: 1.363 + BouquetSelectorScreen.close() 1.364 + 1.365 +def openMosaic(bouquet): 1.366 + if bouquet is not None: 1.367 + services = getBouquetServices(bouquet) 1.368 + if len(services): 1.369 + Session.openWithCallback(closeBouquetSelectorScreen, Mosaic, services) 1.370 + 1.371 +def main(session, servicelist, **kwargs): 1.372 + global Session 1.373 + Session = session 1.374 + global Servicelist 1.375 + Servicelist = servicelist 1.376 + global BouquetSelectorScreen 1.377 + 1.378 + bouquets = Servicelist.getBouquetList() 1.379 + if bouquets is not None: 1.380 + if len(bouquets) == 1: 1.381 + openMosaic(bouquets[0][1]) 1.382 + elif len(bouquets) > 1: 1.383 + BouquetSelectorScreen = Session.open(BouquetSelector, bouquets, openMosaic, enableWrapAround=True) 1.384 + 1.385 +def Plugins(**kwargs): 1.386 + return PluginDescriptor(name=_("Mosaic"), where=PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=main)