228db990b654b2867a7137c0f2219ee5a210818d
[vuplus_dvbapp] / lib / python / Plugins / SystemPlugins / WebBrowser / plugin.py
1 from Plugins.Plugin import PluginDescriptor
2
3 import time, os, socket, thread
4 from socket import gaierror, error
5 from os import path as os_path, remove as os_remove
6
7 import gdata.youtube
8 import gdata.youtube.service
9 from gdata.service import BadAuthentication
10
11 from twisted.web import client
12 from twisted.internet import reactor
13
14 from urlparse import parse_qs
15 from urllib import quote, unquote_plus, unquote
16 from urllib2 import Request, URLError, urlopen as urlopen2
17 from httplib import HTTPConnection, CannotSendRequest, BadStatusLine, HTTPException
18
19 from Components.Button import Button
20 from Components.Label import Label
21 from Components.Pixmap import Pixmap
22 from Components.Sources.List import List
23 from Components.ConfigList import ConfigListScreen
24 from Components.Sources.StaticText import StaticText
25 from Components.ActionMap import NumberActionMap, ActionMap
26 from Components.ServiceEventTracker import ServiceEventTracker
27 from Components.config import config, ConfigSelection, getConfigListEntry
28
29 from Screens.Screen import Screen
30 from Screens.ChoiceBox import ChoiceBox
31 from Screens.MessageBox import MessageBox
32 from Screens.DefaultWizard import DefaultWizard
33 from Screens.InfoBarGenerics import InfoBarNotifications
34
35 from enigma import eTimer, eServiceReference, iPlayableService, fbClass, eRCInput, eConsoleAppContainer
36
37 HTTPConnection.debuglevel = 1
38
39 lock = False
40 def player_lock():
41         global lock
42         lock = True
43         fbClass.getInstance().unlock()
44
45 def player_unlock():
46         global lock
47         fbClass.getInstance().lock()
48         lock = False
49
50 def player_islock():
51         global lock
52         return lock
53
54 class VuPlayer(Screen, InfoBarNotifications):
55         skin =  """
56                 <screen name="VuPlayer" flags="wfNoBorder" position="center,620" size="455,53" title="VuPlayer" backgroundColor="transparent">
57                         <ePixmap pixmap="Vu_HD/mp_wb_background.png" position="0,0" zPosition="-1" size="455,53" />
58                         <ePixmap pixmap="Vu_HD/icons/mp_wb_buttons.png" position="40,23" size="30,13" alphatest="on" />
59
60                         <widget source="session.CurrentService" render="PositionGauge" position="80,25" size="220,10" zPosition="2" pointer="skin_default/position_pointer.png:540,0" transparent="1" foregroundColor="#20224f">
61                                 <convert type="ServicePosition">Gauge</convert>
62                         </widget>
63                         
64                         <widget source="session.CurrentService" render="Label" position="310,20" size="50,20" font="Regular;18" halign="center" valign="center" backgroundColor="#4e5a74" transparent="1" >
65                                 <convert type="ServicePosition">Position</convert>
66                         </widget>
67                         <widget name="sidebar" position="362,20" size="10,20" font="Regular;18" halign="center" valign="center" backgroundColor="#4e5a74" transparent="1" />
68                         <widget source="session.CurrentService" render="Label" position="374,20" size="50,20" font="Regular;18" halign="center" valign="center" backgroundColor="#4e5a74" transparent="1" > 
69                                 <convert type="ServicePosition">Length</convert>
70                         </widget>
71                 </screen>
72                 """
73         PLAYER_IDLE     = 0
74         PLAYER_PLAYING  = 1
75         PLAYER_PAUSED   = 2
76
77         def __init__(self, session, service, lastservice):
78                 Screen.__init__(self, session)
79                 InfoBarNotifications.__init__(self)
80
81                 self.session     = session
82                 self.service     = service
83                 self.lastservice = lastservice
84                 self["actions"] = ActionMap(["OkCancelActions", "InfobarSeekActions", "MediaPlayerActions", "MovieSelectionActions"],
85                 {
86                         "ok": self.doInfoAction,
87                         "cancel": self.doExit,
88                         "stop": self.doExit,
89                         "playpauseService": self.playpauseService,
90                 }, -2)
91                 self["sidebar"] = Label(_("/"))
92
93                 self.__event_tracker = ServiceEventTracker(screen = self, eventmap =
94                 {
95                         iPlayableService.evSeekableStatusChanged: self.__seekableStatusChanged,
96                         iPlayableService.evStart: self.__serviceStarted,
97                         iPlayableService.evEOF: self.__evEOF,
98                 })
99
100                 self.hidetimer = eTimer()
101                 self.hidetimer.timeout.get().append(self.doInfoAction)
102
103                 self.state = self.PLAYER_PLAYING
104                 self.lastseekstate = self.PLAYER_PLAYING
105                 self.__seekableStatusChanged()
106         
107                 self.onClose.append(self.__onClose)
108                 self.doPlay()
109
110         def __onClose(self):
111                 self.session.nav.stopService()
112
113         def __seekableStatusChanged(self):
114                 service = self.session.nav.getCurrentService()
115                 if service is not None:
116                         seek = service.seek()
117                         if seek is None or not seek.isCurrentlySeekable():
118                                 self.setSeekState(self.PLAYER_PLAYING)
119
120         def __serviceStarted(self):
121                 self.state = self.PLAYER_PLAYING
122                 self.__seekableStatusChanged()
123
124         def __evEOF(self):
125                 self.doExit()
126
127         def __setHideTimer(self):
128                 self.hidetimer.start(5000)
129
130         def doExit(self):
131                 list = ((_("Yes"), "y"), (_("No, but play video again"), "n"),)
132                 self.session.openWithCallback(self.cbDoExit, ChoiceBox, title=_("Stop playing this movie?"), list = list)
133
134         def cbDoExit(self, answer):
135                 answer = answer and answer[1]
136                 if answer == "y":
137                         player_unlock()
138                         self.close()
139                 elif answer == "n":
140                         if self.state != self.PLAYER_IDLE:
141                                 self.session.nav.stopService()
142                                 self.state = self.PLAYER_IDLE
143                         self.doPlay()
144
145         def setSeekState(self, wantstate):
146                 service = self.session.nav.getCurrentService()
147                 if service is None:
148                         print "No Service found"
149                         return
150
151                 pauseable = service.pause()
152                 if pauseable is not None:
153                         if wantstate == self.PLAYER_PAUSED:
154                                 pauseable.pause()
155                                 self.state = self.PLAYER_PAUSED
156                                 if not self.shown:
157                                         self.hidetimer.stop()
158                                         self.show()
159                         elif wantstate == self.PLAYER_PLAYING:
160                                 pauseable.unpause()
161                                 self.state = self.PLAYER_PLAYING
162                                 if self.shown:
163                                         self.__setHideTimer()
164                 else:
165                         self.state = self.PLAYER_PLAYING
166
167         def doInfoAction(self):
168                 if self.shown:
169                         self.hide()
170                         self.hidetimer.stop()
171                 else:
172                         self.show()
173                         if self.state == self.PLAYER_PLAYING:
174                                 self.__setHideTimer()
175
176         def doPlay(self):
177                 if self.state == self.PLAYER_PAUSED:
178                         if self.shown:
179                                 self.__setHideTimer()   
180                 self.state = self.PLAYER_PLAYING
181                 self.session.nav.playService(self.service)
182                 if self.shown:
183                         self.__setHideTimer()
184
185         def playpauseService(self):
186                 if self.state == self.PLAYER_PLAYING:
187                         self.setSeekState(self.PLAYER_PAUSED)
188                 elif self.state == self.PLAYER_PAUSED:
189                         self.setSeekState(self.PLAYER_PLAYING)
190
191 VIDEO_FMT_PRIORITY_MAP = {
192         '38' : 1, #MP4 Original (HD)
193         '37' : 2, #MP4 1080p (HD)
194         '22' : 3, #MP4 720p (HD)
195         '18' : 4, #MP4 360p
196         '35' : 5, #FLV 480p
197         '34' : 6, #FLV 360p
198 }
199 std_headers = {
200         'User-Agent': 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100627 Firefox/3.6.6',
201         'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
202         'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
203         'Accept-Language': 'en-us,en;q=0.5',
204 }
205
206 class VuPlayerLauncher:
207         def getVideoUrl(self, video_id):
208                 video_url = None
209
210                 if video_id is None or video_id == "":
211                         return video_url
212
213                 # Getting video webpage
214                 watch_url = 'http://www.youtube.com/watch?v=%s&gl=US&hl=en' % video_id
215                 watchrequest = Request(watch_url, None, std_headers)
216                 try:
217                         #print "trying to find out if a HD Stream is available",watch_url
218                         watchvideopage = urlopen2(watchrequest).read()
219                 except (URLError, HTTPException, socket.error), err:
220                         print "Error: Unable to retrieve watchpage - Error code: ", str(err)
221                         return video_url
222
223                 # Get video info
224                 for el in ['&el=embedded', '&el=detailpage', '&el=vevo', '']:
225                         info_url = ('http://www.youtube.com/get_video_info?&video_id=%s%s&ps=default&eurl=&gl=US&hl=en' % (video_id, el))
226                         request = Request(info_url, None, std_headers)
227                         try:
228                                 infopage = urlopen2(request).read()
229                                 videoinfo = parse_qs(infopage)
230                                 if ('url_encoded_fmt_stream_map' or 'fmt_url_map') in videoinfo:
231                                         break
232                         except (URLError, HTTPException, socket.error), err:
233                                 print "Error: unable to download video infopage",str(err)
234                                 return video_url
235
236                 if ('url_encoded_fmt_stream_map' or 'fmt_url_map') not in videoinfo:
237                         if 'reason' not in videoinfo:
238                                 print 'Error: unable to extract "fmt_url_map" or "url_encoded_fmt_stream_map" parameter for unknown reason'
239                         else:
240                                 reason = unquote_plus(videoinfo['reason'][0])
241                                 print 'Error: YouTube said: %s' % reason.decode('utf-8')
242                         return video_url
243
244                 video_fmt_map = {}
245                 fmt_infomap = {}
246                 if videoinfo.has_key('url_encoded_fmt_stream_map'):
247                         tmp_fmtUrlDATA = videoinfo['url_encoded_fmt_stream_map'][0].split(',url=')
248                 else:
249                         tmp_fmtUrlDATA = videoinfo['fmt_url_map'][0].split(',')
250                 for fmtstring in tmp_fmtUrlDATA:
251                         if videoinfo.has_key('url_encoded_fmt_stream_map'):
252                                 (fmturl, fmtid) = fmtstring.split('&itag=')
253                                 if fmturl.find("url=") !=-1:
254                                         fmturl = fmturl.replace("url=","")
255                         else:
256                                 (fmtid,fmturl) = fmtstring.split('|')
257                         if VIDEO_FMT_PRIORITY_MAP.has_key(fmtid):
258                                 video_fmt_map[VIDEO_FMT_PRIORITY_MAP[fmtid]] = { 'fmtid': fmtid, 'fmturl': unquote_plus(fmturl) }
259                         fmt_infomap[int(fmtid)] = unquote_plus(fmturl)
260                 print "got",sorted(fmt_infomap.iterkeys())
261                 if video_fmt_map and len(video_fmt_map):
262                         video_url = video_fmt_map[sorted(video_fmt_map.iterkeys())[0]]['fmturl'].split(';')[0]
263                         #print "found best available video format:",video_fmt_map[sorted(video_fmt_map.iterkeys())[0]]['fmtid']
264                         #print "found best available video url:",video_url
265                 return video_url
266
267         def run(self, tubeid, session, service):
268                 try:
269                         myurl = self.getVideoUrl(tubeid)
270                         print "Playing URL", myurl
271                         if myurl is None:
272                                 session.open(MessageBox, _("Sorry, video is not available!"), MessageBox.TYPE_INFO)
273                                 return
274
275                         player_lock()
276                         myreference = eServiceReference(4097, 0, myurl)
277                         session.open(VuPlayer, myreference, service)
278                 except Exception, msg:
279                         player_unlock()
280                         print "Error >>", msg
281
282 class VuPlayerService:
283         def __init__(self, session):
284                 self.enable = False
285                 self.socket_timeout = 0
286                 self.max_buffer_size = 1024
287                 self.uds_file = "/tmp/vuplus.tmp"
288                 self.session = session
289                 try:
290                         os.remove(self.uds_file)
291                 except OSError:
292                         pass
293         
294         def start(self, timeout = 1):
295                 self.socket_timeout = timeout
296                 thread.start_new_thread(self.run, (True,))
297
298         def stop(self):
299                 self.enable = False
300
301         def isRunning(self):
302                 return self.enable
303
304         def run(self, e = True):
305                 if self.enable:
306                         return
307                 self.enable = e
308                 self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
309                 self.sock.settimeout(self.socket_timeout)
310                 self.sock.bind(self.uds_file)
311                 self.sock.listen(1)
312                 while(self.enable):
313                         try:
314                                 conn, addr = self.sock.accept()
315                                 self.parseHandle(conn, addr)
316                                 conn.close()
317                         except socket.timeout:
318                                 #print "[socket timeout]"
319                                 pass
320
321         def parseHandle(self, conn, addr):
322                 # [http://www.youtube.com/watch?v=BpThu778qB4&feature=related]
323                 data = conn.recv(self.max_buffer_size)
324                 print "[%s]" % (data) 
325                 tmp = data.split("?")
326                 print tmp # ['http://www.youtube.com/watch', 'v=BpThu778qB4&feature=related']
327                 service = self.session.nav.getCurrentlyPlayingServiceReference()
328                 if len(tmp) == 2 and tmp[0] == "http://www.youtube.com/watch":
329                         tmp = tmp[1].split("&")
330                         print tmp # ['v=BpThu778qB4', 'feature=related']
331                         if len(tmp) == 2:
332                                 tmp = tmp[0].split("=")
333                                 print tmp # ['v', 'BpThu778qB4']
334                                 if len(tmp) == 2 and tmp[0] == "v":
335                                         player = VuPlayerLauncher()
336                                         player.run(tmp[1], self.session, service)
337                                         while player_islock():
338                                                 time.sleep(1)
339                                         self.session.nav.playService(service)
340                                         data = "ok$"
341                                 else:
342                                         data = "nok$parsing fail"
343                         else:
344                                 data = "nok$parsing fail"
345                 else:
346                         data = "nok$parsing fail"
347                 conn.send(data)
348
349 class BrowserLauncher(ConfigListScreen, Screen):
350         skin=   """
351                 <screen name="BrowserLauncher" position="center,center" size="300,160" title="Web Browser">
352                         <ePixmap pixmap="Vu_HD/buttons/red.png" position="50,0" size="140,40" alphatest="on" />
353                         <ePixmap pixmap="Vu_HD/buttons/green.png" position="170,0" size="140,40" alphatest="on" />
354                         <widget source="key_red" render="Label" position="50,0" zPosition="1" size="115,30" font="Regular;20" halign="center" valign="center" transparent="1" />
355                         <widget source="key_green" render="Label" position="170,0" zPosition="1" size="115,30" font="Regular;20" halign="center" valign="center" transparent="1" />
356                         <widget name="config" position="0,50" size="300,70" scrollbarMode="showOnDemand" />
357                         <widget name="introduction" position="0,120" size="300,40" font="Regular;20" halign="center" backgroundColor="#a08500" transparent="1" />
358                 </screen>
359                 """
360         def __init__(self, session): 
361                 Screen.__init__(self, session)
362                 self.session = session
363
364                 self.browser_root = "/usr/bin"
365                 self.browser_name = "arora"
366                 self.mouse_cond = "/proc/stb/fp/mouse"
367                 self["actions"] = ActionMap(["OkCancelActions", "ShortcutActions", "WizardActions", "ColorActions", "SetupActions", ],
368                 {       "red": self.keyCancel,
369                         "cancel": self.keyCancel,
370                         "green": self.keyGo,
371                 }, -2)
372
373                 self.list = []
374                 ConfigListScreen.__init__(self, self.list)
375
376                 self["key_red"] = StaticText(_("Exit"))
377                 self["key_green"] = StaticText(_("Start"))
378                 self.introduntion = Label(_(" "))
379                 self["introduction"] = self.introduntion
380
381                 self.devices_string = ""
382                 self.mouse_choice_list = []
383                 self.mouse_device_list = []
384                 self.keyboard_choice_list = []
385                 self.keyboard_device_list = []
386                 self.makeConfig()
387                 #time.sleep(2)
388
389                 self.lock = False
390                 self.vu_service = VuPlayerService(self.session)
391                 self.vu_service.start(timeout=5)
392
393         def enableRCMouse(self, mode): #mode=[0|1]|[False|True]
394                 if os.path.exists(self.mouse_cond):
395                         self.cmd("echo %d > %s" % (mode, self.mouse_cond))
396
397         def cmd(self, cmd):
398                 print "prepared cmd:", cmd
399                 os.system(cmd)
400
401         def keyNone(self):
402                 None
403
404         def keyCancel(self):
405                 #if self.lock == False:
406                         self.vu_service.stop()
407                         self.cmd("killall -9 %s"%(self.browser_name))
408                         self.cmd("echo 60 > /proc/sys/vm/swappiness")
409                         self.introduntion.setText(" ")
410                         if self.mouse.value == 0:
411                                 self.enableRCMouse(False) #rc-mouse off
412                         fbClass.getInstance().unlock()
413                         #eRCInput.getInstance().unlock()
414                         self.close()
415
416         def makeConfig(self):
417                 self.devices = eConsoleAppContainer()
418                 self.devices.dataAvail.append(self.callbackDevicesDataAvail)
419                 self.devices.appClosed.append(self.callbakcDevicesAppClose)
420                 self.devices.execute(_("cat /proc/bus/input/devices"))
421
422         def callbackDevicesDataAvail(self, ret_data):
423                 self.devices_string = self.devices_string + ret_data
424
425         def callbakcDevicesAppClose(self, retval):
426                 self.parseDeviceData(self.devices_string)
427                 self.makeHandlerList()
428
429                 # none : -1, rc : 0, usb : 1
430                 self.mouse_choice_list.append((2, _("None")))
431                 self.keyboard_choice_list.append((2, _("None")))
432                 
433                 print self.mouse_choice_list
434                 print self.keyboard_choice_list
435                 print self.mouse_device_list
436                 print self.keyboard_device_list
437
438                 self.mouse = ConfigSelection(default = self.mouse_choice_list[0][0], choices = self.mouse_choice_list)
439                 self.keyboard = ConfigSelection(default = self.mouse_choice_list[0][0], choices = self.keyboard_choice_list)
440                 
441                 self.list.append(getConfigListEntry(_('Mouse'), self.mouse))            
442                 self.list.append(getConfigListEntry(_('Keyboard'), self.keyboard))
443                 self["config"].list = self.list
444                 self["config"].l.setList(self.list)
445
446         def parseDeviceData(self, data):
447                 n = ""
448                 p = ""
449                 h = ""
450                 self.devices=[]
451                 lines=data.split('\n')
452                 for line in lines:
453                         if line == None or line == "":
454                                 if h != None and len(h) != 0:
455                                         print "find driver >> name[%s], phys[%s], handler[%s]" % (n, p, h)
456                                         self.devices.append([n, p, h])
457                                 n = ""
458                                 p = ""
459                                 h = ""
460                                 continue
461                         if line[0] == 'N':
462                                 n = line[8:].strip()
463                         elif line[0] == 'P':
464                                 p = line[8:].strip()
465                         elif line[0] == 'H':
466                                 h = line[12:].strip()
467
468         def makeHandlerList(self):
469                 if self.devices == None or self.devices == []:
470                         return False
471
472                 mouse_pc_h = []
473                 mouse_rc_h = []
474                 keyboard_pc_h = []
475                 keyboard_rc_h = []
476                 for dev in self.devices:
477                         n = dev[0]
478                         p = dev[1]
479                         h = dev[2]
480                         if p.startswith("usb-ohci-brcm"):
481                                 if h.rfind("mouse") >= 0:
482                                         mouse_pc_h = [(1, _("USB Mouse")), self.getHandlerName(h, "mouse")]
483                                 else:
484                                         if len(keyboard_pc_h) == 0:
485                                                 keyboard_pc_h = [(1, _("USB Keyboard")), self.getHandlerName(h, "event")]
486                         else:
487                                 if n[1:].startswith("dreambox") and os.path.exists(self.mouse_cond) :
488                                         mouse_rc_h    = [(0, _("RemoteControl")), self.getHandlerName(h, "event")]
489                                         keyboard_rc_h = [(0, _("RemoteControl")), self.getHandlerName(h, "event")]
490                 if len(mouse_rc_h) > 0:
491                         self.mouse_choice_list.append(mouse_rc_h[0])
492                         self.mouse_device_list.append(mouse_rc_h[1])
493                 if len(mouse_pc_h) > 0:
494                         self.mouse_choice_list.append(mouse_pc_h[0])
495                         self.mouse_device_list.append(mouse_pc_h[1])
496
497                 if len(keyboard_rc_h) > 0:
498                         self.keyboard_choice_list.append(keyboard_rc_h[0])
499                         self.keyboard_device_list.append(keyboard_rc_h[1])
500                 if len(keyboard_pc_h) > 0:
501                         self.keyboard_choice_list.append(keyboard_pc_h[0])
502                         self.keyboard_device_list.append(keyboard_pc_h[1])
503                 return True
504
505         def getHandlerName(self, h, s):
506                 if h is None or len(h) == 0:
507                         return ""
508
509                 handles = h.split()                                                
510                 #print "handles >> ", handles
511                 for tmp_h in handles:                                                                                                    
512                         #print "handle_item >> ", tmp_h
513                         if tmp_h.startswith(s):          
514                                 #print "detected : [%s]" % tmp_h
515                                 return tmp_h
516                 return ""
517
518         def keyGo(self):
519                 if self.lock == False:
520                         self.lock = True
521                         
522                         self.introduntion.setText("Run web-browser.\nPlease, wait...")
523                         self.cmd("echo 0 > /proc/sys/vm/swappiness")
524
525                         kbd_cmd = ""
526                         mouse_cmd = ""
527                         extra_cmd = "" 
528                         browser_cmd = "%s/%s -qws" % (self.browser_root, self.browser_name)
529
530                         fbClass.getInstance().lock()
531                         #eRCInput.getInstance().lock()
532
533                         if self.mouse.value == 0:
534                                 self.enableRCMouse(True) #rc-mouse on
535                                 idx = self.getListIndex(self.mouse_choice_list, 0)
536                                 mouse_cmd = "export QWS_MOUSE_PROTO=LinuxInput:/dev/input/%s; " % (self.mouse_device_list[idx])
537                         elif self.mouse.value == 1:
538                                 mouse_cmd = " "
539                                 #mouse_cmd = "export QWS_MOUSE_PROTO=Auto:/dev/input/%s; " % (m)
540                         elif self.mouse.value == 2:
541                                 mouse_cmd = "export QWS_MOUSE_PROTO=None; "
542
543                         if self.keyboard.value == 0:
544                                 idx = self.getListIndex(self.keyboard_choice_list, 0)
545                                 kbd_cmd = "export QWS_KEYBOARD=LinuxInput:/dev/input/%s; " % (self.keyboard_device_list[idx])
546                         elif self.keyboard.value == 1:
547                                 idx = self.getListIndex(self.keyboard_choice_list, 1)
548                                 kbd_cmd = "export QWS_KEYBOARD=LinuxInput:/dev/input/%s; " % (self.keyboard_device_list[idx])
549                         elif self.keyboard.value == 2:
550                                 kbd_cmd = " "
551                         print "mouse cmd >>", mouse_cmd, " >> ", self.mouse.value
552                         print "keyboard cmd >>", kbd_cmd, " >> ", self.keyboard.value
553
554                         cmd = "%s%s%s%s" % (extra_cmd, kbd_cmd, mouse_cmd, browser_cmd)
555                         print "prepared command : [%s]" % cmd
556
557                         self.launcher = eConsoleAppContainer()
558                         self.launcher.appClosed.append(self.callbackLauncherAppClosed)
559                         self.launcher.dataAvail.append(self.callbackLauncherDataAvail)
560                         self.launcher.execute(cmd)
561                         print "running arora..."
562
563         def getListIndex(self, l, v):
564                 idx = 0
565                 for i in l:
566                         if i[0] == v:
567                                 return idx;
568                         idx = idx + 1
569                 return -1
570
571         def callbackLauncherDataAvail(self, ret_data):
572                 print ret_data
573                 if ret_data.startswith("--done--"):
574                         self.lock = False
575                         self.keyCancel()
576                 
577         def callbackLauncherAppClosed(self, retval = 1):
578                 None
579
580 def main(session, **kwargs):
581         session.open(BrowserLauncher)
582                                                            
583 def Plugins(**kwargs):            
584         return PluginDescriptor(name=_("Web Browser"), description="start web browser", where = PluginDescriptor.WHERE_PLUGINMENU, fnc=main)
585
586