--- /dev/null
+(S'Grandcentral_Dialer'
+S'0.6'
+S'user/Communication'
+S'all'
+S'Optional'
+S'Mark Bergman <bergman@mertech.com>'
+S'python2.5, python2.5-hildon, python2.5-gtk2'
+S'Python script that provides a telephone dial-pad in order to enter\nphone numbers to be dialed through Grandcentral.com.\n\nRequirements:\n\tan account with Grandcentral'
+S'/tmp/gc_dialer/build'
+S'/tmp/gc_dialer/gc_dialer_26.png'
+S'#!/bin/sh\n\ngtk-update-icon-cache -f /usr/share/icons/hicolor'
+S''
+S''
+S''
+S'0.6 "Daddy brain"\n\nNative login, UI updates, cleanup, and much more\n\n\n0.5\n\nAdded dialing history, changed UI.'
+S'extras'
+S'chinook'
+S'low'
+S'email@email.com'
+S''
+S'/tmp/gc_dialer/build'
+S'z2n'
+S'1'
+tp1
+.
\ No newline at end of file
--- /dev/null
+"""
+@author: Laszlo Nagy
+@copyright: (c) 2005 by Szoftver Messias Bt.
+@licence: BSD style
+
+Objects of the MozillaEmulator class can emulate a browser that is capable of:
+
+ - cookie management
+ - caching
+ - configurable user agent string
+ - GET and POST
+ - multipart POST (send files)
+ - receive content into file
+ - progress indicator
+
+I have seen many requests on the python mailing list about how to emulate a browser. I'm using this class for years now, without any problems. This is how you can use it:
+
+ 1. Use firefox
+ 2. Install and open the livehttpheaders plugin
+ 3. Use the website manually with firefox
+ 4. Check the GET and POST requests in the livehttpheaders capture window
+ 5. Create an instance of the above class and send the same GET and POST requests to the server.
+
+Optional steps:
+
+ - For testing, use a MozillaCacher instance - this will cache all pages and make testing quicker
+ - You can change user agent string in the build_opened method
+ - The "encode_multipart_formdata" function can be used alone to create POST data from a list of field values and files
+
+TODO:
+
+- should have a method to save/load cookies
+"""
+
+from __future__ import with_statement
+
+import os
+import md5
+import urllib
+import urllib2
+#import mimetypes
+import cookielib
+
+class MozillaEmulator(object):
+
+ def __init__(self,cacher={},trycount=0):
+ """Create a new MozillaEmulator object.
+
+ @param cacher: A dictionary like object, that can cache search results on a storage device.
+ You can use a simple dictionary here, but it is not recommended.
+ You can also put None here to disable caching completely.
+ @param trycount: The download() method will retry the operation if it fails. You can specify -1 for infinite retrying.
+ A value of 0 means no retrying. A value of 1 means one retry. etc."""
+ self.cacher = cacher
+ self.cookies = cookielib.LWPCookieJar()
+ self.debug = False
+ self.trycount = trycount
+
+ def build_opener(self,url,postdata=None,extraheaders={},forbid_redirect=False):
+ txheaders = {
+ 'Accept':'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png',
+ 'Accept-Language':'en,en-us;q=0.5',
+# 'Accept-Encoding': 'gzip, deflate',
+ 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
+# 'Keep-Alive': '300',
+# 'Connection': 'keep-alive',
+# 'Cache-Control': 'max-age=0',
+ }
+ for key,value in extraheaders.iteritems():
+ txheaders[key] = value
+ req = urllib2.Request(url, postdata, txheaders)
+ self.cookies.add_cookie_header(req)
+ if forbid_redirect:
+ redirector = HTTPNoRedirector()
+ else:
+ redirector = urllib2.HTTPRedirectHandler()
+
+ http_handler = urllib2.HTTPHandler(debuglevel=self.debug)
+ https_handler = urllib2.HTTPSHandler(debuglevel=self.debug)
+
+ u = urllib2.build_opener(http_handler,https_handler,urllib2.HTTPCookieProcessor(self.cookies),redirector)
+ u.addheaders = [('User-Agent','Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.8) Gecko/20050511 Firefox/1.0.4')]
+ if not postdata is None:
+ req.add_data(postdata)
+ return (req,u)
+
+ def download(self,url,postdata=None,extraheaders={},forbid_redirect=False,
+ trycount=None,fd=None,onprogress=None,only_head=False):
+ """Download an URL with GET or POST methods.
+
+ @param postdata: It can be a string that will be POST-ed to the URL.
+ When None is given, the method will be GET instead.
+ @param extraheaders: You can add/modify HTTP headers with a dict here.
+ @param forbid_redirect: Set this flag if you do not want to handle
+ HTTP 301 and 302 redirects.
+ @param trycount: Specify the maximum number of retries here.
+ 0 means no retry on error. Using -1 means infinite retring.
+ None means the default value (that is self.trycount).
+ @param fd: You can pass a file descriptor here. In this case,
+ the data will be written into the file. Please note that
+ when you save the raw data into a file then it won't be cached.
+ @param onprogress: A function that has two parameters:
+ the size of the resource and the downloaded size. This will be
+ called for each 1KB chunk. (If the HTTP header does not contain
+ the content-length field, then the size parameter will be zero!)
+ @param only_head: Create the openerdirector and return it. In other
+ words, this will not retrieve any content except HTTP headers.
+
+ @return: The raw HTML page data, unless fd was specified. When fd
+ was given, the return value is undefined.
+ """
+ if trycount is None:
+ trycount = self.trycount
+ cnt = 0
+ while True:
+ try:
+ req,u = self.build_opener(url,postdata,extraheaders,forbid_redirect)
+ openerdirector = u.open(req)
+ if self.debug:
+ print req.get_method(),url
+ print openerdirector.code,openerdirector.msg
+ print openerdirector.headers
+ self.cookies.extract_cookies(openerdirector,req)
+ if only_head:
+ return openerdirector
+ if openerdirector.headers.has_key('content-length'):
+ length = long(openerdirector.headers['content-length'])
+ else:
+ length = 0
+ dlength = 0
+ if fd:
+ while True:
+ data = openerdirector.read(1024)
+ dlength += len(data)
+ fd.write(data)
+ if onprogress:
+ onprogress(length,dlength)
+ if not data:
+ break
+ else:
+ data = ''
+ while True:
+ newdata = openerdirector.read(1024)
+ dlength += len(newdata)
+ data += newdata
+ if onprogress:
+ onprogress(length,dlength)
+ if not newdata:
+ break
+ #data = openerdirector.read()
+ if not (self.cacher is None):
+ self.cacher[key] = data
+ #try:
+ # d2= GzipFile(fileobj=cStringIO.StringIO(data)).read()
+ # data = d2
+ #except IOError:
+ # pass
+ return data
+ except urllib2.URLError:
+ cnt += 1
+ if (trycount > -1) and (trycount < cnt):
+ raise
+ # Retry :-)
+ if self.debug:
+ print "MozillaEmulator: urllib2.URLError, retryting ",cnt
+
+# def post_multipart(self,url,fields, files, forbid_redirect=True):
+# """Post fields and files to an http host as multipart/form-data.
+# fields is a sequence of (name, value) elements for regular form fields.
+# files is a sequence of (name, filename, value) elements for data to be uploaded as files
+# Return the server's response page.
+# """
+# content_type, post_data = encode_multipart_formdata(fields, files)
+# result = self.download(url,post_data, {
+# 'Content-Type': content_type,
+# 'Content-Length': str(len(post_data))
+# },
+# forbid_redirect=forbid_redirect
+# )
+# return result
+
+
+class HTTPNoRedirector(urllib2.HTTPRedirectHandler):
+ """This is a custom http redirect handler that FORBIDS redirection."""
+
+ def http_error_302(self, req, fp, code, msg, headers):
+ e = urllib2.HTTPError(req.get_full_url(), code, msg, headers, fp)
+ if e.code in (301,302):
+ if 'location' in headers:
+ newurl = headers.getheaders('location')[0]
+ elif 'uri' in headers:
+ newurl = headers.getheaders('uri')[0]
+ e.newurl = newurl
+ raise e
+
+
+#def encode_multipart_formdata(fields, files):
+# """
+# fields is a sequence of (name, value) elements for regular form fields.
+# files is a sequence of (name, filename, value) elements for data to be uploaded as files
+# Return (content_type, body) ready for httplib.HTTP instance
+# """
+# BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
+# CRLF = '\r\n'
+# L = []
+# for (key, value) in fields:
+# L.append('--' + BOUNDARY)
+# L.append('Content-Disposition: form-data; name="%s"' % key)
+# L.append('')
+# L.append(value)
+# for (key, filename, value) in files:
+# L.append('--' + BOUNDARY)
+# L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
+# L.append('Content-Type: %s' % get_content_type(filename))
+# L.append('')
+# L.append(value)
+# L.append('--' + BOUNDARY + '--')
+# L.append('')
+# body = CRLF.join(L)
+# content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
+# return content_type, body
+#
+#
+#def get_content_type(filename):
+# return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
+#
--- /dev/null
+#!/bin/sh
+
+# Create PyPackager directory structure from the original files
+# Please make sure the following files are in this directory before
+# running this script
+
+# gc_dialer_256.png
+# gc_dialer_64.png
+# gc_dialer_26.png
+# gc_dialer.py
+# gc_dialer.xml
+# gc_dialer.desktop
+# gcbackend.py
+# browser_emu.py
+
+# The script creates the directories and concatenates the .py into a
+# single python script
+
+mkdir -p build/usr/share/icons/hicolor/scalable/hildon
+mkdir -p build/usr/share/icons/hicolor/26x26/hildon
+mkdir -p build/usr/share/icons/hicolor/64x64/hildon
+mkdir -p build/usr/share/applications/hildon
+mkdir -p build/usr/local/bin
+mkdir -p build/usr/local/lib
+
+cp gc_dialer_256.png build/usr/share/icons/hicolor/scalable/hildon/gc_dialer.png
+cp gc_dialer_64.png build/usr/share/icons/hicolor/64x64/hildon/gc_dialer.png
+cp gc_dialer_26.png build/usr/share/icons/hicolor/26x26/hildon/gc_dialer.png
+
+cp gc_dialer.desktop build/usr/share/applications/hildon
+
+cp gc_dialer.xml build/usr/local/lib
+
+# Compress whitespace for 30% savings, make sure we are a HildonWindow
+sed -i 's/^[ \t]*//;s/[ \t]*$//;s/GtkWindow/HildonWindow/' build/usr/local/lib/gc_dialer.xml
+
+echo "#!/usr/bin/python" > build/usr/local/bin/gc_dialer.py
+echo "from __future__ import with_statement" >> build/usr/local/bin/gc_dialer.py
+cat gc_dialer.py gcbackend.py browser_emu.py | grep -e '^import ' | sort -u >> build/usr/local/bin/gc_dialer.py
+echo "import hildon" >> build/usr/local/bin/gc_dialer.py
+cat browser_emu.py gcbackend.py gc_dialer.py | grep -v 'import ' | grep -v "#!" >> build/usr/local/bin/gc_dialer.py
+chmod 755 build/usr/local/bin/gc_dialer.py
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
-<!--Generated with glade3 3.4.1 on Wed Jun 11 12:40:37 2008 -->
-<glade-interface>
- <widget class="GtkWindow" id="Dialpad">
- <property name="visible">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="title" translatable="yes">Grandcentral Dialer</property>
- <property name="resizable">False</property>
- <property name="default_width">160</property>
- <property name="default_height">120</property>
- <property name="icon">/usr/share/icons/hicolor/scalable/hildon/gc_dialer.png</property>
- <property name="icon_name">gc_dialer</property>
- <property name="deletable">False</property>
- <child>
- <widget class="GtkVPaned" id="vpaned1">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <child>
- <widget class="GtkHBox" id="hbox1">
- <property name="visible">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <child>
- <widget class="GtkComboBox" id="dialer_history">
- <property name="visible">True</property>
- <property name="events">GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="no_show_all">True</property>
- <property name="tooltip" translatable="yes">History of dialed numbers</property>
- <signal name="changed" handler="on_dialer_history_changed"/>
- </widget>
- </child>
- <child>
- <widget class="GtkEntry" id="numberdisplay">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="editable">False</property>
- </widget>
- <packing>
- <property name="position">1</property>
- </packing>
- </child>
- <child>
- <widget class="GtkButton" id="About">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="tooltip" translatable="yes">About</property>
- <property name="label" translatable="yes">gtk-about</property>
- <property name="use_stock">True</property>
- <property name="response_id">0</property>
- <signal name="clicked" handler="on_About_clicked" object="About"/>
- </widget>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="padding">4</property>
- <property name="position">2</property>
- </packing>
- </child>
- </widget>
- <packing>
- <property name="resize">False</property>
- <property name="shrink">False</property>
- </packing>
- </child>
- <child>
- <widget class="GtkTable" id="Dialpad">
- <property name="visible">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="n_rows">5</property>
- <property name="n_columns">3</property>
- <property name="column_spacing">8</property>
- <property name="row_spacing">8</property>
- <property name="homogeneous">True</property>
- <child>
- <placeholder/>
- </child>
- <child>
- <widget class="GtkButton" id="Backspace">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="tooltip" translatable="yes">Backspace</property>
- <property name="label" translatable="yes">gtk-go-back</property>
- <property name="use_stock">True</property>
- <property name="response_id">0</property>
- <signal name="clicked" handler="on_Backspace_clicked" object="Backspace"/>
- </widget>
- <packing>
- <property name="left_attach">2</property>
- <property name="right_attach">3</property>
- <property name="top_attach">3</property>
- <property name="bottom_attach">4</property>
- </packing>
- </child>
- <child>
- <widget class="GtkButton" id="Dial">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="label" translatable="yes">Dial</property>
- <property name="response_id">0</property>
- <signal name="clicked" handler="on_Dial_clicked" object="Dial"/>
- </widget>
- <packing>
- <property name="left_attach">2</property>
- <property name="right_attach">3</property>
- <property name="top_attach">4</property>
- <property name="bottom_attach">5</property>
- </packing>
- </child>
- <child>
- <widget class="GtkButton" id="Clear">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="label" translatable="yes">Clear</property>
- <property name="use_underline">True</property>
- <property name="response_id">0</property>
- <signal name="clicked" handler="on_Clear_clicked" object="Clear"/>
- </widget>
- <packing>
- <property name="top_attach">3</property>
- <property name="bottom_attach">4</property>
- </packing>
- </child>
- <child>
- <widget class="GtkButton" id="button8">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="label" translatable="yes"> 8
-TUV</property>
- <property name="response_id">0</property>
- <signal name="clicked" handler="on_number_clicked" object="8"/>
- </widget>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="top_attach">2</property>
- <property name="bottom_attach">3</property>
- </packing>
- </child>
- <child>
- <widget class="GtkButton" id="button9">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="label" translatable="yes"> 9
-WXYZ</property>
- <property name="response_id">0</property>
- <signal name="clicked" handler="on_number_clicked" object="9"/>
- </widget>
- <packing>
- <property name="left_attach">2</property>
- <property name="right_attach">3</property>
- <property name="top_attach">2</property>
- <property name="bottom_attach">3</property>
- </packing>
- </child>
- <child>
- <widget class="GtkButton" id="button0">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="label" translatable="yes">0</property>
- <property name="response_id">0</property>
- <signal name="clicked" handler="on_number_clicked" object="0"/>
- </widget>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="top_attach">3</property>
- <property name="bottom_attach">4</property>
- </packing>
- </child>
- <child>
- <widget class="GtkButton" id="Cancel">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="label" translatable="yes">gtk-quit</property>
- <property name="use_stock">True</property>
- <property name="response_id">0</property>
- <signal name="clicked" handler="on_Cancel_clicked" object="Cancel"/>
- </widget>
- <packing>
- <property name="top_attach">4</property>
- <property name="bottom_attach">5</property>
- </packing>
- </child>
- <child>
- <widget class="GtkButton" id="button4">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="label" translatable="yes"> 4
-GHI</property>
- <property name="response_id">0</property>
- <signal name="clicked" handler="on_number_clicked" object="4"/>
- </widget>
- <packing>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
- </packing>
- </child>
- <child>
- <widget class="GtkButton" id="button5">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="label" translatable="yes"> 5
-JKL</property>
- <property name="response_id">0</property>
- <signal name="clicked" handler="on_number_clicked" object="5"/>
- </widget>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
- </packing>
- </child>
- <child>
- <widget class="GtkButton" id="button6">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="label" translatable="yes"> 6
-MNO</property>
- <property name="response_id">0</property>
- <signal name="clicked" handler="on_number_clicked" object="6"/>
- </widget>
- <packing>
- <property name="left_attach">2</property>
- <property name="right_attach">3</property>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
- </packing>
- </child>
- <child>
- <widget class="GtkButton" id="button7">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="label" translatable="yes"> 7
-PQRS</property>
- <property name="response_id">0</property>
- <signal name="clicked" handler="on_number_clicked" object="7"/>
- </widget>
- <packing>
- <property name="top_attach">2</property>
- <property name="bottom_attach">3</property>
- </packing>
- </child>
- <child>
- <widget class="GtkButton" id="button1">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="label" translatable="yes">1</property>
- <property name="response_id">0</property>
- <signal name="clicked" handler="on_number_clicked" object="1"/>
- </widget>
- </child>
- <child>
- <widget class="GtkButton" id="button2">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="label" translatable="yes"> 2
-ABC</property>
- <property name="response_id">0</property>
- <signal name="clicked" handler="on_number_clicked" object="2"/>
- </widget>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- </packing>
- </child>
- <child>
- <widget class="GtkButton" id="button3">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
- <property name="label" translatable="yes"> 3
-DEF</property>
- <property name="response_id">0</property>
- <signal name="clicked" handler="on_number_clicked" object="3"/>
- </widget>
- <packing>
- <property name="left_attach">2</property>
- <property name="right_attach">3</property>
- </packing>
- </child>
- </widget>
- <packing>
- <property name="resize">False</property>
- <property name="shrink">False</property>
- </packing>
- </child>
- </widget>
- </child>
- </widget>
-</glade-interface>
-#!/usr/bin/python
+#!/usr/bin/python2.5
# Grandcentral Dialer
# Python front-end to a wget script to use grandcentral.com to place outbound VOIP calls.
import sys
import os
-import time
import re
-try:
- import pygtk
- pygtk.require("2.0")
-except:
- pass
-try:
- import gtk
- import gtk.glade
-except:
- sys.exit(1)
-
-histfile=os.path.expanduser("~")
-histfile=os.path.join(histfile,".gcdialerhist") # Use the native OS file separator
-liststore = gtk.ListStore(str)
-
-class GCDialer:
- _wgetOKstrRe = re.compile("This may take a few seconds", re.M) # string from Grandcentral.com on successful dial
- _validateRe = re.compile("^[0-9]{7,}$")
-
- _wgetoutput = "/tmp/gc_dialer.output" # results from wget command
- _cookiefile = os.path.join(os.path.expanduser("~"),".mozilla/microb/cookies.txt") # file with browser cookies
- _wgetcmd = "wget -nv -O %s --load-cookie=\"%s\" --referer=http://www.grandcentral.com/mobile/messages http://www.grandcentral.com/mobile/calls/click_to_call?destno=%s"
-
- def __init__(self):
- self._msg = ""
- if ( os.path.isfile(GCDialer._cookiefile) == False ) :
- self._msg = 'Error: Failed to locate a file with saved browser cookies at \"' + cookiefile + '\".\n\tPlease use the web browser on your tablet to connect to www.grandcentral.com and then re-run Grandcentral Dialer.'
-
- def validate(self,number):
- return GCDialer._validateRe.match(number) != None
-
- def dial(self,number):
- self._msg = ""
- if self.validate(number) == False:
- self._msg = "Invalid number format %s" % (number)
- return False
-
- # Remove any existing output file...
- if os.path.isfile(GCDialer._wgetoutput) :
- os.unlink(GCDialer._wgetoutput)
- child_stdout, child_stdin, child_stderr = os.popen3(GCDialer._wgetcmd % (GCDialer._wgetoutput, GCDialer._cookiefile, number))
- stderr=child_stderr.read()
-
- child_stdout.close()
- child_stderr.close()
- child_stdin.close()
-
- try:
- wgetresults = open(GCDialer._wgetoutput, 'r' )
- except IOError:
- self._msg = 'IOError: No /tmp/gc_dialer.output file...dial attempt failed\n\tThis probably means that there is no active internet connection, or that\nthe site www.grandcentral.com is inacessible.'
- return False
-
- data = wgetresults.read()
- wgetresults.close()
+import time
+import gobject
+import gtk
+import gc
+#import hildon
- if GCDialer._wgetOKstrRe.search(data) != None:
- return True
- else:
- self._msg = 'Error: Failed to login to www.grandcentral.com.\n\tThis probably means that there is no saved cookie for that site.\n\tPlease use the web browser on your tablet to connect to www.grandcentral.com and then re-run Grandcentral Dialer.'
- return False
-
-
-def load_history_list(histfile,liststore):
- # read the history list, load it into the liststore variable for later
- # assignment to the combobox menu
-
- # clear out existing entries
- dialhist = []
- liststore.clear()
- if os.path.isfile(histfile) :
- histFH = open(histfile,"r")
- for line in histFH.readlines() :
- fields = line.split() # split the input lines on whitespace
- number=fields[0] #...save only the first field (the phone number)
- search4num=re.compile('^' + number + '$')
- newnumber=True # set a flag that the current phone number is not on the history menu
- for num in dialhist :
- if re.match(search4num,num):
- # the number is already in the drop-down menu list...set the
- # flag and bail out
- newnumber = False
- break
- if newnumber == True :
- dialhist.append(number) # append the number to the history list
-
- histlen=len(dialhist)
- if histlen > 10 :
- dialhist=dialhist[histlen - 10:histlen] # keep only the last 10 entries
- dialhist.reverse() # reverse the list, so that the most recent entry is now first
-
- # Now, load the liststore with the entries, for later assignment to the Gtk.combobox menu
- for entry in dialhist :
- entry=makepretty(entry)
- liststore.append([entry])
- # else :
- # print "The history file " + histfile + " does not exist"
+from gcbackend import GCDialer
def makeugly(prettynumber):
# function to take a phone number and strip out all non-numeric
# (...)-...-....
# else if phonenumber is 10 digits:
# ...-....
+ if phonenumber is None:
+ return ""
+
if len(phonenumber) < 3 :
return phonenumber
else:
prettynumber = "+" + phonenumber[0:3] + "-(" + phonenumber[3:6] + ")-" + phonenumber[6:9] + "-" + phonenumber[9:]
return prettynumber
+ elif phonenumber[0] == "1" and len(phonenumber) > 8:
+ prettynumber = "1 (" + phonenumber[1:4] + ")-" + phonenumber[4:7] + "-" + phonenumber[7:]
+ return prettynumber
elif len(phonenumber) <= 7 :
prettynumber = phonenumber[0:3] + "-" + phonenumber[3:]
elif len(phonenumber) > 7 :
class Dialpad:
- phonenumber = ""
-
def __init__(self):
- if os.path.isfile("/usr/local/lib/gc_dialer.glade") :
- self.gladefile = "/usr/local/lib/gc_dialer.glade"
- elif os.path.isfile("./gc_dialer.glade") :
- self.gladefile = "./gc_dialer.glade"
-
+ self.phonenumber = ""
+ self.prettynumber = ""
+ self.areacode = "518"
self.gcd = GCDialer()
- if self.gcd._msg != "":
- self.ErrPopUp(self.gcd._msg)
- sys.exit(1)
+ self.wTree = gtk.Builder()
- self.wTree = gtk.glade.XML(self.gladefile)
- self.window = self.wTree.get_widget("Dialpad")
- if (self.window):
- self.window.connect("destroy", gtk.main_quit)
+ for path in [ './gc_dialer.xml',
+ '../lib/gc_dialer.xml',
+ '/usr/local/lib/gc_dialer.xml' ]:
+ if os.path.isfile(path):
+ self.wTree.add_from_file(path)
+ break
+
+ self.window = self.wTree.get_object("Dialpad")
#Get the buffer associated with the number display
- self.numberdisplay = self.wTree.get_widget("numberdisplay")
- self.dialer_history = self.wTree.get_widget("dialer_history")
+ self.numberdisplay = self.wTree.get_object("numberdisplay")
+ self.setNumber("")
- # Load the liststore array with the numbers from the history file
- load_history_list(histfile,liststore)
- # load the dropdown menu with the numbers from the dial history
- self.dialer_history.set_model(liststore)
- cell = gtk.CellRendererText()
- self.dialer_history.pack_start(cell, True)
- self.dialer_history.set_active(-1)
- self.dialer_history.set_attributes(cell, text=0)
+ self.recentview = self.wTree.get_object("recentview")
+ self.recentmodel = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
+ self.recentview.set_model(self.recentmodel)
+ textrenderer = gtk.CellRendererText()
- self.about_dialog = None
- self.error_dialog = None
+ # Add the column to the treeview
+ column = gtk.TreeViewColumn("Calls", textrenderer, text=1)
+ column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
+ self.recentview.append_column(column)
- dic = {
- # Routine for processing signal from the combobox (ie., when the
- # user selects an entry from the dropdown history
- "on_dialer_history_changed" : self.on_dialer_history_changed,
+ self.recentviewselection = self.recentview.get_selection()
+ self.recentviewselection.set_mode(gtk.SELECTION_SINGLE)
+ self.recenttime = 0.0
+
+ self.notebook = self.wTree.get_object("notebook")
+
+ self.isHildon = False
+ #if True:
+ try:
+ self.app = hildon.Program()
+ self.wTree.get_object("callbackentry").set_property('hildon-input-mode', 1|(1 << 4))
+ self.isHildon = True
+ except:
+ print "No hildon"
+ if (self.window):
+ self.window.connect("destroy", gtk.main_quit)
+ self.window.show_all()
+
+ dic = {
# Process signals from buttons
- "on_number_clicked" : self.on_number_clicked,
- "on_Clear_clicked" : self.on_Clear_clicked,
- "on_Dial_clicked" : self.on_Dial_clicked,
- "on_Backspace_clicked" : self.Backspace,
- "on_Cancel_clicked" : self.on_Cancel_clicked,
- "on_About_clicked" : self.on_About_clicked}
- self.wTree.signal_autoconnect(dic)
+ "on_digit_clicked" : self.on_digit_clicked,
+ "on_dial_clicked" : self.on_dial_clicked,
+ "on_loginbutton_clicked" : self.on_loginbutton_clicked,
+ "on_clearcookies_clicked" : self.on_clearcookies_clicked,
+ "on_callbackentry_changed" : self.on_callbackentry_changed,
+ "on_notebook_switch_page" : self.on_notebook_switch_page,
+ "on_recentview_row_activated" : self.on_recentview_row_activated,
+ "on_back_clicked" : self.Backspace }
+ self.wTree.connect_signals(dic)
+
+ self.attemptLogin(3)
+ if self.gcd.getCallbackNumber() is None:
+ self.gcd.setSaneCallback()
+
+ self.setAccountNumber()
+ self.setupCallbackCombo()
+ self.reduce_memory()
+
+ def reduce_memory(self):
+ re.purge()
+ num = gc.collect()
+ #print "collect %d objects" % ( num )
+
+ def on_recentview_row_activated(self, treeview, path, view_column):
+ model, iter = self.recentviewselection.get_selected()
+ if iter:
+ self.setNumber(self.recentmodel.get_value(iter,0))
+ self.notebook.set_current_page(0)
+ self.recentviewselection.unselect_all()
+
+ def on_notebook_switch_page(self, notebook, page, page_num):
+ if page_num == 1 and (time.time() - self.recenttime) > 300:
+ self.populate_recentview()
+
+ def populate_recentview(self):
+ print "Populating"
+ self.recentmodel.clear()
+ for item in self.gcd.get_recent():
+ self.recentmodel.append(item)
+ self.recenttime = time.time()
+
+ def on_clearcookies_clicked(self, data=None):
+ self.gcd.reset()
+ self.attemptLogin(3)
+
+ def setupCallbackCombo(self):
+ combobox = self.wTree.get_object("callbackcombo")
+ self.callbacklist = gtk.ListStore(gobject.TYPE_STRING)
+ combobox.set_model(self.callbacklist)
+ combobox.set_text_column(0)
+ for k,v in self.gcd.getCallbackNumbers().iteritems():
+ self.callbacklist.append([makepretty(k)] )
+
+ self.wTree.get_object("callbackentry").set_text(makepretty(self.gcd.getCallbackNumber()))
+
+ def on_callbackentry_changed(self, data=None):
+ text = makeugly(self.wTree.get_object("callbackentry").get_text())
+ if self.gcd.validate(text) and text != self.gcd.getCallbackNumber():
+ self.gcd.setCallbackNumber(text)
+ self.wTree.get_object("callbackentry").set_text(self.wTree.get_object("callbackentry").get_text())
+ self.reduce_memory()
+
+
+ def attemptLogin(self, times = 1):
+ if self.isHildon:
+ dialog = hildon.LoginDialog(self.window)
+ dialog.set_message("Grandcentral Login")
+ else:
+ dialog = self.wTree.get_object("login_dialog")
+
+ while ( (times > 0) and (self.gcd.isAuthed() == False) ):
+ if dialog.run() == gtk.RESPONSE_OK:
+ if self.isHildon:
+ username = dialog.get_username()
+ password = dialog.get_password()
+ else:
+ username = self.wTree.get_object("usernameentry").get_text()
+ password = self.wTree.get_object("passwordentry").get_text()
+ self.wTree.get_object("passwordentry").set_text("")
+ self.gcd.login(username, password)
+ dialog.hide()
+ times = times - 1
+ else:
+ times = 0
+
+ if self.isHildon:
+ dialog.destroy()
def ErrPopUp(self,msg):
error_dialog = gtk.MessageDialog(None,0,gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,msg)
editor.about_dialog = None
dialog.destroy()
error_dialog.connect("response", close, self)
- # error_dialog.connect("delete-event", delete_event, self)
self.error_dialog = error_dialog
error_dialog.run()
- def on_About_clicked(self, menuitem, data=None):
- if self.about_dialog:
- self.about_dialog.present()
- return
+ def on_loginbutton_clicked(self, data=None):
+ self.wTree.get_object("login_dialog").response(gtk.RESPONSE_OK)
- authors = [ "Mark Bergman <bergman@merctech.com>",
- "Eric Warnke <ericew@gmail.com>" ]
+ def on_dial_clicked(self, widget):
+ self.attemptLogin(3)
- about_dialog = gtk.AboutDialog()
- about_dialog.set_transient_for(None)
- about_dialog.set_destroy_with_parent(True)
- about_dialog.set_name("Grandcentral Dialer")
- about_dialog.set_version("0.5")
- about_dialog.set_copyright("Copyright \xc2\xa9 2008 Mark Bergman")
- about_dialog.set_comments("GUI front-end to initiate outbound call from Grandcentral.com, typically with Grancentral configured to connect the outbound call to a VOIP number accessible via Gizmo on the Internet Tablet.\n\nRequires an existing browser cookie from a previous login session to http://www.grandcentral.com/mobile/messages and the program 'wget'.")
- about_dialog.set_authors (authors)
- about_dialog.set_logo_icon_name (gtk.STOCK_EDIT)
-
- # callbacks for destroying the dialog
- def close(dialog, response, editor):
- editor.about_dialog = None
- dialog.destroy()
+ if not self.gcd.isAuthed() or self.gcd.getCallbackNumber() == "":
+ self.ErrPopUp("Backend link with grandcentral is not working, please try again")
+ return
- def delete_event(dialog, event, editor):
- editor.about_dialog = None
- return True
-
- about_dialog.connect("response", close, self)
- about_dialog.connect("delete-event", delete_event, self)
- self.about_dialog = about_dialog
- about_dialog.show()
-
- def on_Dial_clicked(self, widget):
- # Strip the leading "1" before the area code, if present
- if len(Dialpad.phonenumber) == 11 and Dialpad.phonenumber[0] == "1" :
- Dialpad.phonenumber = Dialpad.phonenumber[1:]
- prettynumber = makepretty(Dialpad.phonenumber)
- if len(Dialpad.phonenumber) < 7 :
- # It's too short to be a phone number
- msg = 'Phone number "%s" is too short' % ( prettynumber )
- self.ErrPopUp(msg)
- else :
- timestamp=time.asctime(time.localtime())
+ #if len(self.phonenumber) == 7:
+ # #add default area code
+ # self.phonenumber = self.areacode + self.phonenumber
- if self.gcd.dial(Dialpad.phonenumber) == True :
- histFH = open(histfile,"a")
- histFH.write("%s dialed at %s\n" % ( Dialpad.phonenumber, timestamp ) )
- histFH.close()
-
- # Re-load the updated history of dialed numbers
- load_history_list(histfile,liststore)
- self.dialer_history.set_active(-1)
- self.on_Clear_clicked(widget)
- else:
- self.ErrPopUp(self.gcd._msg)
-
- def on_Cancel_clicked(self, widget):
- sys.exit(1)
-
- def Backspace(self, widget):
- Dialpad.phonenumber = Dialpad.phonenumber[:-1]
- prettynumber = makepretty(Dialpad.phonenumber)
- self.numberdisplay.set_text(prettynumber)
+ if self.gcd.dial(self.phonenumber) == False:
+ self.ErrPopUp(self.gcd._msg)
+ else:
+ self.setNumber("")
- def on_Clear_clicked(self, widget):
- Dialpad.phonenumber = ""
- self.numberdisplay.set_text(Dialpad.phonenumber)
+ self.recentmodel.clear()
+ self.recenttime = 0.0
+ self.reduce_memory()
- def on_dialer_history_changed(self,widget):
- # Set the displayed number to the number chosen from the history list
- history_list = self.dialer_history.get_model()
- history_index = self.dialer_history.get_active()
- prettynumber = history_list[history_index][0]
- Dialpad.phonenumber = makeugly(prettynumber)
- self.numberdisplay.set_text(prettynumber)
+ def setNumber(self, number):
+ self.phonenumber = makeugly(number)
+ self.prettynumber = makepretty(self.phonenumber)
+ self.numberdisplay.set_label("<span size='30000' weight='bold'>%s</span>" % ( self.prettynumber ) )
- def on_number_clicked(self, widget):
- Dialpad.phonenumber = Dialpad.phonenumber + re.sub('\D','',widget.get_label())
- prettynumber = makepretty(Dialpad.phonenumber)
- self.numberdisplay.set_text(prettynumber)
+ def setAccountNumber(self):
+ accountnumber = self.gcd.getAccountNumber()
+ self.wTree.get_object("gcnumberlabel").set_label("<span size='23000' weight='bold'>%s</span>" % (accountnumber))
+ def Backspace(self, widget):
+ self.setNumber(self.phonenumber[:-1])
+ def on_digit_clicked(self, widget):
+ self.setNumber(self.phonenumber + re.sub('\D','',widget.get_label()))
if __name__ == "__main__":
+ gc.set_threshold(50,3,3)
title = 'Dialpad'
handle = Dialpad()
gtk.main()
--- /dev/null
+<?xml version="1.0"?>
+<!--Generated with glade3 3.4.4 on Sat Jun 28 00:20:36 2008 -->
+<interface>
+ <object class="GtkWindow" id="Dialpad">
+ <property name="width_request">400</property>
+ <property name="height_request">300</property>
+ <property name="title" translatable="yes">Dialer</property>
+ <child>
+ <object class="GtkNotebook" id="notebook">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tab_pos">GTK_POS_BOTTOM</property>
+ <property name="show_border">False</property>
+ <property name="homogeneous">True</property>
+ <signal handler="on_notebook_switch_page" name="switch_page"/>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="numberdisplay">
+ <property name="height_request">50</property>
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><span size="30000" weight="bold">(518) 555-1212</span></property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="keypadview">
+ <property name="visible">True</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">3</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkButton" id="digit1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">1</property>
+ <property name="focus_on_click">False</property>
+ <signal handler="on_digit_clicked" name="clicked"/>
+ <accelerator key="1" modifiers="" signal="clicked"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="digit2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">2 ABC</property>
+ <property name="focus_on_click">False</property>
+ <signal handler="on_digit_clicked" name="clicked"/>
+ <accelerator key="c" modifiers="" signal="clicked"/>
+ <accelerator key="b" modifiers="" signal="clicked"/>
+ <accelerator key="a" modifiers="" signal="clicked"/>
+ <accelerator key="2" modifiers="" signal="clicked"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="digit3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">3 DEF</property>
+ <property name="focus_on_click">False</property>
+ <signal handler="on_digit_clicked" name="clicked"/>
+ <accelerator key="f" modifiers="" signal="clicked"/>
+ <accelerator key="e" modifiers="" signal="clicked"/>
+ <accelerator key="d" modifiers="" signal="clicked"/>
+ <accelerator key="3" modifiers="" signal="clicked"/>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="digit4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">4 GHI</property>
+ <property name="focus_on_click">False</property>
+ <signal handler="on_digit_clicked" name="clicked"/>
+ <accelerator key="i" modifiers="" signal="clicked"/>
+ <accelerator key="h" modifiers="" signal="clicked"/>
+ <accelerator key="g" modifiers="" signal="clicked"/>
+ <accelerator key="4" modifiers="" signal="clicked"/>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="digit5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">5 JKL</property>
+ <property name="focus_on_click">False</property>
+ <signal handler="on_digit_clicked" name="clicked"/>
+ <accelerator key="l" modifiers="" signal="clicked"/>
+ <accelerator key="k" modifiers="" signal="clicked"/>
+ <accelerator key="j" modifiers="" signal="clicked"/>
+ <accelerator key="5" modifiers="" signal="clicked"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="digit6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">6 MNO</property>
+ <property name="focus_on_click">False</property>
+ <signal handler="on_digit_clicked" name="clicked"/>
+ <accelerator key="o" modifiers="" signal="clicked"/>
+ <accelerator key="n" modifiers="" signal="clicked"/>
+ <accelerator key="m" modifiers="" signal="clicked"/>
+ <accelerator key="6" modifiers="" signal="clicked"/>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="digit7">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">7 PQRS</property>
+ <property name="focus_on_click">False</property>
+ <signal handler="on_digit_clicked" name="clicked"/>
+ <accelerator key="s" modifiers="" signal="clicked"/>
+ <accelerator key="r" modifiers="" signal="clicked"/>
+ <accelerator key="q" modifiers="" signal="clicked"/>
+ <accelerator key="p" modifiers="" signal="clicked"/>
+ <accelerator key="7" modifiers="" signal="clicked"/>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="digit8">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">8 TUV</property>
+ <property name="focus_on_click">False</property>
+ <signal handler="on_digit_clicked" name="clicked"/>
+ <accelerator key="v" modifiers="" signal="clicked"/>
+ <accelerator key="u" modifiers="" signal="clicked"/>
+ <accelerator key="t" modifiers="" signal="clicked"/>
+ <accelerator key="8" modifiers="" signal="clicked"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="digit9">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">9 WXYZ</property>
+ <property name="focus_on_click">False</property>
+ <signal handler="on_digit_clicked" name="clicked"/>
+ <accelerator key="z" modifiers="" signal="clicked"/>
+ <accelerator key="y" modifiers="" signal="clicked"/>
+ <accelerator key="x" modifiers="" signal="clicked"/>
+ <accelerator key="w" modifiers="" signal="clicked"/>
+ <accelerator key="9" modifiers="" signal="clicked"/>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="back">
+ <property name="visible">True</property>
+ <property name="focus_on_click">False</property>
+ <signal handler="on_back_clicked" name="clicked"/>
+ <accelerator key="BackSpace" modifiers="" signal="clicked"/>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="stock">gtk-no</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="xpad">5</property>
+ <property name="label" translatable="yes">Back</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="digit0">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">0</property>
+ <property name="focus_on_click">False</property>
+ <signal handler="on_digit_clicked" name="clicked"/>
+ <accelerator key="0" modifiers="" signal="clicked"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="dial">
+ <property name="visible">True</property>
+ <property name="focus_on_click">False</property>
+ <signal handler="on_dial_clicked" name="clicked"/>
+ <accelerator key="Return" modifiers="" signal="clicked"/>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="stock">gtk-yes</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="xpad">5</property>
+ <property name="label" translatable="yes">Dial</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_expand">True</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="keypad">
+ <property name="height_request">30</property>
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Keypad</property>
+ </object>
+ <packing>
+ <property name="tab_expand">True</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+ <child>
+ <object class="GtkTreeView" id="recentview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="enable_search">False</property>
+ <property name="fixed_height_mode">True</property>
+ <property name="enable_grid_lines">GTK_TREE_VIEW_GRID_LINES_HORIZONTAL</property>
+ <property name="enable_tree_lines">True</property>
+ <signal handler="on_recentview_row_activated" name="row_activated"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_expand">True</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="recent">
+ <property name="height_request">30</property>
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Recent</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_expand">True</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="accountview">
+ <property name="visible">True</property>
+ <property name="border_width">11</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">2</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkComboBoxEntry" id="callbackcombo">
+ <property name="visible">True</property>
+ <child internal-child="entry">
+ <object class="GtkEntry" id="callbackentry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <signal handler="on_callbackentry_changed" name="changed"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="xpad">5</property>
+ <property name="label" translatable="yes">Callback Number:</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="clearcookies">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">Clear Account Information
+must reauthenticate</property>
+ <signal handler="on_clearcookies_clicked" name="clicked"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="gcnumberlabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><span size="15000" weight="bold">(518) 555-1212</span></property>
+ <property name="use_markup">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="xpad">5</property>
+ <property name="label" translatable="yes">GrandCentral
+Number:</property>
+ <property name="justify">GTK_JUSTIFY_RIGHT</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_expand">True</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="account">
+ <property name="height_request">30</property>
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Account</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><span size="20000" weight="bold">GrandCentral Dialer</span>
+Copyright 2008</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">GUI front-end to initiate outbound call from Grandcentral.com, typically with Grancentral configured to connect the outbound call to a VOIP number accessible via Gizmo on the Internet Tablet.</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Authors: Mark Bergman <bergman@merctech.com>, Eric Warnke <ericew@gmail.com></property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab_expand">True</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="about">
+ <property name="height_request">30</property>
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">About</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkDialog" id="login_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Grandcentral Login</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="skip_pager_hint">True</property>
+ <property name="deletable">False</property>
+ <property name="transient_for">Dialpad</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Username</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Password</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="usernameentry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="passwordentry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="loginbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">Login</property>
+ <signal handler="on_loginbutton_clicked" name="clicked"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">loginbutton</action-widget>
+ </action-widgets>
+ </object>
+</interface>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--Generated with glade3 3.4.4 on Sat Jun 28 00:31:26 2008 -->
+<glade-interface>
+ <widget class="GtkWindow" id="Dialpad">
+ <property name="width_request">400</property>
+ <property name="height_request">300</property>
+ <property name="title" translatable="yes">Dialer</property>
+ <child>
+ <widget class="GtkNotebook" id="notebook">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tab_pos">GTK_POS_BOTTOM</property>
+ <property name="show_border">False</property>
+ <property name="homogeneous">True</property>
+ <signal name="switch_page" handler="on_notebook_switch_page"/>
+ <child>
+ <widget class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkLabel" id="numberdisplay">
+ <property name="height_request">50</property>
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><span size="30000" weight="bold">(518) 555-1212</span></property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkTable" id="keypadview">
+ <property name="visible">True</property>
+ <property name="n_rows">4</property>
+ <property name="n_columns">3</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <widget class="GtkButton" id="digit1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">1</property>
+ <property name="focus_on_click">False</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_digit_clicked"/>
+ <accelerator key="1" modifiers="" signal="clicked"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="digit2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">2 ABC</property>
+ <property name="focus_on_click">False</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_digit_clicked"/>
+ <accelerator key="c" modifiers="" signal="clicked"/>
+ <accelerator key="b" modifiers="" signal="clicked"/>
+ <accelerator key="a" modifiers="" signal="clicked"/>
+ <accelerator key="2" modifiers="" signal="clicked"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="digit3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">3 DEF</property>
+ <property name="focus_on_click">False</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_digit_clicked"/>
+ <accelerator key="f" modifiers="" signal="clicked"/>
+ <accelerator key="e" modifiers="" signal="clicked"/>
+ <accelerator key="d" modifiers="" signal="clicked"/>
+ <accelerator key="3" modifiers="" signal="clicked"/>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="digit4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">4 GHI</property>
+ <property name="focus_on_click">False</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_digit_clicked"/>
+ <accelerator key="i" modifiers="" signal="clicked"/>
+ <accelerator key="h" modifiers="" signal="clicked"/>
+ <accelerator key="g" modifiers="" signal="clicked"/>
+ <accelerator key="4" modifiers="" signal="clicked"/>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="digit5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">5 JKL</property>
+ <property name="focus_on_click">False</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_digit_clicked"/>
+ <accelerator key="l" modifiers="" signal="clicked"/>
+ <accelerator key="k" modifiers="" signal="clicked"/>
+ <accelerator key="j" modifiers="" signal="clicked"/>
+ <accelerator key="5" modifiers="" signal="clicked"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="digit6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">6 MNO</property>
+ <property name="focus_on_click">False</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_digit_clicked"/>
+ <accelerator key="o" modifiers="" signal="clicked"/>
+ <accelerator key="n" modifiers="" signal="clicked"/>
+ <accelerator key="m" modifiers="" signal="clicked"/>
+ <accelerator key="6" modifiers="" signal="clicked"/>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="digit7">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">7 PQRS</property>
+ <property name="focus_on_click">False</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_digit_clicked"/>
+ <accelerator key="s" modifiers="" signal="clicked"/>
+ <accelerator key="r" modifiers="" signal="clicked"/>
+ <accelerator key="q" modifiers="" signal="clicked"/>
+ <accelerator key="p" modifiers="" signal="clicked"/>
+ <accelerator key="7" modifiers="" signal="clicked"/>
+ </widget>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="digit8">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">8 TUV</property>
+ <property name="focus_on_click">False</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_digit_clicked"/>
+ <accelerator key="v" modifiers="" signal="clicked"/>
+ <accelerator key="u" modifiers="" signal="clicked"/>
+ <accelerator key="t" modifiers="" signal="clicked"/>
+ <accelerator key="8" modifiers="" signal="clicked"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="digit9">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">9 WXYZ</property>
+ <property name="focus_on_click">False</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_digit_clicked"/>
+ <accelerator key="z" modifiers="" signal="clicked"/>
+ <accelerator key="y" modifiers="" signal="clicked"/>
+ <accelerator key="x" modifiers="" signal="clicked"/>
+ <accelerator key="w" modifiers="" signal="clicked"/>
+ <accelerator key="9" modifiers="" signal="clicked"/>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="back">
+ <property name="visible">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_back_clicked"/>
+ <accelerator key="BackSpace" modifiers="" signal="clicked"/>
+ <child>
+ <widget class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="stock">gtk-no</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="xpad">5</property>
+ <property name="label" translatable="yes">Back</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="digit0">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">0</property>
+ <property name="focus_on_click">False</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_digit_clicked"/>
+ <accelerator key="0" modifiers="" signal="clicked"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="dial">
+ <property name="visible">True</property>
+ <property name="has_default">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_dial_clicked"/>
+ <accelerator key="Return" modifiers="" signal="clicked"/>
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="stock">gtk-yes</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="xpad">5</property>
+ <property name="label" translatable="yes">Dial</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="tab_expand">True</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="keypad">
+ <property name="height_request">30</property>
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Keypad</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ <property name="tab_expand">True</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
+ <child>
+ <widget class="GtkTreeView" id="recentview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="enable_search">False</property>
+ <property name="fixed_height_mode">True</property>
+ <property name="enable_grid_lines">GTK_TREE_VIEW_GRID_LINES_HORIZONTAL</property>
+ <property name="enable_tree_lines">True</property>
+ <signal name="row_activated" handler="on_recentview_row_activated"/>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_expand">True</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="recent">
+ <property name="height_request">30</property>
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Recent</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ <property name="position">1</property>
+ <property name="tab_expand">True</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkTable" id="accountview">
+ <property name="visible">True</property>
+ <property name="border_width">11</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">2</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <widget class="GtkComboBoxEntry" id="callbackcombo">
+ <property name="visible">True</property>
+ <child internal-child="entry">
+ <widget class="GtkEntry" id="callbackentry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <signal name="changed" handler="on_callbackentry_changed"/>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="xpad">5</property>
+ <property name="label" translatable="yes">Callback Number:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="clearcookies">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">Clear Account Information
+must reauthenticate</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_clearcookies_clicked"/>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="gcnumberlabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><span size="15000" weight="bold">(518) 555-1212</span></property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="xpad">5</property>
+ <property name="label" translatable="yes">GrandCentral
+Number:</property>
+ <property name="justify">GTK_JUSTIFY_RIGHT</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_expand">True</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="account">
+ <property name="height_request">30</property>
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Account</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><span size="20000" weight="bold">GrandCentral Dialer</span>
+Copyright 2008</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">GUI front-end to initiate outbound call from Grandcentral.com, typically with Grancentral configured to connect the outbound call to a VOIP number accessible via Gizmo on the Internet Tablet.</property>
+ <property name="wrap">True</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Authors: Mark Bergman <bergman@merctech.com>, Eric Warnke <ericew@gmail.com></property>
+ <property name="wrap">True</property>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab_expand">True</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="about">
+ <property name="height_request">30</property>
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">About</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ <property name="position">3</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkDialog" id="login_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Grandcentral Login</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="skip_pager_hint">True</property>
+ <property name="deletable">False</property>
+ <property name="transient_for">Dialpad</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <child>
+ <widget class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Username</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Password</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="usernameentry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="passwordentry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <widget class="GtkButton" id="loginbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">Login</property>
+ <property name="response_id">0</property>
+ <signal name="clicked" handler="on_loginbutton_clicked"/>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+</glade-interface>
--- /dev/null
+#!/usr/bin/python
+
+# Grandcentral Dialer backend code
+# Eric Warnke <ericew@gmail.com>
+# Copyright 2008 GPLv2
+
+
+import os
+import re
+import urllib
+
+from browser_emu import MozillaEmulator
+
+class GCDialer:
+ # This class encapsulates all of the knowledge necessary to interace with the grandcentral servers
+ # the functions include login, setting up a callback number, and initalting a callback
+
+ _wgetOKstrRe = re.compile("This may take a few seconds", re.M) # string from Grandcentral.com on successful dial
+ _validateRe = re.compile("^[0-9]{10,}$")
+ _accessTokenRe = re.compile(r"""<input type="hidden" name="a_t" [^>]*value="(.*)"/>""")
+ _isLoginPageRe = re.compile(r"""<form method="post" action="https://www.grandcentral.com/mobile/account/login">""")
+ _callbackRe = re.compile(r"""name="default_number" value="(\d+)" />\s+(.*)\s$""", re.M)
+ _accountNumRe = re.compile(r"""<img src="/images/mobile/inbox_logo.gif" alt="GrandCentral" />\s*(.{14})\s* """, re.M)
+ _inboxRe = re.compile(r"""<td>.*?(voicemail|received|missed|call return).*?</td>\s+<td>\s+<font size="2">\s+(.*?)\s+ \| \s+<a href="/mobile/contacts/.*?">(.*?)\s?</a>\s+<br/>\s+(.*?)\s?<a href=""", re.S)
+
+ _forwardselectURL = "http://www.grandcentral.com/mobile/settings/forwarding_select"
+ _loginURL = "https://www.grandcentral.com/mobile/account/login"
+ _setforwardURL = "http://www.grandcentral.com/mobile/settings/set_forwarding?from=settings"
+ _clicktocallURL = "http://www.grandcentral.com/mobile/calls/click_to_call?a_t=%s&destno=%s"
+ _inboxallURL = "http://www.grandcentral.com/mobile/messages/inbox?types=all"
+
+ def __init__(self, cookieFile = None):
+ # Important items in this function are the setup of the browser emulation and cookie file
+
+ self._msg = ""
+ if cookieFile == None:
+ cookieFile = os.path.join(os.path.expanduser("~"),".gc_dialer_cookies.txt")
+ self._browser = MozillaEmulator(None,0)
+ self._browser.cookies.filename = cookieFile
+ if os.path.isfile(cookieFile):
+ self._browser.cookies.load()
+ #else:
+ # self._browser.cookies.save()
+ self._lastData = ""
+ self._accessToken = None
+ self._accountNum = None
+
+
+ def grabToken(self, data):
+ # Pull the magic cookie from the datastream
+
+ atGroup = GCDialer._accessTokenRe.search(data)
+ try:
+ self._accessToken = atGroup.group(1)
+ except:
+ pass
+
+ anGroup = GCDialer._accountNumRe.search(data)
+ try:
+ self._accountNum = anGroup.group(1)
+ except:
+ pass
+
+
+ def getAccountNumber(self):
+ return self._accountNum
+
+ def isAuthed(self):
+ # Attempts to detect a current session and pull the
+ # auth token ( a_t ) from the page
+
+ self._lastData = self._browser.download(GCDialer._forwardselectURL)
+ self._browser.cookies.save()
+ if GCDialer._isLoginPageRe.search(self._lastData) is None:
+ self.grabToken(self._lastData)
+ return True
+ return False
+
+ def login(self, username, password):
+ # Attempt to login to grandcentral
+
+ if self.isAuthed():
+ return
+
+ loginPostData = urllib.urlencode( {'username' : username , 'password' : password } )
+ self._lastData = self._browser.download(GCDialer._loginURL, loginPostData)
+ return self.isAuthed()
+
+ def setSaneCallback(self):
+ # Try to set a sane default callback number on these preferences
+ # 1) 1747 numbers ( Gizmo )
+ # 2) anything with gizmo in the name
+ # 3) anything with compueter in the name
+ # 4) the first value
+
+ numbers = self.getCallbackNumbers()
+
+ for k,v in numbers.iteritems():
+ if not re.compile(r"""1747""").match(k) is None:
+ self.setCallbackNumber(k)
+ return
+
+ for k,v in numbers.iteritems():
+ if not re.compile(r"""gizmo""",re.I).search(v) is None:
+ self.setCallbackNumber(k)
+ return
+
+ for k,v in numbers.iteritems():
+ if not re.compile(r"""computer""",re.I).search(v) is None:
+ self.setCallbackNumber(k)
+ return
+
+ for k,v in numbers.iteritems():
+ self.setCallbackNumber(k)
+ return
+
+ def getCallbackNumbers(self):
+
+ retval = {}
+
+ self._lastData = self._browser.download(GCDialer._forwardselectURL)
+ for match in GCDialer._callbackRe.finditer(self._lastData):
+ retval[match.group(1)] = match.group(2)
+
+ return retval
+
+
+ def setCallbackNumber(self, callbacknumber):
+ # set the number that grandcental calls
+ # this should be a proper 10 digit number
+
+ callbackPostData = urllib.urlencode({'a_t' : self._accessToken, 'default_number' : callbacknumber })
+ self._lastData = self._browser.download(GCDialer._setforwardURL, callbackPostData)
+ self._browser.cookies.save()
+
+ def getCallbackNumber(self):
+ for c in self._browser.cookies:
+ if c.name == "pda_forwarding_number":
+ return c.value
+ return None
+
+ def reset(self):
+ self._browser.cookies.clear()
+ self._browser.cookies.save()
+
+ def validate(self,number):
+ # Can this number be called ( syntax validation only )
+
+ return GCDialer._validateRe.match(number) != None
+
+ def dial(self,number):
+ # This is the main function responsible for initating the callback
+
+ # If the number is not valid throw exception
+ if self.validate(number) == False:
+ raise ValueError, 'number is not valid'
+
+ # No point if we don't have the magic cookie
+ if not self.isAuthed:
+ return False
+
+ # Strip leading 1 from 11 digit dialing
+ if len(number) == 11 and number[0] == 1:
+ number = number[1:]
+
+ self._lastData = self._browser.download(
+ GCDialer._clicktocallURL % (self._accessToken, number),
+ None, {'Referer' : 'http://www.grandcentral.com/mobile/messages'} )
+
+ if GCDialer._wgetOKstrRe.search(self._lastData) != None:
+ return True
+ else:
+ return False
+
+ def get_recent(self):
+ retval = []
+ self._lastData = self._browser.download(GCDialer._inboxallURL)
+ for match in self._inboxRe.finditer(self._lastData):
+ #print "type: %s date: %s person: %s number: %s" % (match.group(1), match.group(2), match.group(3), match.group(4))
+ retval.append([match.group(4), "%s on %s from/to %s - %s" % (match.group(1).capitalize(), match.group(2), match.group(3), match.group(4))])
+ return retval