#!/usr/bin/python # -*- coding: utf-8 -*- """ This is a phonelog GTK+ GUI, designed to use with opimd. Written by Tom Hacohen. (tom@stosb.com), contributions from Sebastian Spaeth (Sebastian@SSpaeth.de) Sebastian Krzyszkowiak Licensed by GPLv2 """ import os from subprocess import Popen, PIPE pids = Popen("/bin/ps -e x | /bin/grep /usr/bin/phonelog | /bin/grep -v grep | /bin/sed -e 's/.*bin.//'", shell=True, stdout=PIPE).communicate()[0].splitlines().count("phonelog") if pids == 2: pidoflog = os.getpid() pidoflog = str(pidoflog) print "pyphonelog is already running!" os.system("kill " +pidoflog) __version__ = "0.16.1" import time, ConfigParser import shutil import re from math import ceil from datetime import datetime import gettext import dbus import gobject, gtk import sqlite3 from dbus.mainloop.glib import DBusGMainLoop import phoneutils try: import mokoui use_mokoui = True except: use_mokoui = False try: cat = gettext.Catalog("phonelog") _ = cat.gettext except IOError: _ = lambda x: x #constants CONFIGURATION_DIR = os.path.join(os.path.expanduser('~'), '.phonelog') TIME_FILE = CONFIGURATION_DIR + "time.dat" CONFIGURATION_FILE = os.path.join(CONFIGURATION_DIR,'phonelog.conf') DEFAULT_CONFIG_PATH = "/usr/share/phonelog/skeleton/phonelog.conf" ICONS_PATH = "/usr/share/phonelog/icons/" #PAGES POSITIONS PAGE_INCOMING = 0 PAGE_OUTGOING = 1 PAGE_MISSED = 2 PAGE_GENERAL = 3 PAGE_SETTINGS = 4 class CallsTab: #POSITION IN MODEL CONTACT_POSITION = 0 NUMBER_POSITION = 1 DATE_POSITION = 2 DURATION_POSITION = 3 VISUAL_POSITION = 4 STATUS_POSITION = 5 #ACTUAL GUI POSTITON CONTACT_COLUMN = 0 DATE_COLUMN = 1 __list = None __contact_show_column = CONTACT_POSITION __date_show_column = DATE_POSITION __tab = None __type = None __mark_new = False __manually_updated = False conf = None #inited with ConfigParser object once! def __init__(self, type, mark_new = False, conf=None): # init the ConfigParser object once, bail out if failing. if CallsTab.conf == None: CallsTab.conf = conf assert CallsTab.conf != None self.createList() self.__type = type self.__createCallsTab() self.last_refresh = CallsTab.conf.getfloat('viewlog',self.__type) self.__mark_new = mark_new def getList(self): return self.__list def getTab(self): return self.__tab def shouldMarkNew(self): return self.__mark_new def getManuallyUpdated(self): return self.__manually_updated def setManuallyUpdated(self, status = True): self.__manually_updated = status def __getContactColumn(self): return self.CONTACT_COLUMN def __getDateColumn(self): return self.DATE_COLUMN def __createCallsTab(self): page = gtk.VBox() page.pack_start( self.__initScrolled() ) align = gtk.Alignment(1,1,1,1) buttons = gtk.HBox(homogeneous=True, spacing = 3) align.add(buttons) page.pack_start(align, False, padding = 3) buttons_height = CallsTab.conf.getint('phonelog','buttons_height') callButton = gtk.Button(_("Call")) callButton.connect ("clicked", self.callButton_clicked) callButton.set_size_request(-1,buttons_height) numberButton = gtk.Button(_("Numbers")) numberButton.connect ("clicked", self.numberButton_clicked) numberButton.set_size_request(-1,buttons_height) contactButton = gtk.Button(_("Add")) contactButton.connect ("clicked", self.contactButton_clicked) contactButton.set_size_request(-1,buttons_height) durationButton = gtk.Button(_("Duration")) durationButton.connect ("clicked", self.durationButton_clicked) durationButton.set_size_request(-1,buttons_height) buttons.pack_start(callButton, True) buttons.pack_start(contactButton, True) buttons.pack_start(numberButton, True) buttons.pack_start(durationButton, True) self.__tab = page def __createTreeView(self): self.__list = gtk.TreeView (gtk.TreeStore( gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_BOOLEAN, #the boolean is there for coloring gobject.TYPE_STRING ) #should be changed to an icon? ) self.__list.set_property('headers-visible', CallsTab.conf.getboolean('phonelog','lists_headers_visible')) def createList(self): self.__createTreeView() renderer = gtk.CellRendererText() #change text size as well? #renderer.set_property('weight', 800) renderer.set_property('foreground', 'red') contact_column = gtk.TreeViewColumn("Contact", renderer, text=self.CONTACT_POSITION, foreground_set=self.VISUAL_POSITION) self.__list.insert_column( contact_column, self.__getContactColumn() ) time_column = gtk.TreeViewColumn("Time", renderer, text=self.DATE_POSITION, foreground_set=self.VISUAL_POSITION) self.__list.insert_column( time_column, self.__getDateColumn()) def __initScrolled(self): tab_content = self.__list if use_mokoui: scrolled = mokoui.FingerScroll() else: scrolled = gtk.ScrolledWindow() scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled.add_with_viewport(tab_content) return scrolled def __getCalls(self): global phone_data type = self.__type calls = phone_data.getCalls(type) return calls def populateList(self, group_type = 1): """ group_type = 0 - never group 1/everything else - always group 2 - group from the same type only. """ global phone_data if config.getboolean('phonelog','debug'): print _("Started populating list") self.setManuallyUpdated() list = self.__list type = self.__type previous_time = datetime.fromtimestamp(CallsTab.conf.getfloat('viewlog', type)) visual_flag = self.shouldMarkNew() calls = self.__getCalls() if calls == None: return False #get and clear the model. model = list.get_model() model.clear() last_parent_added = None child_count = 0 for call in calls: if call.has_key('Peer'): number = call['Peer'] number = number.strip('tel:') contact = phone_data.getContact(number) else: #don't group supressed numbers #and make the contact Unknown Number number = CallsTab.conf.get('phonelog',"suppressed_name") last_parent_added = None contact = CallsTab.conf.get('phonelog',"suppressed_name") timestamp = call['Timestamp'] call_time = datetime.fromtimestamp( timestamp ) if call['Direction'] == 'in' and call['Answered'] == 0: status = 2 elif call['Direction'] == 'in': status = 0 else: status = 1 #if none at least make it 0, else, numerify it. if not call.has_key('Duration'): tmp_duration = 0 else: tmp_duration = ceil(call['Duration']) active_hours = int(tmp_duration / 3600) active_minutes = int(tmp_duration / 60 - active_hours * 60) active_seconds = int(tmp_duration - active_hours*3600 - active_minutes * 60) #don't show hours if not needed. if active_hours == 0: active_time = "%02d:%02d" % (active_minutes, active_seconds) else: active_time = "%02d:%02d:%02d" % (active_hours, active_minutes, active_seconds) #check if should be grouped #if grouping should stop, update the child count to the father parent = None if last_parent_added != None: #also counts the father as a child child_count += 1 if (((last_contact == contact) and (contact != "")) \ or (last_number == number))\ and (last_status == status): parent = last_parent_added else: if child_count > 1: #if set in config, add the '(count)' to the contact field if CallsTab.conf.getboolean('phonelog','show_group_count'): current_value = model[last_parent_added][self.CONTACT_POSITION] model.set_value(last_parent_added,self.CONTACT_POSITION, "%s (%d)" % (current_value, child_count)) child_count = 0 else: child_count = 0 #update the last_statuses so we won't rely on the GUI last_contact = contact last_number = number last_status = status last_missed = missed #We want the contact to show as the number if it's not in the phonebook. if contact == "": contact = number #style status if status == 0: status = CallsTab.conf.get('phonelog','received_mark') elif status == 1: status = CallsTab.conf.get('phonelog','made_mark') elif status == 2: status = CallsTab.conf.get('phonelog','missed_mark') parent_added = model.append(parent, ( contact , number , timeToString(call_time) , active_time, visual_flag and (call_time > previous_time) , status ) ) if parent == None: last_parent_added = parent_added #ugly hack for the last now showing counts bug if child_count > 1: child_count += 1 #if set in config, add the '(count)' to the contact field if CallsTab.conf.getboolean('phonelog','show_group_count'): current_value = model[last_parent_added][self.CONTACT_POSITION] model.set_value(last_parent_added,self.CONTACT_POSITION, "%s (%d)" % (current_value, child_count)) #Set last refresh time to now CallsTab.conf.set('viewlog',type,str(time.time())) if config.getboolean('phonelog','debug'): print _("Done populating list") return True def callButton_clicked (self, button): global phone_data phoneObject = phone_data.getPhoneObject(systemBus) list = self.__list number = self.__getSelectedNumber() if number != None and number != CallsTab.conf.get('phonelog',"suppressed_name"): #TODO, once the highlevel api work, change to them. try: phoneObject.Initiate (number, 'voice') except: print _("Failed to initiate a call") def contactButton_clicked (self, button): global phone_data phoneObject = phone_data.getPhoneObject(systemBus) list = self.__list number = self.__getSelectedNumber() if number != None and number != CallsTab.conf.get('phonelog',"suppressed_name"): #FIXME a bit ugly, until a better solution found. # just to make sure you read the last line, ITS UGLY! try: os.system("shr-contacts " + number + " &") except: print _("Failed to initiate a call") def numberButton_clicked (self, button): list = self.__list get_column = self.__getContactColumn() contact_column = list.get_column(get_column) renderer = contact_column.get_cell_renderers()[0] if self.__contact_show_column == self.CONTACT_POSITION: self.__contact_show_column = self.NUMBER_POSITION button_label = _("Names") else: self.__contact_show_column = self.CONTACT_POSITION button_label = _("Numbers") button.set_label(button_label) contact_column.set_attributes(renderer, text=self.__contact_show_column) def durationButton_clicked (self, button): list = self.__list get_column = self.__getDateColumn() date_column = list.get_column(get_column) renderer = date_column.get_cell_renderers()[0] if self.__date_show_column == self.DATE_POSITION: self.__date_show_column = self.DURATION_POSITION button_label = _("Date") else: self.__date_show_column = self.DATE_POSITION button_label = _("Duration") button.set_label(button_label) date_column.set_attributes(renderer, text=self.__date_show_column) def __getSelectedNumber(self): list = self.__list selected = list.get_selection().get_selected_rows() selected = selected[1][0] row = list.get_model()[selected] number = row[self.NUMBER_POSITION] return number def addToTab(self, notebook): type = typeFromLegacyDaemonType(self.__type) if CallsTab.conf.get('phonelog',type + '_tab_type') == 'icon': tabWidget = gtk.Image() image_path = CallsTab.conf.get('phonelog',type + '_tab_image_file') #if not absolute if image_path[0] != '/': image_path = ICONS_PATH + image_path tabWidget.set_from_file(image_path) else: tabWidget = gtk.Label(CallsTab.conf.get('phonelog',type + '_tab_name') ) notebook.insert_page(self.getTab(), tabWidget) notebook.set_tab_label_packing(self.getTab(), True, True, gtk.PACK_START) class GeneralTab(CallsTab): #ACTUAL GUI POSTITON STATUS_COLUMN = 0 CONTACT_COLUMN = 1 DATE_COLUMN = 2 def __getStatusColumn(self): return self.STATUS_COLUMN def createList(self): CallsTab.createList(self) renderer = gtk.CellRendererText() renderer.set_property('weight', 800) renderer.set_property('size-points', 8) column0 = gtk.TreeViewColumn("Status", renderer, text=self.STATUS_POSITION) list = self.getList() list.insert_column( column0, self.__getStatusColumn() ) def populateList(self, group_type = None): if group_type == None: group_type = 2 CallsTab.populateList(self, 2) class SignalMonitor: PHONE_BUSNAME = 'org.freesmartphone.opimd' PHONE_OBJECTPATH = '/org/freesmartphone/PIM/Calls' PHONE_INTERFACE = 'org.freesmartphone.PIM.Calls' __bus = None def __init__(self, bus): self.__bus = bus self.__connectCallStatus() def __connectCallStatus(self): bus = self.__bus proxy = bus.get_object(self.PHONE_BUSNAME,self.PHONE_OBJECTPATH) iface = dbus.Interface(proxy, self.PHONE_INTERFACE) iface.connect_to_signal("NewCall", self.cb_CallAdded) def cb_CallAdded(self, path): #TODO: check, what to update. incoming.setManuallyUpdated(False) missed.setManuallyUpdated(False) outgoing.setManuallyUpdated(False) general.setManuallyUpdated(False) class PhoneData: #OPHONEKITD related OPHONEKITD_DB = "/var/db/phonelog.db" CALL_STATUS_INCOMING = 0 CALL_STATUS_OUTGOING = 1 CALL_STATUS_ACTIVE = 2 CALL_STATUS_HELD = 3 CALL_STATUS_RELEASE = 4 """PHONELOG_BUSNAME = "org.freesmartphone.opimd" PHONELOG_OBJECTPATH = "/org/freesmartphone/PIM/Log" PHONELOG_INTERFACE = "org.freesmartphone.PIM.Log.Calls" """ PHONELOG_BUSNAME = "org.smartphone.opimd" PHONELOG_OBJECTPATH = "/org/smartphone/PIM/Log" PHONELOG_INTERFACE = "org.smartphone.PIM.Log.Calls" """ CONTACTS_BUSNAME = 'org.freesmartphone.ogsmd' CONTACTS_OBJECTPATH = '/org/freesmartphone/GSM/Device' CONTACTS_INTERFACE = 'org.freesmartphone.GSM.SIM' """ CONTACTS_BUSNAME = 'org.freesmartphone.opimd' CONTACTS_OBJECTPATH = '/org/freesmartphone/PIM/Contacts' CONTACTS_INTERFACE = 'org.freesmartphone.PIM.Contacts' CONTACTS_INTERFACE_QUERY = 'org.freesmartphone.PIM.ContactQuery' CALLS_BUSNAME = 'org.freesmartphone.opimd' CALLS_OBJECTPATH = '/org/freesmartphone/PIM/Calls' CALLS_INTERFACE = 'org.freesmartphone.PIM.Calls' CALLS_INTERFACE_QUERY = 'org.freesmartphone.PIM.CallQuery' PHONE_BUSNAME = 'org.freesmartphone.ogsmd' PHONE_OBJECTPATH = '/org/freesmartphone/GSM/Device' PHONE_INTERFACE = 'org.freesmartphone.GSM.Call' LEGACY_TYPE = 0 OPHONEKITD_TYPE = 1 __phonelog_type = OPHONEKITD_TYPE __database = None __phoneLog = None __contacts_cache = {} conf = None # is inited before use to ConfigParser object def __init__(self, systemBus, config): # init the ConfigParser object once, bail out if failing. if PhoneData.conf == None: PhoneData.conf = config assert PhoneData.conf != None def dbus_ok(self, *args, **kargs): pass def dbus_err(self, x, *args, **kargs): print str(x) def initContacts(self): if config.getboolean('phonelog','debug'): print _("Started caching contacts") contactsObject = self.getContactsObject(systemBus) interface = self.getDbusObject (systemBus, self.CONTACTS_BUSNAME, self.CONTACTS_OBJECTPATH, self.CONTACTS_INTERFACE) x = interface.Query({}) query = self.getDbusObject (systemBus, self.CONTACTS_BUSNAME, x, self.CONTACTS_INTERFACE_QUERY) results = query.GetMultipleResults(query.GetResultCount()) for result in results: self.__contacts_cache[ phoneutils.normalize_number(result['Phone']) ] = result['Name'] query.Dispose(reply_handler = self.dbus_ok, error_handler = self.dbus_err) # delete query result from memory if config.getboolean('phonelog','debug'): print _("Done caching contacts") def getContact(self, number): can_num = phoneutils.normalize_number(number) if can_num in self.__contacts_cache: return self.__contacts_cache[can_num] else: return number def clean(self): if self.__database != None: self.__database.close() def getCalls(self, type): if config.getboolean('phonelog','debug'): print _("Started getting ") + type interface = self.getDbusObject (systemBus, self.CALLS_BUSNAME, self.CALLS_OBJECTPATH, self.CALLS_INTERFACE) if type == "missed": props = {'Answered':0, 'Direction':'in'} props['_limit'] = PhoneData.conf.getint('phonelog', 'missed_limit') elif type == "incoming": props = {'Answered':1, 'Direction':'in'} props['_limit'] = PhoneData.conf.getint('phonelog', 'received_limit') elif type == "outgoing": props = {'Direction':'out'} props['_limit'] = PhoneData.conf.getint('phonelog', 'made_limit') elif type == "all": props = {} props['_limit'] = PhoneData.conf.getint('phonelog', 'general_limit') else: return None props['_sortby'] = 'Timestamp' props['_sortdesc'] = True x = interface.Query(props) query = self.getDbusObject (systemBus, self.CALLS_BUSNAME, x, self.CALLS_INTERFACE_QUERY) calls = query.GetMultipleResults(query.GetResultCount()) query.Dispose() # delete query result from memory if config.getboolean('phonelog','debug'): print _("Finished getting ") + type return calls def getDbusObject (self, bus, busname , objectpath , interface): dbusObject = bus.get_object(busname, objectpath) return dbus.Interface(dbusObject, dbus_interface=interface) def getPhoneLogObject (self, bus): return self.getDbusObject (bus, self.PHONELOG_BUSNAME, self.PHONELOG_OBJECTPATH, self.PHONELOG_INTERFACE ) def getContactsObject (self, bus): return self.getDbusObject (bus, self.CONTACTS_BUSNAME, self.CONTACTS_OBJECTPATH, self.CONTACTS_INTERFACE ) def getPhoneObject (self, bus): return self.getDbusObject (bus, self.PHONE_BUSNAME, self.PHONE_OBJECTPATH, self.PHONE_INTERFACE ) #LEGACY SUPPORT FUNCTIONS def stringToTabNumber(string): if string.lower() == "received": return PAGE_INCOMING elif string.lower() == "made": return PAGE_OUTGOING elif string.lower() == "missed": return PAGE_MISSED elif string.lower() == "general": return PAGE_GENERAL def typeFromLegacyDaemonType(type): if type == "incoming": return 'received' elif type == "outgoing": return 'made' elif type == "missed": return 'missed' elif type == "all": return 'general' else: return "received" #END OF LEGACY FUNCTIONS def timeToString(call_time): now = datetime.now().timetuple() time_tuple = call_time.timetuple() #check if it's today if (now[0] == time_tuple[0]) and \ (now[1] == time_tuple[1]) and \ (now[2] == time_tuple[2]): return "Today, " + call_time.strftime(config.get('phonelog','time_format')) else: return call_time.strftime(config.get('phonelog','date_format') + " " + config.get('phonelog','time_format') ) def quitMainloop(*dump): mainloop.quit() def populateListByPageNum(incoming, outgoing, missed, general, page_num): if page_num == PAGE_GENERAL: if not general.getManuallyUpdated(): general.populateList() elif page_num == PAGE_INCOMING: if not incoming.getManuallyUpdated(): incoming.populateList() elif page_num == PAGE_OUTGOING: if not outgoing.getManuallyUpdated(): outgoing.populateList() elif page_num == PAGE_MISSED: if not missed.getManuallyUpdated(): missed.populateList() #THIS GLOBAL VAR IS PART OF THIS FUNCTION upright = None def cb_win_resize(window, rect, notebook): global upright #update upright, and no need to go further if #we are already in the same position tmp_upright = (rect.height > rect.width) if tmp_upright == upright: return else: upright = tmp_upright if upright: if config.getboolean('phonelog','debug'): print "Upright" notebook.set_property("tab-pos", gtk.POS_TOP) else: if config.getboolean('phonelog','debug'): print "Rotated" notebook.set_property("tab-pos", gtk.POS_LEFT) def init_last_view_times(config): if not config.has_section('viewlog'): config.add_section('viewlog') if not config.has_option('viewlog', 'incoming'): config.set('viewlog','incoming', '0') if not config.has_option('viewlog', 'outgoing'): config.set('viewlog','outgoing', '0') if not config.has_option('viewlog', 'missed'): config.set('viewlog','missed', '0') if not config.has_option('viewlog', 'all'): config.set('viewlog','all', '0') def cb_TabSwitch(notebook, page, page_num, incoming, outgoing, missed, general): populateListByPageNum(incoming, outgoing, missed, general, page_num) ############################################# ################# MAIN ###################### ############################################# print _("Phonelog") #create the config dir if doesn't exist. if not os.path.exists(CONFIGURATION_DIR): os.mkdir(CONFIGURATION_DIR) DBusGMainLoop(set_as_default=True) mainloop = gobject.MainLoop() systemBus = dbus.SystemBus() config = ConfigParser.ConfigParser() config.read([DEFAULT_CONFIG_PATH, CONFIGURATION_FILE]) init_last_view_times(config) phone_data = PhoneData(systemBus, config) phoneutils.init() RefreshMonitor = SignalMonitor(systemBus) if config.getboolean('phonelog','debug'): print _("Initialized global vars") print _("Initializing gtk interface") win = gtk.Window() notebook= gtk.Notebook() win.add (notebook) win.connect('delete-event', quitMainloop ) win.set_title(_("Phone Log")) win.set_default_size(480,640) if config.getboolean('phonelog','change_rotate_layout'): win.connect('size-allocate', cb_win_resize, notebook) #redsign on resize incoming = CallsTab('incoming', conf=config) outgoing = CallsTab('outgoing') missed = CallsTab('missed', True) general = GeneralTab('all') incoming.addToTab(notebook) outgoing.addToTab(notebook) missed.addToTab(notebook) general.addToTab(notebook) if config.getboolean('phonelog','debug'): print _("Showing main window") win.show_all() startup_page = stringToTabNumber(config.get('phonelog','startup_tab') ) notebook.set_current_page(startup_page) notebook.connect ("switch-page", cb_TabSwitch, incoming, outgoing, missed, general) #retrieve contacts try: phone_data.initContacts() except: print _("Failed to initialize contacts") populateListByPageNum(incoming, outgoing, missed, general, startup_page) #end of inits. mainloop.run() phoneutils.deinit() #clean and exit phone_data.clean() # write out config file on exit (also last viewed times) with open(CONFIGURATION_FILE,'w+') as f: config.write(f)