Changeset 5638


Ignore:
Timestamp:
Aug 2, 2017 9:45:09 AM (2 years ago)
Author:
riza
Message:

Close #2034: Add support to Python3 using PJSUA2 API.

Location:
pjproject/trunk/pjsip-apps/src
Files:
12 edited

Legend:

Unmodified
Added
Removed
  • pjproject/trunk/pjsip-apps/src/pygui/account.py

    r4704 r5638  
    2121import sys 
    2222if sys.version_info[0] >= 3: # Python 3 
    23         import tkinter as tk 
    24         from tkinter import ttk 
    25         from tkinter import messagebox as msgbox 
     23    import tkinter as tk 
     24    from tkinter import ttk 
     25    from tkinter import messagebox as msgbox 
    2626else: 
    27         import Tkinter as tk 
    28         import tkMessageBox as msgbox 
    29         import ttk 
     27    import Tkinter as tk 
     28    import tkMessageBox as msgbox 
     29    import ttk 
    3030 
    3131import random 
     
    3737import chat as ch 
    3838 
     39write=sys.stdout.write 
     40 
    3941# Account class 
    4042class Account(pj.Account): 
    41         """ 
    42         High level Python Account object, derived from pjsua2's Account object. 
    43         """ 
    44         def __init__(self, app): 
    45                 pj.Account.__init__(self) 
    46                 self.app = app 
    47                 self.randId = random.randint(1, 9999) 
    48                 self.cfg =  pj.AccountConfig() 
    49                 self.cfgChanged = False 
    50                 self.buddyList = [] 
    51                 self.chatList = [] 
    52                 self.deleting = False 
    53  
    54         def findChat(self, uri_str): 
    55                 uri = ch.ParseSipUri(uri_str) 
    56                 if not uri: return None 
    57                          
    58                 for chat in self.chatList: 
    59                         if chat.isUriParticipant(uri) and chat.isPrivate(): 
    60                                 return chat 
    61                 return None 
    62          
    63         def newChat(self, uri_str): 
    64                 uri = ch.ParseSipUri(uri_str) 
    65                 if not uri: return None 
    66                          
    67                 chat = ch.Chat(self.app, self, uri) 
    68                 self.chatList.append(chat) 
    69                 self.app.updateWindowMenu() 
    70                 return chat 
    71          
    72         def statusText(self): 
    73                 status = '?' 
    74                 if self.isValid(): 
    75                         ai = self.getInfo() 
    76                         if ai.regLastErr: 
    77                                 status = self.app.ep.utilStrError(ai.regLastErr) 
    78                         elif ai.regIsActive: 
    79                                 if ai.onlineStatus: 
    80                                         if len(ai.onlineStatusText): 
    81                                                 status = ai.onlineStatusText 
    82                                         else: 
    83                                                 status = "Online" 
    84                                 else: 
    85                                         status = "Registered" 
    86                         else: 
    87                                 if ai.regIsConfigured: 
    88                                         if ai.regStatus/100 == 2: 
    89                                                 status = "Unregistered" 
    90                                         else: 
    91                                                 status = ai.regStatusText 
    92                                 else: 
    93                                         status = "Doesn't register" 
    94                 else: 
    95                         status = '- not created -' 
    96                 return status 
    97                  
    98         def onRegState(self, prm): 
    99                 self.app.updateAccount(self) 
    100  
    101         def onIncomingCall(self, prm): 
    102                 c = call.Call(self, call_id=prm.callId) 
    103                 call_prm = pj.CallOpParam() 
    104                 call_prm.statusCode = 180 
    105                 c.answer(call_prm) 
    106                 ci = c.getInfo() 
    107                 msg = "Incoming call for account '%s'" % self.cfg.idUri 
    108                 if msgbox.askquestion(msg, "Accept call from '%s'?" % (ci.remoteUri), default=msgbox.YES) == u'yes': 
    109                         call_prm.statusCode = 200 
    110                         c.answer(call_prm) 
    111                          
    112                         # find/create chat instance 
    113                         chat = self.findChat(ci.remoteUri) 
    114                         if not chat: chat = self.newChat(ci.remoteUri) 
    115                          
    116                         chat.showWindow() 
    117                         chat.registerCall(ci.remoteUri, c) 
    118                         chat.updateCallState(c, ci) 
    119                 else: 
    120                         c.hangup(call_prm) 
    121                          
    122         def onInstantMessage(self, prm): 
    123                 chat = self.findChat(prm.fromUri) 
    124                 if not chat: chat = self.newChat(prm.fromUri) 
    125                  
    126                 chat.showWindow() 
    127                 chat.addMessage(prm.fromUri, prm.msgBody) 
    128                  
    129         def onInstantMessageStatus(self, prm): 
    130                 if prm.code/100 == 2: return 
    131                  
    132                 chat = self.findChat(prm.toUri) 
    133                 if not chat: 
    134                         print "=== IM status to '%s' cannot find chat" % prm.toUri 
    135                         return 
    136                  
    137                 chat.addMessage(None, "Failed sending message to '%s': %s" % (prm.toUri, prm.reason)) 
    138                  
    139         def onTypingIndication(self, prm): 
    140                 chat = self.findChat(prm.fromUri) 
    141                 if not chat: 
    142                         print "=== Incoming typing indication from '%s' cannot find chat" % prm.fromUri 
    143                         return 
    144                  
    145                 chat.setTypingIndication(prm.fromUri, prm.isTyping) 
    146  
    147                  
     43    """ 
     44    High level Python Account object, derived from pjsua2's Account object. 
     45    """ 
     46    def __init__(self, app): 
     47        pj.Account.__init__(self) 
     48        self.app = app 
     49        self.randId = random.randint(1, 9999) 
     50        self.cfg =  pj.AccountConfig() 
     51        self.cfgChanged = False 
     52        self.buddyList = [] 
     53        self.chatList = [] 
     54        self.deleting = False 
     55 
     56    def findChat(self, uri_str): 
     57        uri = ch.ParseSipUri(uri_str) 
     58        if not uri: return None 
     59 
     60        for chat in self.chatList: 
     61            if chat.isUriParticipant(uri) and chat.isPrivate(): 
     62                return chat 
     63        return None 
     64 
     65    def newChat(self, uri_str): 
     66        uri = ch.ParseSipUri(uri_str) 
     67        if not uri: return None 
     68 
     69        chat = ch.Chat(self.app, self, uri) 
     70        self.chatList.append(chat) 
     71        self.app.updateWindowMenu() 
     72        return chat 
     73 
     74    def statusText(self): 
     75        status = '?' 
     76        if self.isValid(): 
     77            ai = self.getInfo() 
     78            if ai.regLastErr: 
     79                status = self.app.ep.utilStrError(ai.regLastErr) 
     80            elif ai.regIsActive: 
     81                if ai.onlineStatus: 
     82                    if len(ai.onlineStatusText): 
     83                        status = ai.onlineStatusText 
     84                    else: 
     85                        status = "Online" 
     86                else: 
     87                    status = "Registered" 
     88            else: 
     89                if ai.regIsConfigured: 
     90                    if ai.regStatus/100 == 2: 
     91                        status = "Unregistered" 
     92                    else: 
     93                        status = ai.regStatusText 
     94                else: 
     95                    status = "Doesn't register" 
     96        else: 
     97            status = '- not created -' 
     98        return status 
     99 
     100    def onRegState(self, prm): 
     101        self.app.updateAccount(self) 
     102 
     103    def onIncomingCall(self, prm): 
     104        c = call.Call(self, call_id=prm.callId) 
     105        call_prm = pj.CallOpParam() 
     106        call_prm.statusCode = 180 
     107        c.answer(call_prm) 
     108        ci = c.getInfo() 
     109        msg = "Incoming call for account '%s'" % self.cfg.idUri 
     110        if msgbox.askquestion(msg, "Accept call from '%s'?" % (ci.remoteUri), default=msgbox.YES) == u'yes': 
     111            call_prm.statusCode = 200 
     112            c.answer(call_prm) 
     113 
     114            # find/create chat instance 
     115            chat = self.findChat(ci.remoteUri) 
     116            if not chat: chat = self.newChat(ci.remoteUri) 
     117 
     118            chat.showWindow() 
     119            chat.registerCall(ci.remoteUri, c) 
     120            chat.updateCallState(c, ci) 
     121        else: 
     122            c.hangup(call_prm) 
     123 
     124    def onInstantMessage(self, prm): 
     125        chat = self.findChat(prm.fromUri) 
     126        if not chat: chat = self.newChat(prm.fromUri) 
     127 
     128        chat.showWindow() 
     129        chat.addMessage(prm.fromUri, prm.msgBody) 
     130 
     131    def onInstantMessageStatus(self, prm): 
     132        if prm.code/100 == 2: return 
     133 
     134        chat = self.findChat(prm.toUri) 
     135        if not chat: 
     136            write("=== IM status to " + prm.toUri + "cannot find chat\r\n") 
     137            return 
     138 
     139        chat.addMessage(None, "Failed sending message to '%s': %s" % (prm.toUri, prm.reason)) 
     140 
     141    def onTypingIndication(self, prm): 
     142        chat = self.findChat(prm.fromUri) 
     143        if not chat: 
     144            write("=== Incoming typing indication from " + prm.fromUri + "cannot find chat\r\n") 
     145            return 
     146 
     147        chat.setTypingIndication(prm.fromUri, prm.isTyping) 
     148 
     149 
    148150# Account frame, to list accounts 
    149151class AccountListFrame(ttk.Frame): 
    150         """ 
    151         This implements a Frame which contains account list and buttons to operate 
    152         on them (Add, Modify, Delete, etc.).  
    153         """ 
    154         def __init__(self, parent, app, acc_list = []): 
    155                 ttk.Frame.__init__(self, parent, name='acclist') 
    156                 self.app = app 
    157                 self.accList = acc_list 
    158                 self.accDeletedList = [] 
    159                 self.pack(expand='yes', fill='both') 
    160                 self._createWidgets() 
    161                 for acc in self.accList: 
    162                         self._showAcc(acc) 
    163                  
    164         def _createWidgets(self): 
    165                 self.tv = ttk.Treeview(self, columns=('ID', 'Registrar', 'Default'), selectmode='browse') 
    166                 self.tv.heading('#0', text='Priority') 
    167                 self.tv.heading(0, text='ID') 
    168                 self.tv.heading(1, text='Registrar') 
    169                 self.tv.heading(2, text='Default?') 
    170                 self.tv.column('#0', width=60) 
    171                 self.tv.column(0, width=300) 
    172                 self.tv.column(1, width=200) 
    173                 self.tv.column(2, width=60) 
    174                 self.tv.grid(column=0, row=0, rowspan=4, padx=5, pady=5) 
    175                  
    176                 ttk.Button(self, text='Add..', command=self._onBtnAdd).grid(column=1, row=0, padx=5) 
    177                 ttk.Button(self, text='Settings..', command=self._onBtnSettings).grid(column=1, row=1) 
    178                 ttk.Button(self, text='Set Default', command=self._onBtnSetDefault).grid(column=1, row=2) 
    179                 ttk.Button(self, text='Delete..', command=self._onBtnDelete).grid(column=1, row=3) 
    180  
    181         def _showAcc(self, acc): 
    182                 is_default = 'Yes' if acc.isValid() and acc.isDefault() else '' 
    183                 values = (acc.cfg.idUri, acc.cfg.regConfig.registrarUri, is_default) 
    184                 self.tv.insert('', 0, str(acc.randId), open=True, text=str(acc.cfg.priority), values=values) 
    185          
    186         def updateAccount(self, acc): 
    187                 is_default = 'Yes' if acc.isValid() and acc.isDefault() else '' 
    188                 values = (acc.cfg.idUri, acc.cfg.regConfig.registrarUri, is_default) 
    189                 self.tv.item(str(acc.randId), text=str(acc.cfg.priority), values=values) 
    190          
    191         def _getSelectedAcc(self): 
    192                 items = self.tv.selection() 
    193                 if not items: 
    194                         return None 
    195                 iid = int(items[0]) 
    196                 return [acc for acc in self.accList if acc.randId==iid][0] 
    197          
    198         def _onBtnAdd(self): 
    199                 cfg = pj.AccountConfig() 
    200                 dlg = accountsetting.Dialog(self.master, cfg) 
    201                 if dlg.doModal(): 
    202                         acc = Account(self.app) 
    203                         acc.cfg = cfg 
    204                         self._showAcc(acc) 
    205                         self.accList.append(acc) 
    206                         self.cfgChanged = True 
    207          
    208         def _onBtnSettings(self): 
    209                 acc = self._getSelectedAcc() 
    210                 if not acc: 
    211                         return 
    212                 dlg = accountsetting.Dialog(self.master, acc.cfg) 
    213                 if dlg.doModal(): 
    214                         self.updateAccount(acc) 
    215                         self.cfgChanged = True 
    216  
    217         def _onBtnDelete(self): 
    218                 acc = self._getSelectedAcc() 
    219                 if not acc: 
    220                         return 
    221                 msg = "Do you really want to delete account '%s'" % acc.cfg.idUri 
    222                 if msgbox.askquestion('Delete account?', msg, default=msgbox.NO) != u'yes': 
    223                         return 
    224                 self.accList.remove(acc) 
    225                 self.accDeletedList.append(acc) 
    226                 self.tv.delete( (str(acc.randId),) ) 
    227  
    228         def _onBtnSetDefault(self): 
    229                 acc = self._getSelectedAcc() 
    230                 if not acc: 
    231                         return 
    232                 if acc.isValid(): 
    233                         acc.setDefault() 
    234                 for acc in self.accList: 
    235                         self.updateAccount(acc) 
    236                  
    237                  
     152    """ 
     153    This implements a Frame which contains account list and buttons to operate 
     154    on them (Add, Modify, Delete, etc.). 
     155    """ 
     156    def __init__(self, parent, app, acc_list = []): 
     157        ttk.Frame.__init__(self, parent, name='acclist') 
     158        self.app = app 
     159        self.accList = acc_list 
     160        self.accDeletedList = [] 
     161        self.pack(expand='yes', fill='both') 
     162        self._createWidgets() 
     163        for acc in self.accList: 
     164            self._showAcc(acc) 
     165 
     166    def _createWidgets(self): 
     167        self.tv = ttk.Treeview(self, columns=('ID', 'Registrar', 'Default'), selectmode='browse') 
     168        self.tv.heading('#0', text='Priority') 
     169        self.tv.heading(0, text='ID') 
     170        self.tv.heading(1, text='Registrar') 
     171        self.tv.heading(2, text='Default?') 
     172        self.tv.column('#0', width=60) 
     173        self.tv.column(0, width=300) 
     174        self.tv.column(1, width=200) 
     175        self.tv.column(2, width=60) 
     176        self.tv.grid(column=0, row=0, rowspan=4, padx=5, pady=5) 
     177 
     178        ttk.Button(self, text='Add..', command=self._onBtnAdd).grid(column=1, row=0, padx=5) 
     179        ttk.Button(self, text='Settings..', command=self._onBtnSettings).grid(column=1, row=1) 
     180        ttk.Button(self, text='Set Default', command=self._onBtnSetDefault).grid(column=1, row=2) 
     181        ttk.Button(self, text='Delete..', command=self._onBtnDelete).grid(column=1, row=3) 
     182 
     183    def _showAcc(self, acc): 
     184        is_default = 'Yes' if acc.isValid() and acc.isDefault() else '' 
     185        values = (acc.cfg.idUri, acc.cfg.regConfig.registrarUri, is_default) 
     186        self.tv.insert('', 0, str(acc.randId), open=True, text=str(acc.cfg.priority), values=values) 
     187 
     188    def updateAccount(self, acc): 
     189        is_default = 'Yes' if acc.isValid() and acc.isDefault() else '' 
     190        values = (acc.cfg.idUri, acc.cfg.regConfig.registrarUri, is_default) 
     191        self.tv.item(str(acc.randId), text=str(acc.cfg.priority), values=values) 
     192 
     193    def _getSelectedAcc(self): 
     194        items = self.tv.selection() 
     195        if not items: 
     196            return None 
     197        iid = int(items[0]) 
     198        return [acc for acc in self.accList if acc.randId==iid][0] 
     199 
     200    def _onBtnAdd(self): 
     201        cfg = pj.AccountConfig() 
     202        dlg = accountsetting.Dialog(self.master, cfg) 
     203        if dlg.doModal(): 
     204            acc = Account(self.app) 
     205            acc.cfg = cfg 
     206            self._showAcc(acc) 
     207            self.accList.append(acc) 
     208            self.cfgChanged = True 
     209 
     210    def _onBtnSettings(self): 
     211        acc = self._getSelectedAcc() 
     212        if not acc: 
     213            return 
     214        dlg = accountsetting.Dialog(self.master, acc.cfg) 
     215        if dlg.doModal(): 
     216            self.updateAccount(acc) 
     217            self.cfgChanged = True 
     218 
     219    def _onBtnDelete(self): 
     220        acc = self._getSelectedAcc() 
     221        if not acc: 
     222            return 
     223        msg = "Do you really want to delete account '%s'" % acc.cfg.idUri 
     224        if msgbox.askquestion('Delete account?', msg, default=msgbox.NO) != u'yes': 
     225            return 
     226        self.accList.remove(acc) 
     227        self.accDeletedList.append(acc) 
     228        self.tv.delete( (str(acc.randId),) ) 
     229 
     230    def _onBtnSetDefault(self): 
     231        acc = self._getSelectedAcc() 
     232        if not acc: 
     233            return 
     234        if acc.isValid(): 
     235            acc.setDefault() 
     236        for acc in self.accList: 
     237            self.updateAccount(acc) 
     238 
     239 
    238240if __name__ == '__main__': 
    239         application.main() 
     241    application.main() 
  • pjproject/trunk/pjsip-apps/src/pygui/accountsetting.py

    r4704 r5638  
    2121import sys 
    2222if sys.version_info[0] >= 3: # Python 3 
    23         import tkinter as tk 
    24         from tkinter import ttk 
    25         from tkinter import messagebox as msgbox 
     23    import tkinter as tk 
     24    from tkinter import ttk 
     25    from tkinter import messagebox as msgbox 
    2626else: 
    27         import Tkinter as tk 
    28         import tkMessageBox as msgbox 
    29         import ttk 
     27    import Tkinter as tk 
     28    import tkMessageBox as msgbox 
     29    import ttk 
    3030 
    3131import pjsua2 as pj 
     
    3434 
    3535class Dialog(tk.Toplevel): 
    36         """ 
    37         This implements account settings dialog to manipulate account settings. 
    38         """ 
    39         def __init__(self, parent, cfg): 
    40                 tk.Toplevel.__init__(self, parent) 
    41                 self.transient(parent) 
    42                 self.parent = parent 
    43                 self.geometry("+100+100") 
    44                 self.title('Account settings') 
    45                  
    46                 self.frm = ttk.Frame(self) 
    47                 self.frm.pack(expand='yes', fill='both') 
    48                  
    49                 self.isOk = False 
    50                 self.cfg = cfg 
    51                  
    52                 self.createWidgets() 
    53          
    54         def doModal(self): 
    55                 if self.parent: 
    56                         self.parent.wait_window(self) 
    57                 else: 
    58                         self.wait_window(self) 
    59                 return self.isOk 
    60                  
    61         def createWidgets(self): 
    62                 # The notebook 
    63                 self.frm.rowconfigure(0, weight=1) 
    64                 self.frm.rowconfigure(1, weight=0) 
    65                 self.frm.columnconfigure(0, weight=1) 
    66                 self.frm.columnconfigure(1, weight=1) 
    67                 self.wTab = ttk.Notebook(self.frm) 
    68                 self.wTab.grid(column=0, row=0, columnspan=2, padx=10, pady=10, ipadx=20, ipady=20, sticky=tk.N+tk.S+tk.W+tk.E) 
    69                  
    70                 # Main buttons 
    71                 btnOk = ttk.Button(self.frm, text='Ok', command=self.onOk) 
    72                 btnOk.grid(column=0, row=1, sticky=tk.E, padx=20, pady=10) 
    73                 btnCancel = ttk.Button(self.frm, text='Cancel', command=self.onCancel) 
    74                 btnCancel.grid(column=1, row=1, sticky=tk.W, padx=20, pady=10) 
    75                  
    76                 # Tabs 
    77                 self.createBasicTab() 
    78                 self.createSipTab() 
    79                 self.createMediaTab() 
    80                 self.createMediaNatTab() 
    81                  
    82         def createBasicTab(self): 
    83                 # Prepare the variables to set/receive values from GUI 
    84                 self.cfgPriority = tk.IntVar(value=self.cfg.priority) 
    85                 self.cfgAccId = tk.StringVar(value=self.cfg.idUri) 
    86                 self.cfgRegistrar = tk.StringVar(value=self.cfg.regConfig.registrarUri) 
    87                 self.cfgRegisterOnAdd = tk.IntVar(value=self.cfg.regConfig.registerOnAdd) 
    88                 self.cfgUsername = tk.StringVar() 
    89                 self.cfgPassword = tk.StringVar() 
    90                 if len(self.cfg.sipConfig.authCreds): 
    91                         self.cfgUsername.set( self.cfg.sipConfig.authCreds[0].username ) 
    92                         self.cfgPassword.set( self.cfg.sipConfig.authCreds[0].data ) 
    93                 self.cfgProxy = tk.StringVar() 
    94                 if len(self.cfg.sipConfig.proxies): 
    95                         self.cfgProxy.set( self.cfg.sipConfig.proxies[0] ) 
    96                  
    97                 # Build the tab page 
    98                 frm = ttk.Frame(self.frm) 
    99                 frm.columnconfigure(0, weight=1) 
    100                 frm.columnconfigure(1, weight=2) 
    101                 row = 0 
    102                 ttk.Label(frm, text='Priority:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    103                 tk.Spinbox(frm, from_=0, to=9, textvariable=self.cfgPriority, width=2).grid(row=row, column=1, sticky=tk.W, padx=6) 
    104                 row += 1 
    105                 ttk.Label(frm, text='ID (URI):').grid(row=row, column=0, sticky=tk.E, pady=2) 
    106                 ttk.Entry(frm, textvariable=self.cfgAccId, width=32).grid(row=row, column=1, sticky=tk.W, padx=6) 
    107                 row += 1 
    108                 ttk.Label(frm, text='Registrar URI:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    109                 ttk.Entry(frm, textvariable=self.cfgRegistrar, width=32).grid(row=row, column=1, sticky=tk.W, padx=6) 
    110                 row += 1 
    111                 ttk.Checkbutton(frm, text='Register on add', variable=self.cfgRegisterOnAdd).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
    112                 row += 1 
    113                 ttk.Label(frm, text='Optional proxy URI:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    114                 ttk.Entry(frm, textvariable=self.cfgProxy, width=32).grid(row=row, column=1, sticky=tk.W, padx=6) 
    115                 row += 1 
    116                 ttk.Label(frm, text='Auth username:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    117                 ttk.Entry(frm, textvariable=self.cfgUsername, width=16).grid(row=row, column=1, sticky=tk.W, padx=6) 
    118                 row += 1 
    119                 ttk.Label(frm, text='Password:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    120                 ttk.Entry(frm, textvariable=self.cfgPassword, show='*', width=16).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
    121  
    122                 self.wTab.add(frm, text='Basic Settings') 
    123                  
    124  
    125         def createSipTab(self): 
    126                 # Prepare the variables to set/receive values from GUI 
    127                 self.cfgPrackUse        = tk.IntVar(value=self.cfg.callConfig.prackUse) 
    128                 self.cfgTimerUse        = tk.IntVar(value=self.cfg.callConfig.timerUse) 
    129                 self.cfgTimerExpires    = tk.IntVar(value=self.cfg.callConfig.timerSessExpiresSec) 
    130                 self.cfgPublish         = tk.BooleanVar(value=self.cfg.presConfig.publishEnabled) 
    131                 self.cfgMwiEnabled      = tk.BooleanVar(value=self.cfg.mwiConfig.enabled) 
    132                 self.cfgEnableContactRewrite = tk.BooleanVar(value=self.cfg.natConfig.contactRewriteUse != 0)  
    133                 self.cfgEnableViaRewrite = tk.BooleanVar(value=self.cfg.natConfig.viaRewriteUse != 0)  
    134                 self.cfgEnableSdpRewrite = tk.BooleanVar(value=self.cfg.natConfig.sdpNatRewriteUse != 0) 
    135                 self.cfgEnableSipOutbound = tk.BooleanVar(value=self.cfg.natConfig.sipOutboundUse != 0) 
    136                 self.cfgKaInterval      = tk.IntVar(value=self.cfg.natConfig.udpKaIntervalSec) 
    137                  
    138                 # Build the tab page 
    139                 frm = ttk.Frame(self.frm) 
    140                 frm.columnconfigure(0, weight=1) 
    141                 frm.columnconfigure(1, weight=2) 
    142                 row = 0 
    143                 ttk.Label(frm, text='100rel/PRACK:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    144                 ttk.Radiobutton(frm, text='Only offer PRACK', value=pj.PJSUA_100REL_NOT_USED, variable=self.cfgPrackUse).grid(row=row, column=1, sticky=tk.W, padx=6) 
    145                 row += 1 
    146                 ttk.Radiobutton(frm, text='Offer and use if remote supports', value=pj.PJSUA_100REL_OPTIONAL, variable=self.cfgPrackUse).grid(row=row, column=1, sticky=tk.W, padx=6) 
    147                 row += 1 
    148                 ttk.Radiobutton(frm, text='Required', value=pj.PJSUA_100REL_MANDATORY, variable=self.cfgPrackUse).grid(row=row, column=1, sticky=tk.W, padx=6) 
    149                 row += 1 
    150                 ttk.Label(frm, text='Session Timer:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    151                 ttk.Radiobutton(frm, text='Not offered', value=pj.PJSUA_SIP_TIMER_INACTIVE, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6) 
    152                 row += 1 
    153                 ttk.Radiobutton(frm, text='Optional', value=pj.PJSUA_SIP_TIMER_OPTIONAL, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6) 
    154                 row += 1 
    155                 ttk.Radiobutton(frm, text='Required', value=pj.PJSUA_SIP_TIMER_REQUIRED, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6) 
    156                 row += 1 
    157                 ttk.Radiobutton(frm, text="Always use", value=pj.PJSUA_SIP_TIMER_ALWAYS, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6) 
    158                 row += 1 
    159                 ttk.Label(frm, text='Session Timer Expiration:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    160                 tk.Spinbox(frm, from_=90, to=7200, textvariable=self.cfgTimerExpires, width=5).grid(row=row, column=1, sticky=tk.W, padx=6) 
    161                 ttk.Label(frm, text='(seconds)').grid(row=row, column=1, sticky=tk.E) 
    162                 row += 1 
    163                 ttk.Label(frm, text='Presence:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    164                 ttk.Checkbutton(frm, text='Enable PUBLISH', variable=self.cfgPublish).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
    165                 row += 1 
    166                 ttk.Label(frm, text='Message Waiting Indication:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    167                 ttk.Checkbutton(frm, text='Enable MWI', variable=self.cfgMwiEnabled).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
    168                 row += 1 
    169                 ttk.Label(frm, text='NAT Traversal:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    170                 ttk.Checkbutton(frm, text='Enable Contact Rewrite', variable=self.cfgEnableContactRewrite).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
    171                 row += 1 
    172                 ttk.Checkbutton(frm, text='Enable Via Rewrite', variable=self.cfgEnableViaRewrite).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
    173                 row += 1 
    174                 ttk.Checkbutton(frm, text='Enable SDP IP Address Rewrite', variable=self.cfgEnableSdpRewrite).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
    175                 row += 1 
    176                 ttk.Checkbutton(frm, text='Enable SIP Outbound Extension', variable=self.cfgEnableSipOutbound).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
    177                 row += 1 
    178                 ttk.Label(frm, text='UDP Keep-Alive Interval:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    179                 tk.Spinbox(frm, from_=0, to=3600, textvariable=self.cfgKaInterval, width=5).grid(row=row, column=1, sticky=tk.W, padx=6) 
    180                 ttk.Label(frm, text='(seconds) Zero to disable.').grid(row=row, column=1, sticky=tk.E) 
    181  
    182  
    183                 self.wTab.add(frm, text='SIP Features') 
    184  
    185         def createMediaTab(self): 
    186                 # Prepare the variables to set/receive values from GUI 
    187                 self.cfgMedPort = tk.IntVar(value=self.cfg.mediaConfig.transportConfig.port) 
    188                 self.cfgMedPortRange = tk.IntVar(value=self.cfg.mediaConfig.transportConfig.portRange) 
    189                 self.cfgMedLockCodec = tk.BooleanVar(value=self.cfg.mediaConfig.lockCodecEnabled) 
    190                 self.cfgMedSrtp = tk.IntVar(value=self.cfg.mediaConfig.srtpUse) 
    191                 self.cfgMedSrtpSecure = tk.IntVar(value=self.cfg.mediaConfig.srtpSecureSignaling) 
    192                 self.cfgMedIpv6 = tk.BooleanVar(value=self.cfg.mediaConfig.ipv6Use==pj.PJSUA_IPV6_ENABLED) 
    193                  
    194                 # Build the tab page 
    195                 frm = ttk.Frame(self.frm) 
    196                 frm.columnconfigure(0, weight=1) 
    197                 frm.columnconfigure(1, weight=21) 
    198                 row = 0 
    199                 ttk.Label(frm, text='Secure RTP (SRTP):').grid(row=row, column=0, sticky=tk.E, pady=2) 
    200                 ttk.Radiobutton(frm, text='Disable', value=pj.PJMEDIA_SRTP_DISABLED, variable=self.cfgMedSrtp).grid(row=row, column=1, sticky=tk.W, padx=6) 
    201                 row += 1 
    202                 ttk.Radiobutton(frm, text='Mandatory', value=pj.PJMEDIA_SRTP_MANDATORY, variable=self.cfgMedSrtp).grid(row=row, column=1, sticky=tk.W, padx=6) 
    203                 row += 1 
    204                 ttk.Radiobutton(frm, text='Optional (non-standard)', value=pj.PJMEDIA_SRTP_OPTIONAL, variable=self.cfgMedSrtp).grid(row=row, column=1, sticky=tk.W, padx=6) 
    205                 row += 1 
    206                 ttk.Label(frm, text='SRTP signaling:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    207                 ttk.Radiobutton(frm, text='Does not require secure signaling', value=0, variable=self.cfgMedSrtpSecure).grid(row=row, column=1, sticky=tk.W, padx=6) 
    208                 row += 1 
    209                 ttk.Radiobutton(frm, text='Require secure next hop (TLS)', value=1, variable=self.cfgMedSrtpSecure).grid(row=row, column=1, sticky=tk.W, padx=6) 
    210                 row += 1 
    211                 ttk.Radiobutton(frm, text='Require secure end-to-end (SIPS)', value=2, variable=self.cfgMedSrtpSecure).grid(row=row, column=1, sticky=tk.W, padx=6) 
    212                 row += 1 
    213                 ttk.Label(frm, text='RTP transport start port:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    214                 tk.Spinbox(frm, from_=0, to=65535, textvariable=self.cfgMedPort, width=5).grid(row=row, column=1, sticky=tk.W, padx=6) 
    215                 ttk.Label(frm, text='(0: any)').grid(row=row, column=1, sticky=tk.E, pady=2) 
    216                 row += 1 
    217                 ttk.Label(frm, text='Port range:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    218                 tk.Spinbox(frm, from_=0, to=65535, textvariable=self.cfgMedPortRange, width=5).grid(row=row, column=1, sticky=tk.W, padx=6) 
    219                 ttk.Label(frm, text='(0: not limited)').grid(row=row, column=1, sticky=tk.E, pady=2) 
    220                 row += 1 
    221                 ttk.Label(frm, text='Lock codec:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    222                 ttk.Checkbutton(frm, text='Enable', variable=self.cfgMedLockCodec).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
    223                 row += 1 
    224                 ttk.Label(frm, text='Use IPv6:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    225                 ttk.Checkbutton(frm, text='Yes', variable=self.cfgMedIpv6).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
    226  
    227                 self.wTab.add(frm, text='Media settings') 
    228  
    229         def createMediaNatTab(self): 
    230                 # Prepare the variables to set/receive values from GUI 
    231                 self.cfgSipUseStun = tk.IntVar(value = self.cfg.natConfig.sipStunUse) 
    232                 self.cfgMediaUseStun = tk.IntVar(value = self.cfg.natConfig.mediaStunUse) 
    233                 self.cfgIceEnabled = tk.BooleanVar(value = self.cfg.natConfig.iceEnabled) 
    234                 self.cfgIceAggressive = tk.BooleanVar(value = self.cfg.natConfig.iceAggressiveNomination) 
    235                 self.cfgAlwaysUpdate = tk.BooleanVar(value = True if self.cfg.natConfig.iceAlwaysUpdate else False) 
    236                 self.cfgIceNoHostCands = tk.BooleanVar(value = True if self.cfg.natConfig.iceMaxHostCands == 0 else False) 
    237                 self.cfgTurnEnabled = tk.BooleanVar(value = self.cfg.natConfig.turnEnabled) 
    238                 self.cfgTurnServer = tk.StringVar(value = self.cfg.natConfig.turnServer) 
    239                 self.cfgTurnConnType = tk.IntVar(value = self.cfg.natConfig.turnConnType) 
    240                 self.cfgTurnUser = tk.StringVar(value = self.cfg.natConfig.turnUserName) 
    241                 self.cfgTurnPasswd = tk.StringVar(value = self.cfg.natConfig.turnPassword) 
    242                  
    243                 # Build the tab page 
    244                 frm = ttk.Frame(self.frm) 
    245                 frm.columnconfigure(0, weight=1) 
    246                 frm.columnconfigure(1, weight=2) 
    247                 row = 0 
    248                 ttk.Label(frm, text='SIP STUN Usage:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    249                 ttk.Radiobutton(frm, text='Default', value=pj.PJSUA_STUN_USE_DEFAULT, variable=self.cfgSipUseStun).grid(row=row, column=1, sticky=tk.W, padx=6) 
    250                 row += 1 
    251                 ttk.Radiobutton(frm, text='Disable', value=pj.PJSUA_STUN_USE_DISABLED, variable=self.cfgSipUseStun).grid(row=row, column=1, sticky=tk.W, padx=6) 
    252                 row += 1 
    253                 ttk.Label(frm, text='Media STUN Usage:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    254                 ttk.Radiobutton(frm, text='Default', value=pj.PJSUA_STUN_USE_DEFAULT, variable=self.cfgMediaUseStun).grid(row=row, column=1, sticky=tk.W, padx=6) 
    255                 row += 1 
    256                 ttk.Radiobutton(frm, text='Disable', value=pj.PJSUA_STUN_USE_DISABLED, variable=self.cfgMediaUseStun).grid(row=row, column=1, sticky=tk.W, padx=6) 
    257                 row += 1 
    258                 ttk.Label(frm, text='ICE:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    259                 ttk.Checkbutton(frm, text='Enable', variable=self.cfgIceEnabled).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
    260                 row += 1 
    261                 ttk.Checkbutton(frm, text='Use aggresive nomination', variable=self.cfgIceAggressive).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
    262                 row += 1 
    263                 ttk.Checkbutton(frm, text='Always re-INVITE after negotiation', variable=self.cfgAlwaysUpdate).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
    264                 row += 1 
    265                 ttk.Checkbutton(frm, text='Disable host candidates', variable=self.cfgIceNoHostCands).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
    266                 row += 1 
    267                 ttk.Label(frm, text='TURN:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    268                 ttk.Checkbutton(frm, text='Enable', variable=self.cfgTurnEnabled).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
    269                 row += 1 
    270                 ttk.Label(frm, text='TURN server:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    271                 ttk.Entry(frm, textvariable=self.cfgTurnServer, width=20).grid(row=row, column=1, sticky=tk.W, padx=6) 
    272                 ttk.Label(frm, text='host[:port]').grid(row=row, column=1, sticky=tk.E, pady=6) 
    273                 row += 1 
    274                 ttk.Label(frm, text='TURN connection:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    275                 ttk.Radiobutton(frm, text='UDP', value=pj.PJ_TURN_TP_UDP, variable=self.cfgTurnConnType).grid(row=row, column=1, sticky=tk.W, padx=6) 
    276                 row += 1 
    277                 ttk.Radiobutton(frm, text='TCP', value=pj.PJ_TURN_TP_TCP, variable=self.cfgTurnConnType).grid(row=row, column=1, sticky=tk.W, padx=6) 
    278                 row += 1 
    279                 ttk.Label(frm, text='TURN username:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    280                 ttk.Entry(frm, textvariable=self.cfgTurnUser, width=16).grid(row=row, column=1, sticky=tk.W, padx=6) 
    281                 row += 1 
    282                 ttk.Label(frm, text='TURN password:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    283                 ttk.Entry(frm, textvariable=self.cfgTurnPasswd, show='*', width=16).grid(row=row, column=1, sticky=tk.W, padx=6) 
    284  
    285                 self.wTab.add(frm, text='NAT settings') 
    286                  
    287         def onOk(self): 
    288                 # Check basic settings 
    289                 errors = ""; 
    290                 if not self.cfgAccId.get(): 
    291                         errors += "Account ID is required\n" 
    292                 if self.cfgAccId.get(): 
    293                         if not endpoint.validateSipUri(self.cfgAccId.get()): 
    294                                 errors += "Invalid SIP ID URI: '%s'\n" % (self.cfgAccId.get()) 
    295                 if self.cfgRegistrar.get(): 
    296                         if not endpoint.validateSipUri(self.cfgRegistrar.get()): 
    297                                 errors += "Invalid SIP registrar URI: '%s'\n" % (self.cfgRegistrar.get()) 
    298                 if self.cfgProxy.get(): 
    299                         if not endpoint.validateSipUri(self.cfgProxy.get()): 
    300                                 errors += "Invalid SIP proxy URI: '%s'\n" % (self.cfgProxy.get()) 
    301                 if self.cfgTurnEnabled.get(): 
    302                         if not self.cfgTurnServer.get(): 
    303                                 errors += "TURN server is required\n" 
    304                 if errors: 
    305                         msgbox.showerror("Error detected:", errors) 
    306                         return 
    307                  
    308                 # Basic settings 
    309                 self.cfg.priority = self.cfgPriority.get() 
    310                 self.cfg.idUri = self.cfgAccId.get() 
    311                 self.cfg.regConfig.registrarUri = self.cfgRegistrar.get() 
    312                 self.cfg.regConfig.registerOnAdd = self.cfgRegisterOnAdd.get() 
    313                 while len(self.cfg.sipConfig.authCreds): 
    314                         self.cfg.sipConfig.authCreds.pop() 
    315                 if self.cfgUsername.get(): 
    316                         cred = pj.AuthCredInfo() 
    317                         cred.scheme = "digest" 
    318                         cred.realm = "*" 
    319                         cred.username = self.cfgUsername.get() 
    320                         cred.data = self.cfgPassword.get() 
    321                         self.cfg.sipConfig.authCreds.append(cred) 
    322                 while len(self.cfg.sipConfig.proxies): 
    323                         self.cfg.sipConfig.proxies.pop() 
    324                 if self.cfgProxy.get(): 
    325                         self.cfg.sipConfig.proxies.append(self.cfgProxy.get()) 
    326  
    327                 # SIP features 
    328                 self.cfg.callConfig.prackUse            = self.cfgPrackUse.get()  
    329                 self.cfg.callConfig.timerUse            = self.cfgTimerUse.get() 
    330                 self.cfg.callConfig.timerSessExpiresSec = self.cfgTimerExpires.get()  
    331                 self.cfg.presConfig.publishEnabled      = self.cfgPublish.get()  
    332                 self.cfg.mwiConfig.enabled              = self.cfgMwiEnabled.get()  
    333                 self.cfg.natConfig.contactRewriteUse    = 1 if self.cfgEnableContactRewrite.get() else 0 
    334                 self.cfg.natConfig.viaRewriteUse        = 1 if self.cfgEnableViaRewrite.get() else 0 
    335                 self.cfg.natConfig.sdpNatRewriteUse     = 1 if self.cfgEnableSdpRewrite.get() else 0 
    336                 self.cfg.natConfig.sipOutboundUse       = 1 if self.cfgEnableSipOutbound.get() else 0 
    337                 self.cfg.natConfig.udpKaIntervalSec     = self.cfgKaInterval.get() 
    338  
    339                 # Media 
    340                 self.cfg.mediaConfig.transportConfig.port       = self.cfgMedPort.get() 
    341                 self.cfg.mediaConfig.transportConfig.portRange  = self.cfgMedPortRange.get() 
    342                 self.cfg.mediaConfig.lockCodecEnabled           = self.cfgMedLockCodec.get() 
    343                 self.cfg.mediaConfig.srtpUse                    = self.cfgMedSrtp.get() 
    344                 self.cfg.mediaConfig.srtpSecureSignaling        = self.cfgMedSrtpSecure.get() 
    345                 self.cfg.mediaConfig.ipv6Use                    = pj.PJSUA_IPV6_ENABLED if self.cfgMedIpv6.get() else pj.PJSUA_IPV6_DISABLED 
    346                  
    347                 # NAT 
    348                 self.cfg.natConfig.sipStunUse           = self.cfgSipUseStun.get() 
    349                 self.cfg.natConfig.mediaStunUse         = self.cfgMediaUseStun.get() 
    350                 self.cfg.natConfig.iceEnabled           = self.cfgIceEnabled.get() 
    351                 self.cfg.natConfig.iceAggressiveNomination = self.cfgIceAggressive .get() 
    352                 self.cfg.natConfig.iceAlwaysUpdate      = self.cfgAlwaysUpdate.get() 
    353                 self.cfg.natConfig.iceMaxHostCands      = 0 if self.cfgIceNoHostCands.get() else -1  
    354                 self.cfg.natConfig.turnEnabled          = self.cfgTurnEnabled.get() 
    355                 self.cfg.natConfig.turnServer           = self.cfgTurnServer.get() 
    356                 self.cfg.natConfig.turnConnType         = self.cfgTurnConnType.get() 
    357                 self.cfg.natConfig.turnUserName         = self.cfgTurnUser.get() 
    358                 self.cfg.natConfig.turnPasswordType     = 0 
    359                 self.cfg.natConfig.turnPassword         = self.cfgTurnPasswd.get() 
    360                  
    361                 self.isOk = True 
    362                 self.destroy() 
    363                  
    364         def onCancel(self): 
    365                 self.destroy() 
     36    """ 
     37    This implements account settings dialog to manipulate account settings. 
     38    """ 
     39    def __init__(self, parent, cfg): 
     40        tk.Toplevel.__init__(self, parent) 
     41        self.transient(parent) 
     42        self.parent = parent 
     43        self.geometry("+100+100") 
     44        self.title('Account settings') 
     45 
     46        self.frm = ttk.Frame(self) 
     47        self.frm.pack(expand='yes', fill='both') 
     48 
     49        self.isOk = False 
     50        self.cfg = cfg 
     51 
     52        self.createWidgets() 
     53 
     54    def doModal(self): 
     55        if self.parent: 
     56            self.parent.wait_window(self) 
     57        else: 
     58            self.wait_window(self) 
     59        return self.isOk 
     60 
     61    def createWidgets(self): 
     62        # The notebook 
     63        self.frm.rowconfigure(0, weight=1) 
     64        self.frm.rowconfigure(1, weight=0) 
     65        self.frm.columnconfigure(0, weight=1) 
     66        self.frm.columnconfigure(1, weight=1) 
     67        self.wTab = ttk.Notebook(self.frm) 
     68        self.wTab.grid(column=0, row=0, columnspan=2, padx=10, pady=10, ipadx=20, ipady=20, sticky=tk.N+tk.S+tk.W+tk.E) 
     69 
     70        # Main buttons 
     71        btnOk = ttk.Button(self.frm, text='Ok', command=self.onOk) 
     72        btnOk.grid(column=0, row=1, sticky=tk.E, padx=20, pady=10) 
     73        btnCancel = ttk.Button(self.frm, text='Cancel', command=self.onCancel) 
     74        btnCancel.grid(column=1, row=1, sticky=tk.W, padx=20, pady=10) 
     75 
     76        # Tabs 
     77        self.createBasicTab() 
     78        self.createSipTab() 
     79        self.createMediaTab() 
     80        self.createMediaNatTab() 
     81 
     82    def createBasicTab(self): 
     83        # Prepare the variables to set/receive values from GUI 
     84        self.cfgPriority = tk.IntVar(value=self.cfg.priority) 
     85        self.cfgAccId = tk.StringVar(value=self.cfg.idUri) 
     86        self.cfgRegistrar = tk.StringVar(value=self.cfg.regConfig.registrarUri) 
     87        self.cfgRegisterOnAdd = tk.BooleanVar(value=self.cfg.regConfig.registerOnAdd) 
     88        self.cfgUsername = tk.StringVar() 
     89        self.cfgPassword = tk.StringVar() 
     90        if len(self.cfg.sipConfig.authCreds): 
     91            self.cfgUsername.set( self.cfg.sipConfig.authCreds[0].username ) 
     92            self.cfgPassword.set( self.cfg.sipConfig.authCreds[0].data ) 
     93        self.cfgProxy = tk.StringVar() 
     94        if len(self.cfg.sipConfig.proxies): 
     95            self.cfgProxy.set( self.cfg.sipConfig.proxies[0] ) 
     96 
     97        # Build the tab page 
     98        frm = ttk.Frame(self.frm) 
     99        frm.columnconfigure(0, weight=1) 
     100        frm.columnconfigure(1, weight=2) 
     101        row = 0 
     102        ttk.Label(frm, text='Priority:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     103        tk.Spinbox(frm, from_=0, to=9, textvariable=self.cfgPriority, width=2).grid(row=row, column=1, sticky=tk.W, padx=6) 
     104        row += 1 
     105        ttk.Label(frm, text='ID (URI):').grid(row=row, column=0, sticky=tk.E, pady=2) 
     106        ttk.Entry(frm, textvariable=self.cfgAccId, width=32).grid(row=row, column=1, sticky=tk.W, padx=6) 
     107        row += 1 
     108        ttk.Label(frm, text='Registrar URI:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     109        ttk.Entry(frm, textvariable=self.cfgRegistrar, width=32).grid(row=row, column=1, sticky=tk.W, padx=6) 
     110        row += 1 
     111        ttk.Checkbutton(frm, text='Register on add', variable=self.cfgRegisterOnAdd).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
     112        row += 1 
     113        ttk.Label(frm, text='Optional proxy URI:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     114        ttk.Entry(frm, textvariable=self.cfgProxy, width=32).grid(row=row, column=1, sticky=tk.W, padx=6) 
     115        row += 1 
     116        ttk.Label(frm, text='Auth username:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     117        ttk.Entry(frm, textvariable=self.cfgUsername, width=16).grid(row=row, column=1, sticky=tk.W, padx=6) 
     118        row += 1 
     119        ttk.Label(frm, text='Password:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     120        ttk.Entry(frm, textvariable=self.cfgPassword, show='*', width=16).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
     121 
     122        self.wTab.add(frm, text='Basic Settings') 
     123 
     124 
     125    def createSipTab(self): 
     126        # Prepare the variables to set/receive values from GUI 
     127        self.cfgPrackUse        = tk.IntVar(value=self.cfg.callConfig.prackUse) 
     128        self.cfgTimerUse        = tk.IntVar(value=self.cfg.callConfig.timerUse) 
     129        self.cfgTimerExpires    = tk.IntVar(value=self.cfg.callConfig.timerSessExpiresSec) 
     130        self.cfgPublish         = tk.BooleanVar(value=self.cfg.presConfig.publishEnabled) 
     131        self.cfgMwiEnabled      = tk.BooleanVar(value=self.cfg.mwiConfig.enabled) 
     132        self.cfgEnableContactRewrite = tk.BooleanVar(value=self.cfg.natConfig.contactRewriteUse != 0) 
     133        self.cfgEnableViaRewrite = tk.BooleanVar(value=self.cfg.natConfig.viaRewriteUse != 0) 
     134        self.cfgEnableSdpRewrite = tk.BooleanVar(value=self.cfg.natConfig.sdpNatRewriteUse != 0) 
     135        self.cfgEnableSipOutbound = tk.BooleanVar(value=self.cfg.natConfig.sipOutboundUse != 0) 
     136        self.cfgKaInterval      = tk.IntVar(value=self.cfg.natConfig.udpKaIntervalSec) 
     137 
     138        # Build the tab page 
     139        frm = ttk.Frame(self.frm) 
     140        frm.columnconfigure(0, weight=1) 
     141        frm.columnconfigure(1, weight=2) 
     142        row = 0 
     143        ttk.Label(frm, text='100rel/PRACK:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     144        ttk.Radiobutton(frm, text='Only offer PRACK', value=pj.PJSUA_100REL_NOT_USED, variable=self.cfgPrackUse).grid(row=row, column=1, sticky=tk.W, padx=6) 
     145        row += 1 
     146        ttk.Radiobutton(frm, text='Offer and use if remote supports', value=pj.PJSUA_100REL_OPTIONAL, variable=self.cfgPrackUse).grid(row=row, column=1, sticky=tk.W, padx=6) 
     147        row += 1 
     148        ttk.Radiobutton(frm, text='Required', value=pj.PJSUA_100REL_MANDATORY, variable=self.cfgPrackUse).grid(row=row, column=1, sticky=tk.W, padx=6) 
     149        row += 1 
     150        ttk.Label(frm, text='Session Timer:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     151        ttk.Radiobutton(frm, text='Not offered', value=pj.PJSUA_SIP_TIMER_INACTIVE, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6) 
     152        row += 1 
     153        ttk.Radiobutton(frm, text='Optional', value=pj.PJSUA_SIP_TIMER_OPTIONAL, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6) 
     154        row += 1 
     155        ttk.Radiobutton(frm, text='Required', value=pj.PJSUA_SIP_TIMER_REQUIRED, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6) 
     156        row += 1 
     157        ttk.Radiobutton(frm, text="Always use", value=pj.PJSUA_SIP_TIMER_ALWAYS, variable=self.cfgTimerUse).grid(row=row, column=1, sticky=tk.W, padx=6) 
     158        row += 1 
     159        ttk.Label(frm, text='Session Timer Expiration:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     160        tk.Spinbox(frm, from_=90, to=7200, textvariable=self.cfgTimerExpires, width=5).grid(row=row, column=1, sticky=tk.W, padx=6) 
     161        ttk.Label(frm, text='(seconds)').grid(row=row, column=1, sticky=tk.E) 
     162        row += 1 
     163        ttk.Label(frm, text='Presence:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     164        ttk.Checkbutton(frm, text='Enable PUBLISH', variable=self.cfgPublish).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
     165        row += 1 
     166        ttk.Label(frm, text='Message Waiting Indication:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     167        ttk.Checkbutton(frm, text='Enable MWI', variable=self.cfgMwiEnabled).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
     168        row += 1 
     169        ttk.Label(frm, text='NAT Traversal:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     170        ttk.Checkbutton(frm, text='Enable Contact Rewrite', variable=self.cfgEnableContactRewrite).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
     171        row += 1 
     172        ttk.Checkbutton(frm, text='Enable Via Rewrite', variable=self.cfgEnableViaRewrite).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
     173        row += 1 
     174        ttk.Checkbutton(frm, text='Enable SDP IP Address Rewrite', variable=self.cfgEnableSdpRewrite).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
     175        row += 1 
     176        ttk.Checkbutton(frm, text='Enable SIP Outbound Extension', variable=self.cfgEnableSipOutbound).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
     177        row += 1 
     178        ttk.Label(frm, text='UDP Keep-Alive Interval:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     179        tk.Spinbox(frm, from_=0, to=3600, textvariable=self.cfgKaInterval, width=5).grid(row=row, column=1, sticky=tk.W, padx=6) 
     180        ttk.Label(frm, text='(seconds) Zero to disable.').grid(row=row, column=1, sticky=tk.E) 
     181 
     182 
     183        self.wTab.add(frm, text='SIP Features') 
     184 
     185    def createMediaTab(self): 
     186        # Prepare the variables to set/receive values from GUI 
     187        self.cfgMedPort = tk.IntVar(value=self.cfg.mediaConfig.transportConfig.port) 
     188        self.cfgMedPortRange = tk.IntVar(value=self.cfg.mediaConfig.transportConfig.portRange) 
     189        self.cfgMedLockCodec = tk.BooleanVar(value=self.cfg.mediaConfig.lockCodecEnabled) 
     190        self.cfgMedSrtp = tk.IntVar(value=self.cfg.mediaConfig.srtpUse) 
     191        self.cfgMedSrtpSecure = tk.IntVar(value=self.cfg.mediaConfig.srtpSecureSignaling) 
     192        self.cfgMedIpv6 = tk.BooleanVar(value=self.cfg.mediaConfig.ipv6Use==pj.PJSUA_IPV6_ENABLED) 
     193 
     194        # Build the tab page 
     195        frm = ttk.Frame(self.frm) 
     196        frm.columnconfigure(0, weight=1) 
     197        frm.columnconfigure(1, weight=21) 
     198        row = 0 
     199        ttk.Label(frm, text='Secure RTP (SRTP):').grid(row=row, column=0, sticky=tk.E, pady=2) 
     200        ttk.Radiobutton(frm, text='Disable', value=pj.PJMEDIA_SRTP_DISABLED, variable=self.cfgMedSrtp).grid(row=row, column=1, sticky=tk.W, padx=6) 
     201        row += 1 
     202        ttk.Radiobutton(frm, text='Mandatory', value=pj.PJMEDIA_SRTP_MANDATORY, variable=self.cfgMedSrtp).grid(row=row, column=1, sticky=tk.W, padx=6) 
     203        row += 1 
     204        ttk.Radiobutton(frm, text='Optional (non-standard)', value=pj.PJMEDIA_SRTP_OPTIONAL, variable=self.cfgMedSrtp).grid(row=row, column=1, sticky=tk.W, padx=6) 
     205        row += 1 
     206        ttk.Label(frm, text='SRTP signaling:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     207        ttk.Radiobutton(frm, text='Does not require secure signaling', value=0, variable=self.cfgMedSrtpSecure).grid(row=row, column=1, sticky=tk.W, padx=6) 
     208        row += 1 
     209        ttk.Radiobutton(frm, text='Require secure next hop (TLS)', value=1, variable=self.cfgMedSrtpSecure).grid(row=row, column=1, sticky=tk.W, padx=6) 
     210        row += 1 
     211        ttk.Radiobutton(frm, text='Require secure end-to-end (SIPS)', value=2, variable=self.cfgMedSrtpSecure).grid(row=row, column=1, sticky=tk.W, padx=6) 
     212        row += 1 
     213        ttk.Label(frm, text='RTP transport start port:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     214        tk.Spinbox(frm, from_=0, to=65535, textvariable=self.cfgMedPort, width=5).grid(row=row, column=1, sticky=tk.W, padx=6) 
     215        ttk.Label(frm, text='(0: any)').grid(row=row, column=1, sticky=tk.E, pady=2) 
     216        row += 1 
     217        ttk.Label(frm, text='Port range:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     218        tk.Spinbox(frm, from_=0, to=65535, textvariable=self.cfgMedPortRange, width=5).grid(row=row, column=1, sticky=tk.W, padx=6) 
     219        ttk.Label(frm, text='(0: not limited)').grid(row=row, column=1, sticky=tk.E, pady=2) 
     220        row += 1 
     221        ttk.Label(frm, text='Lock codec:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     222        ttk.Checkbutton(frm, text='Enable', variable=self.cfgMedLockCodec).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
     223        row += 1 
     224        ttk.Label(frm, text='Use IPv6:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     225        ttk.Checkbutton(frm, text='Yes', variable=self.cfgMedIpv6).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
     226 
     227        self.wTab.add(frm, text='Media settings') 
     228 
     229    def createMediaNatTab(self): 
     230        # Prepare the variables to set/receive values from GUI 
     231        self.cfgSipUseStun = tk.IntVar(value = self.cfg.natConfig.sipStunUse) 
     232        self.cfgMediaUseStun = tk.IntVar(value = self.cfg.natConfig.mediaStunUse) 
     233        self.cfgIceEnabled = tk.BooleanVar(value = self.cfg.natConfig.iceEnabled) 
     234        self.cfgIceAggressive = tk.BooleanVar(value = self.cfg.natConfig.iceAggressiveNomination) 
     235        self.cfgAlwaysUpdate = tk.BooleanVar(value = True if self.cfg.natConfig.iceAlwaysUpdate else False) 
     236        self.cfgIceNoHostCands = tk.BooleanVar(value = True if self.cfg.natConfig.iceMaxHostCands == 0 else False) 
     237        self.cfgTurnEnabled = tk.BooleanVar(value = self.cfg.natConfig.turnEnabled) 
     238        self.cfgTurnServer = tk.StringVar(value = self.cfg.natConfig.turnServer) 
     239        self.cfgTurnConnType = tk.IntVar(value = self.cfg.natConfig.turnConnType) 
     240        self.cfgTurnUser = tk.StringVar(value = self.cfg.natConfig.turnUserName) 
     241        self.cfgTurnPasswd = tk.StringVar(value = self.cfg.natConfig.turnPassword) 
     242 
     243        # Build the tab page 
     244        frm = ttk.Frame(self.frm) 
     245        frm.columnconfigure(0, weight=1) 
     246        frm.columnconfigure(1, weight=2) 
     247        row = 0 
     248        ttk.Label(frm, text='SIP STUN Usage:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     249        ttk.Radiobutton(frm, text='Default', value=pj.PJSUA_STUN_USE_DEFAULT, variable=self.cfgSipUseStun).grid(row=row, column=1, sticky=tk.W, padx=6) 
     250        row += 1 
     251        ttk.Radiobutton(frm, text='Disable', value=pj.PJSUA_STUN_USE_DISABLED, variable=self.cfgSipUseStun).grid(row=row, column=1, sticky=tk.W, padx=6) 
     252        row += 1 
     253        ttk.Label(frm, text='Media STUN Usage:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     254        ttk.Radiobutton(frm, text='Default', value=pj.PJSUA_STUN_USE_DEFAULT, variable=self.cfgMediaUseStun).grid(row=row, column=1, sticky=tk.W, padx=6) 
     255        row += 1 
     256        ttk.Radiobutton(frm, text='Disable', value=pj.PJSUA_STUN_USE_DISABLED, variable=self.cfgMediaUseStun).grid(row=row, column=1, sticky=tk.W, padx=6) 
     257        row += 1 
     258        ttk.Label(frm, text='ICE:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     259        ttk.Checkbutton(frm, text='Enable', variable=self.cfgIceEnabled).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
     260        row += 1 
     261        ttk.Checkbutton(frm, text='Use aggresive nomination', variable=self.cfgIceAggressive).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
     262        row += 1 
     263        ttk.Checkbutton(frm, text='Always re-INVITE after negotiation', variable=self.cfgAlwaysUpdate).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
     264        row += 1 
     265        ttk.Checkbutton(frm, text='Disable host candidates', variable=self.cfgIceNoHostCands).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
     266        row += 1 
     267        ttk.Label(frm, text='TURN:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     268        ttk.Checkbutton(frm, text='Enable', variable=self.cfgTurnEnabled).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
     269        row += 1 
     270        ttk.Label(frm, text='TURN server:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     271        ttk.Entry(frm, textvariable=self.cfgTurnServer, width=20).grid(row=row, column=1, sticky=tk.W, padx=6) 
     272        ttk.Label(frm, text='host[:port]').grid(row=row, column=1, sticky=tk.E, pady=6) 
     273        row += 1 
     274        ttk.Label(frm, text='TURN connection:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     275        ttk.Radiobutton(frm, text='UDP', value=pj.PJ_TURN_TP_UDP, variable=self.cfgTurnConnType).grid(row=row, column=1, sticky=tk.W, padx=6) 
     276        row += 1 
     277        ttk.Radiobutton(frm, text='TCP', value=pj.PJ_TURN_TP_TCP, variable=self.cfgTurnConnType).grid(row=row, column=1, sticky=tk.W, padx=6) 
     278        row += 1 
     279        ttk.Label(frm, text='TURN username:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     280        ttk.Entry(frm, textvariable=self.cfgTurnUser, width=16).grid(row=row, column=1, sticky=tk.W, padx=6) 
     281        row += 1 
     282        ttk.Label(frm, text='TURN password:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     283        ttk.Entry(frm, textvariable=self.cfgTurnPasswd, show='*', width=16).grid(row=row, column=1, sticky=tk.W, padx=6) 
     284 
     285        self.wTab.add(frm, text='NAT settings') 
     286 
     287    def onOk(self): 
     288        # Check basic settings 
     289        errors = ""; 
     290        if not self.cfgAccId.get(): 
     291            errors += "Account ID is required\n" 
     292        if self.cfgAccId.get(): 
     293            if not endpoint.validateSipUri(self.cfgAccId.get()): 
     294                errors += "Invalid SIP ID URI: '%s'\n" % (self.cfgAccId.get()) 
     295        if self.cfgRegistrar.get(): 
     296            if not endpoint.validateSipUri(self.cfgRegistrar.get()): 
     297                errors += "Invalid SIP registrar URI: '%s'\n" % (self.cfgRegistrar.get()) 
     298        if self.cfgProxy.get(): 
     299            if not endpoint.validateSipUri(self.cfgProxy.get()): 
     300                errors += "Invalid SIP proxy URI: '%s'\n" % (self.cfgProxy.get()) 
     301        if self.cfgTurnEnabled.get(): 
     302            if not self.cfgTurnServer.get(): 
     303                errors += "TURN server is required\n" 
     304        if errors: 
     305            msgbox.showerror("Error detected:", errors) 
     306            return 
     307 
     308        # Basic settings 
     309        self.cfg.priority = self.cfgPriority.get() 
     310        self.cfg.idUri = self.cfgAccId.get() 
     311        self.cfg.regConfig.registrarUri = self.cfgRegistrar.get() 
     312        self.cfg.regConfig.registerOnAdd = self.cfgRegisterOnAdd.get() 
     313        while len(self.cfg.sipConfig.authCreds): 
     314            self.cfg.sipConfig.authCreds.pop() 
     315        if self.cfgUsername.get(): 
     316            cred = pj.AuthCredInfo() 
     317            cred.scheme = "digest" 
     318            cred.realm = "*" 
     319            cred.username = self.cfgUsername.get() 
     320            cred.data = self.cfgPassword.get() 
     321            self.cfg.sipConfig.authCreds.append(cred) 
     322        while len(self.cfg.sipConfig.proxies): 
     323            self.cfg.sipConfig.proxies.pop() 
     324        if self.cfgProxy.get(): 
     325            self.cfg.sipConfig.proxies.append(self.cfgProxy.get()) 
     326 
     327        # SIP features 
     328        self.cfg.callConfig.prackUse            = self.cfgPrackUse.get() 
     329        self.cfg.callConfig.timerUse            = self.cfgTimerUse.get() 
     330        self.cfg.callConfig.timerSessExpiresSec = self.cfgTimerExpires.get() 
     331        self.cfg.presConfig.publishEnabled      = self.cfgPublish.get() 
     332        self.cfg.mwiConfig.enabled              = self.cfgMwiEnabled.get() 
     333        self.cfg.natConfig.contactRewriteUse    = 1 if self.cfgEnableContactRewrite.get() else 0 
     334        self.cfg.natConfig.viaRewriteUse        = 1 if self.cfgEnableViaRewrite.get() else 0 
     335        self.cfg.natConfig.sdpNatRewriteUse     = 1 if self.cfgEnableSdpRewrite.get() else 0 
     336        self.cfg.natConfig.sipOutboundUse       = 1 if self.cfgEnableSipOutbound.get() else 0 
     337        self.cfg.natConfig.udpKaIntervalSec     = self.cfgKaInterval.get() 
     338 
     339        # Media 
     340        self.cfg.mediaConfig.transportConfig.port       = self.cfgMedPort.get() 
     341        self.cfg.mediaConfig.transportConfig.portRange  = self.cfgMedPortRange.get() 
     342        self.cfg.mediaConfig.lockCodecEnabled           = self.cfgMedLockCodec.get() 
     343        self.cfg.mediaConfig.srtpUse                    = self.cfgMedSrtp.get() 
     344        self.cfg.mediaConfig.srtpSecureSignaling        = self.cfgMedSrtpSecure.get() 
     345        self.cfg.mediaConfig.ipv6Use                    = pj.PJSUA_IPV6_ENABLED if self.cfgMedIpv6.get() else pj.PJSUA_IPV6_DISABLED 
     346 
     347        # NAT 
     348        self.cfg.natConfig.sipStunUse           = self.cfgSipUseStun.get() 
     349        self.cfg.natConfig.mediaStunUse         = self.cfgMediaUseStun.get() 
     350        self.cfg.natConfig.iceEnabled           = self.cfgIceEnabled.get() 
     351        self.cfg.natConfig.iceAggressiveNomination = self.cfgIceAggressive .get() 
     352        self.cfg.natConfig.iceAlwaysUpdate      = self.cfgAlwaysUpdate.get() 
     353        self.cfg.natConfig.iceMaxHostCands      = 0 if self.cfgIceNoHostCands.get() else -1 
     354        self.cfg.natConfig.turnEnabled          = self.cfgTurnEnabled.get() 
     355        self.cfg.natConfig.turnServer           = self.cfgTurnServer.get() 
     356        self.cfg.natConfig.turnConnType         = self.cfgTurnConnType.get() 
     357        self.cfg.natConfig.turnUserName         = self.cfgTurnUser.get() 
     358        self.cfg.natConfig.turnPasswordType     = 0 
     359        self.cfg.natConfig.turnPassword         = self.cfgTurnPasswd.get() 
     360 
     361        self.isOk = True 
     362        self.destroy() 
     363 
     364    def onCancel(self): 
     365        self.destroy() 
    366366 
    367367 
    368368if __name__ == '__main__': 
    369         application.main() 
     369    application.main() 
  • pjproject/trunk/pjsip-apps/src/pygui/application.py

    r4798 r5638  
    2121import sys 
    2222if sys.version_info[0] >= 3: # Python 3 
    23         import tkinter as tk 
    24         from tkinter import ttk 
    25         from tkinter import messagebox as msgbox 
     23    import tkinter as tk 
     24    from tkinter import ttk 
     25    from tkinter import messagebox as msgbox 
    2626else: 
    27         import Tkinter as tk 
    28         import tkMessageBox as msgbox 
    29         import ttk 
     27    import Tkinter as tk 
     28    import tkMessageBox as msgbox 
     29    import ttk 
    3030 
    3131import pjsua2 as pj 
     
    4545# http://lists.pjsip.org/pipermail/pjsip_lists.pjsip.org/2014-March/017223.html 
    4646USE_THREADS = False 
    47          
     47 
     48write=sys.stdout.write 
     49 
    4850class Application(ttk.Frame): 
    49         """ 
    50         The Application main frame. 
    51         """ 
    52         def __init__(self): 
    53                 global USE_THREADS 
    54                 ttk.Frame.__init__(self, name='application', width=300, height=500) 
    55                 self.pack(expand='yes', fill='both') 
    56                 self.master.title('pjsua2 Demo') 
    57                 self.master.geometry('500x500+100+100') 
    58                  
    59                 # Logger 
    60                 self.logger = log.Logger() 
    61                  
    62                 # Accounts 
    63                 self.accList = [] 
    64                  
    65                 # GUI variables 
    66                 self.showLogWindow = tk.IntVar(value=0) 
    67                 self.quitting = False  
    68                  
    69                 # Construct GUI 
    70                 self._createWidgets() 
    71                  
    72                 # Log window 
    73                 self.logWindow = log.LogWindow(self) 
    74                 self._onMenuShowHideLogWindow() 
    75                  
    76                 # Instantiate endpoint 
    77                 self.ep = endpoint.Endpoint() 
    78                 self.ep.libCreate() 
    79                  
    80                 # Default config 
    81                 self.appConfig = settings.AppConfig() 
    82                 if USE_THREADS: 
    83                         self.appConfig.epConfig.uaConfig.threadCnt = 1 
    84                         self.appConfig.epConfig.uaConfig.mainThreadOnly = False 
    85                 else: 
    86                         self.appConfig.epConfig.uaConfig.threadCnt = 0 
    87                         self.appConfig.epConfig.uaConfig.mainThreadOnly = True 
    88                 self.appConfig.epConfig.logConfig.writer = self.logger 
    89                 self.appConfig.epConfig.logConfig.filename = "pygui.log" 
    90                 self.appConfig.epConfig.logConfig.fileFlags = pj.PJ_O_APPEND 
    91                 self.appConfig.epConfig.logConfig.level = 5 
    92                 self.appConfig.epConfig.logConfig.consoleLevel = 5 
    93                  
    94         def saveConfig(self, filename='pygui.js'): 
    95                 # Save disabled accounts since they are not listed in self.accList 
    96                 disabled_accs = [ac for ac in self.appConfig.accounts if not ac.enabled] 
    97                 self.appConfig.accounts = [] 
    98                  
    99                 # Get account configs from active accounts 
    100                 for acc in self.accList: 
    101                         acfg = settings.AccConfig() 
    102                         acfg.enabled = True 
    103                         acfg.config = acc.cfg 
    104                         for bud in acc.buddyList: 
    105                                 acfg.buddyConfigs.append(bud.cfg) 
    106                         self.appConfig.accounts.append(acfg) 
    107                  
    108                 # Put back disabled accounts 
    109                 self.appConfig.accounts.extend(disabled_accs) 
    110                 # Save 
    111                 self.appConfig.saveFile(filename) 
    112          
    113         def start(self, cfg_file='pygui.js'): 
    114                 global USE_THREADS 
    115                 # Load config 
    116                 if cfg_file and os.path.exists(cfg_file): 
    117                         self.appConfig.loadFile(cfg_file) 
    118  
    119                 if USE_THREADS: 
    120                         self.appConfig.epConfig.uaConfig.threadCnt = 1 
    121                         self.appConfig.epConfig.uaConfig.mainThreadOnly = False 
    122                 else: 
    123                         self.appConfig.epConfig.uaConfig.threadCnt = 0 
    124                         self.appConfig.epConfig.uaConfig.mainThreadOnly = True 
    125                 self.appConfig.epConfig.uaConfig.threadCnt = 0 
    126                 self.appConfig.epConfig.uaConfig.mainThreadOnly = True 
    127                 self.appConfig.epConfig.logConfig.writer = self.logger 
    128                 self.appConfig.epConfig.logConfig.level = 5 
    129                 self.appConfig.epConfig.logConfig.consoleLevel = 5 
    130                                  
    131                 # Initialize library 
    132                 self.appConfig.epConfig.uaConfig.userAgent = "pygui-" + self.ep.libVersion().full; 
    133                 self.ep.libInit(self.appConfig.epConfig) 
    134                 self.master.title('pjsua2 Demo version ' + self.ep.libVersion().full) 
    135                  
    136                 # Create transports 
    137                 if self.appConfig.udp.enabled: 
    138                         self.ep.transportCreate(self.appConfig.udp.type, self.appConfig.udp.config) 
    139                 if self.appConfig.tcp.enabled: 
    140                         self.ep.transportCreate(self.appConfig.tcp.type, self.appConfig.tcp.config) 
    141                 if self.appConfig.tls.enabled: 
    142                         self.ep.transportCreate(self.appConfig.tls.type, self.appConfig.tls.config) 
    143                          
    144                 # Add accounts 
    145                 for cfg in self.appConfig.accounts: 
    146                         if cfg.enabled: 
    147                                 self._createAcc(cfg.config) 
    148                                 acc = self.accList[-1] 
    149                                 for buddy_cfg in cfg.buddyConfigs: 
    150                                         self._createBuddy(acc, buddy_cfg) 
    151                                  
    152                 # Start library 
    153                 self.ep.libStart() 
    154                  
    155                 # Start polling 
    156                 if not USE_THREADS: 
    157                         self._onTimer() 
    158  
    159         def updateAccount(self, acc): 
    160                 if acc.deleting: 
    161                         return  # ignore 
    162                 iid = str(acc.randId) 
    163                 text = acc.cfg.idUri 
    164                 status = acc.statusText() 
    165                  
    166                 values = (status,) 
    167                 if self.tv.exists(iid): 
    168                         self.tv.item(iid, text=text, values=values) 
    169                 else: 
    170                         self.tv.insert('', 'end',  iid, open=True, text=text, values=values) 
    171                  
    172         def updateBuddy(self, bud): 
    173                 iid = 'buddy' + str(bud.randId) 
    174                 text = bud.cfg.uri 
    175                 status = bud.statusText() 
    176                  
    177                 values = (status,) 
    178                 if self.tv.exists(iid): 
    179                         self.tv.item(iid, text=text, values=values) 
    180                 else: 
    181                         self.tv.insert(str(bud.account.randId), 'end',  iid, open=True, text=text, values=values) 
    182                  
    183         def _createAcc(self, acc_cfg): 
    184                 acc = account.Account(self) 
    185                 acc.cfg = acc_cfg 
    186                 self.accList.append(acc) 
    187                 self.updateAccount(acc) 
    188                 acc.create(acc.cfg) 
    189                 acc.cfgChanged = False 
    190                 self.updateAccount(acc) 
    191                                  
    192         def _createBuddy(self, acc, buddy_cfg): 
    193                 bud = buddy.Buddy(self) 
    194                 bud.cfg = buddy_cfg 
    195                 bud.account = acc 
    196                 bud.create(acc, bud.cfg) 
    197                 self.updateBuddy(bud) 
    198                 acc.buddyList.append(bud) 
    199  
    200         def _createWidgets(self): 
    201                 self._createAppMenu() 
    202                  
    203                 # Main pane, a Treeview 
    204                 self.tv = ttk.Treeview(self, columns=('Status'), show='tree') 
    205                 self.tv.pack(side='top', fill='both', expand='yes', padx=5, pady=5) 
    206  
    207                 self._createContextMenu() 
    208                  
    209                 # Handle close event 
    210                 self.master.protocol("WM_DELETE_WINDOW", self._onClose) 
    211          
    212         def _createAppMenu(self): 
    213                 # Main menu bar 
    214                 top = self.winfo_toplevel() 
    215                 self.menubar = tk.Menu() 
    216                 top.configure(menu=self.menubar) 
    217                  
    218                 # File menu 
    219                 file_menu = tk.Menu(self.menubar, tearoff=False) 
    220                 self.menubar.add_cascade(label="File", menu=file_menu) 
    221                 file_menu.add_command(label="Add account..", command=self._onMenuAddAccount) 
    222                 file_menu.add_checkbutton(label="Show/hide log window", command=self._onMenuShowHideLogWindow, variable=self.showLogWindow) 
    223                 file_menu.add_separator() 
    224                 file_menu.add_command(label="Settings...", command=self._onMenuSettings) 
    225                 file_menu.add_command(label="Save Settings", command=self._onMenuSaveSettings) 
    226                 file_menu.add_separator() 
    227                 file_menu.add_command(label="Quit", command=self._onMenuQuit) 
    228  
    229                 # Window menu 
    230                 self.window_menu = tk.Menu(self.menubar, tearoff=False) 
    231                 self.menubar.add_cascade(label="Window", menu=self.window_menu) 
    232                  
    233                 # Help menu 
    234                 help_menu = tk.Menu(self.menubar, tearoff=False) 
    235                 self.menubar.add_cascade(label="Help", menu=help_menu) 
    236                 help_menu.add_command(label="About", underline=2, command=self._onMenuAbout) 
    237          
    238         def _showChatWindow(self, chat_inst): 
    239                 chat_inst.showWindow() 
    240                  
    241         def updateWindowMenu(self): 
    242                 # Chat windows 
    243                 self.window_menu.delete(0, tk.END) 
    244                 for acc in self.accList: 
    245                         for c in acc.chatList: 
    246                                 cmd = lambda arg=c: self._showChatWindow(arg) 
    247                                 self.window_menu.add_command(label=c.title, command=cmd) 
    248                  
    249         def _createContextMenu(self): 
    250                 top = self.winfo_toplevel() 
    251  
    252                 # Create Account context menu 
    253                 self.accMenu = tk.Menu(top, tearoff=False) 
    254                 # Labels, must match with _onAccContextMenu() 
    255                 labels = ['Unregister', 'Reregister', 'Add buddy...', '-', 
    256                           'Online', 'Invisible', 'Away', 'Busy', '-', 
    257                           'Settings...', '-', 
    258                           'Delete...'] 
    259                 for label in labels: 
    260                         if label=='-': 
    261                                 self.accMenu.add_separator() 
    262                         else: 
    263                                 cmd = lambda arg=label: self._onAccContextMenu(arg) 
    264                                 self.accMenu.add_command(label=label, command=cmd) 
    265                  
    266                 # Create Buddy context menu 
    267                 # Labels, must match with _onBuddyContextMenu() 
    268                 self.buddyMenu = tk.Menu(top, tearoff=False) 
    269                 labels = ['Audio call', 'Send instant message', '-', 
    270                           'Subscribe', 'Unsubscribe', '-', 
    271                           'Settings...', '-', 
    272                           'Delete...'] 
    273                  
    274                 for label in labels: 
    275                         if label=='-': 
    276                                 self.buddyMenu.add_separator() 
    277                         else: 
    278                                 cmd = lambda arg=label: self._onBuddyContextMenu(arg) 
    279                                 self.buddyMenu.add_command(label=label, command=cmd) 
    280                  
    281                 if (top.tk.call('tk', 'windowingsystem')=='aqua'): 
    282                         self.tv.bind('<2>', self._onTvRightClick) 
    283                         self.tv.bind('<Control-1>', self._onTvRightClick) 
    284                 else: 
    285                         self.tv.bind('<3>', self._onTvRightClick) 
    286                 self.tv.bind('<Double-Button-1>', self._onTvDoubleClick) 
    287  
    288         def _getSelectedAccount(self): 
    289                 items = self.tv.selection() 
    290                 if not items: 
    291                         return None 
    292                 try: 
    293                         iid = int(items[0]) 
    294                 except: 
    295                         return None 
    296                 accs = [acc for acc in self.accList if acc.randId==iid] 
    297                 if not accs: 
    298                         return None 
    299                 return accs[0] 
    300          
    301         def _getSelectedBuddy(self): 
    302                 items = self.tv.selection() 
    303                 if not items: 
    304                         return None 
    305                 try: 
    306                         iid = int(items[0][5:]) 
    307                         iid_parent = int(self.tv.parent(items[0])) 
    308                 except: 
    309                         return None 
    310                          
    311                 accs = [acc for acc in self.accList if acc.randId==iid_parent] 
    312                 if not accs: 
    313                         return None 
    314                          
    315                 buds = [b for b in accs[0].buddyList if b.randId==iid] 
    316                 if not buds: 
    317                         return None 
    318                          
    319                 return buds[0] 
    320          
    321         def _onTvRightClick(self, event): 
    322                 iid = self.tv.identify_row(event.y) 
    323                 #iid = self.tv.identify('item', event.x, event.y) 
    324                 if iid: 
    325                         self.tv.selection_set( (iid,) ) 
    326                         acc = self._getSelectedAccount() 
    327                         if acc: 
    328                                 self.accMenu.post(event.x_root, event.y_root) 
    329                         else: 
    330                                 # A buddy is selected 
    331                                 self.buddyMenu.post(event.x_root, event.y_root) 
    332          
    333         def _onTvDoubleClick(self, event): 
    334                 iid = self.tv.identify_row(event.y) 
    335                 if iid: 
    336                         self.tv.selection_set( (iid,) ) 
    337                         acc = self._getSelectedAccount() 
    338                         if acc: 
    339                                 self.cfgChanged = False 
    340                                 dlg = accountsetting.Dialog(self.master, acc.cfg) 
    341                                 if dlg.doModal(): 
    342                                         self.updateAccount(acc) 
    343                                         acc.modify(acc.cfg) 
    344                         else: 
    345                                 bud = self._getSelectedBuddy() 
    346                                 acc = bud.account 
    347                                 chat = acc.findChat(bud.cfg.uri) 
    348                                 if not chat: 
    349                                         chat = acc.newChat(bud.cfg.uri) 
    350                                 chat.showWindow() 
    351          
    352         def _onAccContextMenu(self, label): 
    353                 acc = self._getSelectedAccount() 
    354                 if not acc: 
    355                         return 
    356                  
    357                 if label=='Unregister': 
    358                         acc.setRegistration(False) 
    359                 elif label=='Reregister': 
    360                         acc.setRegistration(True) 
    361                 elif label=='Online': 
    362                         ps = pj.PresenceStatus() 
    363                         ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE 
    364                         acc.setOnlineStatus(ps) 
    365                 elif label=='Invisible': 
    366                         ps = pj.PresenceStatus() 
    367                         ps.status = pj.PJSUA_BUDDY_STATUS_OFFLINE 
    368                         acc.setOnlineStatus(ps) 
    369                 elif label=='Away': 
    370                         ps = pj.PresenceStatus() 
    371                         ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE 
    372                         ps.activity = pj.PJRPID_ACTIVITY_AWAY 
    373                         ps.note = "Away" 
    374                         acc.setOnlineStatus(ps) 
    375                 elif label=='Busy': 
    376                         ps = pj.PresenceStatus() 
    377                         ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE 
    378                         ps.activity = pj.PJRPID_ACTIVITY_BUSY 
    379                         ps.note = "Busy" 
    380                         acc.setOnlineStatus(ps) 
    381                 elif label=='Settings...': 
    382                         self.cfgChanged = False 
    383                         dlg = accountsetting.Dialog(self.master, acc.cfg) 
    384                         if dlg.doModal(): 
    385                                 self.updateAccount(acc) 
    386                                 acc.modify(acc.cfg) 
    387                 elif label=='Delete...': 
    388                         msg = "Do you really want to delete account '%s'?" % acc.cfg.idUri 
    389                         if msgbox.askquestion('Delete account?', msg, default=msgbox.NO) != u'yes': 
    390                                 return 
    391                         iid = str(acc.randId) 
    392                         self.accList.remove(acc) 
    393                         acc.setRegistration(False) 
    394                         acc.deleting = True 
    395                         del acc 
    396                         self.tv.delete( (iid,) ) 
    397                 elif label=='Add buddy...': 
    398                         cfg = pj.BuddyConfig() 
    399                         dlg = buddy.SettingDialog(self.master, cfg) 
    400                         if dlg.doModal(): 
    401                                 self._createBuddy(acc, cfg) 
    402                 else: 
    403                         assert not ("Unknown menu " + label) 
    404          
    405         def _onBuddyContextMenu(self, label): 
    406                 bud = self._getSelectedBuddy() 
    407                 if not bud: 
    408                         return 
    409                 acc = bud.account 
    410                          
    411                 if label=='Audio call': 
    412                         chat = acc.findChat(bud.cfg.uri) 
    413                         if not chat: chat = acc.newChat(bud.cfg.uri) 
    414                         chat.showWindow() 
    415                         chat.startCall() 
    416                 elif label=='Send instant message': 
    417                         chat = acc.findChat(bud.cfg.uri) 
    418                         if not chat: chat = acc.newChat(bud.cfg.uri) 
    419                         chat.showWindow(True) 
    420                 elif label=='Subscribe': 
    421                         bud.subscribePresence(True) 
    422                 elif label=='Unsubscribe': 
    423                         bud.subscribePresence(False) 
    424                 elif label=='Settings...': 
    425                         subs = bud.cfg.subscribe 
    426                         uri  = bud.cfg.uri 
    427                         dlg = buddy.SettingDialog(self.master, bud.cfg) 
    428                         if dlg.doModal(): 
    429                                 self.updateBuddy(bud) 
    430                                 # URI updated? 
    431                                 if uri != bud.cfg.uri: 
    432                                         cfg = bud.cfg 
    433                                         # del old 
    434                                         iid = 'buddy' + str(bud.randId) 
    435                                         acc.buddyList.remove(bud) 
    436                                         del bud 
    437                                         self.tv.delete( (iid,) ) 
    438                                         # add new 
    439                                         self._createBuddy(acc, cfg) 
    440                                 # presence subscribe setting updated 
    441                                 elif subs != bud.cfg.subscribe: 
    442                                         bud.subscribePresence(bud.cfg.subscribe) 
    443                 elif label=='Delete...': 
    444                         msg = "Do you really want to delete buddy '%s'?" % bud.cfg.uri 
    445                         if msgbox.askquestion('Delete buddy?', msg, default=msgbox.NO) != u'yes': 
    446                                 return 
    447                         iid = 'buddy' + str(bud.randId) 
    448                         acc.buddyList.remove(bud) 
    449                         del bud 
    450                         self.tv.delete( (iid,) ) 
    451                 else: 
    452                         assert not ("Unknown menu " + label) 
    453                          
    454         def _onTimer(self): 
    455                 if not self.quitting: 
    456                         self.ep.libHandleEvents(10) 
    457                         if not self.quitting: 
    458                                 self.master.after(50, self._onTimer) 
    459                          
    460         def _onClose(self): 
    461                 self.saveConfig() 
    462                 self.quitting = True 
    463                 self.ep.libDestroy() 
    464                 self.ep = None 
    465                 self.update() 
    466                 self.quit() 
    467                  
    468         def _onMenuAddAccount(self): 
    469                 cfg = pj.AccountConfig() 
    470                 dlg = accountsetting.Dialog(self.master, cfg) 
    471                 if dlg.doModal(): 
    472                         self._createAcc(cfg) 
    473                          
    474         def _onMenuShowHideLogWindow(self): 
    475                 if self.showLogWindow.get(): 
    476                         self.logWindow.deiconify() 
    477                 else: 
    478                         self.logWindow.withdraw() 
    479          
    480         def _onMenuSettings(self): 
    481                 dlg = settings.Dialog(self, self.appConfig) 
    482                 if dlg.doModal(): 
    483                         msgbox.showinfo(self.master.title(), 'You need to restart for new settings to take effect') 
    484          
    485         def _onMenuSaveSettings(self): 
    486                 self.saveConfig() 
    487                  
    488         def _onMenuQuit(self): 
    489                 self._onClose() 
    490  
    491         def _onMenuAbout(self): 
    492                 msgbox.showinfo(self.master.title(), 'About') 
    493                  
     51    """ 
     52    The Application main frame. 
     53    """ 
     54    def __init__(self): 
     55        global USE_THREADS 
     56        ttk.Frame.__init__(self, name='application', width=300, height=500) 
     57        self.pack(expand='yes', fill='both') 
     58        self.master.title('pjsua2 Demo') 
     59        self.master.geometry('500x500+100+100') 
     60 
     61        # Logger 
     62        self.logger = log.Logger() 
     63 
     64        # Accounts 
     65        self.accList = [] 
     66 
     67        # GUI variables 
     68        self.showLogWindow = tk.IntVar(value=0) 
     69        self.quitting = False 
     70 
     71        # Construct GUI 
     72        self._createWidgets() 
     73 
     74        # Log window 
     75        self.logWindow = log.LogWindow(self) 
     76        self._onMenuShowHideLogWindow() 
     77 
     78        # Instantiate endpoint 
     79        self.ep = endpoint.Endpoint() 
     80        self.ep.libCreate() 
     81 
     82        # Default config 
     83        self.appConfig = settings.AppConfig() 
     84        if USE_THREADS: 
     85            self.appConfig.epConfig.uaConfig.threadCnt = 1 
     86            self.appConfig.epConfig.uaConfig.mainThreadOnly = False 
     87        else: 
     88            self.appConfig.epConfig.uaConfig.threadCnt = 0 
     89            self.appConfig.epConfig.uaConfig.mainThreadOnly = True 
     90        self.appConfig.epConfig.logConfig.writer = self.logger 
     91        self.appConfig.epConfig.logConfig.filename = "pygui.log" 
     92        self.appConfig.epConfig.logConfig.fileFlags = pj.PJ_O_APPEND 
     93        self.appConfig.epConfig.logConfig.level = 5 
     94        self.appConfig.epConfig.logConfig.consoleLevel = 5 
     95 
     96    def saveConfig(self, filename='pygui.js'): 
     97        # Save disabled accounts since they are not listed in self.accList 
     98        disabled_accs = [ac for ac in self.appConfig.accounts if not ac.enabled] 
     99        self.appConfig.accounts = [] 
     100 
     101        # Get account configs from active accounts 
     102        for acc in self.accList: 
     103            acfg = settings.AccConfig() 
     104            acfg.enabled = True 
     105            acfg.config = acc.cfg 
     106            for bud in acc.buddyList: 
     107                acfg.buddyConfigs.append(bud.cfg) 
     108            self.appConfig.accounts.append(acfg) 
     109 
     110        # Put back disabled accounts 
     111        self.appConfig.accounts.extend(disabled_accs) 
     112        # Save 
     113        self.appConfig.saveFile(filename) 
     114 
     115    def start(self, cfg_file='pygui.js'): 
     116        global USE_THREADS 
     117        # Load config 
     118        if cfg_file and os.path.exists(cfg_file): 
     119            self.appConfig.loadFile(cfg_file) 
     120 
     121        if USE_THREADS: 
     122            self.appConfig.epConfig.uaConfig.threadCnt = 1 
     123            self.appConfig.epConfig.uaConfig.mainThreadOnly = False 
     124        else: 
     125            self.appConfig.epConfig.uaConfig.threadCnt = 0 
     126            self.appConfig.epConfig.uaConfig.mainThreadOnly = True 
     127        self.appConfig.epConfig.uaConfig.threadCnt = 0 
     128        self.appConfig.epConfig.uaConfig.mainThreadOnly = True 
     129        self.appConfig.epConfig.logConfig.writer = self.logger 
     130        self.appConfig.epConfig.logConfig.level = 5 
     131        self.appConfig.epConfig.logConfig.consoleLevel = 5 
     132 
     133        # Initialize library 
     134        self.appConfig.epConfig.uaConfig.userAgent = "pygui-" + self.ep.libVersion().full; 
     135        self.ep.libInit(self.appConfig.epConfig) 
     136        self.master.title('pjsua2 Demo version ' + self.ep.libVersion().full) 
     137 
     138        # Create transports 
     139        if self.appConfig.udp.enabled: 
     140            self.ep.transportCreate(self.appConfig.udp.type, self.appConfig.udp.config) 
     141        if self.appConfig.tcp.enabled: 
     142            self.ep.transportCreate(self.appConfig.tcp.type, self.appConfig.tcp.config) 
     143        if self.appConfig.tls.enabled: 
     144            self.ep.transportCreate(self.appConfig.tls.type, self.appConfig.tls.config) 
     145 
     146        # Add accounts 
     147        for cfg in self.appConfig.accounts: 
     148            if cfg.enabled: 
     149                self._createAcc(cfg.config) 
     150                acc = self.accList[-1] 
     151                for buddy_cfg in cfg.buddyConfigs: 
     152                    self._createBuddy(acc, buddy_cfg) 
     153 
     154        # Start library 
     155        self.ep.libStart() 
     156 
     157        # Start polling 
     158        if not USE_THREADS: 
     159            self._onTimer() 
     160 
     161    def updateAccount(self, acc): 
     162        if acc.deleting: 
     163            return      # ignore 
     164        iid = str(acc.randId) 
     165        text = acc.cfg.idUri 
     166        status = acc.statusText() 
     167 
     168        values = (status,) 
     169        if self.tv.exists(iid): 
     170            self.tv.item(iid, text=text, values=values) 
     171        else: 
     172            self.tv.insert('', 'end',  iid, open=True, text=text, values=values) 
     173 
     174    def updateBuddy(self, bud): 
     175        iid = 'buddy' + str(bud.randId) 
     176        text = bud.cfg.uri 
     177        status = bud.statusText() 
     178 
     179        values = (status,) 
     180        if self.tv.exists(iid): 
     181            self.tv.item(iid, text=text, values=values) 
     182        else: 
     183            self.tv.insert(str(bud.account.randId), 'end',  iid, open=True, text=text, values=values) 
     184 
     185    def _createAcc(self, acc_cfg): 
     186        acc = account.Account(self) 
     187        acc.cfg = acc_cfg 
     188        self.accList.append(acc) 
     189        self.updateAccount(acc) 
     190        acc.create(acc.cfg) 
     191        acc.cfgChanged = False 
     192        self.updateAccount(acc) 
     193 
     194    def _createBuddy(self, acc, buddy_cfg): 
     195        bud = buddy.Buddy(self) 
     196        bud.cfg = buddy_cfg 
     197        bud.account = acc 
     198        bud.create(acc, bud.cfg) 
     199        self.updateBuddy(bud) 
     200        acc.buddyList.append(bud) 
     201 
     202    def _createWidgets(self): 
     203        self._createAppMenu() 
     204 
     205        # Main pane, a Treeview 
     206        self.tv = ttk.Treeview(self, columns=('Status'), show='tree') 
     207        self.tv.pack(side='top', fill='both', expand='yes', padx=5, pady=5) 
     208 
     209        self._createContextMenu() 
     210 
     211        # Handle close event 
     212        self.master.protocol("WM_DELETE_WINDOW", self._onClose) 
     213 
     214    def _createAppMenu(self): 
     215        # Main menu bar 
     216        top = self.winfo_toplevel() 
     217        self.menubar = tk.Menu() 
     218        top.configure(menu=self.menubar) 
     219 
     220        # File menu 
     221        file_menu = tk.Menu(self.menubar, tearoff=False) 
     222        self.menubar.add_cascade(label="File", menu=file_menu) 
     223        file_menu.add_command(label="Add account..", command=self._onMenuAddAccount) 
     224        file_menu.add_checkbutton(label="Show/hide log window", command=self._onMenuShowHideLogWindow, variable=self.showLogWindow) 
     225        file_menu.add_separator() 
     226        file_menu.add_command(label="Settings...", command=self._onMenuSettings) 
     227        file_menu.add_command(label="Save Settings", command=self._onMenuSaveSettings) 
     228        file_menu.add_separator() 
     229        file_menu.add_command(label="Quit", command=self._onMenuQuit) 
     230 
     231        # Window menu 
     232        self.window_menu = tk.Menu(self.menubar, tearoff=False) 
     233        self.menubar.add_cascade(label="Window", menu=self.window_menu) 
     234 
     235        # Help menu 
     236        help_menu = tk.Menu(self.menubar, tearoff=False) 
     237        self.menubar.add_cascade(label="Help", menu=help_menu) 
     238        help_menu.add_command(label="About", underline=2, command=self._onMenuAbout) 
     239 
     240    def _showChatWindow(self, chat_inst): 
     241        chat_inst.showWindow() 
     242 
     243    def updateWindowMenu(self): 
     244        # Chat windows 
     245        self.window_menu.delete(0, tk.END) 
     246        for acc in self.accList: 
     247            for c in acc.chatList: 
     248                cmd = lambda arg=c: self._showChatWindow(arg) 
     249                self.window_menu.add_command(label=c.title, command=cmd) 
     250 
     251    def _createContextMenu(self): 
     252        top = self.winfo_toplevel() 
     253 
     254        # Create Account context menu 
     255        self.accMenu = tk.Menu(top, tearoff=False) 
     256        # Labels, must match with _onAccContextMenu() 
     257        labels = ['Unregister', 'Reregister', 'Add buddy...', '-', 
     258              'Online', 'Invisible', 'Away', 'Busy', '-', 
     259              'Settings...', '-', 
     260              'Delete...'] 
     261        for label in labels: 
     262            if label=='-': 
     263                self.accMenu.add_separator() 
     264            else: 
     265                cmd = lambda arg=label: self._onAccContextMenu(arg) 
     266                self.accMenu.add_command(label=label, command=cmd) 
     267 
     268        # Create Buddy context menu 
     269        # Labels, must match with _onBuddyContextMenu() 
     270        self.buddyMenu = tk.Menu(top, tearoff=False) 
     271        labels = ['Audio call', 'Send instant message', '-', 
     272              'Subscribe', 'Unsubscribe', '-', 
     273              'Settings...', '-', 
     274              'Delete...'] 
     275 
     276        for label in labels: 
     277            if label=='-': 
     278                self.buddyMenu.add_separator() 
     279            else: 
     280                cmd = lambda arg=label: self._onBuddyContextMenu(arg) 
     281                self.buddyMenu.add_command(label=label, command=cmd) 
     282 
     283        if (top.tk.call('tk', 'windowingsystem')=='aqua'): 
     284            self.tv.bind('<2>', self._onTvRightClick) 
     285            self.tv.bind('<Control-1>', self._onTvRightClick) 
     286        else: 
     287            self.tv.bind('<3>', self._onTvRightClick) 
     288        self.tv.bind('<Double-Button-1>', self._onTvDoubleClick) 
     289 
     290    def _getSelectedAccount(self): 
     291        items = self.tv.selection() 
     292        if not items: 
     293            return None 
     294        try: 
     295            iid = int(items[0]) 
     296        except: 
     297            return None 
     298        accs = [acc for acc in self.accList if acc.randId==iid] 
     299        if not accs: 
     300            return None 
     301        return accs[0] 
     302 
     303    def _getSelectedBuddy(self): 
     304        items = self.tv.selection() 
     305        if not items: 
     306            return None 
     307        try: 
     308            iid = int(items[0][5:]) 
     309            iid_parent = int(self.tv.parent(items[0])) 
     310        except: 
     311            return None 
     312 
     313        accs = [acc for acc in self.accList if acc.randId==iid_parent] 
     314        if not accs: 
     315            return None 
     316 
     317        buds = [b for b in accs[0].buddyList if b.randId==iid] 
     318        if not buds: 
     319            return None 
     320 
     321        return buds[0] 
     322 
     323    def _onTvRightClick(self, event): 
     324        iid = self.tv.identify_row(event.y) 
     325        #iid = self.tv.identify('item', event.x, event.y) 
     326        if iid: 
     327            self.tv.selection_set( (iid,) ) 
     328            acc = self._getSelectedAccount() 
     329            if acc: 
     330                self.accMenu.post(event.x_root, event.y_root) 
     331            else: 
     332                # A buddy is selected 
     333                self.buddyMenu.post(event.x_root, event.y_root) 
     334 
     335    def _onTvDoubleClick(self, event): 
     336        iid = self.tv.identify_row(event.y) 
     337        if iid: 
     338            self.tv.selection_set( (iid,) ) 
     339            acc = self._getSelectedAccount() 
     340            if acc: 
     341                self.cfgChanged = False 
     342                dlg = accountsetting.Dialog(self.master, acc.cfg) 
     343                if dlg.doModal(): 
     344                    self.updateAccount(acc) 
     345                    acc.modify(acc.cfg) 
     346            else: 
     347                bud = self._getSelectedBuddy() 
     348                acc = bud.account 
     349                chat = acc.findChat(bud.cfg.uri) 
     350                if not chat: 
     351                    chat = acc.newChat(bud.cfg.uri) 
     352                chat.showWindow() 
     353 
     354    def _onAccContextMenu(self, label): 
     355        acc = self._getSelectedAccount() 
     356        if not acc: 
     357            return 
     358 
     359        if label=='Unregister': 
     360            acc.setRegistration(False) 
     361        elif label=='Reregister': 
     362            acc.setRegistration(True) 
     363        elif label=='Online': 
     364            ps = pj.PresenceStatus() 
     365            ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE 
     366            acc.setOnlineStatus(ps) 
     367        elif label=='Invisible': 
     368            ps = pj.PresenceStatus() 
     369            ps.status = pj.PJSUA_BUDDY_STATUS_OFFLINE 
     370            acc.setOnlineStatus(ps) 
     371        elif label=='Away': 
     372            ps = pj.PresenceStatus() 
     373            ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE 
     374            ps.activity = pj.PJRPID_ACTIVITY_AWAY 
     375            ps.note = "Away" 
     376            acc.setOnlineStatus(ps) 
     377        elif label=='Busy': 
     378            ps = pj.PresenceStatus() 
     379            ps.status = pj.PJSUA_BUDDY_STATUS_ONLINE 
     380            ps.activity = pj.PJRPID_ACTIVITY_BUSY 
     381            ps.note = "Busy" 
     382            acc.setOnlineStatus(ps) 
     383        elif label=='Settings...': 
     384            self.cfgChanged = False 
     385            dlg = accountsetting.Dialog(self.master, acc.cfg) 
     386            if dlg.doModal(): 
     387                self.updateAccount(acc) 
     388                acc.modify(acc.cfg) 
     389        elif label=='Delete...': 
     390            msg = "Do you really want to delete account '%s'?" % acc.cfg.idUri 
     391            if msgbox.askquestion('Delete account?', msg, default=msgbox.NO) != u'yes': 
     392                return 
     393            iid = str(acc.randId) 
     394            self.accList.remove(acc) 
     395            acc.setRegistration(False) 
     396            acc.deleting = True 
     397            del acc 
     398            self.tv.delete( (iid,) ) 
     399        elif label=='Add buddy...': 
     400            cfg = pj.BuddyConfig() 
     401            dlg = buddy.SettingDialog(self.master, cfg) 
     402            if dlg.doModal(): 
     403                self._createBuddy(acc, cfg) 
     404        else: 
     405            assert not ("Unknown menu " + label) 
     406 
     407    def _onBuddyContextMenu(self, label): 
     408        bud = self._getSelectedBuddy() 
     409        if not bud: 
     410            return 
     411        acc = bud.account 
     412 
     413        if label=='Audio call': 
     414            chat = acc.findChat(bud.cfg.uri) 
     415            if not chat: chat = acc.newChat(bud.cfg.uri) 
     416            chat.showWindow() 
     417            chat.startCall() 
     418        elif label=='Send instant message': 
     419            chat = acc.findChat(bud.cfg.uri) 
     420            if not chat: chat = acc.newChat(bud.cfg.uri) 
     421            chat.showWindow(True) 
     422        elif label=='Subscribe': 
     423            bud.subscribePresence(True) 
     424        elif label=='Unsubscribe': 
     425            bud.subscribePresence(False) 
     426        elif label=='Settings...': 
     427            subs = bud.cfg.subscribe 
     428            uri  = bud.cfg.uri 
     429            dlg = buddy.SettingDialog(self.master, bud.cfg) 
     430            if dlg.doModal(): 
     431                self.updateBuddy(bud) 
     432                # URI updated? 
     433                if uri != bud.cfg.uri: 
     434                    cfg = bud.cfg 
     435                    # del old 
     436                    iid = 'buddy' + str(bud.randId) 
     437                    acc.buddyList.remove(bud) 
     438                    del bud 
     439                    self.tv.delete( (iid,) ) 
     440                    # add new 
     441                    self._createBuddy(acc, cfg) 
     442                # presence subscribe setting updated 
     443                elif subs != bud.cfg.subscribe: 
     444                    bud.subscribePresence(bud.cfg.subscribe) 
     445        elif label=='Delete...': 
     446            msg = "Do you really want to delete buddy '%s'?" % bud.cfg.uri 
     447            if msgbox.askquestion('Delete buddy?', msg, default=msgbox.NO) != u'yes': 
     448                return 
     449            iid = 'buddy' + str(bud.randId) 
     450            acc.buddyList.remove(bud) 
     451            del bud 
     452            self.tv.delete( (iid,) ) 
     453        else: 
     454            assert not ("Unknown menu " + label) 
     455 
     456    def _onTimer(self): 
     457        if not self.quitting: 
     458            self.ep.libHandleEvents(10) 
     459            if not self.quitting: 
     460                self.master.after(50, self._onTimer) 
     461 
     462    def _onClose(self): 
     463        self.saveConfig() 
     464        self.quitting = True 
     465        self.ep.libDestroy() 
     466        self.ep = None 
     467        self.update() 
     468        self.quit() 
     469 
     470    def _onMenuAddAccount(self): 
     471        cfg = pj.AccountConfig() 
     472        dlg = accountsetting.Dialog(self.master, cfg) 
     473        if dlg.doModal(): 
     474            self._createAcc(cfg) 
     475 
     476    def _onMenuShowHideLogWindow(self): 
     477        if self.showLogWindow.get(): 
     478            self.logWindow.deiconify() 
     479        else: 
     480            self.logWindow.withdraw() 
     481 
     482    def _onMenuSettings(self): 
     483        dlg = settings.Dialog(self, self.appConfig) 
     484        if dlg.doModal(): 
     485            msgbox.showinfo(self.master.title(), 'You need to restart for new settings to take effect') 
     486 
     487    def _onMenuSaveSettings(self): 
     488        self.saveConfig() 
     489 
     490    def _onMenuQuit(self): 
     491        self._onClose() 
     492 
     493    def _onMenuAbout(self): 
     494        msgbox.showinfo(self.master.title(), 'About') 
     495 
    494496 
    495497class ExceptionCatcher: 
    496         """Custom Tk exception catcher, mainly to display more information  
    497            from pj.Error exception 
    498         """  
    499         def __init__(self, func, subst, widget): 
    500                 self.func = func  
    501                 self.subst = subst 
    502                 self.widget = widget 
    503         def __call__(self, *args): 
    504                 try: 
    505                         if self.subst: 
    506                                 args = apply(self.subst, args) 
    507                         return apply(self.func, args) 
    508                 except pj.Error, error: 
    509                         print 'Exception:' 
    510                         print '  ', error.info() 
    511                         print 'Traceback:' 
    512                         print traceback.print_stack() 
    513                         log.writeLog2(1, 'Exception: ' + error.info() + '\n') 
    514                 except Exception, error: 
    515                         print 'Exception:' 
    516                         print '  ', str(error) 
    517                         print 'Traceback:' 
    518                         print traceback.print_stack() 
    519                         log.writeLog2(1, 'Exception: ' + str(error) + '\n') 
     498    """Custom Tk exception catcher, mainly to display more information 
     499       from pj.Error exception 
     500    """ 
     501    def __init__(self, func, subst, widget): 
     502        self.func = func 
     503        self.subst = subst 
     504        self.widget = widget 
     505    def __call__(self, *args): 
     506        try: 
     507            if self.subst: 
     508                args = apply(self.subst, args) 
     509            return apply(self.func, args) 
     510        except pj.Error as error: 
     511            write("Exception:\r\n") 
     512            write("  ," + error.info() + "\r\n") 
     513            write("Traceback:\r\n") 
     514            write(traceback.print_stack()) 
     515            log.writeLog2(1, 'Exception: ' + error.info() + '\n') 
     516        except Exception as error: 
     517            write("Exception:\r\n") 
     518            write("  ," +  str(error) + "\r\n") 
     519            write("Traceback:\r\n") 
     520            write(traceback.print_stack()) 
     521            log.writeLog2(1, 'Exception: ' + str(error) + '\n') 
    520522 
    521523def main(): 
    522         #tk.CallWrapper = ExceptionCatcher 
    523         app = Application() 
    524         app.start() 
    525         app.mainloop() 
    526                  
     524    #tk.CallWrapper = ExceptionCatcher 
     525    app = Application() 
     526    app.start() 
     527    app.mainloop() 
     528 
    527529if __name__ == '__main__': 
    528         main() 
     530    main() 
  • pjproject/trunk/pjsip-apps/src/pygui/buddy.py

    r4704 r5638  
    2121import sys 
    2222if sys.version_info[0] >= 3: # Python 3 
    23         import tkinter as tk 
    24         from tkinter import ttk 
    25         from tkinter import messagebox as msgbox 
     23    import tkinter as tk 
     24    from tkinter import ttk 
     25    from tkinter import messagebox as msgbox 
    2626else: 
    27         import Tkinter as tk 
    28         import tkMessageBox as msgbox 
    29         import ttk 
     27    import Tkinter as tk 
     28    import tkMessageBox as msgbox 
     29    import ttk 
    3030 
    3131import random 
     
    3636# Buddy class 
    3737class Buddy(pj.Buddy): 
    38         """ 
    39         High level Python Buddy object, derived from pjsua2's Buddy object. 
    40         """ 
    41         def __init__(self, app): 
    42                 pj.Buddy.__init__(self) 
    43                 self.app = app 
    44                 self.randId = random.randint(1, 9999) 
    45                 self.cfg = None 
    46                 self.account = None 
     38    """ 
     39    High level Python Buddy object, derived from pjsua2's Buddy object. 
     40    """ 
     41    def __init__(self, app): 
     42        pj.Buddy.__init__(self) 
     43        self.app = app 
     44        self.randId = random.randint(1, 9999) 
     45        self.cfg = None 
     46        self.account = None 
    4747 
    48         def statusText(self): 
    49                 bi = self.getInfo() 
    50                 status = '' 
    51                 if bi.subState == pj.PJSIP_EVSUB_STATE_ACTIVE: 
    52                         if bi.presStatus.status == pj.PJSUA_BUDDY_STATUS_ONLINE: 
    53                                 status = bi.presStatus.statusText 
    54                                 if not status: 
    55                                         status = 'Online' 
    56                         elif bi.presStatus.status == pj.PJSUA_BUDDY_STATUS_OFFLINE: 
    57                                 status = 'Offline' 
    58                         else: 
    59                                 status = 'Unknown' 
    60                 return status 
    61          
    62         def onBuddyState(self): 
    63                 self.app.updateBuddy(self) 
     48    def statusText(self): 
     49        bi = self.getInfo() 
     50        status = '' 
     51        if bi.subState == pj.PJSIP_EVSUB_STATE_ACTIVE: 
     52            if bi.presStatus.status == pj.PJSUA_BUDDY_STATUS_ONLINE: 
     53                status = bi.presStatus.statusText 
     54                if not status: 
     55                    status = 'Online' 
     56            elif bi.presStatus.status == pj.PJSUA_BUDDY_STATUS_OFFLINE: 
     57                status = 'Offline' 
     58            else: 
     59                status = 'Unknown' 
     60        return status 
     61 
     62    def onBuddyState(self): 
     63        self.app.updateBuddy(self) 
    6464 
    6565class SettingDialog(tk.Toplevel): 
    66         """ 
    67         This implements buddy settings dialog to manipulate buddy settings. 
    68         """ 
    69         def __init__(self, parent, cfg): 
    70                 tk.Toplevel.__init__(self, parent) 
    71                 self.transient(parent) 
    72                 self.parent = parent 
    73                 self.geometry("+100+100") 
    74                 self.title('Buddy settings') 
    75                  
    76                 self.frm = ttk.Frame(self) 
    77                 self.frm.pack(expand='yes', fill='both') 
    78                  
    79                 self.isOk = False 
    80                 self.cfg = cfg 
    81                  
    82                 self.createWidgets() 
    83          
    84         def doModal(self): 
    85                 if self.parent: 
    86                         self.parent.wait_window(self) 
    87                 else: 
    88                         self.wait_window(self) 
    89                 return self.isOk 
    90                  
    91         def createWidgets(self): 
    92                 # The notebook 
    93                 self.frm.rowconfigure(0, weight=1) 
    94                 self.frm.rowconfigure(1, weight=0) 
    95                 self.frm.columnconfigure(0, weight=1) 
    96                 self.frm.columnconfigure(1, weight=1) 
    97                 self.wTab = ttk.Notebook(self.frm) 
    98                 self.wTab.grid(column=0, row=0, columnspan=2, padx=5, pady=5, sticky=tk.N+tk.S+tk.W+tk.E) 
    99                  
    100                 # Main buttons 
    101                 btnOk = ttk.Button(self.frm, text='Ok', command=self.onOk) 
    102                 btnOk.grid(column=0, row=1, sticky=tk.E, padx=20, pady=10) 
    103                 btnCancel = ttk.Button(self.frm, text='Cancel', command=self.onCancel) 
    104                 btnCancel.grid(column=1, row=1, sticky=tk.W, padx=20, pady=10) 
    105                  
    106                 # Tabs 
    107                 self.createBasicTab() 
    108                  
    109         def createBasicTab(self): 
    110                 # Prepare the variables to set/receive values from GUI 
    111                 self.cfgUri = tk.StringVar() 
    112                 self.cfgUri.set( self.cfg.uri ) 
    113                 self.cfgSubscribe = tk.IntVar() 
    114                 self.cfgSubscribe.set(self.cfg.subscribe) 
    115                  
    116                 # Build the tab page 
    117                 frm = ttk.Frame(self.frm) 
    118                 frm.columnconfigure(0, weight=1) 
    119                 frm.columnconfigure(1, weight=2) 
    120                 row = 0 
    121                 ttk.Label(frm, text='URI:').grid(row=row, column=0, sticky=tk.E, pady=2) 
    122                 ttk.Entry(frm, textvariable=self.cfgUri, width=40).grid(row=row, column=1, sticky=tk.W+tk.E, padx=6) 
    123                 row += 1 
    124                 ttk.Checkbutton(frm, text='Subscribe presence', variable=self.cfgSubscribe).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
     66    """ 
     67    This implements buddy settings dialog to manipulate buddy settings. 
     68    """ 
     69    def __init__(self, parent, cfg): 
     70        tk.Toplevel.__init__(self, parent) 
     71        self.transient(parent) 
     72        self.parent = parent 
     73        self.geometry("+100+100") 
     74        self.title('Buddy settings') 
    12575 
    126                 self.wTab.add(frm, text='Basic Settings') 
    127                  
    128          
    129         def onOk(self): 
    130                 # Check basic settings 
    131                 errors = ""; 
    132                 if self.cfgUri.get(): 
    133                         if not endpoint.validateSipUri(self.cfgUri.get()): 
    134                                 errors += "Invalid Buddy URI: '%s'\n" % (self.cfgUri.get()) 
    135                                  
    136                 if errors: 
    137                         msgbox.showerror("Error detected:", errors) 
    138                         return 
    139                  
    140                 # Basic settings 
    141                 self.cfg.uri = self.cfgUri.get() 
    142                 self.cfg.subscribe = self.cfgSubscribe.get() 
    143                  
    144                 self.isOk = True 
    145                 self.destroy() 
    146                  
    147         def onCancel(self): 
    148                 self.destroy() 
     76        self.frm = ttk.Frame(self) 
     77        self.frm.pack(expand='yes', fill='both') 
     78 
     79        self.isOk = False 
     80        self.cfg = cfg 
     81 
     82        self.createWidgets() 
     83 
     84    def doModal(self): 
     85        if self.parent: 
     86            self.parent.wait_window(self) 
     87        else: 
     88            self.wait_window(self) 
     89        return self.isOk 
     90 
     91    def createWidgets(self): 
     92        # The notebook 
     93        self.frm.rowconfigure(0, weight=1) 
     94        self.frm.rowconfigure(1, weight=0) 
     95        self.frm.columnconfigure(0, weight=1) 
     96        self.frm.columnconfigure(1, weight=1) 
     97        self.wTab = ttk.Notebook(self.frm) 
     98        self.wTab.grid(column=0, row=0, columnspan=2, padx=5, pady=5, sticky=tk.N+tk.S+tk.W+tk.E) 
     99 
     100        # Main buttons 
     101        btnOk = ttk.Button(self.frm, text='Ok', command=self.onOk) 
     102        btnOk.grid(column=0, row=1, sticky=tk.E, padx=20, pady=10) 
     103        btnCancel = ttk.Button(self.frm, text='Cancel', command=self.onCancel) 
     104        btnCancel.grid(column=1, row=1, sticky=tk.W, padx=20, pady=10) 
     105 
     106        # Tabs 
     107        self.createBasicTab() 
     108 
     109    def createBasicTab(self): 
     110        # Prepare the variables to set/receive values from GUI 
     111        self.cfgUri = tk.StringVar() 
     112        self.cfgUri.set( self.cfg.uri ) 
     113        self.cfgSubscribe = tk.IntVar() 
     114        self.cfgSubscribe.set(self.cfg.subscribe) 
     115 
     116        # Build the tab page 
     117        frm = ttk.Frame(self.frm) 
     118        frm.columnconfigure(0, weight=1) 
     119        frm.columnconfigure(1, weight=2) 
     120        row = 0 
     121        ttk.Label(frm, text='URI:').grid(row=row, column=0, sticky=tk.E, pady=2) 
     122        ttk.Entry(frm, textvariable=self.cfgUri, width=40).grid(row=row, column=1, sticky=tk.W+tk.E, padx=6) 
     123        row += 1 
     124        ttk.Checkbutton(frm, text='Subscribe presence', variable=self.cfgSubscribe).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2) 
     125 
     126        self.wTab.add(frm, text='Basic Settings') 
     127 
     128 
     129    def onOk(self): 
     130        # Check basic settings 
     131        errors = ""; 
     132        if self.cfgUri.get(): 
     133            if not endpoint.validateSipUri(self.cfgUri.get()): 
     134                errors += "Invalid Buddy URI: '%s'\n" % (self.cfgUri.get()) 
     135 
     136        if errors: 
     137            msgbox.showerror("Error detected:", errors) 
     138            return 
     139 
     140        # Basic settings 
     141        self.cfg.uri = self.cfgUri.get() 
     142        self.cfg.subscribe = self.cfgSubscribe.get() 
     143 
     144        self.isOk = True 
     145        self.destroy() 
     146 
     147    def onCancel(self): 
     148        self.destroy() 
    149149 
    150150 
    151151if __name__ == '__main__': 
    152         application.main() 
     152    application.main() 
  • pjproject/trunk/pjsip-apps/src/pygui/call.py

    r4757 r5638  
    2121import sys 
    2222if sys.version_info[0] >= 3: # Python 3 
    23         import tkinter as tk 
    24         from tkinter import ttk 
    25         from tkinter import messagebox as msgbox 
     23    import tkinter as tk 
     24    from tkinter import ttk 
     25    from tkinter import messagebox as msgbox 
    2626else: 
    27         import Tkinter as tk 
    28         import tkMessageBox as msgbox 
    29         import ttk 
     27    import Tkinter as tk 
     28    import tkMessageBox as msgbox 
     29    import ttk 
    3030 
    3131import random 
     
    3636# Call class 
    3737class Call(pj.Call): 
    38         """ 
    39         High level Python Call object, derived from pjsua2's Call object. 
    40         """ 
    41         def __init__(self, acc, peer_uri='', chat=None, call_id = pj.PJSUA_INVALID_ID): 
    42                 pj.Call.__init__(self, acc, call_id) 
    43                 self.acc = acc 
    44                 self.peerUri = peer_uri 
    45                 self.chat = chat 
    46                 self.connected = False 
    47                 self.onhold = False 
     38    """ 
     39    High level Python Call object, derived from pjsua2's Call object. 
     40    """ 
     41    def __init__(self, acc, peer_uri='', chat=None, call_id = pj.PJSUA_INVALID_ID): 
     42        pj.Call.__init__(self, acc, call_id) 
     43        self.acc = acc 
     44        self.peerUri = peer_uri 
     45        self.chat = chat 
     46        self.connected = False 
     47        self.onhold = False 
    4848 
    49         def onCallState(self, prm): 
    50                 ci = self.getInfo() 
    51                 self.connected = ci.state == pj.PJSIP_INV_STATE_CONFIRMED                        
    52                 if self.chat: 
    53                         self.chat.updateCallState(self, ci) 
    54                          
    55         def onCallMediaState(self, prm): 
    56                 ci = self.getInfo() 
    57                 for mi in ci.media: 
    58                         if mi.type == pj.PJMEDIA_TYPE_AUDIO and \ 
    59                           (mi.status == pj.PJSUA_CALL_MEDIA_ACTIVE or \ 
    60                            mi.status == pj.PJSUA_CALL_MEDIA_REMOTE_HOLD): 
    61                                 m = self.getMedia(mi.index) 
    62                                 am = pj.AudioMedia.typecastFromMedia(m) 
    63                                 # connect ports 
    64                                 ep.Endpoint.instance.audDevManager().getCaptureDevMedia().startTransmit(am) 
    65                                 am.startTransmit(ep.Endpoint.instance.audDevManager().getPlaybackDevMedia()) 
     49    def onCallState(self, prm): 
     50        ci = self.getInfo() 
     51        self.connected = ci.state == pj.PJSIP_INV_STATE_CONFIRMED 
     52        if self.chat: 
     53            self.chat.updateCallState(self, ci) 
    6654 
    67                                 if mi.status == pj.PJSUA_CALL_MEDIA_REMOTE_HOLD and not self.onhold: 
    68                                         self.chat.addMessage(None, "'%s' sets call onhold" % (self.peerUri)) 
    69                                         self.onhold = True 
    70                                 elif mi.status == pj.PJSUA_CALL_MEDIA_ACTIVE and self.onhold: 
    71                                         self.chat.addMessage(None, "'%s' sets call active" % (self.peerUri)) 
    72                                         self.onhold = False 
    73                 if self.chat: 
    74                         self.chat.updateCallMediaState(self, ci) 
    75                          
    76         def onInstantMessage(self, prm): 
    77                 # chat instance should have been initalized 
    78                 if not self.chat: return 
    79                          
    80                 self.chat.addMessage(self.peerUri, prm.msgBody) 
    81                 self.chat.showWindow() 
    82                          
    83         def onInstantMessageStatus(self, prm): 
    84                 if prm.code/100 == 2: return 
    85                 # chat instance should have been initalized 
    86                 if not self.chat: return 
    87                  
    88                 self.chat.addMessage(None, "Failed sending message to '%s' (%d): %s" % (self.peerUri, prm.code, prm.reason)) 
    89                  
    90         def onTypingIndication(self, prm): 
    91                 # chat instance should have been initalized 
    92                 if not self.chat: return 
    93                  
    94                 self.chat.setTypingIndication(self.peerUri, prm.isTyping) 
    95          
    96         def onDtmfDigit(self, prm): 
    97                 #msgbox.showinfo("pygui", 'Got DTMF:' + prm.digit) 
    98                 pass 
    99                  
    100         def onCallMediaTransportState(self, prm): 
    101                 #msgbox.showinfo("pygui", "Media transport state") 
    102                 pass 
    103                  
    104                  
     55    def onCallMediaState(self, prm): 
     56        ci = self.getInfo() 
     57        for mi in ci.media: 
     58            if mi.type == pj.PJMEDIA_TYPE_AUDIO and \ 
     59              (mi.status == pj.PJSUA_CALL_MEDIA_ACTIVE or \ 
     60               mi.status == pj.PJSUA_CALL_MEDIA_REMOTE_HOLD): 
     61                m = self.getMedia(mi.index) 
     62                am = pj.AudioMedia.typecastFromMedia(m) 
     63                # connect ports 
     64                ep.Endpoint.instance.audDevManager().getCaptureDevMedia().startTransmit(am) 
     65                am.startTransmit(ep.Endpoint.instance.audDevManager().getPlaybackDevMedia()) 
     66 
     67                if mi.status == pj.PJSUA_CALL_MEDIA_REMOTE_HOLD and not self.onhold: 
     68                    self.chat.addMessage(None, "'%s' sets call onhold" % (self.peerUri)) 
     69                    self.onhold = True 
     70                elif mi.status == pj.PJSUA_CALL_MEDIA_ACTIVE and self.onhold: 
     71                    self.chat.addMessage(None, "'%s' sets call active" % (self.peerUri)) 
     72                    self.onhold = False 
     73        if self.chat: 
     74            self.chat.updateCallMediaState(self, ci) 
     75 
     76    def onInstantMessage(self, prm): 
     77        # chat instance should have been initalized 
     78        if not self.chat: return 
     79 
     80        self.chat.addMessage(self.peerUri, prm.msgBody) 
     81        self.chat.showWindow() 
     82 
     83    def onInstantMessageStatus(self, prm): 
     84        if prm.code/100 == 2: return 
     85        # chat instance should have been initalized 
     86        if not self.chat: return 
     87 
     88        self.chat.addMessage(None, "Failed sending message to '%s' (%d): %s" % (self.peerUri, prm.code, prm.reason)) 
     89 
     90    def onTypingIndication(self, prm): 
     91        # chat instance should have been initalized 
     92        if not self.chat: return 
     93 
     94        self.chat.setTypingIndication(self.peerUri, prm.isTyping) 
     95 
     96    def onDtmfDigit(self, prm): 
     97        #msgbox.showinfo("pygui", 'Got DTMF:' + prm.digit) 
     98        pass 
     99 
     100    def onCallMediaTransportState(self, prm): 
     101        #msgbox.showinfo("pygui", "Media transport state") 
     102        pass 
     103 
     104 
    105105if __name__ == '__main__': 
    106         application.main() 
     106    application.main() 
  • pjproject/trunk/pjsip-apps/src/pygui/chat.py

    r4757 r5638  
    2121import sys 
    2222if sys.version_info[0] >= 3: # Python 3 
    23         import tkinter as tk 
    24         from tkinter import ttk 
     23    import tkinter as tk 
     24    from tkinter import ttk 
    2525else: 
    26         import Tkinter as tk 
    27         import ttk 
     26    import Tkinter as tk 
     27    import ttk 
    2828 
    2929import buddy 
     
    3636SipUriRegex = re.compile('(sip|sips):([^:;>\@]*)@?([^:;>]*):?([^:;>]*)') 
    3737ConfIdx = 1 
     38write=sys.stdout.write 
    3839 
    3940# Simple SIP uri parser, input URI must have been validated 
    4041def ParseSipUri(sip_uri_str): 
    41         m = SipUriRegex.search(sip_uri_str) 
    42         if not m: 
    43                 assert(0) 
    44                 return None 
    45          
    46         scheme = m.group(1) 
    47         user = m.group(2) 
    48         host = m.group(3) 
    49         port = m.group(4) 
    50         if host == '': 
    51                 host = user 
    52                 user = '' 
    53                  
    54         return SipUri(scheme.lower(), user, host.lower(), port) 
    55          
     42    m = SipUriRegex.search(sip_uri_str) 
     43    if not m: 
     44        assert(0) 
     45        return None 
     46 
     47    scheme = m.group(1) 
     48    user = m.group(2) 
     49    host = m.group(3) 
     50    port = m.group(4) 
     51    if host == '': 
     52        host = user 
     53        user = '' 
     54 
     55    return SipUri(scheme.lower(), user, host.lower(), port) 
     56 
    5657class SipUri: 
    57         def __init__(self, scheme, user, host, port): 
    58                 self.scheme = scheme 
    59                 self.user = user 
    60                 self.host = host 
    61                 self.port = port 
    62                  
    63         def __cmp__(self, sip_uri): 
    64                 if self.scheme == sip_uri.scheme and self.user == sip_uri.user and self.host == sip_uri.host: 
    65                         # don't check port, at least for now 
    66                         return 0 
    67                 return -1 
    68          
    69         def __str__(self): 
    70                 s = self.scheme + ':' 
    71                 if self.user: s += self.user + '@' 
    72                 s += self.host 
    73                 if self.port: s+= ':' + self.port 
    74                 return s 
    75          
     58    def __init__(self, scheme, user, host, port): 
     59        self.scheme = scheme 
     60        self.user = user 
     61        self.host = host 
     62        self.port = port 
     63 
     64    def __cmp__(self, sip_uri): 
     65        if self.scheme == sip_uri.scheme and self.user == sip_uri.user and self.host == sip_uri.host: 
     66            # don't check port, at least for now 
     67            return 0 
     68        return -1 
     69 
     70    def __str__(self): 
     71        s = self.scheme + ':' 
     72        if self.user: s += self.user + '@' 
     73        s += self.host 
     74        if self.port: s+= ':' + self.port 
     75        return s 
     76 
    7677class Chat(gui.ChatObserver): 
    77         def __init__(self, app, acc, uri, call_inst=None): 
    78                 self._app = app 
    79                 self._acc = acc 
    80                 self.title = '' 
    81                  
    82                 global ConfIdx 
    83                 self.confIdx = ConfIdx 
    84                 ConfIdx += 1 
    85                  
    86                 # each participant call/buddy instances are stored in call list 
    87                 # and buddy list with same index as in particpant list 
    88                 self._participantList = []      # list of SipUri 
    89                 self._callList = []             # list of Call 
    90                 self._buddyList = []            # list of Buddy 
    91                  
    92                 self._gui = gui.ChatFrame(self) 
    93                 self.addParticipant(uri, call_inst) 
    94          
    95         def _updateGui(self): 
    96                 if self.isPrivate(): 
    97                         self.title = str(self._participantList[0]) 
    98                 else: 
    99                         self.title = 'Conference #%d (%d participants)' % (self.confIdx, len(self._participantList)) 
    100                 self._gui.title(self.title) 
    101                 self._app.updateWindowMenu() 
    102                  
    103         def _getCallFromUriStr(self, uri_str, op = ''): 
    104                 uri = ParseSipUri(uri_str) 
    105                 if uri not in self._participantList: 
    106                         print "=== %s cannot find participant with URI '%s'" % (op, uri_str) 
    107                         return None 
    108                 idx = self._participantList.index(uri) 
    109                 if idx < len(self._callList): 
    110                         return self._callList[idx] 
    111                 return None 
    112          
    113         def _getActiveMediaIdx(self, thecall): 
    114                 ci = thecall.getInfo() 
    115                 for mi in ci.media: 
    116                         if mi.type == pj.PJMEDIA_TYPE_AUDIO and \ 
    117                           (mi.status != pj.PJSUA_CALL_MEDIA_NONE and \ 
    118                            mi.status != pj.PJSUA_CALL_MEDIA_ERROR): 
    119                                 return mi.index 
    120                 return -1 
    121                  
    122         def _getAudioMediaFromUriStr(self, uri_str): 
    123                 c = self._getCallFromUriStr(uri_str) 
    124                 if not c: return None 
    125  
    126                 idx = self._getActiveMediaIdx(c) 
    127                 if idx < 0: return None 
    128  
    129                 m = c.getMedia(idx) 
    130                 am = pj.AudioMedia.typecastFromMedia(m) 
    131                 return am 
    132                  
    133         def _sendTypingIndication(self, is_typing, sender_uri_str=''): 
    134                 sender_uri = ParseSipUri(sender_uri_str) if sender_uri_str else None 
    135                 type_ind_param = pj.SendTypingIndicationParam() 
    136                 type_ind_param.isTyping = is_typing 
    137                 for idx, p in enumerate(self._participantList): 
    138                         # don't echo back to the original sender 
    139                         if sender_uri and p == sender_uri: 
    140                                 continue 
    141                                  
    142                         # send via call, if any, or buddy 
    143                         target = None 
    144                         if self._callList[idx] and self._callList[idx].connected: 
    145                                 target = self._callList[idx] 
    146                         else: 
    147                                 target = self._buddyList[idx] 
    148                         assert(target) 
    149                                  
    150                         try: 
    151                                 target.sendTypingIndication(type_ind_param) 
    152                         except: 
    153                                 pass 
    154  
    155         def _sendInstantMessage(self, msg, sender_uri_str=''): 
    156                 sender_uri = ParseSipUri(sender_uri_str) if sender_uri_str else None 
    157                 send_im_param = pj.SendInstantMessageParam() 
    158                 send_im_param.content = str(msg) 
    159                 for idx, p in enumerate(self._participantList): 
    160                         # don't echo back to the original sender 
    161                         if sender_uri and p == sender_uri: 
    162                                 continue 
    163                                  
    164                         # send via call, if any, or buddy 
    165                         target = None 
    166                         if self._callList[idx] and self._callList[idx].connected: 
    167                                 target = self._callList[idx] 
    168                         else: 
    169                                 target = self._buddyList[idx] 
    170                         assert(target) 
    171                          
    172                         try: 
    173                                 target.sendInstantMessage(send_im_param) 
    174                         except: 
    175                                 # error will be handled via Account::onInstantMessageStatus() 
    176                                 pass 
    177  
    178         def isPrivate(self): 
    179                 return len(self._participantList) <= 1 
    180                  
    181         def isUriParticipant(self, uri): 
    182                 return uri in self._participantList 
    183                  
    184         def registerCall(self, uri_str, call_inst): 
    185                 uri = ParseSipUri(uri_str) 
    186                 try: 
    187                         idx = self._participantList.index(uri) 
    188                         bud = self._buddyList[idx] 
    189                         self._callList[idx] = call_inst 
    190                         call_inst.chat = self 
    191                         call_inst.peerUri = bud.cfg.uri 
    192                 except: 
    193                         assert(0) # idx must be found! 
    194                  
    195         def showWindow(self, show_text_chat = False): 
    196                 self._gui.bringToFront() 
    197                 if show_text_chat: 
    198                         self._gui.textShowHide(True) 
    199                  
    200         def addParticipant(self, uri, call_inst=None): 
    201                 # avoid duplication 
    202                 if self.isUriParticipant(uri): return 
    203                  
    204                 uri_str = str(uri) 
    205                  
    206                 # find buddy, create one if not found (e.g: for IM/typing ind), 
    207                 # it is a temporary one and not really registered to acc 
    208                 bud = None 
    209                 try: 
    210                         bud = self._acc.findBuddy(uri_str) 
    211                 except: 
    212                         bud = buddy.Buddy(None) 
    213                         bud_cfg = pj.BuddyConfig() 
    214                         bud_cfg.uri = uri_str 
    215                         bud_cfg.subscribe = False 
    216                         bud.create(self._acc, bud_cfg) 
    217                         bud.cfg = bud_cfg 
    218                         bud.account = self._acc 
    219                          
    220                 # update URI from buddy URI 
    221                 uri = ParseSipUri(bud.cfg.uri) 
    222                  
    223                 # add it 
    224                 self._participantList.append(uri) 
    225                 self._callList.append(call_inst) 
    226                 self._buddyList.append(bud) 
    227                 self._gui.addParticipant(str(uri)) 
    228                 self._updateGui() 
    229          
    230         def kickParticipant(self, uri): 
    231                 if (not uri) or (uri not in self._participantList): 
    232                         assert(0) 
    233                         return 
    234                  
    235                 idx = self._participantList.index(uri) 
    236                 del self._participantList[idx] 
    237                 del self._callList[idx] 
    238                 del self._buddyList[idx] 
    239                 self._gui.delParticipant(str(uri)) 
    240                  
    241                 if self._participantList: 
    242                         self._updateGui() 
    243                 else: 
    244                         self.onCloseWindow() 
    245                          
    246         def addMessage(self, from_uri_str, msg): 
    247                 if from_uri_str: 
    248                         # print message on GUI 
    249                         msg = from_uri_str + ': ' + msg 
    250                         self._gui.textAddMessage(msg) 
    251                         # now relay to all participants 
    252                         self._sendInstantMessage(msg, from_uri_str) 
    253                 else: 
    254                         self._gui.textAddMessage(msg, False) 
    255                          
    256         def setTypingIndication(self, from_uri_str, is_typing): 
    257                 # notify GUI 
    258                 self._gui.textSetTypingIndication(from_uri_str, is_typing) 
    259                 # now relay to all participants 
    260                 self._sendTypingIndication(is_typing, from_uri_str) 
    261                  
    262         def startCall(self): 
    263                 self._gui.enableAudio() 
    264                 call_param = pj.CallOpParam() 
    265                 call_param.opt.audioCount = 1 
    266                 call_param.opt.videoCount = 0 
    267                 fails = [] 
    268                 for idx, p in enumerate(self._participantList): 
    269                         # just skip if call is instantiated 
    270                         if self._callList[idx]: 
    271                                 continue 
    272                          
    273                         uri_str = str(p) 
    274                         c = call.Call(self._acc, uri_str, self) 
    275                         self._callList[idx] = c 
    276                         self._gui.audioUpdateState(uri_str, gui.AudioState.INITIALIZING) 
    277                          
    278                         try: 
    279                                 c.makeCall(uri_str, call_param) 
    280                         except: 
    281                                 self._callList[idx] = None 
    282                                 self._gui.audioUpdateState(uri_str, gui.AudioState.FAILED) 
    283                                 fails.append(p) 
    284                                  
    285                 for p in fails: 
    286                         # kick participants with call failure, but spare the last (avoid zombie chat) 
    287                         if not self.isPrivate(): 
    288                                 self.kickParticipant(p) 
    289                          
    290         def stopCall(self): 
    291                 for idx, p in enumerate(self._participantList): 
    292                         self._gui.audioUpdateState(str(p), gui.AudioState.DISCONNECTED) 
    293                         c = self._callList[idx] 
    294                         if c: 
    295                                 c.hangup(pj.CallOpParam()) 
    296  
    297         def updateCallState(self, thecall, info = None): 
    298                 # info is optional here, just to avoid calling getInfo() twice (in the caller and here) 
    299                 if not info: info = thecall.getInfo() 
    300                  
    301                 if info.state < pj.PJSIP_INV_STATE_CONFIRMED: 
    302                         self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.INITIALIZING) 
    303                 elif info.state == pj.PJSIP_INV_STATE_CONFIRMED: 
    304                         self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.CONNECTED) 
    305                         if not self.isPrivate(): 
    306                                 # inform peer about conference participants 
    307                                 conf_welcome_str  = '\n---\n' 
    308                                 conf_welcome_str += 'Welcome to the conference, participants:\n' 
    309                                 conf_welcome_str += '%s (host)\n' % (self._acc.cfg.idUri) 
    310                                 for p in self._participantList: 
    311                                         conf_welcome_str += '%s\n' % (str(p)) 
    312                                 conf_welcome_str += '---\n' 
    313                                 send_im_param = pj.SendInstantMessageParam() 
    314                                 send_im_param.content = conf_welcome_str 
    315                                 try: 
    316                                         thecall.sendInstantMessage(send_im_param) 
    317                                 except: 
    318                                         pass 
    319                                          
    320                                 # inform others, including self 
    321                                 msg = "[Conf manager] %s has joined" % (thecall.peerUri) 
    322                                 self.addMessage(None, msg) 
    323                                 self._sendInstantMessage(msg, thecall.peerUri) 
    324                                  
    325                 elif info.state == pj.PJSIP_INV_STATE_DISCONNECTED: 
    326                         if info.lastStatusCode/100 != 2: 
    327                                 self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.FAILED) 
    328                         else: 
    329                                 self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.DISCONNECTED) 
    330                          
    331                         # reset entry in the callList 
    332                         try: 
    333                                 idx = self._callList.index(thecall) 
    334                                 if idx >= 0: self._callList[idx] = None 
    335                         except: 
    336                                 pass 
    337                          
    338                         self.addMessage(None, "Call to '%s' disconnected: %s" % (thecall.peerUri, info.lastReason)) 
    339                          
    340                         # kick the disconnected participant, but the last (avoid zombie chat) 
    341                         if not self.isPrivate(): 
    342                                 self.kickParticipant(ParseSipUri(thecall.peerUri)) 
    343                                  
    344                                 # inform others, including self 
    345                                 msg = "[Conf manager] %s has left" % (thecall.peerUri) 
    346                                 self.addMessage(None, msg) 
    347                                 self._sendInstantMessage(msg, thecall.peerUri) 
    348  
    349         def updateCallMediaState(self, thecall, info = None): 
    350                 # info is optional here, just to avoid calling getInfo() twice (in the caller and here) 
    351                 if not info: info = thecall.getInfo() 
    352                  
    353                 med_idx = self._getActiveMediaIdx(thecall) 
    354                 if (med_idx < 0): 
    355                         self._gui.audioSetStatsText(thecall.peerUri, 'No active media') 
    356                         return 
    357  
    358                 si = thecall.getStreamInfo(med_idx) 
    359                 dir_str = '' 
    360                 if si.dir == 0: 
    361                         dir_str = 'inactive' 
    362                 else: 
    363                         if si.dir & pj.PJMEDIA_DIR_ENCODING: 
    364                                 dir_str += 'send ' 
    365                         if si.dir & pj.PJMEDIA_DIR_DECODING: 
    366                                 dir_str += 'receive ' 
    367                 stats_str  = "Direction   : %s\n" % (dir_str) 
    368                 stats_str += "Audio codec : %s (%sHz)" % (si.codecName, si.codecClockRate) 
    369                 self._gui.audioSetStatsText(thecall.peerUri, stats_str) 
    370                 m = pj.AudioMedia.typecastFromMedia(thecall.getMedia(med_idx)) 
    371                  
    372                 # make conference 
    373                 for c in self._callList: 
    374                         if c == thecall: 
    375                                 continue 
    376                         med_idx = self._getActiveMediaIdx(c) 
    377                         if med_idx < 0: 
    378                                 continue 
    379                         mm = pj.AudioMedia.typecastFromMedia(c.getMedia(med_idx)) 
    380                         m.startTransmit(mm) 
    381                         mm.startTransmit(m) 
    382  
    383                          
    384         # ** callbacks from GUI (ChatObserver implementation) ** 
    385          
    386         # Text 
    387         def onSendMessage(self, msg): 
    388                 self._sendInstantMessage(msg) 
    389  
    390         def onStartTyping(self): 
    391                 self._sendTypingIndication(True) 
    392                  
    393         def onStopTyping(self): 
    394                 self._sendTypingIndication(False) 
    395                  
    396         # Audio 
    397         def onHangup(self, peer_uri_str): 
    398                 c = self._getCallFromUriStr(peer_uri_str, "onHangup()") 
    399                 if not c: return 
    400                 call_param = pj.CallOpParam() 
    401                 c.hangup(call_param) 
    402  
    403         def onHold(self, peer_uri_str): 
    404                 c = self._getCallFromUriStr(peer_uri_str, "onHold()") 
    405                 if not c: return 
    406                 call_param = pj.CallOpParam() 
    407                 c.setHold(call_param) 
    408  
    409         def onUnhold(self, peer_uri_str): 
    410                 c = self._getCallFromUriStr(peer_uri_str, "onUnhold()") 
    411                 if not c: return 
    412                  
    413                 call_param = pj.CallOpParam() 
    414                 call_param.opt.audioCount = 1 
    415                 call_param.opt.videoCount = 0 
    416                 call_param.opt.flag |= pj.PJSUA_CALL_UNHOLD 
    417                 c.reinvite(call_param) 
    418                  
    419         def onRxMute(self, peer_uri_str, mute): 
    420                 am = self._getAudioMediaFromUriStr(peer_uri_str) 
    421                 if not am: return 
    422                 if mute: 
    423                         am.stopTransmit(ep.Endpoint.instance.audDevManager().getPlaybackDevMedia()) 
    424                         self.addMessage(None, "Muted audio from '%s'" % (peer_uri_str)) 
    425                 else: 
    426                         am.startTransmit(ep.Endpoint.instance.audDevManager().getPlaybackDevMedia()) 
    427                         self.addMessage(None, "Unmuted audio from '%s'" % (peer_uri_str)) 
    428                  
    429         def onRxVol(self, peer_uri_str, vol_pct): 
    430                 am = self._getAudioMediaFromUriStr(peer_uri_str) 
    431                 if not am: return 
    432                 # pjsua volume range = 0:mute, 1:no adjustment, 2:100% louder 
    433                 am.adjustRxLevel(vol_pct/50.0) 
    434                 self.addMessage(None, "Adjusted volume level audio from '%s'" % (peer_uri_str)) 
    435                          
    436         def onTxMute(self, peer_uri_str, mute): 
    437                 am = self._getAudioMediaFromUriStr(peer_uri_str) 
    438                 if not am: return 
    439                 if mute: 
    440                         ep.Endpoint.instance.audDevManager().getCaptureDevMedia().stopTransmit(am) 
    441                         self.addMessage(None, "Muted audio to '%s'" % (peer_uri_str)) 
    442                 else: 
    443                         ep.Endpoint.instance.audDevManager().getCaptureDevMedia().startTransmit(am) 
    444                         self.addMessage(None, "Unmuted audio to '%s'" % (peer_uri_str)) 
    445  
    446         # Chat room 
    447         def onAddParticipant(self): 
    448                 buds = [] 
    449                 dlg = AddParticipantDlg(None, self._app, buds) 
    450                 if dlg.doModal(): 
    451                         for bud in buds: 
    452                                 uri = ParseSipUri(bud.cfg.uri) 
    453                                 self.addParticipant(uri) 
    454                         if not self.isPrivate(): 
    455                                 self.startCall() 
    456                                  
    457         def onStartAudio(self): 
    458                 self.startCall() 
    459  
    460         def onStopAudio(self): 
    461                 self.stopCall() 
    462                  
    463         def onCloseWindow(self): 
    464                 self.stopCall() 
    465                 # will remove entry from list eventually destroy this chat? 
    466                 if self in self._acc.chatList: self._acc.chatList.remove(self) 
    467                 self._app.updateWindowMenu() 
    468                 # destroy GUI 
    469                 self._gui.destroy() 
     78    def __init__(self, app, acc, uri, call_inst=None): 
     79        self._app = app 
     80        self._acc = acc 
     81        self.title = '' 
     82 
     83        global ConfIdx 
     84        self.confIdx = ConfIdx 
     85        ConfIdx += 1 
     86 
     87        # each participant call/buddy instances are stored in call list 
     88        # and buddy list with same index as in particpant list 
     89        self._participantList = []      # list of SipUri 
     90        self._callList = []             # list of Call 
     91        self._buddyList = []            # list of Buddy 
     92 
     93        self._gui = gui.ChatFrame(self) 
     94        self.addParticipant(uri, call_inst) 
     95 
     96    def _updateGui(self): 
     97        if self.isPrivate(): 
     98            self.title = str(self._participantList[0]) 
     99        else: 
     100            self.title = 'Conference #%d (%d participants)' % (self.confIdx, len(self._participantList)) 
     101        self._gui.title(self.title) 
     102        self._app.updateWindowMenu() 
     103 
     104    def _getCallFromUriStr(self, uri_str, op = ''): 
     105        uri = ParseSipUri(uri_str) 
     106        if uri not in self._participantList: 
     107            write("=== "+ op +" cannot find participant with URI '" + uri_str + "'\r\n") 
     108            return None 
     109        idx = self._participantList.index(uri) 
     110        if idx < len(self._callList): 
     111            return self._callList[idx] 
     112        return None 
     113 
     114    def _getActiveMediaIdx(self, thecall): 
     115        ci = thecall.getInfo() 
     116        for mi in ci.media: 
     117            if mi.type == pj.PJMEDIA_TYPE_AUDIO and \ 
     118              (mi.status != pj.PJSUA_CALL_MEDIA_NONE and \ 
     119               mi.status != pj.PJSUA_CALL_MEDIA_ERROR): 
     120                return mi.index 
     121        return -1 
     122 
     123    def _getAudioMediaFromUriStr(self, uri_str): 
     124        c = self._getCallFromUriStr(uri_str) 
     125        if not c: return None 
     126 
     127        idx = self._getActiveMediaIdx(c) 
     128        if idx < 0: return None 
     129 
     130        m = c.getMedia(idx) 
     131        am = pj.AudioMedia.typecastFromMedia(m) 
     132        return am 
     133 
     134    def _sendTypingIndication(self, is_typing, sender_uri_str=''): 
     135        sender_uri = ParseSipUri(sender_uri_str) if sender_uri_str else None 
     136        type_ind_param = pj.SendTypingIndicationParam() 
     137        type_ind_param.isTyping = is_typing 
     138        for idx, p in enumerate(self._participantList): 
     139            # don't echo back to the original sender 
     140            if sender_uri and p == sender_uri: 
     141                continue 
     142 
     143            # send via call, if any, or buddy 
     144            target = None 
     145            if self._callList[idx] and self._callList[idx].connected: 
     146                target = self._callList[idx] 
     147            else: 
     148                target = self._buddyList[idx] 
     149            assert(target) 
     150 
     151            try: 
     152                target.sendTypingIndication(type_ind_param) 
     153            except: 
     154                pass 
     155 
     156    def _sendInstantMessage(self, msg, sender_uri_str=''): 
     157        sender_uri = ParseSipUri(sender_uri_str) if sender_uri_str else None 
     158        send_im_param = pj.SendInstantMessageParam() 
     159        send_im_param.content = str(msg) 
     160        for idx, p in enumerate(self._participantList): 
     161            # don't echo back to the original sender 
     162            if sender_uri and p == sender_uri: 
     163                continue 
     164 
     165            # send via call, if any, or buddy 
     166            target = None 
     167            if self._callList[idx] and self._callList[idx].connected: 
     168                target = self._callList[idx] 
     169            else: 
     170                target = self._buddyList[idx] 
     171            assert(target) 
     172 
     173            try: 
     174                target.sendInstantMessage(send_im_param) 
     175            except: 
     176                # error will be handled via Account::onInstantMessageStatus() 
     177                pass 
     178 
     179    def isPrivate(self): 
     180        return len(self._participantList) <= 1 
     181 
     182    def isUriParticipant(self, uri): 
     183        return uri in self._participantList 
     184 
     185    def registerCall(self, uri_str, call_inst): 
     186        uri = ParseSipUri(uri_str) 
     187        try: 
     188            idx = self._participantList.index(uri) 
     189            bud = self._buddyList[idx] 
     190            self._callList[idx] = call_inst 
     191            call_inst.chat = self 
     192            call_inst.peerUri = bud.cfg.uri 
     193        except: 
     194            assert(0) # idx must be found! 
     195 
     196    def showWindow(self, show_text_chat = False): 
     197        self._gui.bringToFront() 
     198        if show_text_chat: 
     199            self._gui.textShowHide(True) 
     200 
     201    def addParticipant(self, uri, call_inst=None): 
     202        # avoid duplication 
     203        if self.isUriParticipant(uri): return 
     204 
     205        uri_str = str(uri) 
     206 
     207        # find buddy, create one if not found (e.g: for IM/typing ind), 
     208        # it is a temporary one and not really registered to acc 
     209        bud = None 
     210        try: 
     211            bud = self._acc.findBuddy(uri_str) 
     212        except: 
     213            bud = buddy.Buddy(None) 
     214            bud_cfg = pj.BuddyConfig() 
     215            bud_cfg.uri = uri_str 
     216            bud_cfg.subscribe = False 
     217            bud.create(self._acc, bud_cfg) 
     218            bud.cfg = bud_cfg 
     219            bud.account = self._acc 
     220 
     221        # update URI from buddy URI 
     222        uri = ParseSipUri(bud.cfg.uri) 
     223 
     224        # add it 
     225        self._participantList.append(uri) 
     226        self._callList.append(call_inst) 
     227        self._buddyList.append(bud) 
     228        self._gui.addParticipant(str(uri)) 
     229        self._updateGui() 
     230 
     231    def kickParticipant(self, uri): 
     232        if (not uri) or (uri not in self._participantList): 
     233            assert(0) 
     234            return 
     235 
     236        idx = self._participantList.index(uri) 
     237        del self._participantList[idx] 
     238        del self._callList[idx] 
     239        del self._buddyList[idx] 
     240        self._gui.delParticipant(str(uri)) 
     241 
     242        if self._participantList: 
     243            self._updateGui() 
     244        else: 
     245            self.onCloseWindow() 
     246 
     247    def addMessage(self, from_uri_str, msg): 
     248        if from_uri_str: 
     249            # print message on GUI 
     250            msg = from_uri_str + ': ' + msg 
     251            self._gui.textAddMessage(msg) 
     252            # now relay to all participants 
     253            self._sendInstantMessage(msg, from_uri_str) 
     254        else: 
     255            self._gui.textAddMessage(msg, False) 
     256 
     257    def setTypingIndication(self, from_uri_str, is_typing): 
     258        # notify GUI 
     259        self._gui.textSetTypingIndication(from_uri_str, is_typing) 
     260        # now relay to all participants 
     261        self._sendTypingIndication(is_typing, from_uri_str) 
     262 
     263    def startCall(self): 
     264        self._gui.enableAudio() 
     265        call_param = pj.CallOpParam() 
     266        call_param.opt.audioCount = 1 
     267        call_param.opt.videoCount = 0 
     268        fails = [] 
     269        for idx, p in enumerate(self._participantList): 
     270            # just skip if call is instantiated 
     271            if self._callList[idx]: 
     272                continue 
     273 
     274            uri_str = str(p) 
     275            c = call.Call(self._acc, uri_str, self) 
     276            self._callList[idx] = c 
     277            self._gui.audioUpdateState(uri_str, gui.AudioState.INITIALIZING) 
     278 
     279            try: 
     280                c.makeCall(uri_str, call_param) 
     281            except: 
     282                self._callList[idx] = None 
     283                self._gui.audioUpdateState(uri_str, gui.AudioState.FAILED) 
     284                fails.append(p) 
     285 
     286        for p in fails: 
     287            # kick participants with call failure, but spare the last (avoid zombie chat) 
     288            if not self.isPrivate(): 
     289                self.kickParticipant(p) 
     290 
     291    def stopCall(self): 
     292        for idx, p in enumerate(self._participantList): 
     293            self._gui.audioUpdateState(str(p), gui.AudioState.DISCONNECTED) 
     294            c = self._callList[idx] 
     295            if c: 
     296                c.hangup(pj.CallOpParam()) 
     297 
     298    def updateCallState(self, thecall, info = None): 
     299        # info is optional here, just to avoid calling getInfo() twice (in the caller and here) 
     300        if not info: info = thecall.getInfo() 
     301 
     302        if info.state < pj.PJSIP_INV_STATE_CONFIRMED: 
     303            self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.INITIALIZING) 
     304        elif info.state == pj.PJSIP_INV_STATE_CONFIRMED: 
     305            self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.CONNECTED) 
     306            if not self.isPrivate(): 
     307                # inform peer about conference participants 
     308                conf_welcome_str  = '\n---\n' 
     309                conf_welcome_str += 'Welcome to the conference, participants:\n' 
     310                conf_welcome_str += '%s (host)\n' % (self._acc.cfg.idUri) 
     311                for p in self._participantList: 
     312                    conf_welcome_str += '%s\n' % (str(p)) 
     313                conf_welcome_str += '---\n' 
     314                send_im_param = pj.SendInstantMessageParam() 
     315                send_im_param.content = conf_welcome_str 
     316                try: 
     317                    thecall.sendInstantMessage(send_im_param) 
     318                except: 
     319                    pass 
     320 
     321                # inform others, including self 
     322                msg = "[Conf manager] %s has joined" % (thecall.peerUri) 
     323                self.addMessage(None, msg) 
     324                self._sendInstantMessage(msg, thecall.peerUri) 
     325 
     326        elif info.state == pj.PJSIP_INV_STATE_DISCONNECTED: 
     327            if info.lastStatusCode/100 != 2: 
     328                self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.FAILED) 
     329            else: 
     330                self._gui.audioUpdateState(thecall.peerUri, gui.AudioState.DISCONNECTED) 
     331 
     332            # reset entry in the callList 
     333            try: 
     334                idx = self._callList.index(thecall) 
     335                if idx >= 0: self._callList[idx] = None 
     336            except: 
     337                pass 
     338 
     339            self.addMessage(None, "Call to '%s' disconnected: %s" % (thecall.peerUri, info.lastReason)) 
     340 
     341            # kick the disconnected participant, but the last (avoid zombie chat) 
     342            if not self.isPrivate(): 
     343                self.kickParticipant(ParseSipUri(thecall.peerUri)) 
     344 
     345                # inform others, including self 
     346                msg = "[Conf manager] %s has left" % (thecall.peerUri) 
     347                self.addMessage(None, msg) 
     348                self._sendInstantMessage(msg, thecall.peerUri) 
     349 
     350    def updateCallMediaState(self, thecall, info = None): 
     351        # info is optional here, just to avoid calling getInfo() twice (in the caller and here) 
     352        if not info: info = thecall.getInfo() 
     353 
     354        med_idx = self._getActiveMediaIdx(thecall) 
     355        if (med_idx < 0): 
     356            self._gui.audioSetStatsText(thecall.peerUri, 'No active media') 
     357            return 
     358 
     359        si = thecall.getStreamInfo(med_idx) 
     360        dir_str = '' 
     361        if si.dir == 0: 
     362            dir_str = 'inactive' 
     363        else: 
     364            if si.dir & pj.PJMEDIA_DIR_ENCODING: 
     365                dir_str += 'send ' 
     366            if si.dir & pj.PJMEDIA_DIR_DECODING: 
     367                dir_str += 'receive ' 
     368        stats_str  = "Direction   : %s\n" % (dir_str) 
     369        stats_str += "Audio codec : %s (%sHz)" % (si.codecName, si.codecClockRate) 
     370        self._gui.audioSetStatsText(thecall.peerUri, stats_str) 
     371        m = pj.AudioMedia.typecastFromMedia(thecall.getMedia(med_idx)) 
     372 
     373        # make conference 
     374        for c in self._callList: 
     375            if c == thecall: 
     376                continue 
     377            med_idx = self._getActiveMediaIdx(c) 
     378            if med_idx < 0: 
     379                continue 
     380            mm = pj.AudioMedia.typecastFromMedia(c.getMedia(med_idx)) 
     381            m.startTransmit(mm) 
     382            mm.startTransmit(m) 
     383 
     384 
     385    # ** callbacks from GUI (ChatObserver implementation) ** 
     386 
     387    # Text 
     388    def onSendMessage(self, msg): 
     389        self._sendInstantMessage(msg) 
     390 
     391    def onStartTyping(self): 
     392        self._sendTypingIndication(True) 
     393 
     394    def onStopTyping(self): 
     395        self._sendTypingIndication(False) 
     396 
     397    # Audio 
     398    def onHangup(self, peer_uri_str): 
     399        c = self._getCallFromUriStr(peer_uri_str, "onHangup()") 
     400        if not c: return 
     401        call_param = pj.CallOpParam() 
     402        c.hangup(call_param) 
     403 
     404    def onHold(self, peer_uri_str): 
     405        c = self._getCallFromUriStr(peer_uri_str, "onHold()") 
     406        if not c: return 
     407        call_param = pj.CallOpParam() 
     408        c.setHold(call_param) 
     409 
     410    def onUnhold(self, peer_uri_str): 
     411        c = self._getCallFromUriStr(peer_uri_str, "onUnhold()") 
     412        if not c: return 
     413 
     414        call_param = pj.CallOpParam() 
     415        call_param.opt.audioCount = 1 
     416        call_param.opt.videoCount = 0 
     417        call_param.opt.flag |= pj.PJSUA_CALL_UNHOLD 
     418        c.reinvite(call_param) 
     419 
     420    def onRxMute(self, peer_uri_str, mute): 
     421        am = self._getAudioMediaFromUriStr(peer_uri_str) 
     422        if not am: return 
     423        if mute: 
     424            am.stopTransmit(ep.Endpoint.instance.audDevManager().getPlaybackDevMedia()) 
     425            self.addMessage(None, "Muted audio from '%s'" % (peer_uri_str)) 
     426        else: 
     427            am.startTransmit(ep.Endpoint.instance.audDevManager().getPlaybackDevMedia()) 
     428            self.addMessage(None, "Unmuted audio from '%s'" % (peer_uri_str)) 
     429 
     430    def onRxVol(self, peer_uri_str, vol_pct): 
     431        am = self._getAudioMediaFromUriStr(peer_uri_str) 
     432        if not am: return 
     433        # pjsua volume range = 0:mute, 1:no adjustment, 2:100% louder 
     434        am.adjustRxLevel(vol_pct/50.0) 
     435        self.addMessage(None, "Adjusted volume level audio from '%s'" % (peer_uri_str)) 
     436 
     437    def onTxMute(self, peer_uri_str, mute): 
     438        am = self._getAudioMediaFromUriStr(peer_uri_str) 
     439        if not am: return 
     440        if mute: 
     441            ep.Endpoint.instance.audDevManager().getCaptureDevMedia().stopTransmit(am) 
     442            self.addMessage(None, "Muted audio to '%s'" % (peer_uri_str)) 
     443        else: 
     444            ep.Endpoint.instance.audDevManager().getCaptureDevMedia().startTransmit(am) 
     445            self.addMessage(None, "Unmuted audio to '%s'" % (peer_uri_str)) 
     446 
     447    # Chat room 
     448    def onAddParticipant(self): 
     449        buds = [] 
     450        dlg = AddParticipantDlg(None, self._app, buds) 
     451        if dlg.doModal(): 
     452            for bud in buds: 
     453                uri = ParseSipUri(bud.cfg.uri) 
     454                self.addParticipant(uri) 
     455            if not self.isPrivate(): 
     456                self.startCall() 
     457 
     458    def onStartAudio(self): 
     459        self.startCall() 
     460 
     461    def onStopAudio(self): 
     462        self.stopCall() 
     463 
     464    def onCloseWindow(self): 
     465        self.stopCall() 
     466        # will remove entry from list eventually destroy this chat? 
     467        if self in self._acc.chatList: self._acc.chatList.remove(self) 
     468        self._app.updateWindowMenu() 
     469        # destroy GUI 
     470        self._gui.destroy() 
    470471 
    471472 
    472473class AddParticipantDlg(tk.Toplevel): 
    473         """ 
    474         List of buddies 
    475         """ 
    476         def __init__(self, parent, app, bud_list): 
    477                 tk.Toplevel.__init__(self, parent) 
    478                 self.title('Add participants..') 
    479                 self.transient(parent) 
    480                 self.parent = parent 
    481                 self._app = app 
    482                 self.buddyList = bud_list 
    483                  
    484                 self.isOk = False 
    485                  
    486                 self.createWidgets() 
    487          
    488         def doModal(self): 
    489                 if self.parent: 
    490                         self.parent.wait_window(self) 
    491                 else: 
    492                         self.wait_window(self) 
    493                 return self.isOk 
    494                  
    495         def createWidgets(self): 
    496                 # buddy list 
    497                 list_frame = ttk.Frame(self) 
    498                 list_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=1, padx=20, pady=20) 
    499                 #scrl = ttk.Scrollbar(self, orient=tk.VERTICAL, command=list_frame.yview) 
    500                 #list_frame.config(yscrollcommand=scrl.set) 
    501                 #scrl.pack(side=tk.RIGHT, fill=tk.Y) 
    502                  
    503                 # draw buddy list 
    504                 self.buddies = [] 
    505                 for acc in self._app.accList: 
    506                         self.buddies.append((0, acc.cfg.idUri)) 
    507                         for bud in acc.buddyList: 
    508                                 self.buddies.append((1, bud)) 
    509                  
    510                 self.bud_var = [] 
    511                 for idx,(flag,bud) in enumerate(self.buddies): 
    512                         self.bud_var.append(tk.IntVar()) 
    513                         if flag==0: 
    514                                 s = ttk.Separator(list_frame, orient=tk.HORIZONTAL) 
    515                                 s.pack(fill=tk.X) 
    516                                 l = tk.Label(list_frame, anchor=tk.W, text="Account '%s':" % (bud)) 
    517                                 l.pack(fill=tk.X) 
    518                         else: 
    519                                 c = tk.Checkbutton(list_frame, anchor=tk.W, text=bud.cfg.uri, variable=self.bud_var[idx]) 
    520                                 c.pack(fill=tk.X) 
    521                 s = ttk.Separator(list_frame, orient=tk.HORIZONTAL) 
    522                 s.pack(fill=tk.X) 
    523  
    524                 # Ok/cancel buttons 
    525                 tail_frame = ttk.Frame(self) 
    526                 tail_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1) 
    527                  
    528                 btnOk = ttk.Button(tail_frame, text='Ok', default=tk.ACTIVE, command=self.onOk) 
    529                 btnOk.pack(side=tk.LEFT, padx=20, pady=10) 
    530                 btnCancel = ttk.Button(tail_frame, text='Cancel', command=self.onCancel) 
    531                 btnCancel.pack(side=tk.RIGHT, padx=20, pady=10) 
    532                  
    533         def onOk(self): 
    534                 self.buddyList[:] = [] 
    535                 for idx,(flag,bud) in enumerate(self.buddies): 
    536                         if not flag: continue 
    537                         if self.bud_var[idx].get() and not (bud in self.buddyList): 
    538                                 self.buddyList.append(bud) 
    539                          
    540                 self.isOk = True 
    541                 self.destroy() 
    542                  
    543         def onCancel(self): 
    544                 self.destroy() 
     474    """ 
     475    List of buddies 
     476    """ 
     477    def __init__(self, parent, app, bud_list): 
     478        tk.Toplevel.__init__(self, parent) 
     479        self.title('Add participants..') 
     480        self.transient(parent) 
     481        self.parent = parent 
     482        self._app = app 
     483        self.buddyList = bud_list 
     484 
     485        self.isOk = False 
     486 
     487        self.createWidgets() 
     488 
     489    def doModal(self): 
     490        if self.parent: 
     491            self.parent.wait_window(self) 
     492        else: 
     493            self.wait_window(self) 
     494        return self.isOk 
     495 
     496    def createWidgets(self): 
     497        # buddy list 
     498        list_frame = ttk.Frame(self) 
     499        list_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=1, padx=20, pady=20) 
     500        #scrl = ttk.Scrollbar(self, orient=tk.VERTICAL, command=list_frame.yview) 
     501        #list_frame.config(yscrollcommand=scrl.set) 
     502        #scrl.pack(side=tk.RIGHT, fill=tk.Y) 
     503 
     504        # draw buddy list 
     505        self.buddies = [] 
     506        for acc in self._app.accList: 
     507            self.buddies.append((0, acc.cfg.idUri)) 
     508            for bud in acc.buddyList: 
     509                self.buddies.append((1, bud)) 
     510 
     511        self.bud_var = [] 
     512        for idx,(flag,bud) in enumerate(self.buddies): 
     513            self.bud_var.append(tk.IntVar()) 
     514            if flag==0: 
     515                s = ttk.Separator(list_frame, orient=tk.HORIZONTAL) 
     516                s.pack(fill=tk.X) 
     517                l = tk.Label(list_frame, anchor=tk.W, text="Account '%s':" % (bud)) 
     518                l.pack(fill=tk.X) 
     519            else: 
     520                c = tk.Checkbutton(list_frame, anchor=tk.W, text=bud.cfg.uri, variable=self.bud_var[idx]) 
     521                c.pack(fill=tk.X) 
     522        s = ttk.Separator(list_frame, orient=tk.HORIZONTAL) 
     523        s.pack(fill=tk.X) 
     524 
     525        # Ok/cancel buttons 
     526        tail_frame = ttk.Frame(self) 
     527        tail_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1) 
     528 
     529        btnOk = ttk.Button(tail_frame, text='Ok', default=tk.ACTIVE, command=self.onOk) 
     530        btnOk.pack(side=tk.LEFT, padx=20, pady=10) 
     531        btnCancel = ttk.Button(tail_frame, text='Cancel', command=self.onCancel) 
     532        btnCancel.pack(side=tk.RIGHT, padx=20, pady=10) 
     533 
     534    def onOk(self): 
     535        self.buddyList[:] = [] 
     536        for idx,(flag,bud) in enumerate(self.buddies): 
     537            if not flag: continue 
     538            if self.bud_var[idx].get() and not (bud in self.buddyList): 
     539                self.buddyList.append(bud) 
     540 
     541        self.isOk = True 
     542        self.destroy() 
     543 
     544    def onCancel(self): 
     545        self.destroy() 
  • pjproject/trunk/pjsip-apps/src/pygui/chatgui.py

    r4757 r5638  
    2121import sys 
    2222if sys.version_info[0] >= 3: # Python 3 
    23         import tkinter as tk 
    24         from tkinter import ttk 
    25         from tkinter import messagebox as msgbox 
     23    import tkinter as tk 
     24    from tkinter import ttk 
     25    from tkinter import messagebox as msgbox 
    2626else: 
    27         import Tkinter as tk 
    28         import ttk 
    29         import tkMessageBox as msgbox 
     27    import Tkinter as tk 
     28    import ttk 
     29    import tkMessageBox as msgbox 
    3030 
    3131 
    3232class TextObserver: 
    33         def onSendMessage(self, msg): 
    34                 pass 
    35         def onStartTyping(self): 
    36                 pass 
    37         def onStopTyping(self): 
    38                 pass 
    39                  
     33    def onSendMessage(self, msg): 
     34        pass 
     35    def onStartTyping(self): 
     36        pass 
     37    def onStopTyping(self): 
     38        pass 
     39 
    4040class TextFrame(ttk.Frame): 
    41         def __init__(self, master, observer): 
    42                 ttk.Frame.__init__(self, master) 
    43                 self._observer = observer 
    44                 self._isTyping = False 
    45                 self._createWidgets() 
    46  
    47         def _onSendMessage(self, event): 
    48                 send_text = self._typingBox.get("1.0", tk.END).strip() 
    49                 if send_text == '': 
    50                         return 
    51                  
    52                 self.addMessage('me: ' + send_text) 
    53                 self._typingBox.delete("0.0", tk.END) 
    54                 self._onTyping(None) 
    55                  
    56                 # notify app for sending message 
    57                 self._observer.onSendMessage(send_text) 
    58                  
    59         def _onTyping(self, event): 
    60                 # notify app for typing indication 
    61                 is_typing = self._typingBox.get("1.0", tk.END).strip() != '' 
    62                 if is_typing != self._isTyping: 
    63                         self._isTyping = is_typing 
    64                         if is_typing: 
    65                                 self._observer.onStartTyping() 
    66                         else: 
    67                                 self._observer.onStopTyping() 
    68                  
    69         def _createWidgets(self): 
    70                 self.rowconfigure(0, weight=1) 
    71                 self.rowconfigure(1, weight=0) 
    72                 self.rowconfigure(2, weight=0) 
    73                 self.columnconfigure(0, weight=1) 
    74                 self.columnconfigure(1, weight=0) 
    75                  
    76                 self._text = tk.Text(self, width=50, height=30, font=("Arial", "10")) 
    77                 self._text.grid(row=0, column=0, sticky='nswe') 
    78                 self._text.config(state=tk.DISABLED) 
    79                 self._text.tag_config("info", foreground="darkgray", font=("Arial", "9", "italic")) 
    80                  
    81                 scrl = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self._text.yview) 
    82                 self._text.config(yscrollcommand=scrl.set) 
    83                 scrl.grid(row=0, column=1, sticky='nsw') 
    84                  
    85                 self._typingBox = tk.Text(self, width=50, height=1, font=("Arial", "10")) 
    86                 self._typingBox.grid(row=1, columnspan=2, sticky='we', pady=0) 
    87                  
    88                 self._statusBar = tk.Label(self, anchor='w', font=("Arial", "8", "italic")) 
    89                 self._statusBar.grid(row=2, columnspan=2, sticky='we') 
    90                  
    91                 self._typingBox.bind('<Return>', self._onSendMessage) 
    92                 self._typingBox.bind("<Key>", self._onTyping) 
    93                 self._typingBox.focus_set() 
    94                  
    95         def addMessage(self, msg, is_chat = True): 
    96                 self._text.config(state=tk.NORMAL) 
    97                 if is_chat: 
    98                         self._text.insert(tk.END, msg+'\r\n') 
    99                 else: 
    100                         self._text.insert(tk.END, msg+'\r\n', 'info') 
    101                 self._text.config(state=tk.DISABLED) 
    102                 self._text.yview(tk.END) 
    103  
    104         def setTypingIndication(self, who, is_typing): 
    105                 if is_typing: 
    106                         self._statusBar['text'] = "'%s' is typing.." % (who) 
    107                 else: 
    108                         self._statusBar['text'] = '' 
     41    def __init__(self, master, observer): 
     42        ttk.Frame.__init__(self, master) 
     43        self._observer = observer 
     44        self._isTyping = False 
     45        self._createWidgets() 
     46 
     47    def _onSendMessage(self, event): 
     48        send_text = self._typingBox.get("1.0", tk.END).strip() 
     49        if send_text == '': 
     50            return 
     51 
     52        self.addMessage('me: ' + send_text) 
     53        self._typingBox.delete("0.0", tk.END) 
     54        self._onTyping(None) 
     55 
     56        # notify app for sending message 
     57        self._observer.onSendMessage(send_text) 
     58 
     59    def _onTyping(self, event): 
     60        # notify app for typing indication 
     61        is_typing = self._typingBox.get("1.0", tk.END).strip() != '' 
     62        if is_typing != self._isTyping: 
     63            self._isTyping = is_typing 
     64            if is_typing: 
     65                self._observer.onStartTyping() 
     66            else: 
     67                self._observer.onStopTyping() 
     68 
     69    def _createWidgets(self): 
     70        self.rowconfigure(0, weight=1) 
     71        self.rowconfigure(1, weight=0) 
     72        self.rowconfigure(2, weight=0) 
     73        self.columnconfigure(0, weight=1) 
     74        self.columnconfigure(1, weight=0) 
     75 
     76        self._text = tk.Text(self, width=50, height=30, font=("Arial", "10")) 
     77        self._text.grid(row=0, column=0, sticky='nswe') 
     78        self._text.config(state=tk.DISABLED) 
     79        self._text.tag_config("info", foreground="darkgray", font=("Arial", "9", "italic")) 
     80 
     81        scrl = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self._text.yview) 
     82        self._text.config(yscrollcommand=scrl.set) 
     83        scrl.grid(row=0, column=1, sticky='nsw') 
     84 
     85        self._typingBox = tk.Text(self, width=50, height=1, font=("Arial", "10")) 
     86        self._typingBox.grid(row=1, columnspan=2, sticky='we', pady=0) 
     87 
     88        self._statusBar = tk.Label(self, anchor='w', font=("Arial", "8", "italic")) 
     89        self._statusBar.grid(row=2, columnspan=2, sticky='we') 
     90 
     91        self._typingBox.bind('<Return>', self._onSendMessage) 
     92        self._typingBox.bind("<Key>", self._onTyping) 
     93        self._typingBox.focus_set() 
     94 
     95    def addMessage(self, msg, is_chat = True): 
     96        self._text.config(state=tk.NORMAL) 
     97        if is_chat: 
     98            self._text.insert(tk.END, msg+'\r\n') 
     99        else: 
     100            self._text.insert(tk.END, msg+'\r\n', 'info') 
     101        self._text.config(state=tk.DISABLED) 
     102        self._text.yview(tk.END) 
     103 
     104    def setTypingIndication(self, who, is_typing): 
     105        if is_typing: 
     106            self._statusBar['text'] = "'%s' is typing.." % (who) 
     107        else: 
     108            self._statusBar['text'] = '' 
    109109 
    110110class AudioState: 
    111         NULL, INITIALIZING, CONNECTED, DISCONNECTED, FAILED = range(5) 
    112                          
     111    NULL, INITIALIZING, CONNECTED, DISCONNECTED, FAILED = range(5) 
     112 
    113113class AudioObserver: 
    114         def onHangup(self, peer_uri): 
    115                 pass 
    116         def onHold(self, peer_uri): 
    117                 pass 
    118         def onUnhold(self, peer_uri): 
    119                 pass 
    120         def onRxMute(self, peer_uri, is_muted): 
    121                 pass 
    122         def onRxVol(self, peer_uri, vol_pct): 
    123                 pass 
    124         def onTxMute(self, peer_uri, is_muted): 
    125                 pass 
    126                          
     114    def onHangup(self, peer_uri): 
     115        pass 
     116    def onHold(self, peer_uri): 
     117        pass 
     118    def onUnhold(self, peer_uri): 
     119        pass 
     120    def onRxMute(self, peer_uri, is_muted): 
     121        pass 
     122    def onRxVol(self, peer_uri, vol_pct): 
     123        pass 
     124    def onTxMute(self, peer_uri, is_muted): 
     125        pass 
     126 
    127127 
    128128class AudioFrame(ttk.Labelframe): 
    129         def __init__(self, master, peer_uri, observer): 
    130                 ttk.Labelframe.__init__(self, master, text=peer_uri) 
    131                 self.peerUri = peer_uri 
    132                 self._observer = observer 
    133                 self._initFrame = None 
    134                 self._callFrame = None 
    135                 self._rxMute = False 
    136                 self._txMute = False 
    137                 self._state = AudioState.NULL 
    138                  
    139                 self._createInitWidgets() 
    140                 self._createWidgets() 
    141                  
    142         def updateState(self, state): 
    143                 if self._state == state: 
    144                         return 
    145  
    146                 if state == AudioState.INITIALIZING: 
    147                         self._callFrame.pack_forget() 
    148                         self._initFrame.pack(fill=tk.BOTH) 
    149                         self._btnCancel.pack(side=tk.TOP) 
    150                         self._lblInitState['text'] = 'Intializing..' 
    151  
    152                 elif state == AudioState.CONNECTED: 
    153                         self._initFrame.pack_forget() 
    154                         self._callFrame.pack(fill=tk.BOTH)                       
    155                 else: 
    156                         self._callFrame.pack_forget() 
    157                         self._initFrame.pack(fill=tk.BOTH) 
    158                         if state == AudioState.FAILED: 
    159                                 self._lblInitState['text'] = 'Failed' 
    160                         else: 
    161                                 self._lblInitState['text'] = 'Normal cleared' 
    162                                 self._btnCancel.pack_forget() 
    163                          
    164                         self._btnHold['text'] = 'Hold' 
    165                         self._btnHold.config(state=tk.NORMAL) 
    166                         self._rxMute = False 
    167                         self._txMute = False 
    168                         self.btnRxMute['text'] = 'Mute' 
    169                         self.btnTxMute['text'] = 'Mute' 
    170                         self.rxVol.set(5.0) 
    171                  
    172                 # save last state 
    173                 self._state = state 
    174                  
    175         def setStatsText(self, stats_str): 
    176                 self.stat.config(state=tk.NORMAL) 
    177                 self.stat.delete("0.0", tk.END) 
    178                 self.stat.insert(tk.END, stats_str) 
    179                 self.stat.config(state=tk.DISABLED) 
    180                  
    181         def _onHold(self): 
    182                 self._btnHold.config(state=tk.DISABLED) 
    183                 # notify app 
    184                 if self._btnHold['text'] == 'Hold': 
    185                         self._observer.onHold(self.peerUri) 
    186                         self._btnHold['text'] = 'Unhold' 
    187                 else: 
    188                         self._observer.onUnhold(self.peerUri) 
    189                         self._btnHold['text'] = 'Hold' 
    190                 self._btnHold.config(state=tk.NORMAL) 
    191  
    192         def _onHangup(self): 
    193                 # notify app 
    194                 self._observer.onHangup(self.peerUri) 
    195  
    196         def _onRxMute(self): 
    197                 # notify app 
    198                 self._rxMute = not self._rxMute 
    199                 self._observer.onRxMute(self.peerUri, self._rxMute) 
    200                 self.btnRxMute['text'] = 'Unmute' if self._rxMute else 'Mute' 
    201                  
    202         def _onRxVol(self, event): 
    203                 # notify app 
    204                 vol = self.rxVol.get() 
    205                 self._observer.onRxVol(self.peerUri, vol*10.0) 
    206  
    207         def _onTxMute(self): 
    208                 # notify app 
    209                 self._txMute = not self._txMute 
    210                 self._observer.onTxMute(self.peerUri, self._txMute) 
    211                 self.btnTxMute['text'] = 'Unmute' if self._txMute else 'Mute' 
    212  
    213         def _createInitWidgets(self): 
    214                 self._initFrame = ttk.Frame(self) 
    215                 #self._initFrame.pack(fill=tk.BOTH) 
    216  
    217          
    218                 self._lblInitState = tk.Label(self._initFrame, font=("Arial", "12"), text='') 
    219                 self._lblInitState.pack(side=tk.TOP, fill=tk.X, expand=1) 
    220                  
    221                 # Operation: cancel/kick 
    222                 self._btnCancel = ttk.Button(self._initFrame, text = 'Cancel', command=self._onHangup) 
    223                 self._btnCancel.pack(side=tk.TOP) 
    224                                  
    225         def _createWidgets(self): 
    226                 self._callFrame = ttk.Frame(self) 
    227                 #self._callFrame.pack(fill=tk.BOTH) 
    228                  
    229                 # toolbar 
    230                 toolbar = ttk.Frame(self._callFrame) 
    231                 toolbar.pack(side=tk.TOP, fill=tk.X) 
    232                 self._btnHold = ttk.Button(toolbar, text='Hold', command=self._onHold) 
    233                 self._btnHold.pack(side=tk.LEFT, fill=tk.Y) 
    234                 #self._btnXfer = ttk.Button(toolbar, text='Transfer..') 
    235                 #self._btnXfer.pack(side=tk.LEFT, fill=tk.Y) 
    236                 self._btnHangUp = ttk.Button(toolbar, text='Hangup', command=self._onHangup) 
    237                 self._btnHangUp.pack(side=tk.LEFT, fill=tk.Y) 
    238  
    239                 # volume tool 
    240                 vol_frm = ttk.Frame(self._callFrame) 
    241                 vol_frm.pack(side=tk.TOP, fill=tk.X) 
    242                  
    243                 self.rxVolFrm = ttk.Labelframe(vol_frm, text='RX volume') 
    244                 self.rxVolFrm.pack(side=tk.LEFT, fill=tk.Y) 
    245                  
    246                 self.btnRxMute = ttk.Button(self.rxVolFrm, width=8, text='Mute', command=self._onRxMute) 
    247                 self.btnRxMute.pack(side=tk.LEFT) 
    248                 self.rxVol = tk.Scale(self.rxVolFrm, orient=tk.HORIZONTAL, from_=0.0, to=10.0, showvalue=1) #, tickinterval=10.0, showvalue=1) 
    249                 self.rxVol.set(5.0) 
    250                 self.rxVol.bind("<ButtonRelease-1>", self._onRxVol) 
    251                 self.rxVol.pack(side=tk.LEFT) 
    252                  
    253                 self.txVolFrm = ttk.Labelframe(vol_frm, text='TX volume') 
    254                 self.txVolFrm.pack(side=tk.RIGHT, fill=tk.Y) 
    255                  
    256                 self.btnTxMute = ttk.Button(self.txVolFrm, width=8, text='Mute', command=self._onTxMute) 
    257                 self.btnTxMute.pack(side=tk.LEFT) 
    258                  
    259                 # stat 
    260                 self.stat = tk.Text(self._callFrame, width=10, height=2, bg='lightgray', relief=tk.FLAT, font=("Courier", "9")) 
    261                 self.stat.insert(tk.END, 'stat here') 
    262                 self.stat.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1) 
     129    def __init__(self, master, peer_uri, observer): 
     130        ttk.Labelframe.__init__(self, master, text=peer_uri) 
     131        self.peerUri = peer_uri 
     132        self._observer = observer 
     133        self._initFrame = None 
     134        self._callFrame = None 
     135        self._rxMute = False 
     136        self._txMute = False 
     137        self._state = AudioState.NULL 
     138 
     139        self._createInitWidgets() 
     140        self._createWidgets() 
     141 
     142    def updateState(self, state): 
     143        if self._state == state: 
     144            return 
     145 
     146        if state == AudioState.INITIALIZING: 
     147            self._callFrame.pack_forget() 
     148            self._initFrame.pack(fill=tk.BOTH) 
     149            self._btnCancel.pack(side=tk.TOP) 
     150            self._lblInitState['text'] = 'Intializing..' 
     151 
     152        elif state == AudioState.CONNECTED: 
     153            self._initFrame.pack_forget() 
     154            self._callFrame.pack(fill=tk.BOTH) 
     155        else: 
     156            self._callFrame.pack_forget() 
     157            self._initFrame.pack(fill=tk.BOTH) 
     158            if state == AudioState.FAILED: 
     159                self._lblInitState['text'] = 'Failed' 
     160            else: 
     161                self._lblInitState['text'] = 'Normal cleared' 
     162                self._btnCancel.pack_forget() 
     163 
     164            self._btnHold['text'] = 'Hold' 
     165            self._btnHold.config(state=tk.NORMAL) 
     166            self._rxMute = False 
     167            self._txMute = False 
     168            self.btnRxMute['text'] = 'Mute' 
     169            self.btnTxMute['text'] = 'Mute' 
     170            self.rxVol.set(5.0) 
     171 
     172        # save last state 
     173        self._state = state 
     174 
     175    def setStatsText(self, stats_str): 
     176        self.stat.config(state=tk.NORMAL) 
     177        self.stat.delete("0.0", tk.END) 
     178        self.stat.insert(tk.END, stats_str) 
     179        self.stat.config(state=tk.DISABLED) 
     180 
     181    def _onHold(self): 
     182        self._btnHold.config(state=tk.DISABLED) 
     183        # notify app 
     184        if self._btnHold['text'] == 'Hold': 
     185            self._observer.onHold(self.peerUri) 
     186            self._btnHold['text'] = 'Unhold' 
     187        else: 
     188            self._observer.onUnhold(self.peerUri) 
     189            self._btnHold['text'] = 'Hold' 
     190        self._btnHold.config(state=tk.NORMAL) 
     191 
     192    def _onHangup(self): 
     193        # notify app 
     194        self._observer.onHangup(self.peerUri) 
     195 
     196    def _onRxMute(self): 
     197        # notify app 
     198        self._rxMute = not self._rxMute 
     199        self._observer.onRxMute(self.peerUri, self._rxMute) 
     200        self.btnRxMute['text'] = 'Unmute' if self._rxMute else 'Mute' 
     201 
     202    def _onRxVol(self, event): 
     203        # notify app 
     204        vol = self.rxVol.get() 
     205        self._observer.onRxVol(self.peerUri, vol*10.0) 
     206 
     207    def _onTxMute(self): 
     208        # notify app 
     209        self._txMute = not self._txMute 
     210        self._observer.onTxMute(self.peerUri, self._txMute) 
     211        self.btnTxMute['text'] = 'Unmute' if self._txMute else 'Mute' 
     212 
     213    def _createInitWidgets(self): 
     214        self._initFrame = ttk.Frame(self) 
     215        #self._initFrame.pack(fill=tk.BOTH) 
     216 
     217 
     218        self._lblInitState = tk.Label(self._initFrame, font=("Arial", "12"), text='') 
     219        self._lblInitState.pack(side=tk.TOP, fill=tk.X, expand=1) 
     220 
     221        # Operation: cancel/kick 
     222        self._btnCancel = ttk.Button(self._initFrame, text = 'Cancel', command=self._onHangup) 
     223        self._btnCancel.pack(side=tk.TOP) 
     224 
     225    def _createWidgets(self): 
     226        self._callFrame = ttk.Frame(self) 
     227        #self._callFrame.pack(fill=tk.BOTH) 
     228 
     229        # toolbar 
     230        toolbar = ttk.Frame(self._callFrame) 
     231        toolbar.pack(side=tk.TOP, fill=tk.X) 
     232        self._btnHold = ttk.Button(toolbar, text='Hold', command=self._onHold) 
     233        self._btnHold.pack(side=tk.LEFT, fill=tk.Y) 
     234        #self._btnXfer = ttk.Button(toolbar, text='Transfer..') 
     235        #self._btnXfer.pack(side=tk.LEFT, fill=tk.Y) 
     236        self._btnHangUp = ttk.Button(toolbar, text='Hangup', command=self._onHangup) 
     237        self._btnHangUp.pack(side=tk.LEFT, fill=tk.Y) 
     238 
     239        # volume tool 
     240        vol_frm = ttk.Frame(self._callFrame) 
     241        vol_frm.pack(side=tk.TOP, fill=tk.X) 
     242 
     243        self.rxVolFrm = ttk.Labelframe(vol_frm, text='RX volume') 
     244        self.rxVolFrm.pack(side=tk.LEFT, fill=tk.Y) 
     245 
     246        self.btnRxMute = ttk.Button(self.rxVolFrm, width=8, text='Mute', command=self._onRxMute) 
     247        self.btnRxMute.pack(side=tk.LEFT) 
     248        self.rxVol = tk.Scale(self.rxVolFrm, orient=tk.HORIZONTAL, from_=0.0, to=10.0, showvalue=1) #, tickinterval=10.0, showvalue=1) 
     249        self.rxVol.set(5.0) 
     250        self.rxVol.bind("<ButtonRelease-1>", self._onRxVol) 
     251        self.rxVol.pack(side=tk.LEFT) 
     252 
     253        self.txVolFrm = ttk.Labelframe(vol_frm, text='TX volume') 
     254        self.txVolFrm.pack(side=tk.RIGHT, fill=tk.Y) 
     255 
     256        self.btnTxMute = ttk.Button(self.txVolFrm, width=8, text='Mute', command=self._onTxMute) 
     257        self.btnTxMute.pack(side=tk.LEFT) 
     258 
     259        # stat 
     260        self.stat = tk.Text(self._callFrame, width=10, height=2, bg='lightgray', relief=tk.FLAT, font=("Courier", "9")) 
     261        self.stat.insert(tk.END, 'stat here') 
     262        self.stat.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1) 
    263263 
    264264 
    265265class ChatObserver(TextObserver, AudioObserver): 
    266         def onAddParticipant(self): 
    267                 pass 
    268         def onStartAudio(self): 
    269                 pass 
    270         def onStopAudio(self): 
    271                 pass 
    272         def onCloseWindow(self): 
    273                 pass 
    274                  
     266    def onAddParticipant(self): 
     267        pass 
     268    def onStartAudio(self): 
     269        pass 
     270    def onStopAudio(self): 
     271        pass 
     272    def onCloseWindow(self): 
     273        pass 
     274 
    275275class ChatFrame(tk.Toplevel): 
    276         """ 
    277         Room 
    278         """ 
    279         def __init__(self, observer): 
    280                 tk.Toplevel.__init__(self) 
    281                 self.protocol("WM_DELETE_WINDOW", self._onClose) 
    282                 self._observer = observer 
    283  
    284                 self._text = None 
    285                 self._text_shown = True 
    286                  
    287                 self._audioEnabled = False 
    288                 self._audioFrames = [] 
    289                 self._createWidgets() 
    290          
    291         def _createWidgets(self): 
    292                 # toolbar 
    293                 self.toolbar = ttk.Frame(self) 
    294                 self.toolbar.pack(side=tk.TOP, fill=tk.BOTH) 
    295                  
    296                 btnText = ttk.Button(self.toolbar, text='Show/hide text', command=self._onShowHideText) 
    297                 btnText.pack(side=tk.LEFT, fill=tk.Y) 
    298                 btnAudio = ttk.Button(self.toolbar, text='Start/stop audio', command=self._onStartStopAudio) 
    299                 btnAudio.pack(side=tk.LEFT, fill=tk.Y) 
    300                  
    301                 ttk.Separator(self.toolbar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx = 4) 
    302  
    303                 btnAdd = ttk.Button(self.toolbar, text='Add participant..', command=self._onAddParticipant) 
    304                 btnAdd.pack(side=tk.LEFT, fill=tk.Y) 
    305  
    306                 # media frame 
    307                 self.media = ttk.Frame(self) 
    308                 self.media.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1) 
    309                  
    310                 # create Text Chat frame 
    311                 self.media_left = ttk.Frame(self.media) 
    312                 self._text = TextFrame(self.media_left, self._observer) 
    313                 self._text.pack(fill=tk.BOTH, expand=1) 
    314                 self.media_left.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 
    315                  
    316                 # create other media frame 
    317                 self.media_right = ttk.Frame(self.media) 
    318                  
    319         def _arrangeMediaFrames(self): 
    320                 if len(self._audioFrames) == 0: 
    321                         self.media_right.pack_forget() 
    322                         return 
    323                  
    324                 self.media_right.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1) 
    325                 MAX_ROWS = 3 
    326                 row_num = 0 
    327                 col_num = 1 
    328                 for frm in self._audioFrames: 
    329                         frm.grid(row=row_num, column=col_num, sticky='nsew', padx=5, pady=5) 
    330                         row_num += 1 
    331                         if row_num >= MAX_ROWS: 
    332                                 row_num  = 0 
    333                                 col_num += 1 
    334          
    335         def _onShowHideText(self): 
    336                 self.textShowHide(not self._text_shown) 
    337                  
    338         def _onAddParticipant(self): 
    339                 self._observer.onAddParticipant() 
    340          
    341         def _onStartStopAudio(self): 
    342                 self._audioEnabled = not self._audioEnabled 
    343                 if self._audioEnabled: 
    344                         self._observer.onStartAudio() 
    345                 else: 
    346                         self._observer.onStopAudio() 
    347                 self.enableAudio(self._audioEnabled) 
    348                  
    349         def _onClose(self): 
    350                 self._observer.onCloseWindow() 
    351                          
    352         # APIs 
    353          
    354         def bringToFront(self): 
    355                 self.deiconify() 
    356                 self.lift() 
    357                 self._text._typingBox.focus_set() 
    358                  
    359         def textAddMessage(self, msg, is_chat = True): 
    360                 self._text.addMessage(msg, is_chat) 
    361                  
    362         def textSetTypingIndication(self, who, is_typing = True): 
    363                 self._text.setTypingIndication(who, is_typing) 
    364                  
    365         def addParticipant(self, participant_uri): 
    366                 aud_frm = AudioFrame(self.media_right, participant_uri, self._observer) 
    367                 self._audioFrames.append(aud_frm) 
    368          
    369         def delParticipant(self, participant_uri): 
    370                 for aud_frm in self._audioFrames: 
    371                         if participant_uri == aud_frm.peerUri: 
    372                                 self._audioFrames.remove(aud_frm) 
    373                                 # need to delete aud_frm manually? 
    374                                 aud_frm.destroy() 
    375                                 return 
    376  
    377         def textShowHide(self, show = True): 
    378                 if show: 
    379                         self.media_left.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 
    380                         self._text._typingBox.focus_set() 
    381                 else: 
    382                         self.media_left.pack_forget() 
    383                 self._text_shown = show 
    384          
    385         def enableAudio(self, is_enabled = True): 
    386                 if is_enabled: 
    387                         self._arrangeMediaFrames() 
    388                 else: 
    389                         self.media_right.pack_forget() 
    390                 self._audioEnabled = is_enabled 
    391                          
    392         def audioUpdateState(self, participant_uri, state): 
    393                 for aud_frm in self._audioFrames: 
    394                         if participant_uri == aud_frm.peerUri: 
    395                                 aud_frm.updateState(state) 
    396                                 break 
    397                 if state >= AudioState.DISCONNECTED and len(self._audioFrames) == 1: 
    398                         self.enableAudio(False) 
    399                 else: 
    400                         self.enableAudio(True) 
    401                          
    402         def audioSetStatsText(self, participant_uri, stats_str): 
    403                 for aud_frm in self._audioFrames: 
    404                         if participant_uri == aud_frm.peerUri: 
    405                                 aud_frm.setStatsText(stats_str) 
    406                                 break 
    407                                  
     276    """ 
     277    Room 
     278    """ 
     279    def __init__(self, observer): 
     280        tk.Toplevel.__init__(self) 
     281        self.protocol("WM_DELETE_WINDOW", self._onClose) 
     282        self._observer = observer 
     283 
     284        self._text = None 
     285        self._text_shown = True 
     286 
     287        self._audioEnabled = False 
     288        self._audioFrames = [] 
     289        self._createWidgets() 
     290 
     291    def _createWidgets(self): 
     292        # toolbar 
     293        self.toolbar = ttk.Frame(self) 
     294        self.toolbar.pack(side=tk.TOP, fill=tk.BOTH) 
     295 
     296        btnText = ttk.Button(self.toolbar, text='Show/hide text', command=self._onShowHideText) 
     297        btnText.pack(side=tk.LEFT, fill=tk.Y) 
     298        btnAudio = ttk.Button(self.toolbar, text='Start/stop audio', command=self._onStartStopAudio) 
     299        btnAudio.pack(side=tk.LEFT, fill=tk.Y) 
     300 
     301        ttk.Separator(self.toolbar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx = 4) 
     302 
     303        btnAdd = ttk.Button(self.toolbar, text='Add participant..', command=self._onAddParticipant) 
     304        btnAdd.pack(side=tk.LEFT, fill=tk.Y) 
     305 
     306        # media frame 
     307        self.media = ttk.Frame(self) 
     308        self.media.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=1) 
     309 
     310        # create Text Chat frame 
     311        self.media_left = ttk.Frame(self.media) 
     312        self._text = TextFrame(self.media_left, self._observer) 
     313        self._text.pack(fill=tk.BOTH, expand=1) 
     314        self.media_left.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 
     315 
     316        # create other media frame 
     317        self.media_right = ttk.Frame(self.media) 
     318 
     319    def _arrangeMediaFrames(self): 
     320        if len(self._audioFrames) == 0: 
     321            self.media_right.pack_forget() 
     322            return 
     323 
     324        self.media_right.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1) 
     325        MAX_ROWS = 3 
     326        row_num = 0 
     327        col_num = 1 
     328        for frm in self._audioFrames: 
     329            frm.grid(row=row_num, column=col_num, sticky='nsew', padx=5, pady=5) 
     330            row_num += 1 
     331            if row_num >= MAX_ROWS: 
     332                row_num  = 0 
     333                col_num += 1 
     334 
     335    def _onShowHideText(self): 
     336        self.textShowHide(not self._text_shown) 
     337 
     338    def _onAddParticipant(self): 
     339        self._observer.onAddParticipant() 
     340 
     341    def _onStartStopAudio(self): 
     342        self._audioEnabled = not self._audioEnabled 
     343        if self._audioEnabled: 
     344            self._observer.onStartAudio() 
     345        else: 
     346            self._observer.onStopAudio() 
     347        self.enableAudio(self._audioEnabled) 
     348 
     349    def _onClose(self): 
     350        self._observer.onCloseWindow() 
     351 
     352    # APIs 
     353 
     354    def bringToFront(self): 
     355        self.deiconify() 
     356        self.lift() 
     357        self._text._typingBox.focus_set() 
     358 
     359    def textAddMessage(self, msg, is_chat = True): 
     360        self._text.addMessage(msg, is_chat) 
     361 
     362    def textSetTypingIndication(self, who, is_typing = True): 
     363        self._text.setTypingIndication(who, is_typing) 
     364 
     365    def addParticipant(self, participant_uri): 
     366        aud_frm = AudioFrame(self.media_right, participant_uri, self._observer) 
     367        self._audioFrames.append(aud_frm) 
     368 
     369    def delParticipant(self, participant_uri): 
     370        for aud_frm in self._audioFrames: 
     371            if participant_uri == aud_frm.peerUri: 
     372                self._audioFrames.remove(aud_frm) 
     373                # need to delete aud_frm manually? 
     374                aud_frm.destroy() 
     375                return 
     376 
     377    def textShowHide(self, show = True): 
     378        if show: 
     379            self.media_left.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) 
     380            self._text._typingBox.focus_set() 
     381        else: 
     382            self.media_left.pack_forget() 
     383        self._text_shown = show 
     384 
     385    def enableAudio(self, is_enabled = True): 
     386        if is_enabled: 
     387            self._arrangeMediaFrames() 
     388        else: 
     389            self.media_right.pack_forget() 
     390        self._audioEnabled = is_enabled 
     391 
     392    def audioUpdateState(self, participant_uri, state): 
     393        for aud_frm in self._audioFrames: 
     394            if participant_uri == aud_frm.peerUri: 
     395                aud_frm.updateState(state) 
     396                break 
     397        if state >= AudioState.DISCONNECTED and len(self._audioFrames) == 1: 
     398            self.enableAudio(False) 
     399        else: 
     400            self.enableAudio(True) 
     401 
     402    def audioSetStatsText(self, participant_uri, stats_str): 
     403        for aud_frm in self._audioFrames: 
     404            if participant_uri == aud_frm.peerUri: 
     405                aud_frm.setStatsText(stats_str) 
     406                break 
     407 
    408408if __name__ == '__main__': 
    409         root = tk.Tk() 
    410         root.title("Chat") 
    411         root.columnconfigure(0, weight=1) 
    412         root.rowconfigure(0, weight=1) 
    413          
    414         obs = ChatObserver() 
    415         dlg = ChatFrame(obs) 
    416         #dlg = TextFrame(root) 
    417         #dlg = AudioFrame(root) 
    418  
    419         #dlg.pack(fill=tk.BOTH, expand=1) 
    420         root.mainloop() 
     409    root = tk.Tk() 
     410    root.title("Chat") 
     411    root.columnconfigure(0, weight=1) 
     412    root.rowconfigure(0, weight=1) 
     413 
     414    obs = ChatObserver() 
     415    dlg = ChatFrame(obs) 
     416    #dlg = TextFrame(root) 
     417    #dlg = AudioFrame(root) 
     418 
     419    #dlg.pack(fill=tk.BOTH, expand=1) 
     420    root.mainloop() 
  • pjproject/trunk/pjsip-apps/src/pygui/endpoint.py

    r4704 r5638  
    2121import sys 
    2222if sys.version_info[0] >= 3: # Python 3 
    23         import tkinter as tk 
    24         from tkinter import ttk 
    25         from tkinter import messagebox as msgbox 
     23    import tkinter as tk 
     24    from tkinter import ttk 
     25    from tkinter import messagebox as msgbox 
    2626else: 
    27         import Tkinter as tk 
    28         import tkMessageBox as msgbox 
    29         import ttk 
     27    import Tkinter as tk 
     28    import tkMessageBox as msgbox 
     29    import ttk 
    3030 
    3131import pjsua2 as pj 
     
    3434 
    3535class Endpoint(pj.Endpoint): 
    36         """ 
    37         This is high level Python object inherited from pj.Endpoint 
    38         """ 
    39         instance = None 
    40         def __init__(self): 
    41                 pj.Endpoint.__init__(self) 
    42                 Endpoint.instance = self 
    43          
    44          
     36    """ 
     37    This is high level Python object inherited from pj.Endpoint 
     38    """ 
     39    instance = None 
     40    def __init__(self): 
     41        pj.Endpoint.__init__(self) 
     42        Endpoint.instance = self 
     43 
     44 
    4545def validateUri(uri): 
    46         return Endpoint.instance.utilVerifyUri(uri) == pj.PJ_SUCCESS 
     46    return Endpoint.instance.utilVerifyUri(uri) == pj.PJ_SUCCESS 
    4747 
    4848def validateSipUri(uri): 
    49         return Endpoint.instance.utilVerifySipUri(uri) == pj.PJ_SUCCESS 
     49    return Endpoint.instance.utilVerifySipUri(uri) == pj.PJ_SUCCESS 
    5050 
    5151 
    5252if __name__ == '__main__': 
    53         application.main() 
     53    application.main() 
  • pjproject/trunk/pjsip-apps/src/pygui/log.py

    r4704 r5638  
    2121import sys 
    2222if sys.version_info[0] >= 3: # Python 3 
    23         import tkinter as tk 
    24         from tkinter import ttk 
    25         from tkinter import messagebox as msgbox 
     23    import tkinter as tk 
     24    from tkinter import ttk 
     25    from tkinter import messagebox as msgbox 
    2626else: 
    27         import Tkinter as tk 
    28         import tkMessageBox as msgbox 
    29         import ttk 
     27    import Tkinter as tk 
     28    import tkMessageBox as msgbox 
     29    import ttk 
    3030 
    3131import pjsua2 as pj 
    3232import application 
    3333 
     34write=sys.stdout.write 
    3435 
    3536class LogWindow(tk.Toplevel): 
    36         """ 
    37         Log window 
    38         """ 
    39         instance = None 
    40         def __init__(self, app): 
    41                 tk.Toplevel.__init__(self, name='logwnd', width=640, height=480) 
    42                 LogWindow.instance = self 
    43                 self.app = app 
    44                 self.state('withdrawn') 
    45                 self.title('Log') 
    46                 self._createWidgets() 
    47                 self.protocol("WM_DELETE_WINDOW", self._onHide) 
     37    """ 
     38    Log window 
     39    """ 
     40    instance = None 
     41    def __init__(self, app): 
     42        tk.Toplevel.__init__(self, name='logwnd', width=640, height=480) 
     43        LogWindow.instance = self 
     44        self.app = app 
     45        self.state('withdrawn') 
     46        self.title('Log') 
     47        self._createWidgets() 
     48        self.protocol("WM_DELETE_WINDOW", self._onHide) 
    4849 
    49         def addLog(self, entry): 
    50                 """entry fields: 
    51                     int         level; 
    52                     string      msg; 
    53                     long        threadId; 
    54                     string      threadName; 
    55                 """ 
    56                 self.addLog2(entry.level, entry.msg) 
    57                  
    58         def addLog2(self, level, msg): 
    59                 if level==5: 
    60                         tags = ('trace',) 
    61                 elif level==3: 
    62                         tags = ('info',) 
    63                 elif level==2: 
    64                         tags = ('warning',) 
    65                 elif level<=1: 
    66                         tags = ('error',) 
    67                 else: 
    68                         tags = None 
    69                 self.text.insert(tk.END, msg, tags) 
    70                 self.text.see(tk.END) 
    71                  
    72         def _createWidgets(self): 
    73                 self.rowconfigure(0, weight=1) 
    74                 self.rowconfigure(1, weight=0) 
    75                 self.columnconfigure(0, weight=1) 
    76                 self.columnconfigure(1, weight=0) 
    77                  
    78                 self.text = tk.Text(self, font=('Courier New', '8'), wrap=tk.NONE, undo=False, padx=4, pady=5) 
    79                 self.text.grid(row=0, column=0, sticky='nswe', padx=5, pady=5) 
    80                  
    81                 scrl = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.text.yview) 
    82                 self.text.config(yscrollcommand=scrl.set) 
    83                 scrl.grid(row=0, column=1, sticky='nsw', padx=5, pady=5) 
     50    def addLog(self, entry): 
     51        """entry fields: 
     52            int         level; 
     53            string      msg; 
     54            long        threadId; 
     55            string      threadName; 
     56        """ 
     57        self.addLog2(entry.level, entry.msg) 
    8458 
    85                 scrl = ttk.Scrollbar(self, orient=tk.HORIZONTAL, command=self.text.xview) 
    86                 self.text.config(xscrollcommand=scrl.set) 
    87                 scrl.grid(row=1, column=0, sticky='we', padx=5, pady=5) 
    88                  
    89                 self.text.bind("<Key>", self._onKey) 
    90                  
    91                 self.text.tag_configure('normal', font=('Courier New', '8'), foreground='black') 
    92                 self.text.tag_configure('trace', font=('Courier New', '8'), foreground='#777777') 
    93                 self.text.tag_configure('info', font=('Courier New', '8', 'bold'), foreground='black') 
    94                 self.text.tag_configure('warning', font=('Courier New', '8', 'bold'), foreground='cyan') 
    95                 self.text.tag_configure('error', font=('Courier New', '8', 'bold'), foreground='red') 
    96          
    97         def _onKey(self, event): 
    98                 # Ignore key event to make text widget read-only 
    99                 return "break" 
    100          
    101         def _onHide(self): 
    102                 # Hide when close ('x') button is clicked 
    103                 self.withdraw() 
    104                 self.app.showLogWindow.set(0) 
    105                  
    106          
     59    def addLog2(self, level, msg): 
     60        if level==5: 
     61            tags = ('trace',) 
     62        elif level==3: 
     63            tags = ('info',) 
     64        elif level==2: 
     65            tags = ('warning',) 
     66        elif level<=1: 
     67            tags = ('error',) 
     68        else: 
     69            tags = None 
     70        self.text.insert(tk.END, msg, tags) 
     71        self.text.see(tk.END) 
     72 
     73    def _createWidgets(self): 
     74        self.rowconfigure(0, weight=1) 
     75        self.rowconfigure(1, weight=0) 
     76        self.columnconfigure(0, weight=1) 
     77        self.columnconfigure(1, weight=0) 
     78 
     79        self.text = tk.Text(self, font=('Courier New', '8'), wrap=tk.NONE, undo=False, padx=4, pady=5) 
     80        self.text.grid(row=0, column=0, sticky='nswe', padx=5, pady=5) 
     81 
     82        scrl = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.text.yview) 
     83        self.text.config(yscrollcommand=scrl.set) 
     84        scrl.grid(row=0, column=1, sticky='nsw', padx=5, pady=5) 
     85 
     86        scrl = ttk.Scrollbar(self, orient=tk.HORIZONTAL, command=self.text.xview) 
     87        self.text.config(xscrollcommand=scrl.set) 
     88        scrl.grid(row=1, column=0, sticky='we', padx=5, pady=5) 
     89 
     90        self.text.bind("<Key>", self._onKey) 
     91 
     92        self.text.tag_configure('normal', font=('Courier New', '8'), foreground='black') 
     93        self.text.tag_configure('trace', font=('Courier New', '8'), foreground='#777777') 
     94        self.text.tag_configure('info', font=('Courier New', '8', 'bold'), foreground='black') 
     95        self.text.tag_configure('warning', font=('Courier New', '8', 'bold'), foreground='cyan') 
     96        self.text.tag_configure('error', font=('Courier New', '8', 'bold'), foreground='red') 
     97 
     98    def _onKey(self, event): 
     99        # Ignore key event to make text widget read-only 
     100        return "break" 
     101 
     102    def _onHide(self): 
     103        # Hide when close ('x') button is clicked 
     104        self.withdraw() 
     105        self.app.showLogWindow.set(0) 
     106 
     107 
    107108def writeLog2(level, msg): 
    108         if LogWindow.instance: 
    109                 LogWindow.instance.addLog2(level, msg)   
     109    if LogWindow.instance: 
     110        LogWindow.instance.addLog2(level, msg) 
    110111 
    111112def writeLog(entry): 
    112         if LogWindow.instance: 
    113                 LogWindow.instance.addLog(entry) 
     113    if LogWindow.instance: 
     114        LogWindow.instance.addLog(entry) 
    114115 
    115116class Logger(pj.LogWriter): 
    116         """ 
    117         Logger to receive log messages from pjsua2 
    118         """ 
    119         def __init__(self): 
    120                 pj.LogWriter.__init__(self) 
    121                  
    122         def write(self, entry): 
    123                 print entry.msg, 
    124                 writeLog(entry) 
     117    """ 
     118    Logger to receive log messages from pjsua2 
     119    """ 
     120    def __init__(self): 
     121        pj.LogWriter.__init__(self) 
     122 
     123    def write(self, entry): 
     124        write(entry.msg + "\r\n") 
     125        writeLog(entry) 
    125126 
    126127if __name__ == '__main__': 
    127         application.main() 
     128    application.main() 
  • pjproject/trunk/pjsip-apps/src/swig/python/Makefile

    r4798 r5638  
    11PYTHON_SO=_pjsua2.so 
     2 
     3USE_PYTHON3=1 
     4 
     5ifeq ($(USE_PYTHON3),1) 
     6  PYTHON_EXE=python3 
     7  PYTHON_PKG_DIR=$(HOME)/.local/lib/python3.6/site-packages 
     8else 
     9  PYTHON_EXE=python 
     10  PYTHON_PKG_DIR=$(HOME)/.local/lib/python2.7/site-packages 
     11endif 
    212 
    313#PYTHON_SETUP_FLAGS = --inplace  
     
    1727 
    1828$(PYTHON_SO): pjsua2_wrap.cpp setup.py $(GCC_EXE) 
    19         python setup.py build $(PYTHON_SETUP_FLAGS) 
     29        $(PYTHON_EXE) setup.py build $(PYTHON_SETUP_FLAGS) 
    2030 
    2131gcc.exe: cc_mingw.c 
     
    3141 
    3242install: 
    33         python setup.py install --user 
     43        $(PYTHON_EXE) setup.py install --user 
    3444 
    3545uninstall: 
    36         rm -f $(HOME)/.local/lib/python2.7/site-packages/pjsua2* 
    37         rm -f $(HOME)/.local/lib/python2.7/site-packages/_pjsua2* 
     46        rm -f $(PYTHON_PKG_DIR)/pjsua2* 
     47        rm -f $(PYTHON_PKG_DIR)/_pjsua2* 
    3848 
  • pjproject/trunk/pjsip-apps/src/swig/python/setup.py

    r5281 r5638  
    3030pj_version_rev="" 
    3131pj_version_suffix="" 
     32write=sys.stdout.write 
    3233f = open('../../../../version.mak', 'r') 
    3334for line in f: 
     35    tokens="" 
    3436    if line.find("export PJ_VERSION_MAJOR") != -1: 
    35         tokens=line.split("=") 
    36         if len(tokens)>1: 
    37                 pj_version_major= tokens[1].strip() 
     37        tokens=line.split("=") 
     38    if len(tokens)>1: 
     39        pj_version_major= tokens[1].strip() 
    3840    elif line.find("export PJ_VERSION_MINOR") != -1: 
    39         tokens=line.split("=") 
    40         if len(tokens)>1: 
    41                 pj_version_minor= line.split("=")[1].strip() 
     41        tokens=line.split("=") 
     42    if len(tokens)>1: 
     43        pj_version_minor= line.split("=")[1].strip() 
    4244    elif line.find("export PJ_VERSION_REV") != -1: 
    43         tokens=line.split("=") 
    44         if len(tokens)>1: 
    45                 pj_version_rev= line.split("=")[1].strip() 
     45        tokens=line.split("=") 
     46    if len(tokens)>1: 
     47        pj_version_rev= line.split("=")[1].strip() 
    4648    elif line.find("export PJ_VERSION_SUFFIX") != -1: 
    47         tokens=line.split("=") 
    48         if len(tokens)>1: 
    49                 pj_version_suffix= line.split("=")[1].strip() 
     49        tokens=line.split("=") 
     50    if len(tokens)>1: 
     51        pj_version_suffix= line.split("=")[1].strip() 
    5052 
    5153f.close() 
    5254if not pj_version_major: 
    53     print 'Unable to get PJ_VERSION_MAJOR' 
     55    write("Unable to get PJ_VERSION_MAJOR" + "\r\n") 
    5456    sys.exit(1) 
    5557 
    5658pj_version = pj_version_major + "." + pj_version_minor 
    5759if pj_version_rev: 
    58         pj_version += "." + pj_version_rev 
     60    pj_version += "." + pj_version_rev 
    5961if pj_version_suffix: 
    60         pj_version += "-" + pj_version_suffix 
     62    pj_version += "-" + pj_version_suffix 
    6163 
    6264#print 'PJ_VERSION = "'+ pj_version + '"' 
  • pjproject/trunk/pjsip-apps/src/swig/python/test.py

    r4845 r5638  
    22import sys 
    33import time 
     4 
     5write=sys.stdout.write 
    46 
    57# 
     
    810# 
    911def ua_data_test(): 
    10         # 
    11         # AuthCredInfo 
    12         # 
    13         print "UA data types test.." 
    14         the_realm = "pjsip.org" 
    15         ci = pj.AuthCredInfo() 
    16         ci.realm = the_realm 
    17         ci.dataType = 20 
    18          
    19         ci2 = ci 
    20         assert ci.dataType == 20 
    21         assert ci2.realm == the_realm 
    22          
    23         # 
    24         # UaConfig 
    25         # See here how we manipulate std::vector 
    26         # 
    27         uc = pj.UaConfig() 
    28         uc.maxCalls = 10 
    29         uc.userAgent = "Python" 
    30         uc.nameserver = pj.StringVector(["10.0.0.1", "10.0.0.2"]) 
    31         uc.nameserver.append("NS1") 
    32          
    33         uc2 = uc 
    34         assert uc2.maxCalls == 10 
    35         assert uc2.userAgent == "Python" 
    36         assert len(uc2.nameserver) == 3 
    37         assert uc2.nameserver[0] == "10.0.0.1" 
    38         assert uc2.nameserver[1] == "10.0.0.2" 
    39         assert uc2.nameserver[2] == "NS1" 
     12    # 
     13    # AuthCredInfo 
     14    # 
     15    write("UA data types test..") 
     16    the_realm = "pjsip.org" 
     17    ci = pj.AuthCredInfo() 
     18    ci.realm = the_realm 
     19    ci.dataType = 20 
    4020 
    41         print "  Dumping nameservers: ", 
    42         for s in uc2.nameserver: 
    43                 print s, 
    44         print "" 
     21    ci2 = ci 
     22    assert ci.dataType == 20 
     23    assert ci2.realm == the_realm 
     24 
     25    # 
     26    # UaConfig 
     27    # See here how we manipulate std::vector 
     28    # 
     29    uc = pj.UaConfig() 
     30    uc.maxCalls = 10 
     31    uc.userAgent = "Python" 
     32    uc.nameserver = pj.StringVector(["10.0.0.1", "10.0.0.2"]) 
     33    uc.nameserver.append("NS1") 
     34 
     35    uc2 = uc 
     36    assert uc2.maxCalls == 10 
     37    assert uc2.userAgent == "Python" 
     38    assert len(uc2.nameserver) == 3 
     39    assert uc2.nameserver[0] == "10.0.0.1" 
     40    assert uc2.nameserver[1] == "10.0.0.2" 
     41    assert uc2.nameserver[2] == "NS1" 
     42 
     43    write("  Dumping nameservers: " + "\r\n") 
     44    for s in uc2.nameserver: 
     45        write(s  + "\r\n") 
     46    write("\r\n") 
    4547 
    4648# 
     
    4850# 
    4951def ua_run_test_exception(): 
    50         print "Exception test.." 
    51         ep = pj.Endpoint() 
    52         ep.libCreate() 
    53         got_exception = False 
    54         try: 
    55                 ep.natDetectType() 
    56         except pj.Error, e: 
    57                 got_exception = True 
    58                 print "  Got exception: status=%u, reason=%s,\n  title=%s,\n  srcFile=%s, srcLine=%d" % \ 
    59                         (e.status, e.reason, e.title, e.srcFile, e.srcLine) 
    60                 assert e.status == 370050 
    61                 assert e.reason.find("PJNATH_ESTUNINSERVER") >= 0 
    62                 assert e.title == "pjsua_detect_nat_type()" 
    63         assert got_exception 
     52    write("Exception test.." + "\r\n") 
     53    ep = pj.Endpoint() 
     54    ep.libCreate() 
     55    got_exception = False 
     56    try: 
     57        ep.natDetectType() 
     58    except pj.Error as e: 
     59        #t, e = sys.exc_info()[:2] 
     60        got_exception = True 
     61        write("  Got exception: status=%u, reason=%s,\n  title=%s,\n  srcFile=%s, srcLine=%d" % \ 
     62            (e.status, e.reason, e.title, e.srcFile, e.srcLine) + "\r\n") 
     63        assert e.status == 370050 
     64        assert e.reason.find("PJNATH_ESTUNINSERVER") >= 0 
     65        assert e.title == "pjsua_detect_nat_type()" 
     66    assert got_exception 
    6467 
    6568# 
     
    6770# 
    6871class MyLogWriter(pj.LogWriter): 
    69         def write(self, entry): 
    70                 print "This is Python:", entry.msg 
    71                  
     72    def write(self, entry): 
     73        write("This is Python:" + entry.msg + "\r\n") 
     74 
    7275# 
    7376# Testing log writer callback 
    7477# 
    7578def ua_run_log_test(): 
    76         print "Logging test.." 
    77         ep_cfg = pj.EpConfig() 
    78          
    79         lw = MyLogWriter() 
    80         ep_cfg.logConfig.writer = lw 
    81         ep_cfg.logConfig.decor = ep_cfg.logConfig.decor & ~(pj.PJ_LOG_HAS_CR | pj.PJ_LOG_HAS_NEWLINE)  
    82          
    83         ep = pj.Endpoint() 
    84         ep.libCreate() 
    85         ep.libInit(ep_cfg) 
    86         ep.libDestroy() 
    87          
     79    write("Logging test.." + "\r\n") 
     80    ep_cfg = pj.EpConfig() 
     81 
     82    lw = MyLogWriter() 
     83    ep_cfg.logConfig.writer = lw 
     84    ep_cfg.logConfig.decor = ep_cfg.logConfig.decor & ~(pj.PJ_LOG_HAS_CR | pj.PJ_LOG_HAS_NEWLINE) 
     85 
     86    ep = pj.Endpoint() 
     87    ep.libCreate() 
     88    ep.libInit(ep_cfg) 
     89    ep.libDestroy() 
     90 
    8891# 
    8992# Simple create, init, start, and destroy sequence 
    9093# 
    9194def ua_run_ua_test(): 
    92         print "UA test run.." 
    93         ep_cfg = pj.EpConfig() 
    94          
    95         ep = pj.Endpoint() 
    96         ep.libCreate() 
    97         ep.libInit(ep_cfg) 
    98         ep.libStart() 
    99          
    100         print "************* Endpoint started ok, now shutting down... *************" 
    101         ep.libDestroy() 
     95    write("UA test run.." + "\r\n") 
     96    ep_cfg = pj.EpConfig() 
     97 
     98    ep = pj.Endpoint() 
     99    ep.libCreate() 
     100    ep.libInit(ep_cfg) 
     101    ep.libStart() 
     102 
     103    write("************* Endpoint started ok, now shutting down... *************" + "\r\n") 
     104    ep.libDestroy() 
    102105 
    103106# 
     
    105108# 
    106109def ua_tonegen_test(): 
    107         print "UA tonegen test.." 
    108         ep_cfg = pj.EpConfig() 
     110    write("UA tonegen test.." + "\r\n") 
     111    ep_cfg = pj.EpConfig() 
    109112 
    110         ep = pj.Endpoint() 
    111         ep.libCreate() 
    112         ep.libInit(ep_cfg) 
    113         ep.libStart() 
    114          
    115         tonegen = pj.ToneGenerator() 
    116         tonegen.createToneGenerator() 
     113    ep = pj.Endpoint() 
     114    ep.libCreate() 
     115    ep.libInit(ep_cfg) 
     116    ep.libStart() 
    117117 
    118         tone = pj.ToneDesc() 
    119         tone.freq1 = 400 
    120         tone.freq2 = 600 
    121         tone.on_msec = 1000 
    122         tone.off_msec = 1000 
    123         tones = pj.ToneDescVector() 
    124         tones.append(tone) 
     118    tonegen = pj.ToneGenerator() 
     119    tonegen.createToneGenerator() 
    125120 
    126         digit = pj.ToneDigit() 
    127         digit.digit = '0' 
    128         digit.on_msec = 1000 
    129         digit.off_msec = 1000 
    130         digits = pj.ToneDigitVector() 
    131         digits.append(digit) 
     121    tone = pj.ToneDesc() 
     122    tone.freq1 = 400 
     123    tone.freq2 = 600 
     124    tone.on_msec = 1000 
     125    tone.off_msec = 1000 
     126    tones = pj.ToneDescVector() 
     127    tones.append(tone) 
    132128 
    133         adm = ep.audDevManager() 
    134         spk = adm.getPlaybackDevMedia() 
     129    digit = pj.ToneDigit() 
     130    digit.digit = '0' 
     131    digit.on_msec = 1000 
     132    digit.off_msec = 1000 
     133    digits = pj.ToneDigitVector() 
     134    digits.append(digit) 
    135135 
    136         tonegen.play(tones, True) 
    137         tonegen.startTransmit(spk) 
    138         time.sleep(5) 
     136    adm = ep.audDevManager() 
     137    spk = adm.getPlaybackDevMedia() 
    139138 
    140         tonegen.stop() 
    141         tonegen.playDigits(digits, True) 
    142         time.sleep(5) 
     139    tonegen.play(tones, True) 
     140    tonegen.startTransmit(spk) 
     141    time.sleep(5) 
    143142 
    144         dm = tonegen.getDigitMap() 
    145         print dm[0].digit 
    146         dm[0].freq1 = 400 
    147         dm[0].freq2 = 600 
    148         tonegen.setDigitMap(dm) 
    149          
    150         tonegen.stop() 
    151         tonegen.playDigits(digits, True) 
    152         time.sleep(5) 
    153          
    154         tonegen = None 
     143    tonegen.stop() 
     144    tonegen.playDigits(digits, True) 
     145    time.sleep(5) 
    155146 
    156         ep.libDestroy() 
     147    dm = tonegen.getDigitMap() 
     148    write(dm[0].digit + "\r\n") 
     149    dm[0].freq1 = 400 
     150    dm[0].freq2 = 600 
     151    tonegen.setDigitMap(dm) 
     152 
     153    tonegen.stop() 
     154    tonegen.playDigits(digits, True) 
     155    time.sleep(5) 
     156 
     157    tonegen = None 
     158 
     159    ep.libDestroy() 
    157160 
    158161# 
     
    160163# 
    161164if __name__ == "__main__": 
    162         ua_data_test() 
    163         ua_run_test_exception() 
    164         ua_run_log_test() 
    165         ua_run_ua_test() 
    166         ua_tonegen_test() 
    167         sys.exit(0) 
     165    ua_data_test() 
     166    ua_run_test_exception() 
     167    ua_run_log_test() 
     168    ua_run_ua_test() 
     169    ua_tonegen_test() 
     170    sys.exit(0) 
    168171 
    169          
     172 
Note: See TracChangeset for help on using the changeset viewer.