Re #1708 (PyGUI: New Python GUI Application based on pjsua2+SWIG API)

Initial implementation, some account functionality has been implemented. Details:
 - Uses Tk for GUI and pickle for object serialization. These are Python built-in modules
 - So far so good, everything (=Account API only so far!) seems to work, including:
    - STL string
    - STL vector
    - inheritance
    - calling callback that is implemented in Python with inheritance (the "director" feature")
 - Some notes though:
    - SIP worker threads need to be disabled because Tk GUI cannot be called from other threads (the worker thread will invoke callback which in turn will update GUI)
 - Features implemented in the GUI so far:
    - Account addition/deletion/modification
    - Registration
    - Logging to window
    - Persistent config (limited)





git-svn-id: https://svn.pjsip.org/repos/pjproject/branches/projects/pjsua2@4640 74dad513-b988-da41-8d7b-12977e46ad98
This commit is contained in:
Benny Prijono 2013-11-01 08:49:43 +00:00
parent f673a6ef92
commit 64ff3f68b4
5 changed files with 930 additions and 0 deletions

View File

@ -0,0 +1,168 @@
# $Id$
#
# pjsua Python GUI Demo
#
# Copyright (C)2013 Teluu Inc. (http://www.teluu.com)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import sys
if sys.version_info[0] >= 3: # Python 3
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as msgbox
else:
import Tkinter as tk
import tkMessageBox as msgbox
import ttk
import random
import pjsua2 as pj
import _pjsua2
import accountsetting
import application
# Account class
class Account(pj.Account):
"""
High level Python Account object, derived from pjsua2's Account object.
"""
def __init__(self, app):
pj.Account.__init__(self)
self.app = app
self.randId = random.randint(1, 9999)
self.cfg = pj.AccountConfig()
self.cfgChanged = False
def statusText(self):
status = '?'
if self.isValid():
ai = self.getInfo()
if ai.regIsActive:
if ai.onlineStatus:
if len(ai.onlineStatusText):
status = ai.onlineStatusText
else:
status = "Online"
else:
status = "Registered"
else:
if ai.regIsConfigured:
if ai.regStatus/100 == 2:
status = "Unregistered"
else:
status = ai.regStatusText
else:
status = "Doesn't register"
else:
status = '- not created -'
return status
def onRegState(self, prm):
self.app.updateAccount(self)
# Account frame, to list accounts
class AccountListFrame(ttk.Frame):
"""
This implements a Frame which contains account list and buttons to operate
on them (Add, Modify, Delete, etc.).
"""
def __init__(self, parent, app, acc_list = []):
ttk.Frame.__init__(self, parent, name='acclist')
self.app = app
self.accList = acc_list
self.accDeletedList = []
self.pack(expand='yes', fill='both')
self._createWidgets()
for acc in self.accList:
self._showAcc(acc)
def _createWidgets(self):
self.tv = ttk.Treeview(self, columns=('ID', 'Registrar', 'Default'), selectmode='browse')
self.tv.heading('#0', text='Priority')
self.tv.heading(0, text='ID')
self.tv.heading(1, text='Registrar')
self.tv.heading(2, text='Default?')
self.tv.column('#0', width=60)
self.tv.column(0, width=300)
self.tv.column(1, width=200)
self.tv.column(2, width=60)
self.tv.grid(column=0, row=0, rowspan=4, padx=5, pady=5)
ttk.Button(self, text='Add..', command=self._onBtnAdd).grid(column=1, row=0, padx=5)
ttk.Button(self, text='Settings..', command=self._onBtnSettings).grid(column=1, row=1)
ttk.Button(self, text='Set Default', command=self._onBtnSetDefault).grid(column=1, row=2)
ttk.Button(self, text='Delete..', command=self._onBtnDelete).grid(column=1, row=3)
def _showAcc(self, acc):
is_default = 'Yes' if acc.isValid() and acc.isDefault() else ''
values = (acc.cfg.idUri, acc.cfg.regConfig.registrarUri, is_default)
self.tv.insert('', 0, str(acc.randId), open=True, text=str(acc.cfg.priority), values=values)
def updateAccount(self, acc):
is_default = 'Yes' if acc.isValid() and acc.isDefault() else ''
values = (acc.cfg.idUri, acc.cfg.regConfig.registrarUri, is_default)
self.tv.item(str(acc.randId), text=str(acc.cfg.priority), values=values)
def _getSelectedAcc(self):
items = self.tv.selection()
if not items:
return None
iid = int(items[0])
return [acc for acc in self.accList if acc.randId==iid][0]
def _onBtnAdd(self):
cfg = pj.AccountConfig()
dlg = accountsetting.Dialog(self.master, cfg)
if dlg.doModal():
acc = Account(self.app)
acc.cfg = cfg
self._showAcc(acc)
self.accList.append(acc)
self.cfgChanged = True
def _onBtnSettings(self):
acc = self._getSelectedAcc()
if not acc:
return
dlg = accountsetting.Dialog(self.master, acc.cfg)
if dlg.doModal():
self.updateAccount(acc)
self.cfgChanged = True
def _onBtnDelete(self):
acc = self._getSelectedAcc()
if not acc:
return
msg = "Do you really want to delete account '%s'" % acc.cfg.idUri
if msgbox.askquestion('Delete account?', msg, default=msgbox.NO) != u'yes':
return
self.accList.remove(acc)
self.accDeletedList.append(acc)
self.tv.delete( (str(acc.randId),) )
def _onBtnSetDefault(self):
acc = self._getSelectedAcc()
if not acc:
return
if acc.isValid():
acc.setDefault()
for acc in self.accList:
self.updateAccount(acc)
if __name__ == '__main__':
application.main()

View File

@ -0,0 +1,169 @@
# $Id$
#
# pjsua Python GUI Demo
#
# Copyright (C)2013 Teluu Inc. (http://www.teluu.com)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import sys
if sys.version_info[0] >= 3: # Python 3
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as msgbox
else:
import Tkinter as tk
import tkMessageBox as msgbox
import ttk
import pjsua2 as pj
import endpoint
import application
class Dialog(tk.Toplevel):
"""
This implements account settings dialog to manipulate account settings.
"""
def __init__(self, parent, cfg):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.parent = parent
self.title('Account settings')
self.frm = ttk.Frame(self)
self.frm.pack(expand='yes', fill='both')
self.isOk = False
self.cfg = cfg
self.createWidgets()
def doModal(self):
if self.parent:
self.parent.wait_window(self)
else:
self.wait_window(self)
return self.isOk
def createWidgets(self):
# The notebook
self.frm.rowconfigure(0, weight=1)
self.frm.rowconfigure(1, weight=0)
self.frm.columnconfigure(0, weight=1)
self.frm.columnconfigure(1, weight=1)
self.wTab = ttk.Notebook(self.frm)
self.wTab.grid(column=0, row=0, columnspan=2, padx=5, pady=5, sticky=tk.N+tk.S+tk.W+tk.E)
# Main buttons
btnOk = ttk.Button(self.frm, text='Ok', command=self.onOk)
btnOk.grid(column=0, row=1, sticky=tk.E, padx=20, pady=10)
btnCancel = ttk.Button(self.frm, text='Cancel', command=self.onCancel)
btnCancel.grid(column=1, row=1, sticky=tk.W, padx=20, pady=10)
# Tabs
self.createBasicTab()
def createBasicTab(self):
# Prepare the variables to set/receive values from GUI
self.cfgPriority = tk.IntVar()
self.cfgPriority.set( self.cfg.priority )
self.cfgAccId = tk.StringVar()
self.cfgAccId.set( self.cfg.idUri )
self.cfgRegistrar = tk.StringVar()
self.cfgRegistrar.set( self.cfg.regConfig.registrarUri )
self.cfgRegisterOnAdd = tk.IntVar()
self.cfgRegisterOnAdd.set(self.cfg.regConfig.registerOnAdd)
self.cfgUsername = tk.StringVar()
self.cfgPassword = tk.StringVar()
if len(self.cfg.sipConfig.authCreds):
self.cfgUsername.set( self.cfg.sipConfig.authCreds[0].username )
self.cfgPassword.set( self.cfg.sipConfig.authCreds[0].data )
self.cfgProxy = tk.StringVar()
if len(self.cfg.sipConfig.proxies):
self.cfgProxy.set( self.cfg.sipConfig.proxies[0] )
# Build the tab page
frm = ttk.Frame(self.frm)
frm.columnconfigure(0, weight=1)
frm.columnconfigure(1, weight=2)
row = 0
ttk.Label(frm, text='Priority:').grid(row=row, column=0, sticky=tk.E, pady=2)
tk.Spinbox(frm, from_=0, to=9, textvariable=self.cfgPriority, width=2).grid(row=row, column=1, sticky=tk.W, padx=6)
row += 1
ttk.Label(frm, text='ID (URI):').grid(row=row, column=0, sticky=tk.E, pady=2)
ttk.Entry(frm, textvariable=self.cfgAccId, width=25).grid(row=row, column=1, sticky=tk.W+tk.E, padx=6)
row += 1
ttk.Label(frm, text='Registrar URI:').grid(row=row, column=0, sticky=tk.E, pady=2)
ttk.Entry(frm, textvariable=self.cfgRegistrar, width=25).grid(row=row, column=1, sticky=tk.W+tk.E, padx=6)
row += 1
ttk.Checkbutton(frm, text='Register on add', variable=self.cfgRegisterOnAdd).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
row += 1
ttk.Label(frm, text='Optional proxy URI:').grid(row=row, column=0, sticky=tk.E, pady=2)
ttk.Entry(frm, textvariable=self.cfgProxy, width=25).grid(row=row, column=1, sticky=tk.W+tk.E, padx=6)
row += 1
ttk.Label(frm, text='Username:').grid(row=row, column=0, sticky=tk.E, pady=2)
ttk.Entry(frm, textvariable=self.cfgUsername, width=12).grid(row=row, column=1, sticky=tk.W, padx=6)
row += 1
ttk.Label(frm, text='Password:').grid(row=row, column=0, sticky=tk.E, pady=2)
ttk.Entry(frm, textvariable=self.cfgPassword, show='*', width=12).grid(row=row, column=1, sticky=tk.W, padx=6, pady=2)
self.wTab.add(frm, text='Basic Settings')
def onOk(self):
# Check basic settings
errors = "";
if self.cfgAccId.get():
if not endpoint.validateSipUri(self.cfgAccId.get()):
errors += "Invalid SIP ID URI: '%s'\n" % (self.cfgAccId.get())
if self.cfgRegistrar.get():
if not endpoint.validateSipUri(self.cfgRegistrar.get()):
errors += "Invalid SIP registrar URI: '%s'\n" % (self.cfgRegistrar.get())
if self.cfgProxy.get():
if not endpoint.validateSipUri(self.cfgProxy.get()):
errors += "Invalid SIP proxy URI: '%s'\n" % (self.cfgProxy.get())
if errors:
msgbox.showerror("Error detected:", errors)
return
# Basic settings
self.cfg.priority = self.cfgPriority.get()
self.cfg.idUri = self.cfgAccId.get()
self.cfg.regConfig.registrarUri = self.cfgRegistrar.get()
self.cfg.regConfig.registerOnAdd = self.cfgRegisterOnAdd.get()
while len(self.cfg.sipConfig.authCreds):
self.cfg.sipConfig.authCreds.pop()
if self.cfgUsername.get():
cred = pj.AuthCredInfo()
cred.scheme = "digest"
cred.realm = "*"
cred.username = self.cfgUsername.get()
cred.data = self.cfgPassword.get()
self.cfg.sipConfig.authCreds.append(cred)
while len(self.cfg.sipConfig.proxies):
self.cfg.sipConfig.proxies.pop()
if self.cfgProxy.get():
self.cfg.sipConfig.proxies.append(self.cfgProxy.get())
self.isOk = True
self.destroy()
def onCancel(self):
self.destroy()
if __name__ == '__main__':
application.main()

View File

@ -0,0 +1,396 @@
# $Id$
#
# pjsua Python GUI Demo
#
# Copyright (C)2013 Teluu Inc. (http://www.teluu.com)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import sys
if sys.version_info[0] >= 3: # Python 3
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as msgbox
else:
import Tkinter as tk
import tkMessageBox as msgbox
import ttk
import pjsua2 as pj
import log
import accountsetting
import account
import endpoint
import os
import pickle
import traceback
#----------------------------------------------------------
# Unfortunately SWIG classes cannot be serialized directly
# hence need to write these config classes
#
class AccountConfig:
"""
"Proxy" for the pj.AccountConfig class, serializable
"""
def __init__(self, acc_cfg = None):
if not acc_cfg:
acc_cfg = pj.AccountConfig()
self.priority = acc_cfg.priority
self.idUri = acc_cfg.idUri
self.regUri = acc_cfg.regConfig.registrarUri
self.registerOnAdd = acc_cfg.regConfig.registerOnAdd
self.proxies = [proxy for proxy in acc_cfg.sipConfig.proxies]
self.userName = ""
self.password = ""
if len(acc_cfg.sipConfig.authCreds):
self.userName = acc_cfg.sipConfig.authCreds[0].username
self.password = acc_cfg.sipConfig.authCreds[0].data
def getAccConfig(self, acc_cfg = None):
"""
Convert this class to pj.AccountConfig class
"""
if not acc_cfg:
acc_cfg = pj.AccountConfig()
acc_cfg.priority = self.priority
acc_cfg.idUri = self.idUri
acc_cfg.regConfig.registrarUri = self.regUri
acc_cfg.regConfig.registerOnAdd = self.registerOnAdd
for proxy in self.proxies:
acc_cfg.sipConfig.proxies.append(proxy)
if self.userName:
cred = pj.AuthCredInfo()
cred.scheme = "digest"
cred.realm = "*"
cred.username = self.userName
cred.data = self.password
acc_cfg.sipConfig.authCreds.append(cred)
return acc_cfg
class ApplicationConfig:
"""
Application config is serializable and contains all settings that application need.
"""
def __init__(self, ep_cfg = None, acc_cfgs = []):
if not ep_cfg:
ep_cfg = pj.EpConfig()
self.logFile = ep_cfg.logConfig.filename
self.logFlags = ep_cfg.logConfig.fileFlags
self.logLevel = ep_cfg.logConfig.consoleLevel
self.accCfgs = []
for acc_cfg in acc_cfgs:
self.accCfgs.append( AccountConfig(acc_cfg) )
def getEpConfig(self, ep_cfg = None):
if not ep_cfg:
ep_cfg = pj.EpConfig()
ep_cfg.logConfig.filename = self.logFile
ep_cfg.logConfig.fileFlags = self.logFlags
ep_cfg.logConfig.consoleLevel = self.logLevel
return ep_cfg
#----------------------------------------------------------
class Application(ttk.Frame):
"""
The Application main frame.
"""
def __init__(self):
ttk.Frame.__init__(self, name='application', width=300, height=500)
self.pack(expand='yes', fill='both')
self.master.title('pjsua2 Demo')
# Logger
self.logger = log.Logger()
# Global config, fill with default
self.epCfg = pj.EpConfig()
self.epCfg.uaConfig.threadCnt = 0;
self.epCfg.logConfig.writer = self.logger
self.epCfg.logConfig.filename = "pygui.log"
self.epCfg.logConfig.fileFlags = pj.PJ_O_APPEND
self.epCfg.logConfig.level = 5
self.epCfg.logConfig.consoleLevel = 5
# Accounts
self.accList = []
# GUI variables
self.showLogWindow = tk.IntVar()
self.showLogWindow.set(1)
self.quitting = False
# Construct GUI
self._createWidgets()
# Log window
self.logWindow = log.LogWindow(self)
self._onMenuShowHideLogWindow()
def saveConfig(self, filename='pygui.dat'):
acc_cfgs = [acc.cfg for acc in self.accList]
app_cfg = ApplicationConfig(self.epCfg, acc_cfgs)
f = open(filename, 'wb')
pickle.dump(app_cfg, f, 0)
f.close()
def start(self, cfg_file='pygui.dat'):
# Load config
acc_cfgs = []
if cfg_file and os.path.exists(cfg_file):
f = open(cfg_file, 'rb')
app_cfg = pickle.load(f)
app_cfg.getEpConfig(self.epCfg)
for c in app_cfg.accCfgs:
cfg = c.getAccConfig()
acc_cfgs.append(cfg)
f.close()
# Instantiate endpoint
self.ep = endpoint.Endpoint()
self.epCfg.uaConfig.userAgent = "pygui-" + self.ep.libVersion().full;
self.ep.startLib(self.epCfg)
self.master.title('pjsua2 Demo version ' + self.ep.libVersion().full)
# Add accounts
for cfg in acc_cfgs:
self._createAcc(cfg)
# Start polling
self._onTimer()
def updateAccount(self, acc):
iid = str(acc.randId)
text = acc.cfg.idUri
status = acc.statusText()
values = (status,)
if self.tv.exists(iid):
self.tv.item(iid, text=text, values=values)
else:
self.tv.insert('', 0, iid, open=True, text=text, values=values)
self.tv.insert(iid, 0, '', open=True, text='Buddy 1', values=('Online',))
self.tv.insert(iid, 1, '', open=True, text='Buddy 2', values=('Online',))
def _createAcc(self, acc_cfg):
acc = account.Account(self)
acc.cfg = acc_cfg
self.accList.append(acc)
self.updateAccount(acc)
acc.create(acc.cfg)
acc.cfgChanged = False
self.updateAccount(acc)
def _createWidgets(self):
self._createAppMenu()
# Main pane, a Treeview
self.tv = ttk.Treeview(self, columns=('Status'), show='tree')
self.tv.pack(side='top', fill='both', expand='yes', padx=5, pady=5)
self._createContextMenu()
# Handle close event
self.master.protocol("WM_DELETE_WINDOW", self._onClose)
def _createAppMenu(self):
# Main menu bar
top = self.winfo_toplevel()
self.menubar = tk.Menu()
top.configure(menu=self.menubar)
# File menu
file_menu = tk.Menu(self.menubar, tearoff=False)
self.menubar.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="Add account..", command=self._onMenuAddAccount)
file_menu.add_checkbutton(label="Show/hide log window", command=self._onMenuShowHideLogWindow, variable=self.showLogWindow)
file_menu.add_separator()
file_menu.add_command(label="Settings...", command=self._onMenuSettings)
file_menu.add_command(label="Save Settings", command=self._onMenuSaveSettings)
file_menu.add_separator()
file_menu.add_command(label="Quit", command=self._onMenuQuit)
# Help menu
help_menu = tk.Menu(self.menubar, tearoff=False)
self.menubar.add_cascade(label="Help", menu=help_menu)
help_menu.add_command(label="About", underline=2, command=self._onMenuAbout)
def _createContextMenu(self):
top = self.winfo_toplevel()
self.accMenu = tk.Menu(top, tearoff=False)
# Labels, must match with _onAccContextMenu()
labels = ['Unregister', 'Reregister', '-', 'Online', 'Invisible', 'Away', 'Busy', '-', 'Settings...', '-', 'Delete...']
for label in labels:
if label=='-':
self.accMenu.add_separator()
else:
cmd = lambda arg=label: self._onAccContextMenu(arg)
self.accMenu.add_command(label=label, command=cmd)
if (top.tk.call('tk', 'windowingsystem')=='aqua'):
self.tv.bind('<2>', self._onTvRightClick)
self.tv.bind('<Control-1>', self._onTvRightClick)
else:
self.tv.bind('<3>', self._onTvRightClick)
def _getSelectedAccount(self):
items = self.tv.selection()
if not items:
return None
try:
iid = int(items[0])
except:
return None
accs = [acc for acc in self.accList if acc.randId==iid]
if not accs:
return None
return accs[0]
def _onTvRightClick(self, event):
iid = self.tv.identify('item', event.x, event.y)
if iid:
self.tv.selection_add( (iid,) )
acc = self._getSelectedAccount()
if acc:
self.accMenu.post(event.x_root, event.y_root)
else:
# A buddy is selected
pass
def _onAccContextMenu(self, label):
acc = self._getSelectedAccount()
if not acc:
return
if label=='Unregister':
acc.setRegistration(False)
elif label=='Reregister':
acc.setRegistration(True)
elif label=='Online':
ps = pj.AccountPresenceStatus()
ps.isOnline = True
acc.setOnlineStatus(ps)
elif label=='Invisible':
ps = pj.AccountPresenceStatus()
ps.isOnline = False
acc.setOnlineStatus(ps)
elif label=='Away':
ps = pj.AccountPresenceStatus()
ps.isOnline = True
ps.activity = pj.PJRPID_ACTIVITY_AWAY
ps.note = "Away"
acc.setOnlineStatus(ps)
elif label=='Busy':
ps = pj.AccountPresenceStatus()
ps.isOnline = True
ps.activity = pj.PJRPID_ACTIVITY_BUSY
ps.note = "Busy"
acc.setOnlineStatus(ps)
elif label=='Settings...':
self.cfgChanged = False
dlg = accountsetting.Dialog(self.master, acc.cfg)
if dlg.doModal():
self.updateAccount(acc)
acc.modify(acc.cfg)
elif label=='Delete...':
msg = "Do you really want to delete account '%s'?" % acc.cfg.idUri
if msgbox.askquestion('Delete account?', msg, default=msgbox.NO) != u'yes':
return
iid = str(acc.randId)
self.accList.remove(acc)
del acc
self.tv.delete( (iid,) )
else:
assert not ("Unknown menu " + label)
def _onTimer(self):
if not self.quitting:
self.ep.libHandleEvents(10)
if not self.quitting:
self.master.after(50, self._onTimer)
def _onClose(self):
self.quitting = True
self.ep.stopLib()
self.ep = None
self.update()
self.quit()
def _onMenuAddAccount(self):
cfg = pj.AccountConfig()
dlg = accountsetting.Dialog(self.master, cfg)
if dlg.doModal():
self._createAcc(cfg)
def _onMenuShowHideLogWindow(self):
if self.showLogWindow.get():
self.logWindow.deiconify()
else:
self.logWindow.withdraw()
def _onMenuSettings(self):
msgbox.showinfo(self.master.title(), 'Settings')
def _onMenuSaveSettings(self):
self.saveConfig()
def _onMenuQuit(self):
self._onClose()
def _onMenuAbout(self):
msgbox.showinfo(self.master.title(), 'About')
class ExceptionCatcher:
"""Custom Tk exception catcher, mainly to display more information
from pj.Error exception
"""
def __init__(self, func, subst, widget):
self.func = func
self.subst = subst
self.widget = widget
def __call__(self, *args):
try:
if self.subst:
args = apply(self.subst, args)
return apply(self.func, args)
except pj.Error, error:
print 'Exception:'
print ' ', error.info()
print 'Traceback:'
print traceback.print_stack()
log.writeLog2(1, 'Exception: ' + error.info() + '\n')
except Exception, error:
print 'Exception:'
print ' ', str(error)
print 'Traceback:'
print traceback.print_stack()
log.writeLog2(1, 'Exception: ' + str(error) + '\n')
def main():
#tk.CallWrapper = ExceptionCatcher
app = Application()
app.start()
app.mainloop()
app.saveConfig()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,70 @@
# $Id$
#
# pjsua Python GUI Demo
#
# Copyright (C)2013 Teluu Inc. (http://www.teluu.com)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import sys
if sys.version_info[0] >= 3: # Python 3
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as msgbox
else:
import Tkinter as tk
import tkMessageBox as msgbox
import ttk
import pjsua2 as pj
import application
class Endpoint(pj.Endpoint):
"""
This is high level Python object inherited from pj.Endpoint
"""
instance = None
def __init__(self):
pj.Endpoint.__init__(self)
Endpoint.instance = self
def startLib(self, ep_cfg):
# Create lib
self.libCreate()
# Init lib
self.libInit(ep_cfg)
# Add transport
tcfg = pj.TransportConfig()
tcfg.port = 50060;
self.transportCreate(pj.PJSIP_TRANSPORT_UDP, tcfg)
# Start!
self.libStart()
def stopLib(self):
self.libDestroy()
def validateUri(uri):
return Endpoint.instance.utilVerifyUri(uri) == pj.PJ_SUCCESS
def validateSipUri(uri):
return Endpoint.instance.utilVerifySipUri(uri) == pj.PJ_SUCCESS
if __name__ == '__main__':
application.main()

