1 # -*- coding: utf-8 -*-
5 from enigma import eTimer, eSize, ePoint, getDesktop, eDVBVolumecontrol, eBackgroundFileEraser
7 from Screens.Screen import Screen
8 from Screens.MessageBox import MessageBox
9 from Screens.NumericalTextInputHelpDialog import NumericalTextInputHelpDialog
10 from Screens.InputBox import InputBox
11 from Screens import Standby
12 from Screens.HelpMenu import HelpableScreen
14 from Components.config import config, ConfigSubsection, ConfigSelection, ConfigEnableDisable, getConfigListEntry, ConfigText, ConfigInteger
15 from Components.ActionMap import ActionMap
16 from Components.Label import Label
17 from Components.Button import Button
18 from Components.Pixmap import Pixmap
19 from Components.Sources.List import List
20 from Components.ConfigList import ConfigListScreen
21 from Components.Harddisk import harddiskmanager
23 from Plugins.Plugin import PluginDescriptor
24 from Tools import Notifications
25 from Tools.NumericalTextInput import NumericalTextInput
26 from Tools.Directories import resolveFilename, SCOPE_PLUGINS, SCOPE_SKIN_IMAGE, SCOPE_CONFIG, SCOPE_MEDIA
27 from Tools.LoadPixmap import LoadPixmap
28 from GlobalActions import globalActionMap # for muting
30 from twisted.internet import reactor
31 from twisted.internet.protocol import ReconnectingClientFactory
32 from twisted.protocols.basic import LineReceiver
36 from datetime import datetime
38 from . import debug, _
39 from reverselookup import ReverseLookupAndNotify
41 my_global_session = None
44 ("0049", _("Germany")),
45 ("0031", _("The Netherlands")),
46 ("0033", _("France")),
48 ("0041", _("Switzerland")),
49 ("0043", _("Austria"))
52 DESKTOP_WIDTH = getDesktop(0).size().width()
53 DESKTOP_HEIGHT = getDesktop(0).size().height()
57 # It returns the first value, if HD (1280x720),
58 # the second if SD (720x576),
59 # else something scaled accordingly
60 # if one of the parameters is -1, scale proportionally
67 return scale(y2, y1, 1280, 720, DESKTOP_WIDTH)
73 return scale(y2, y1, 720, 576, DESKTOP_HEIGHT)
74 def scale(y2, y1, x2, x1, x):
75 return (y2 - y1) * (x - x1) / (x2 - x1) + y1
77 def getMountedDevices():
78 def handleMountpoint(loc):
79 # debug("[NcidClient] handleMountpoint: %s" %repr(loc))
85 return (mp, desc + " (" + mp + ")")
87 mountedDevs = [(resolveFilename(SCOPE_CONFIG), _("Flash")),
88 (resolveFilename(SCOPE_MEDIA, "cf"), _("Compact Flash")),
89 (resolveFilename(SCOPE_MEDIA, "usb"), _("USB Device"))]
90 mountedDevs += map(lambda p: (p.mountpoint, (_(p.description) if p.description else "")), harddiskmanager.getMountedPartitions(True))
91 mediaDir = resolveFilename(SCOPE_MEDIA)
92 for p in os.listdir(mediaDir):
93 if os.path.join(mediaDir, p) not in [path[0] for path in mountedDevs]:
94 mountedDevs.append((os.path.join(mediaDir, p), _("Media directory")))
95 debug("[NcidClient] getMountedDevices1: %s" % repr(mountedDevs))
96 mountedDevs = filter(lambda path: os.path.isdir(path[0]) and os.access(path[0], os.W_OK | os.X_OK), mountedDevs)
97 # put this after the write/executable check, that is far too slow...
98 netDir = resolveFilename(SCOPE_MEDIA, "net")
99 if os.path.isdir(netDir):
100 mountedDevs += map(lambda p: (os.path.join(netDir, p), _("Network mount")), os.listdir(netDir))
101 mountedDevs = map(handleMountpoint, mountedDevs)
104 config.plugins.NcidClient = ConfigSubsection()
105 config.plugins.NcidClient.debug = ConfigEnableDisable(default=False)
106 config.plugins.NcidClient.muteOnCall = ConfigEnableDisable(default=False)
107 config.plugins.NcidClient.hostname = ConfigText(default="easy.box", fixed_size=False)
108 config.plugins.NcidClient.port = ConfigInteger(limits=[1, 65535], default=3333)
109 config.plugins.NcidClient.afterStandby = ConfigSelection(choices=[("none", _("show nothing")), ("inList", _("show as list")), ("each", _("show each call"))])
110 config.plugins.NcidClient.timeout = ConfigInteger(default=15, limits=(0, 60))
111 config.plugins.NcidClient.lookup = ConfigEnableDisable(default=False)
112 config.plugins.NcidClient.internal = ConfigEnableDisable(default=False)
114 config.plugins.NcidClient.addcallers = ConfigEnableDisable(default=False)
115 config.plugins.NcidClient.enable = ConfigEnableDisable(default=True)
116 config.plugins.NcidClient.extension = ConfigText(default='1', fixed_size=False)
117 config.plugins.NcidClient.extension.setUseableChars('0123456789')
118 config.plugins.NcidClient.showType = ConfigEnableDisable(default=True)
119 config.plugins.NcidClient.prefix = ConfigText(default="", fixed_size=False)
120 config.plugins.NcidClient.prefix.setUseableChars('0123456789')
121 config.plugins.NcidClient.connectionVerbose = ConfigEnableDisable(default=True)
122 config.plugins.NcidClient.phonebook = ConfigEnableDisable(default=False)
123 config.plugins.NcidClient.phonebookLocation = ConfigSelection(choices=getMountedDevices())
124 config.plugins.NcidClient.country = ConfigSelection(choices=COUNTRY_CODES)
125 config.plugins.NcidClient.name = ConfigText(default="", fixed_size=False)
126 config.plugins.NcidClient.number = ConfigText(default="", fixed_size=False)
127 config.plugins.NcidClient.number.setUseableChars('0123456789')
135 avonFileName = resolveFilename(SCOPE_PLUGINS, "Extensions/NcidClient/avon.dat")
136 if os.path.exists(avonFileName):
137 for line in open(avonFileName):
138 line = line.decode("iso-8859-1").encode('utf-8')
141 parts = line.split(':')
143 avon[parts[0].replace('-', '').replace('*', '').replace('/', '')] = parts[1]
145 def resolveNumberWithAvon(number, countrycode):
146 if not number or number[0] != '0':
149 countrycode = countrycode.replace('00', '+')
150 if number[:2] == '00':
151 normNumber = '+' + number[2:]
152 elif number[:1] == '0':
153 normNumber = countrycode + number[1:]
154 else: # this should can not happen, but safety first
157 # debug('normNumer: ' + normNumber)
158 for i in reversed(range(min(10, len(number)))):
159 if avon.has_key(normNumber[:i]):
160 return '[' + avon[normNumber[:i]].strip() + ']'
163 def handleReverseLookupResult(name):
164 found = re.match("NA: ([^;]*);VN: ([^;]*);STR: ([^;]*);HNR: ([^;]*);PLZ: ([^;]*);ORT: ([^;]*)", name)
166 (name, firstname, street, streetno, zipcode, city) = (found.group(1),
174 name += ' ' + firstname
175 if street or streetno or zipcode or city:
180 name += ' ' + streetno
181 if (street or streetno) and (zipcode or city):
184 name += zipcode + ' ' + city
191 from xml.dom.minidom import parse
194 callbycallFileName = resolveFilename(SCOPE_PLUGINS, "Extensions/NcidClient/callbycall_world.xml")
195 if os.path.exists(callbycallFileName):
196 dom = parse(callbycallFileName)
197 for top in dom.getElementsByTagName("callbycalls"):
198 for cbc in top.getElementsByTagName("country"):
199 code = cbc.getAttribute("code").replace("+", "00")
200 cbcInfos[code] = cbc.getElementsByTagName("callbycall")
202 debug("[NcidClient] initCbC: callbycallFileName does not exist?!?!")
204 def stripCbCPrefix(number, countrycode):
205 if number and number[:2] != "00" and cbcInfos.has_key(countrycode):
206 for cbc in cbcInfos[countrycode]:
207 if len(cbc.getElementsByTagName("length")) < 1 or len(cbc.getElementsByTagName("prefix")) < 1:
208 debug("[NcidClient] stripCbCPrefix: entries for " + countrycode + " %s invalid")
210 length = int(cbc.getElementsByTagName("length")[0].childNodes[0].data)
211 prefix = cbc.getElementsByTagName("prefix")[0].childNodes[0].data
212 # if re.match('^'+prefix, number):
213 if number[:len(prefix)] == prefix:
214 return number[length:]
230 debug("[NcidCall] __init__")
231 self._callScreen = None
232 self._callTimestamp = 0
235 def _notify(self, text):
236 debug("[NcidCall] notify: " + text)
237 self._md5LoginTimestamp = None
239 debug("[NcidCall] notify: try to close callScreen")
240 self._callScreen.close()
241 self._callScreen = None
242 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_ERROR, timeout=config.plugins.NcidClient.timeout.value)
244 class NcidClientPhonebook:
246 debug("[NcidClientPhonebook] init")
247 # Beware: strings in phonebook.phonebook have to be in utf-8!
252 debug("[NcidClientPhonebook] reload")
253 # Beware: strings in phonebook.phonebook have to be in utf-8!
256 if not config.plugins.NcidClient.enable.value:
259 phonebookFilename = os.path.join(config.plugins.NcidClient.phonebookLocation.value, "PhoneBook.txt")
260 if config.plugins.NcidClient.phonebook.value and os.path.exists(phonebookFilename):
261 debug("[NcidClientPhonebook] reload: read " + phonebookFilename)
262 phonebookTxtCorrupt = False
264 for line in open(phonebookFilename):
266 # Beware: strings in phonebook.phonebook have to be in utf-8!
267 line = line.decode("utf-8")
268 except UnicodeDecodeError: # this is just for the case, somebody wrote latin1 chars into PhoneBook.txt
270 line = line.decode("iso-8859-1")
271 debug("[NcidClientPhonebook] Fallback to ISO-8859-1 in %s" % line)
272 phonebookTxtCorrupt = True
273 except UnicodeDecodeError:
274 debug("[NcidClientPhonebook] Could not parse internal Phonebook Entry %s" % line)
275 phonebookTxtCorrupt = True
276 line = line.encode("utf-8")
277 elems = line.split('#')
280 self.phonebook[elems[0]] = elems[1]
281 except ValueError: # how could this possibly happen?!?!
282 debug("[NcidClientPhonebook] Could not parse internal Phonebook Entry %s" % line)
283 phonebookTxtCorrupt = True
285 debug("[NcidClientPhonebook] Could not parse internal Phonebook Entry %s" % line)
286 phonebookTxtCorrupt = True
288 if phonebookTxtCorrupt:
289 # dump phonebook to PhoneBook.txt
290 debug("[NcidClientPhonebook] dump Phonebook.txt")
292 os.rename(phonebookFilename, phonebookFilename + ".bck")
293 fNew = open(phonebookFilename, 'w')
294 # Beware: strings in phonebook.phonebook are utf-8!
295 for (number, name) in self.phonebook.iteritems():
296 # Beware: strings in PhoneBook.txt have to be in utf-8!
297 fNew.write(number + "#" + name.encode("utf-8"))
299 except (IOError, OSError):
300 debug("[NcidClientPhonebook] error renaming or writing to %s" % phonebookFilename)
302 def search(self, number):
303 # debug("[NcidClientPhonebook] Searching for %s" %number)
305 if not self.phonebook or not number:
308 if config.plugins.NcidClient.prefix.value:
309 prefix = config.plugins.NcidClient.prefix.value
311 number = prefix + number
312 # debug("[NcidClientPhonebook] search: added prefix: %s" %number)
313 elif number[:len(prefix)] == prefix and self.phonebook.has_key(number[len(prefix):]):
314 # debug("[NcidClientPhonebook] search: same prefix")
315 name = self.phonebook[number[len(prefix):]]
316 # debug("[NcidClientPhonebook] search: result: %s" %name)
320 if not name and self.phonebook.has_key(number):
321 name = self.phonebook[number]
323 return name.replace(", ", "\n").strip()
325 def add(self, number, name):
328 @param number: number of entry
329 @param name: name of entry, has to be in utf-8
331 debug("[NcidClientPhonebook] add")
332 name = name.replace("\n", ", ").replace('#', '') # this is just for safety reasons. add should only be called with newlines converted into commas
334 self.phonebook[number] = name
335 if number and number != 0:
336 if config.plugins.NcidClient.phonebook.value:
338 name = name.strip() + "\n"
339 string = "%s#%s" % (number, name)
340 # Beware: strings in PhoneBook.txt have to be in utf-8!
341 f = open(os.path.join(config.plugins.NcidClient.phonebookLocation.value, "PhoneBook.txt"), 'a')
344 debug("[NcidClientPhonebook] added %s with %s to Phonebook.txt" % (number, name.strip()))
350 def remove(self, number):
351 if number in self.phonebook:
352 debug("[NcidClientPhonebook] remove entry in phonebook")
353 del self.phonebook[number]
354 if config.plugins.NcidClient.phonebook.value:
356 phonebookFilename = os.path.join(config.plugins.NcidClient.phonebookLocation.value, "PhoneBook.txt")
357 debug("[NcidClientPhonebook] remove entry in Phonebook.txt")
358 fOld = open(phonebookFilename, 'r')
359 fNew = open(phonebookFilename + str(os.getpid()), 'w')
360 line = fOld.readline()
362 elems = line.split('#')
363 if len(elems) == 2 and not elems[0] == number:
365 line = fOld.readline()
368 # os.remove(phonebookFilename)
369 eBackgroundFileEraser.getInstance().erase(phonebookFilename)
370 os.rename(phonebookFilename + str(os.getpid()), phonebookFilename)
371 debug("[NcidClientPhonebook] removed %s from Phonebook.txt" % number)
374 except (IOError, OSError):
375 debug("[NcidClientPhonebook] error removing %s from %s" % (number, phonebookFilename))
378 class NcidDisplayPhonebook(Screen, NumericalTextInput):
380 def __init__(self, session):
381 self.entriesWidth = DESKTOP_WIDTH * scaleH(75, 85) / 100
382 self.height = DESKTOP_HEIGHT * 0.75
383 numberFieldWidth = scaleH(220, 160)
384 fieldWidth = self.entriesWidth - 5 - numberFieldWidth - 10
385 fontSize = scaleV(22, 18)
386 fontHeight = scaleV(24, 20)
387 buttonGap = (self.entriesWidth - 4 * 140) / 5
388 debug("[NcidDisplayPhonebook] width: " + str(self.entriesWidth))
390 <screen name="NcidDisplayPhonebook" position="center,center" size="%d,%d" title="Phonebook" >
391 <eLabel position="0,0" size="%d,2" backgroundColor="#aaaaaa" />
392 <widget source="entries" render="Listbox" position="%d,%d" size="%d,%d" scrollbarMode="showOnDemand" transparent="1">
393 <convert type="TemplatedMultiContent">
395 MultiContentEntryText(pos = (%d,%d), size = (%d,%d), font=0, flags = RT_HALIGN_LEFT, text = 1), # index 0 is the name, index 1 is shortname
396 MultiContentEntryText(pos = (%d,%d), size = (%d,%d), font=0, flags = RT_HALIGN_LEFT, text = 2), # index 2 is number
398 "fonts": [gFont("Regular", %d)],
403 <eLabel position="0,%d" size="%d,2" backgroundColor="#aaaaaa" />
404 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
405 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
406 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
407 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
408 <widget name="key_red" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
409 <widget name="key_green" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
410 <widget name="key_yellow" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
411 <widget name="key_blue" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
413 # scaleH(90, 75), scaleV(100, 73), # position
414 self.entriesWidth, self.height, # size
415 self.entriesWidth, # eLabel width
416 scaleH(40, 5), scaleV(20, 5), # entries position
417 self.entriesWidth - scaleH(40, 5), self.height - scaleV(20, 5) - 5 - 5 - 40, # entries size
418 0, 0, fieldWidth, scaleH(24, 20), # name pos/size
419 fieldWidth + 5, 0, numberFieldWidth, scaleH(24, 20), # dir pos/size
421 fontHeight, # itemHeight
422 self.height - 40 - 5, # eLabel position vertical
423 self.entriesWidth, # eLabel width
424 buttonGap, self.height - 40, "skin_default/buttons/red.png", # ePixmap red
425 2 * buttonGap + 140, self.height - 40, "skin_default/buttons/green.png", # ePixmap green
426 3 * buttonGap + 2 * 140, self.height - 40, "skin_default/buttons/yellow.png", # ePixmap yellow
427 4 * buttonGap + 3 * 140, self.height - 40, "skin_default/buttons/blue.png", # ePixmap blue
428 buttonGap, self.height - 40, scaleV(22, 21), # widget red
429 2 * buttonGap + 140, self.height - 40, scaleV(22, 21), # widget green
430 3 * buttonGap + 2 * 140, self.height - 40, scaleV(22, 21), # widget yellow
431 4 * buttonGap + 3 * 140, self.height - 40, scaleV(22, 21), # widget blue
434 # debug("[NcidDisplayCalls] skin: " + self.skin)
435 Screen.__init__(self, session)
436 NumericalTextInput.__init__(self)
437 HelpableScreen.__init__(self)
439 # TRANSLATORS: keep it short, this is a button
440 self["key_red"] = Button(_("Delete"))
441 # TRANSLATORS: keep it short, this is a button
442 self["key_green"] = Button(_("New"))
443 # TRANSLATORS: keep it short, this is a button
444 self["key_yellow"] = Button(_("Edit"))
445 # TRANSLATORS: keep it short, this is a button
446 self["key_blue"] = Button(_("Search"))
448 self["setupActions"] = ActionMap(["OkCancelActions", "ColorActions"],
455 "ok": self.showEntry, }, -2)
457 self["entries"] = List([])
458 debug("[NcidClientPhonebook] displayPhonebook init")
459 self.help_window = None
461 self.onLayoutFinish.append(self.setWindowTitle)
464 def setWindowTitle(self):
465 # TRANSLATORS: this is a window title.
466 self.setTitle(_("Phonebook"))
468 def display(self, filterNumber=""):
469 debug("[NcidClientPhonebook] displayPhonebook/display")
471 # Beware: strings in phonebook.phonebook are utf-8!
472 sortlistHelp = sorted((name.lower(), name, number) for (number, name) in phonebook.phonebook.iteritems())
473 for (low, name, number) in sortlistHelp:
474 if number == "01234567890":
477 low = low.decode("utf-8")
478 except UnicodeDecodeError: # this should definitely not happen
480 low = low.decode("iso-8859-1")
481 except UnicodeDecodeError:
482 debug("[NcidClientPhonebook] displayPhonebook/display: corrupt phonebook entry for %s" % number)
483 # self.session.open(MessageBox, _("Corrupt phonebook entry\nfor number %s\nDeleting.") %number, type = MessageBox.TYPE_ERROR)
484 phonebook.remove(number)
488 filterNumber = filterNumber.lower()
489 if low.find(filterNumber) == -1:
491 name = name.strip().decode("utf-8")
492 number = number.strip().decode("utf-8")
493 comma = name.find(',')
495 shortname = name[:comma]
498 number = number.encode("utf-8", "replace")
499 name = name.encode("utf-8", "replace")
500 shortname = shortname.encode('utf-8', 'replace')
501 self.sortlist.append((name, shortname, number))
503 self["entries"].setList(self.sortlist)
506 cur = self["entries"].getCurrent()
508 debug("[NcidClientPhonebook] displayPhonebook/showEntry %s" % (repr(cur)))
511 self.session.open(NcidOfferAction, self, number, name)
514 cur = self["entries"].getCurrent()
516 debug("[NcidClientPhonebook] displayPhonebook/delete %s" % (repr(cur)))
517 self.session.openWithCallback(
518 self.deleteConfirmed,
520 _("Do you really want to delete entry for\n\n%(number)s\n\n%(name)s?")
521 % { 'number':str(cur[2]), 'name':str(cur[0]).replace(", ", "\n") }
524 self.session.open(MessageBox, _("No entry selected"), MessageBox.TYPE_INFO)
526 def deleteConfirmed(self, ret):
527 debug("[NcidClientPhonebook] displayPhonebook/deleteConfirmed")
529 # if ret: delete number from sortlist, delete number from phonebook.phonebook and write it to disk
531 cur = self["entries"].getCurrent()
534 # delete number from sortlist, delete number from phonebook.phonebook and write it to disk
535 debug("[NcidClientPhonebook] displayPhonebook/deleteConfirmed %s" % (repr(cur)))
536 phonebook.remove(cur[2])
539 # self.session.open(MessageBox, _("Not deleted."), MessageBox.TYPE_INFO)
541 self.session.open(MessageBox, _("No entry selected"), MessageBox.TYPE_INFO)
543 def add(self, parent=None, number="", name=""):
544 class AddScreen(Screen, ConfigListScreen):
545 '''ConfiglistScreen with two ConfigTexts for Name and Number'''
547 def __init__(self, session, parent, number="", name=""):
549 # setup screen with two ConfigText and OK and ABORT button
552 width = max(scaleH(-1, 570), noButtons * 140)
553 height = scaleV(-1, 100) # = 5 + 126 + 40 + 5; 6 lines of text possible
554 buttonsGap = (width - noButtons * 140) / (noButtons + 1)
555 buttonsVPos = height - 40 - 5
557 <screen position="center,center" size="%d,%d" title="Add entry to phonebook" >
558 <widget name="config" position="5,5" size="%d,%d" scrollbarMode="showOnDemand" />
559 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
560 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
561 <widget name="key_red" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
562 <widget name="key_green" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
565 width - 5 - 5, height - 5 - 40 - 5,
566 buttonsGap, buttonsVPos, "skin_default/buttons/red.png",
567 buttonsGap + 140 + buttonsGap, buttonsVPos, "skin_default/buttons/green.png",
568 buttonsGap, buttonsVPos,
569 buttonsGap + 140 + buttonsGap, buttonsVPos,
571 Screen.__init__(self, session)
572 self.session = session
574 # TRANSLATORS: keep it short, this is a button
575 self["key_red"] = Button(_("Cancel"))
576 # TRANSLATORS: keep it short, this is a button
577 self["key_green"] = Button(_("OK"))
578 self["setupActions"] = ActionMap(["SetupActions", "ColorActions"],
580 "cancel": self.cancel,
587 ConfigListScreen.__init__(self, self.list, session=session)
590 config.plugins.NcidClient.name.value = name
591 config.plugins.NcidClient.number.value = number
592 self.list.append(getConfigListEntry(_("Name"), config.plugins.NcidClient.name))
593 self.list.append(getConfigListEntry(_("Number"), config.plugins.NcidClient.number))
594 self["config"].list = self.list
595 self["config"].l.setList(self.list)
596 self.onLayoutFinish.append(self.setWindowTitle)
598 def setWindowTitle(self):
599 # TRANSLATORS: this is a window title.
600 self.setTitle(_("Add entry to phonebook"))
603 # get texts from Screen
604 # add (number,name) to sortlist and phonebook.phonebook and disk
605 self.name = config.plugins.NcidClient.name.value
606 self.number = config.plugins.NcidClient.number.value
607 if not self.number or not self.name:
608 self.session.open(MessageBox, _("Entry incomplete."), type=MessageBox.TYPE_ERROR)
611 phonebook.add(self.number, self.name)
613 self.parent.display()
615 def overwriteConfirmed(self, ret):
617 phonebook.remove(self.number)
618 phonebook.add(self.number, self.name)
619 self.parent.display()
624 debug("[NcidClientPhonebook] displayPhonebook/add")
627 self.session.open(AddScreen, parent, number, name)
630 debug("[NcidClientPhonebook] displayPhonebook/edit")
631 cur = self["entries"].getCurrent()
633 self.session.open(MessageBox, _("No entry selected"), MessageBox.TYPE_INFO)
635 self.add(self, cur[2], cur[0])
638 debug("[NcidClientPhonebook] displayPhonebook/search")
639 self.help_window = self.session.instantiateDialog(NumericalTextInputHelpDialog, self)
640 self.help_window.show()
641 # VirtualKeyboard instead of InputBox?
642 self.session.openWithCallback(self.doSearch, InputBox, _("Enter Search Terms"), _("Search phonebook"))
644 def doSearch(self, searchTerms):
647 debug("[NcidClientPhonebook] displayPhonebook/doSearch: " + searchTerms)
649 self.session.deleteDialog(self.help_window)
650 self.help_window = None
651 self.display(searchTerms)
656 phonebook = NcidClientPhonebook()
658 class NcidClientSetup(Screen, ConfigListScreen):
660 def __init__(self, session, args=None): #@UnusedVariable # pylint: disable=W0613
661 self.width = scaleH(20 + 4 * (140 + 90) + 2 * (35 + 40) + 20, 4 * 140 + 2 * 35)
663 debug("[NcidClientSetup] width: " + str(self.width))
665 <screen name="NcidClientSetup" position="center,center" size="%d,%d" title="NcidClient Setup" >
666 <eLabel position="0,0" size="%d,2" backgroundColor="#aaaaaa" />
667 <eLabel position="0,%d" size="%d,2" backgroundColor="#aaaaaa" />
668 <widget name="config" position="%d,%d" size="%d,%d" scrollbarMode="showOnDemand" backgroundColor="#20040404" transparent="1" />
669 <eLabel position="0,%d" size="%d,2" backgroundColor="#aaaaaa" />
670 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
671 <ePixmap position="%d,%d" zPosition="4" size="140,40" pixmap="%s" transparent="1" alphatest="on" />
672 <widget name="key_red" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
673 <widget name="key_green" position="%d,%d" zPosition="5" size="140,40" valign="center" halign="center" font="Regular;%d" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
675 # (DESKTOP_WIDTH-width)/2, scaleV(100, 73), # position
676 width, scaleV(560, 430), # size
677 width, # eLabel width
678 scaleV(40, 50), # eLabel position vertical
679 width, # eLabel width
680 scaleH(40, 5), scaleV(60, 57), # config position
681 scaleH(width - 80, width - 10), scaleV(453, 328), # config size
682 scaleV(518, 390), # eLabel position vertical
683 width, # eLabel width
684 scaleH(20, 0), scaleV(525, 395), "skin_default/buttons/red.png", # pixmap red
685 scaleH(20 + 140 + 90, 140), scaleV(525, 395), "skin_default/buttons/green.png", # pixmap green
686 scaleH(20, 0), scaleV(525, 395), scaleV(21, 21), # widget red
687 scaleH(20 + (140 + 90), 140), scaleV(525, 395), scaleV(21, 21), # widget green
690 Screen.__init__(self, session)
691 self.session = session
696 # TRANSLATORS: keep it short, this is a button
697 self["key_red"] = Button(_("Cancel"))
698 # TRANSLATORS: keep it short, this is a button
699 self["key_green"] = Button(_("OK"))
701 self["setupActions"] = ActionMap(["ColorActions", "OkCancelActions", "MenuActions", "EPGSelectActions"],
705 "cancel": self.cancel,
709 ConfigListScreen.__init__(self, self.list, session=session)
711 # get new list of locations for PhoneBook.txt
712 self._mountedDevices = getMountedDevices()
714 self.onLayoutFinish.append(self.setWindowTitle)
716 def setWindowTitle(self):
717 # TRANSLATORS: this is a window title.
718 self.setTitle(_("NCID Client - Setup"))
721 ConfigListScreen.keyLeft(self)
725 ConfigListScreen.keyRight(self)
728 def createSetup(self):
730 self.list.append(getConfigListEntry(_("Call monitoring"), config.plugins.NcidClient.enable))
731 if config.plugins.NcidClient.enable.value:
732 self.list.append(getConfigListEntry(_("NCID server (Name or IP)"), config.plugins.NcidClient.hostname))
733 self.list.append(getConfigListEntry(_("NCID server listening port (1-65535)"), config.plugins.NcidClient.port))
735 self.list.append(getConfigListEntry(_("Show after Standby"), config.plugins.NcidClient.afterStandby))
737 # not only for outgoing: config.plugins.NcidClient.showOutgoing.value:
738 self.list.append(getConfigListEntry(_("Areacode to add to calls without one (if necessary)"), config.plugins.NcidClient.prefix))
739 self.list.append(getConfigListEntry(_("Timeout for Call Notifications (seconds)"), config.plugins.NcidClient.timeout))
740 self.list.append(getConfigListEntry(_("Reverse Lookup Caller ID (select country below)"), config.plugins.NcidClient.lookup))
741 if config.plugins.NcidClient.lookup.value:
742 self.list.append(getConfigListEntry(_("Country"), config.plugins.NcidClient.country))
744 # self.list.append(getConfigListEntry(_("Use internal PhoneBook"), config.plugins.NcidClient.phonebook))
745 # if config.plugins.NcidClient.phonebook.value:
746 # if config.plugins.NcidClient.phonebookLocation.value in self._mountedDevices:
747 # config.plugins.NcidClient.phonebookLocation.setChoices(self._mountedDevices, config.plugins.NcidClient.phonebookLocation.value)
749 # config.plugins.NcidClient.phonebookLocation.setChoices(self._mountedDevices)
750 # path = config.plugins.NcidClient.phonebookLocation.value
751 # # check whether we can write to PhoneBook.txt
752 # if os.path.exists(os.path.join(path[0], "PhoneBook.txt")):
753 # if not os.access(os.path.join(path[0], "PhoneBook.txt"), os.W_OK):
754 # debug("[NcidClientSetup] createSetup: %s/PhoneBook.txt not writable, resetting to default" % (path[0]))
755 # config.plugins.NcidClient.phonebookLocation.setChoices(self._mountedDevices)
756 # elif not (os.path.isdir(path[0]) and os.access(path[0], os.W_OK | os.X_OK)):
757 # debug("[NcidClientSetup] createSetup: directory %s not writable, resetting to default" % (path[0]))
758 # config.plugins.NcidClient.phonebookLocation.setChoices(self._mountedDevices)
760 # self.list.append(getConfigListEntry(_("PhoneBook Location"), config.plugins.NcidClient.phonebookLocation))
761 # if config.plugins.NcidClient.lookup.value:
762 # self.list.append(getConfigListEntry(_("Automatically add new Caller to PhoneBook"), config.plugins.NcidClient.addcallers))
764 self.list.append(getConfigListEntry(_("Strip Leading 0"), config.plugins.NcidClient.internal))
765 self.list.append(getConfigListEntry(_("Show connection information popups"), config.plugins.NcidClient.connectionVerbose))
767 self["config"].list = self.list
768 self["config"].l.setList(self.list)
771 # debug("[NcidClientSetup] save"
772 for x in self["config"].list:
774 if config.plugins.NcidClient.phonebookLocation.isChanged():
776 phonebook = NcidClientPhonebook()
778 if config.plugins.NcidClient.enable.value:
785 # debug("[NcidClientSetup] cancel"
786 for x in self["config"].list:
790 def displayPhonebook(self):
792 if config.plugins.NcidClient.enable.value:
793 self.session.open(phonebook.NcidDisplayPhonebook)
795 self.session.open(MessageBox, _("Plugin not active"), type=MessageBox.TYPE_INFO)
797 self.session.open(MessageBox, _("No phonebook"), type=MessageBox.TYPE_INFO)
806 def add(self, date, number, caller):
807 debug("[NcidCallList] add: %s %s" % (number, caller))
808 if len(self.callList) > 10:
809 if self.callList[0] != "Start":
810 self.callList[0] = "Start"
813 self.callList.append((number, date, caller))
816 debug("[NcidCallList] display")
819 # Standby.inStandby.onClose.remove(self.display) object does not exist anymore...
820 # build screen from call list
823 if not self.callList:
826 if self.callList[0] == "Start":
827 text = text + _("Last 10 calls:\n")
830 for call in self.callList:
831 (number, date, caller) = call
833 # should not happen, for safety reasons
835 caller = _("UNKNOWN")
837 # if we have an unknown number, show the number
838 if caller == _("UNKNOWN") and number != "":
841 # strip off the address part of the remote caller/callee, if there is any
842 nl = caller.find('\n')
845 elif caller[0] == '[' and caller[-1] == ']':
846 # that means, we've got an unknown number with a city added from avon.dat
847 if (len(number) + 1 + len(caller)) <= 40:
848 caller = number + ' ' + caller
852 while (len(caller)) > 40:
855 text = text + "%s - %s\n" % (date, caller)
856 debug("[NcidCallList] display: '%s - %s'" % (date, caller))
859 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_INFO)
860 # TODO please HELP: from where can I get a session?
861 # my_global_session.open(NcidDisplayCalls, text)
864 callList = NcidCallList()
867 def notifyCall(date, number, caller):
868 if Standby.inStandby is None or config.plugins.NcidClient.afterStandby.value == "each":
870 if config.plugins.NcidClient.muteOnCall.value and not global_muted:
871 # eDVBVolumecontrol.getInstance().volumeMute() # with this, we get no mute icon...
872 if not eDVBVolumecontrol.getInstance().isMuted():
873 globalActionMap.actions["volumeMute"]()
874 text = _("Incoming Call on %s from\n---------------------------------------------\n%s\n%s\n---------------------------------------------") % (date, number, caller)
875 debug("[NcidClient] notifyCall:\n%s" % text)
876 Notifications.AddNotification(MessageBox, text, type=MessageBox.TYPE_INFO, timeout=config.plugins.NcidClient.timeout.value)
877 #Notifications.AddNotification(MessageBoxPixmap, text, number=number, name=caller, timeout=config.plugins.NcidClient.timeout.value)
878 elif config.plugins.NcidClient.afterStandby.value == "inList":
880 # if not yet done, register function to show call list
884 Standby.inStandby.onHide.append(callList.display) #@UndefinedVariable
885 # add text/timeout to call list
886 callList.add(date, number, caller)
887 debug("[NcidClient] notifyCall: added to callList")
888 else: # this is the "None" case
889 debug("[NcidClient] notifyCall: standby and no show")
892 #===============================================================================
893 # We need a separate class for each invocation of reverseLookup to retain
894 # the necessary data for the notification
895 #===============================================================================
898 reverselookupMtime = 0
900 class NcidReverseLookupAndNotify:
901 def __init__(self, number, caller, date):
904 Initiate a reverse lookup for the given number in the configured country
905 @param number: number to be looked up
906 @param caller: caller including name and address
907 @param date: date of call
909 debug("[NcidReverseLookupAndNotify] reverse Lookup for %s!" % number)
915 self.notifyAndReset(number, caller)
918 ReverseLookupAndNotify(number, self.notifyAndReset, "UTF-8", config.plugins.NcidClient.country.value)
920 def notifyAndReset(self, number, caller):
923 this gets called with the result of the reverse lookup
925 @param number: number
926 @param caller: name and address of remote. it comes in with name, address and city separated by commas
928 debug("[NcidReverseLookupAndNotify] got: %s" % caller)
930 name = handleReverseLookupResult(caller)
932 self.caller = name.replace(", ", "\n").replace('#', '')
934 if self.number != 0 and config.plugins.NcidClient.addcallers.value:
935 debug("[NcidReverseLookupAndNotify] add to phonebook")
936 phonebook.add(self.number, self.caller)
938 name = resolveNumberWithAvon(self.number, config.plugins.NcidClient.country.value)
940 self.caller = _("UNKNOWN")
943 notifyCall(self.date, self.number, self.caller)
945 class NcidLineReceiver(LineReceiver):
951 def resetValues(self):
954 self.date = '01011970'
958 def notifyAndReset(self):
959 notifyCall(self.date, self.number, self.caller)
962 def lineReceived(self, line):
963 debug("[NcidLineReceiver] lineReceived: %s" % line)
964 #200 NCID Server: ARC_ncidd 0.01
965 #CIDLOG: *DATE*21102010*TIME*1454*LINE**NMBR*089999999999*MESG*NONE*NAME*NO NAME*
966 #CIDLOG: *DATE*21102010*TIME*1456*LINE**NMBR*089999999999*MESG*NONE*NAME*NO NAME*
967 #CID: *DATE*22102010*TIME*1502*LINE**NMBR*015209957840*MESG*NONE*NAME*NO NAME*
969 #Callog entries begin with CIDLOG, "current" events begin with CID
970 #we don't want to do anything with log-entries
971 if line.startswith("CID:"):
973 debug("[NcidLineReceiver.lineReceived] filtered Line: %s" % line)
977 items = line.split('*')
979 for i in range(0, len(items)):
983 self.date = items[i + 1]
985 self.time = items[i + 1]
987 self.line = items[i + 1]
989 self.number = items[i + 1]
991 date = datetime.strptime("%s - %s" % (self.date, self.time), "%d%m%Y - %H%M")
992 self.date = date.strftime("%d.%m.%Y - %H:%M")
995 debug("[NcidLineReceiver] lineReceived: no number")
996 self.number = _("number suppressed")
997 self.caller = _("UNKNOWN")
999 if config.plugins.NcidClient.internal.value and len(self.number) > 3 and self.number[0] == "0":
1000 debug("[NcidLineReceiver] lineReceived: strip leading 0")
1001 self.number = self.number[1:]
1003 if self.number[0] != '0':
1004 debug("[NcidLineReceiver] lineReceived: add local prefix")
1005 self.number = config.plugins.NcidClient.prefix.value + self.number
1007 self.number = stripCbCPrefix(self.number, config.plugins.NcidClient.country.value)
1009 debug("[NcidLineReceiver] lineReceived phonebook.search: %s" % self.number)
1010 self.caller = phonebook.search(self.number)
1011 debug("[NcidLineReceiver] lineReceived phonebook.search reault: %s" % self.caller)
1013 if config.plugins.NcidClient.lookup.value:
1014 NcidReverseLookupAndNotify(self.number, self.caller, self.date)
1015 return # reverselookup is supposed to handle the message itself
1017 self.caller = _("UNKNOWN")
1019 self.notifyAndReset()
1021 class NcidClientFactory(ReconnectingClientFactory):
1026 self.hangup_ok = False
1027 def startedConnecting(self, connector): #@UnusedVariable # pylint: disable=W0613
1028 if config.plugins.NcidClient.connectionVerbose.value:
1029 Notifications.AddNotification(MessageBox, _("Connecting to NCID Server..."), type=MessageBox.TYPE_INFO, timeout=2)
1031 def buildProtocol(self, addr): #@UnusedVariable # pylint: disable=W0613
1032 global ncidsrv, phonebook
1033 if config.plugins.NcidClient.connectionVerbose.value:
1034 Notifications.AddNotification(MessageBox, _("Connected to NCID Server"), type=MessageBox.TYPE_INFO, timeout=4)
1037 ncidsrv = NcidCall()
1038 phonebook = NcidClientPhonebook()
1039 return NcidLineReceiver()
1041 def clientConnectionLost(self, connector, reason):
1043 if not self.hangup_ok and config.plugins.NcidClient.connectionVerbose.value:
1044 Notifications.AddNotification(MessageBox, _("Connection to NCID Server lost\n (%s)\nretrying...") % reason.getErrorMessage(), type=MessageBox.TYPE_INFO, timeout=config.plugins.NcidClient.timeout.value)
1045 ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
1046 # config.plugins.NcidClient.enable.value = False
1049 def clientConnectionFailed(self, connector, reason):
1051 if config.plugins.NcidClient.connectionVerbose.value:
1052 Notifications.AddNotification(MessageBox, _("Connecting to NCID Server failed\n (%s)\nretrying...") % reason.getErrorMessage(), type=MessageBox.TYPE_INFO, timeout=config.plugins.NcidClient.timeout.value)
1053 ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
1054 # config.plugins.NcidClient.enable.value = False
1061 if config.plugins.NcidClient.enable.value:
1066 if config.plugins.NcidClient.enable.value:
1067 factory = NcidClientFactory()
1068 self.desc = (factory, reactor.connectTCP(config.plugins.NcidClient.hostname.value, config.plugins.NcidClient.port.value, factory))
1074 if self.desc is not None:
1075 self.desc[0].hangup_ok = True
1076 self.desc[0].stopTrying()
1077 self.desc[1].disconnect()
1081 session.open(NcidClientSetup)
1085 def autostart(reason, **kwargs):
1088 # ouch, this is a hack
1089 if kwargs.has_key("session"):
1090 global my_global_session
1091 my_global_session = kwargs["session"]
1094 debug("[NcidClient] - Autostart")
1096 ncid_call = NcidClient()
1098 ncid_call.shutdown()
1101 def Plugins(**kwargs): #@UnusedVariable # pylint: disable=W0613,C0103
1102 what = _("Display Fon calls on screen")
1103 return [ PluginDescriptor(name="NCID Client", description=what, where=PluginDescriptor.WHERE_PLUGINMENU, icon="plugin.png", fnc=main),
1104 PluginDescriptor(where=[PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc=autostart) ]