127
pjsip-apps/src/pygui/log.py Normal file
View File

@ -0,0 +1,127 @@
# $Id$
#
# pjsua Python GUI Demo
#
# Copyright (C)2013 Teluu Inc. (http://www.teluu.com)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import sys
if sys.version_info[0] >= 3: # Python 3
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox as msgbox
else:
import Tkinter as tk
import tkMessageBox as msgbox
import ttk
import pjsua2 as pj
import application
class LogWindow(tk.Toplevel):
"""
Log window
"""
instance = None
def __init__(self, app):
tk.Toplevel.__init__(self, name='logwnd', width=640, height=480)
LogWindow.instance = self
self.app = app
self.state('withdrawn')
self.title('Log')
self._createWidgets()
self.protocol("WM_DELETE_WINDOW", self._onHide)
def addLog(self, entry):
"""entry fields:
int level;
string msg;
long threadId;
string threadName;
"""
self.addLog2(entry.level, entry.msg)
def addLog2(self, level, msg):
if level==5:
tags = ('trace',)
elif level==3:
tags = ('info',)
elif level==2:
tags = ('warning',)
elif level<=1:
tags = ('error',)
else:
tags = None
self.text.insert(tk.END, msg, tags)
self.text.see(tk.END)
def _createWidgets(self):
self.rowconfigure(0, weight=1)
self.rowconfigure(1, weight=0)
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=0)
self.text = tk.Text(self, font=('Courier New', '8'), wrap=tk.NONE, undo=False, padx=4, pady=5)
self.text.grid(row=0, column=0, sticky='nswe', padx=5, pady=5)
scrl = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.text.yview)
self.text.config(yscrollcommand=scrl.set)
scrl.grid(row=0, column=1, sticky='nsw', padx=5, pady=5)
scrl = ttk.Scrollbar(self, orient=tk.HORIZONTAL, command=self.text.xview)
self.text.config(xscrollcommand=scrl.set)
scrl.grid(row=1, column=0, sticky='we', padx=5, pady=5)
self.text.bind("<Key>", self._onKey)
self.text.tag_configure('normal', font=('Courier New', '8'), foreground='black')
self.text.tag_configure('trace', font=('Courier New', '8'), foreground='#777777')
self.text.tag_configure('info', font=('Courier New', '8', 'bold'), foreground='black')
self.text.tag_configure('warning', font=('Courier New', '8', 'bold'), foreground='cyan')
self.text.tag_configure('error', font=('Courier New', '8', 'bold'), foreground='red')
def _onKey(self, event):
# Ignore key event to make text widget read-only
return "break"
def _onHide(self):
# Hide when close ('x') button is clicked
self.withdraw()
self.app.showLogWindow.set(0)
def writeLog2(level, msg):
if LogWindow.instance:
LogWindow.instance.addLog2(level, msg)
def writeLog(entry):
if LogWindow.instance:
LogWindow.instance.addLog(entry)
class Logger(pj.LogWriter):
"""
Logger to receive log messages from pjsua2
"""
def __init__(self):
pj.LogWriter.__init__(self)
def write(self, entry):
#print entry.msg,
writeLog(entry)
if __name__ == '__main__':
application.main()