aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore14
-rw-r--r--AUTHORS4
-rw-r--r--ChangeLog0
-rw-r--r--Makefile.am10
-rw-r--r--NEWS0
-rw-r--r--README26
-rw-r--r--TODO6
-rwxr-xr-xautogen.sh21
-rw-r--r--configure.ac23
-rw-r--r--po/.gitignore5
-rw-r--r--po/LINGUAS1
-rw-r--r--po/POTFILES.in5
-rw-r--r--po/de.po161
-rw-r--r--src/.gitignore4
-rw-r--r--src/Makefile.am23
-rw-r--r--src/ppm-modem-request.ui66
-rw-r--r--src/ppm-provider-assistant.ui209
-rw-r--r--src/ppm.ui320
-rw-r--r--src/ppm/.gitignore2
-rw-r--r--src/ppm/Makefile.am23
-rw-r--r--src/ppm/__init__.py.in5
-rw-r--r--src/ppm/modemproxy.py162
-rw-r--r--src/ppm/provider.py78
-rw-r--r--src/ppm/providerdb.py140
-rw-r--r--src/prepaid-manager-applet.desktop.in6
-rwxr-xr-xsrc/prepaid-manager-applet.in3
-rwxr-xr-xsrc/prepaid-manager-applet.py563
27 files changed, 1880 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c81b2d7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+config.log
+config.status
+aclocal.m4
+autom4te.cache/
+configure
+COPYING
+gnome-doc-utils.make
+INSTALL
+install-sh
+Makefile
+Makefile.in
+missing
+mkinstalldirs
+prepaid-manager-applet-*.tar.gz
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..8383b14
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,4 @@
+Prepaid Manager Applet code is developed by:
+
+ Guido Günther <agx@sigxcpu.org>
+
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ChangeLog
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..fb38ea7
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,10 @@
+NULL =
+
+SUBDIRS = src po
+
+DISTCLEANFILES = \
+ intltool-extract \
+ intltool-merge \
+ intltool-update \
+ gnome-doc-utils.make \
+ $(NULL)
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/NEWS
diff --git a/README b/README
new file mode 100644
index 0000000..0feba1e
--- /dev/null
+++ b/README
@@ -0,0 +1,26 @@
+About
+-----
+prepaid-manager-applet aims to ease the handling of mobile internet connections
+using GSM mobile prepaid cards on the GNOME Desktop. Such a SIM card can either
+be in a mobile phone used as a modem, a usb 3g module (usb surf stick) or used
+by the built in 3G chipset in your laptop/netbook.
+
+* It allows you to check the current balance and to top up the credit.
+* It uses ModemManager to talk to your GSM chipset.
+
+Requirements
+------------
+* ModemManager with ussd support (current git)
+* mobile-broadband-provider-info with top-up support:
+ http://git.debian.org/?p=users/agx/mobile-broadband-provider-info.git
+* pygtk2 >= 2.17
+
+Project Page
+------------
+https://honk.sigxcpu.org/piki/projects/ppm
+
+Contact
+-------
+Send comments, patches and suggestions to
+
+Guido Günther <agx@sigxcpu.org>
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..e5d42ac
--- /dev/null
+++ b/TODO
@@ -0,0 +1,6 @@
+* Add raw mode to send arbitrary USSD commands
+* Remember settings with gsettings
+* Switch to PyGI
+* Collect balance statistics in sqlitedb
+* Handle multiple modems
+* Add support for SMS top-up messages as used by some providers
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..ed5671b
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+REQUIRED_AUTOMAKE_VERSION=1.9
+REQUIRED_INTLTOOL_VERSION=0.35.0
+PKG_NAME=prepaid-manager-applet
+
+(test -f $srcdir/configure.ac \
+ && test -f $srcdir/src/prepaid-manager-applet.py) || {
+ echo -n "**Error**: Directory "\`$srcdir\'" does not look like the"
+ echo " top-level $PKG_NAME directory"
+ exit 1
+}
+
+which gnome-autogen.sh || {
+ echo "You need to install gnome-common from the GNOME git"
+ exit 1
+}
+USE_GNOME2_MACROS=1 . gnome-autogen.sh
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..8c14dca
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,23 @@
+# -*- Autoconf -*-
+
+AC_PREREQ([2.67])
+AC_INIT([prepaid-manager-applet], [0.0.0],
+ [http://honk.sigxcpu.org/projects/prepaid-manager.py])
+AM_INIT_AUTOMAKE([-Wno-portability])
+
+IT_PROG_INTLTOOL([0.35.0])
+
+GETTEXT_PACKAGE=prepaid-manager-applet
+AC_SUBST(GETTEXT_PACKAGE)
+AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE", [Gettext package])
+AM_GLIB_GNU_GETTEXT
+
+GNOME_DOC_INIT
+
+AC_CONFIG_FILES([
+Makefile
+po/Makefile.in
+src/Makefile
+src/ppm/Makefile
+])
+AC_OUTPUT
diff --git a/po/.gitignore b/po/.gitignore
new file mode 100644
index 0000000..c7d5b98
--- /dev/null
+++ b/po/.gitignore
@@ -0,0 +1,5 @@
+*.gmo
+.intltool-merge-cache
+Makefile.in.in
+POTFILES
+stamp-it
diff --git a/po/LINGUAS b/po/LINGUAS
new file mode 100644
index 0000000..7673daa
--- /dev/null
+++ b/po/LINGUAS
@@ -0,0 +1 @@
+de
diff --git a/po/POTFILES.in b/po/POTFILES.in
new file mode 100644
index 0000000..0b74d48
--- /dev/null
+++ b/po/POTFILES.in
@@ -0,0 +1,5 @@
+[type: gettext/glade]src/ppm.ui
+[type: gettext/glade]src/ppm-modem-request.ui
+[type: gettext/glade]src/ppm-provider-assistant.ui
+src/prepaid-manager-applet.desktop.in
+src/prepaid-manager-applet.py
diff --git a/po/de.po b/po/de.po
new file mode 100644
index 0000000..c6e2cfa
--- /dev/null
+++ b/po/de.po
@@ -0,0 +1,161 @@
+# German translation of prepaid-manager-applet
+# Copyright (C) 2010 Guido Günther <agx@sigxcpu.org>
+# This file is distributed under the same license as the prepaid-manager-applet
+# package.
+# Guido Günther <agx@sigxcpu.org>, 2010
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: 0.0.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-11-17 18:32+0100\n"
+"PO-Revision-Date: 2010-11-11 13:38+0100\n"
+"Last-Translator: Guido Günther <agx@sigxcpu.org>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: German\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../src/ppm.ui.h:1
+msgid "Balance"
+msgstr "Guthaben"
+
+#: ../src/ppm.ui.h:2
+msgid "Balance information from"
+msgstr "Guthaben Information vom"
+
+#: ../src/ppm.ui.h:3
+msgid "Balance unknown"
+msgstr "Guthaben unbekannt"
+
+#: ../src/ppm.ui.h:4
+msgid "Change provider"
+msgstr "Provider wechseln"
+
+#: ../src/ppm.ui.h:5
+msgid "Check your prepaid cards balance."
+msgstr "Überprüfen Sie das Guthaben auf ihrer Prepaid Karte."
+
+#: ../src/ppm.ui.h:6
+msgid "Code:"
+msgstr "Code:"
+
+#: ../src/ppm.ui.h:7
+msgid "Enter code to top up credit to your prepaid card."
+msgstr "Geben Sie den Code ein um Guthaben auf ihre Prepaid-Karte zu laden."
+
+#: ../src/ppm.ui.h:8 ../src/ppm-modem-request.ui.h:1
+#: ../src/prepaid-manager-applet.desktop.in.h:1
+msgid "Prepaid Manager"
+msgstr "Prepaid Manager"
+
+#: ../src/ppm.ui.h:9
+msgid "Provider:"
+msgstr "Provider:"
+
+#: ../src/ppm.ui.h:10
+msgid "Top up"
+msgstr "Aufladen"
+
+#: ../src/ppm.ui.h:11
+msgid "Top up your prepaid accounts balance"
+msgstr ""
+
+#: ../src/ppm.ui.h:12
+msgid "change"
+msgstr ""
+
+#: ../src/ppm.ui.h:13
+msgid "check"
+msgstr ""
+
+#: ../src/ppm.ui.h:14
+msgid "top up"
+msgstr "Aufladen"
+
+#: ../src/ppm.ui.h:15
+msgid "unknown"
+msgstr "unbekannt"
+
+#: ../src/ppm.ui.h:16
+msgid "unknown provider"
+msgstr "unbekannter Provider"
+
+#: ../src/ppm-modem-request.ui.h:2
+msgid "Sending request to modem"
+msgstr "Sende Anfrage an das Modem"
+
+#: ../src/ppm-modem-request.ui.h:3
+msgid "Waiting for reply..."
+msgstr "Warte auf Antwort..."
+
+#: ../src/ppm-provider-assistant.ui.h:1
+msgid "Check your selection"
+msgstr "Überprüfen Sie Ihre Auswahl"
+
+#: ../src/ppm-provider-assistant.ui.h:2
+msgid "Please select the country of your mobile broadband provider."
+msgstr ""
+
+#: ../src/ppm-provider-assistant.ui.h:3
+msgid "Please select your mobile broadband provider."
+msgstr ""
+
+#: ../src/ppm-provider-assistant.ui.h:4
+msgid ""
+"Prepaid Manager couldn't autodetect your mobile broadband provider. The "
+"following pages will guide you through the process of setting it up."
+msgstr ""
+
+#: ../src/ppm-provider-assistant.ui.h:5
+msgid "Provider configuration"
+msgstr "Provider Konfiguration"
+
+#: ../src/ppm-provider-assistant.ui.h:6
+msgid "Select your country"
+msgstr "Wählen Sie ihr Land aus"
+
+#: ../src/ppm-provider-assistant.ui.h:7
+msgid "Select your provider"
+msgstr "Wählen Sie ihren Provider aus"
+
+#: ../src/ppm-provider-assistant.ui.h:8
+msgid "Your mobile broadband provider is"
+msgstr "Ihr Provider für den mobilen Breitbandanschluß ist"
+
+#: ../src/ppm-provider-assistant.ui.h:9
+msgid "in"
+msgstr "in"
+
+#: ../src/prepaid-manager-applet.desktop.in.h:2
+msgid "Prepaid Manager Applet for GSM"
+msgstr "Prepaid Manager Applet für GSM"
+
+#: ../src/prepaid-manager-applet.py:281
+msgid "Enable Modem?"
+msgstr "Modem aktivieren?"
+
+#: ../src/prepaid-manager-applet.py:395
+#, python-format
+msgid ""
+"We can't find the information on how to query the account balance from your "
+"provider '%s' in our database."
+msgstr ""
+
+#: ../src/prepaid-manager-applet.py:398
+#, python-format
+msgid ""
+"We can't find any information about your provider with mcc '%s' and mnc '%s'."
+msgstr ""
+
+#: ../src/prepaid-manager-applet.py:401
+#, python-format
+msgid ""
+"\n"
+"\n"
+"You can go to %s to learn how to provide that information."
+msgstr ""
+
+#~ msgid "Statistics"
+#~ msgstr "Statistiken"
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..e54c960
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,4 @@
+prepaid-manager-applet
+prepaid-manager-applet.desktop
+*.pyc
+
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..cdb887a
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,23 @@
+SUBDIRS = ppm
+
+bin_SCRIPTS_IN = prepaid-manager-applet.in
+bin_SCRIPTS = prepaid-manager-applet
+
+desktopdir = $(datadir)/applications
+desktop_in_files = prepaid-manager-applet.desktop.in
+desktop_DATA = $(desktop_in_files:.desktop.in=.desktop)
+
+pythondir = $(pkgdatadir)
+python_DATA = $(PACKAGE).py
+
+EXTRA_DIST = $(python_DATA) $(desktop_in_files) $(ui_DATA)
+
+@INTLTOOL_DESKTOP_RULE@
+
+uidir = $(pkgdatadir)
+ui_DATA = $(wildcard $(srcdir)/*.ui)
+
+$(PACKAGE): $(srcdir)/$(PACKAGE).in
+ sed -e "s,::PACKAGE::,$(PACKAGE)," -e "s,::PYTHONDIR::,$(pkgdatadir)," < $< > $@
+
+CLEANFILES = *.pyc $(desktop_DATA) $(bin_SCRIPTS)
diff --git a/src/ppm-modem-request.ui b/src/ppm-modem-request.ui
new file mode 100644
index 0000000..6ddd166
--- /dev/null
+++ b/src/ppm-modem-request.ui
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkDialog" id="ppm_modem_request">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Prepaid Manager</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="skip_pager_hint">True</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkLabel" id="label_processing">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Sending request to modem</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">10</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkProgressBar" id="progressbar">
+ <property name="visible">True</property>
+ <property name="activity_mode">True</property>
+ <property name="show_text">True</property>
+ <property name="text" translatable="yes">Waiting for reply...</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">10</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/src/ppm-provider-assistant.ui b/src/ppm-provider-assistant.ui
new file mode 100644
index 0000000..f07314d
--- /dev/null
+++ b/src/ppm-provider-assistant.ui
@@ -0,0 +1,209 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkListStore" id="liststore_providers">
+ <columns>
+ <!-- column-name column_provider -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkListStore" id="liststore_countries">
+ <columns>
+ <!-- column-name column_countries -->
+ <column type="gchararray"/>
+ <!-- column-name column_country_codes -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkAssistant" id="ppm_provider_assistant">
+ <property name="border_width">12</property>
+ <property name="title">Provider Configuration</property>
+ <property name="modal">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="skip-pager-hint">True</property>
+ <property name="skip-taskbar-hint">True</property>
+ <signal name="prepare" handler="on_ppm_provider_assistant_prepare"/>
+ <signal name="cancel" handler="on_ppm_provider_assistant_cancel"/>
+ <signal name="close" handler="on_ppm_provider_assistant_close"/>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Prepaid Manager couldn't autodetect your mobile broadband provider. The following pages will guide you through the process of setting it up.</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="page_type">intro</property>
+ <property name="complete">True</property>
+ <property name="title" translatable="yes">Provider configuration</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox_countries">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="label_countries">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Please select the country of your mobile broadband provider.</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">10</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="treeview_countries">
+ <property name="height_request">200</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="model">liststore_countries</property>
+ <property name="search_column">0</property>
+ <signal name="cursor_changed" handler="on_treeview_countries_changed"/>
+ <child>
+ <object class="GtkTreeViewColumn" id="treeviewcolumn1">
+ <property name="title">Country</property>
+ <property name="sort_column_id">0</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer_countries"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="title" translatable="yes">Select your country</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox_providers">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="label_providers">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Please select your mobile broadband provider.</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">10</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="treeview_providers">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="model">liststore_providers</property>
+ <property name="search_column">0</property>
+ <signal name="cursor_changed" handler="on_treeview_providers_changed"/>
+ <child>
+ <object class="GtkTreeViewColumn" id="treeviewcolumn_providers">
+ <property name="title">Providers</property>
+ <property name="sort_column_id">0</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer_providers"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="title" translatable="yes">Select your provider</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox_confirm">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel" id="label_provider0">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Your mobile broadband provider is</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_provider">
+ <property name="visible">True</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_country0">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">in</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_country">
+ <property name="visible">True</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="page_type">confirm</property>
+ <property name="complete">True</property>
+ <property name="title" translatable="yes">Check your selection</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/src/ppm.ui b/src/ppm.ui
new file mode 100644
index 0000000..3e73f42
--- /dev/null
+++ b/src/ppm.ui
@@ -0,0 +1,320 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkDialog" id="ppm_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Prepaid Manager</property>
+ <property name="type_hint">dialog</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkNotebook" id="notebook1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkTable" id="table_balance">
+ <property name="visible">True</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">4</property>
+ <property name="row_spacing">4</property>
+ <child>
+ <object class="GtkLabel" id="label_provider">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Provider:</property>
+ </object>
+ <packing>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ <property name="x_padding">5</property>
+ <property name="y_padding">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_balance_provider_name">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">unknown provider</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"></property>
+ <property name="x_padding">5</property>
+ <property name="y_padding">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_provider_change">
+ <property name="label" translatable="yes">change</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Change provider</property>
+ <signal name="clicked" handler="on_provider_change_clicked"/>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ <property name="x_padding">5</property>
+ <property name="y_padding">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_balance_from">
+ <property name="label" translatable="yes">Balance information from</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ <property name="x_padding">5</property>
+ <property name="y_padding">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_balance_timestamp">
+ <property name="label" translatable="yes">unknown</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>
+ <property name="y_options"></property>
+ <property name="x_padding">5</property>
+ <property name="y_padding">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_balance_info">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Balance unknown</property>
+ <attributes>
+ <attribute name="style" value="normal"/>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ <property name="x_padding">5</property>
+ <property name="y_padding">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_check_balance">
+ <property name="label" translatable="yes">check</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Check your prepaid cards balance.</property>
+ <signal name="clicked" handler="on_balance_info_renew_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>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ <property name="x_padding">5</property>
+ <property name="y_padding">5</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="tab_balance">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Balance</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="table_top_up">
+ <property name="visible">True</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">4</property>
+ <property name="row_spacing">4</property>
+ <child>
+ <object class="GtkLabel" id="label_provider1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Provider:</property>
+ </object>
+ <packing>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ <property name="x_padding">5</property>
+ <property name="y_padding">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_topup_provider_name">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">unknown provider</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"></property>
+ <property name="x_padding">5</property>
+ <property name="y_padding">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_provider_change1">
+ <property name="label" translatable="yes">change</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ <property name="x_padding">5</property>
+ <property name="y_padding">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_top_up">
+ <property name="label" translatable="yes">top up</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Top up your prepaid accounts balance</property>
+ <signal name="clicked" handler="on_balance_top_up_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>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ <property name="x_padding">5</property>
+ <property name="y_padding">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_code">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">Enter code to top up credit to your prepaid card.</property>
+ <property name="invisible_char">&#x25CF;</property>
+ <signal name="changed" handler="on_entry_code_insert"/>
+ </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"></property>
+ <property name="x_padding">5</property>
+ <property name="y_padding">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_code">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Code:</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ <property name="x_padding">5</property>
+ <property name="y_padding">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_top_up_reply">
+ <property name="visible">True</property>
+ <property name="label" translatable="no"></property>
+ </object>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ <property name="x_padding">5</property>
+ <property name="y_padding">5</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="tab_top_up">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Top up</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</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">end</property>
+ <child>
+ <object class="GtkButton" id="button_close">
+ <property name="label">gtk-close</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_close_clicked"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button_close</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/src/ppm/.gitignore b/src/ppm/.gitignore
new file mode 100644
index 0000000..1a38d62
--- /dev/null
+++ b/src/ppm/.gitignore
@@ -0,0 +1,2 @@
+__init__.py
+*.pyc
diff --git a/src/ppm/Makefile.am b/src/ppm/Makefile.am
new file mode 100644
index 0000000..7649cbb
--- /dev/null
+++ b/src/ppm/Makefile.am
@@ -0,0 +1,23 @@
+NULL =
+
+pythondir = $(pkgdatadir)/ppm/
+python_DATA_IN = __init__.py.in
+python_DATA = \
+ __init__.py \
+ modemproxy.py \
+ provider.py \
+ providerdb.py \
+ $(NULL)
+
+
+EXTRA_DIST = $(python_DATA) $(python_DATA_IN)
+
+%.py: $(srcdir)/%.py.in
+ sed -e "s,::PACKAGE::,$(PACKAGE)," \
+ -e "s,::PYLIBDIR::,$(pkgdatadir)," \
+ -e "s,::GETTEXTDIR::,$(datadir)/locale," \
+ -e "s,::DATADIR::,$(datadir)," \
+ -e "s,::PKGDATADIR::,$(pkgdatadir)," \
+ < $< > $@
+
+CLEANFILES = *.pyc
diff --git a/src/ppm/__init__.py.in b/src/ppm/__init__.py.in
new file mode 100644
index 0000000..ab3c59d
--- /dev/null
+++ b/src/ppm/__init__.py.in
@@ -0,0 +1,5 @@
+# Paths for prepaid-manager-applet
+
+gettext_app = '::PACKAGE::'
+gettext_dir = '::GETTEXTDIR::'
+ui_dir = '::PKGDATADIR::'
diff --git a/src/ppm/modemproxy.py b/src/ppm/modemproxy.py
new file mode 100644
index 0000000..352b539
--- /dev/null
+++ b/src/ppm/modemproxy.py
@@ -0,0 +1,162 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2010 Guido Guenther <agx@sigxcpu.org>
+# 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 3 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 dbus
+import dbus.glib
+import dbus.service
+import gobject
+
+
+class ModemError(Exception):
+ def __init__(self, msg):
+ self.msg = msg
+
+ def is_forbidden(self):
+ return [False, True][self.msg.find("Operation not allowed") != -1]
+
+ def is_disabled(self):
+ return [False, True][self.msg.find("not enabled") != -1]
+
+
+class ModemManagerProxy(gobject.GObject):
+ """Interface to ModemManager DBus API
+ @ivar request: current pending request to ModemManager
+ @type request: string
+ @ivar modem: dbus path of modem we're currently acting on
+ @type modem: string
+ """
+
+ MM_DBUS_SERVICE='org.freedesktop.ModemManager'
+ MM_DBUS_INTERFACE_MODEM_MANAGER='org.freedesktop.ModemManager'
+ MM_DBUS_OBJECT_MODEM_MANAGER='/org/freedesktop/ModemManager'
+ MM_DBUS_INTERFACE_MODEM='org.freedesktop.ModemManager.Modem'
+ MM_DBUS_INTERFACE_MODEM_GSM_CARD='org.freedesktop.ModemManager.Modem.Gsm.Card'
+ MM_DBUS_INTERFACE_MODEM_GSM_USSD='org.freedesktop.ModemManager.Modem.Gsm.Ussd'
+
+ __gsignals__ = {
+ # Emitted when we got the new account balance from the provider
+ 'request-started': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ [object]),
+ 'request-finished': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ [object]),
+ }
+
+ def __init__(self):
+ self.__gobject_init__()
+ self.bus = dbus.SystemBus()
+ self.request = None
+ self.reply_func = None
+ self.error_func = None
+ self.modem = None
+ self.obj = None
+
+ def set_modem(self, modem):
+ self.modem = modem
+ self.obj = self.bus.get_object(self.MM_DBUS_SERVICE, self.modem)
+
+ def mm_request(func):
+ def wrapped_f( self, *args, **kw) :
+ self.request = "%s" % func.func_name
+ if kw.has_key('reply_func'):
+ self.reply_func = kw['reply_func']
+ if kw.has_key('error_func'):
+ self.error_func = kw['error_func']
+ self.emit('request-started', self)
+ ret = func(self, *args, **kw)
+ return ret
+ wrapped_f.__name__= func.__name__
+ wrapped_f.__doc__= func.__doc__
+ return wrapped_f
+
+ def mm_request_done(func):
+ def wrapped_f( self, *args, **kw):
+ self.emit('request-finished', self)
+ ret = func(self, *args, **kw)
+ self.reply_func = None
+ self.error_func = None
+ self.request = None
+ return ret
+ wrapped_f.__name__= func.__name__
+ wrapped_f.__doc__= func.__doc__
+ return wrapped_f
+
+ def request_pending(self):
+ if self.request:
+ return True
+ else:
+ return False
+
+ @mm_request_done
+ def handle_dbus_reply(self, *args):
+ if self.reply_func:
+ self.reply_func(*args)
+
+ @mm_request_done
+ def handle_dbus_error(self, e):
+ if self.error_func:
+ me = ModemError("%s failed: %s" % (self.request, e))
+ self.error_func(me)
+
+ def get_modems(self):
+ modems = []
+ proxy = self.bus.get_object(self.MM_DBUS_SERVICE,
+ self.MM_DBUS_OBJECT_MODEM_MANAGER)
+ mm = dbus.Interface(proxy,
+ dbus_interface=self.MM_DBUS_INTERFACE_MODEM_MANAGER)
+ ret = mm.EnumerateDevices()
+ for modem in ret:
+ modems.append(modem)
+ return modems
+
+ def get_imsi(self):
+ card = dbus.Interface(self.obj,
+ dbus_interface=self.MM_DBUS_INTERFACE_MODEM_GSM_CARD)
+ try:
+ return card.GetImsi()
+ except dbus.exceptions.DBusException as msg:
+ raise ModemError("Getting IMSI failed: %s" % msg)
+
+ def get_network_id(self):
+ imsi = self.get_imsi()
+ mcc = imsi[0:3]
+ mnc = imsi[3:5]
+ return (mcc, mnc)
+
+ @mm_request
+ def ussd_initiate(self, command, reply_func=None, error_func=None):
+ ussd = dbus.Interface(self.obj,
+ dbus_interface=self.MM_DBUS_INTERFACE_MODEM_GSM_USSD)
+ return ussd.Initiate(command,
+ reply_handler=self.handle_dbus_reply,
+ error_handler=self.handle_dbus_error)
+
+ @mm_request
+ def _modem__enable(self, enable, reply_func=None, error_func=None):
+ ussd = dbus.Interface(self.obj,
+ dbus_interface=self.MM_DBUS_INTERFACE_MODEM)
+ ussd.Enable(enable,
+ reply_handler=self.handle_dbus_reply,
+ error_handler=self.handle_dbus_error)
+
+ def modem_enable(self, reply_func=None, error_func=None):
+ self._modem__enable(True)
+
+ def modem_disable(self, reply_func=None, error_func=None):
+ self._modem_enable(False)
+
+gobject.type_register(ModemManagerProxy)
diff --git a/src/ppm/provider.py b/src/ppm/provider.py
new file mode 100644
index 0000000..484270b
--- /dev/null
+++ b/src/ppm/provider.py
@@ -0,0 +1,78 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2010 Guido Guenther <agx@sigxcpu.org>
+# 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 3 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 logging
+
+class ProviderError(Exception):
+ def __init__(self, msg):
+ self.msg = msg
+
+
+class Provider(object):
+ """Keeps the information on howto top up the ballance and how to query the
+ current ballance"""
+
+ def __init__(self, country, name):
+ self.country = country
+ self.name = name
+ self.fetch_balance_cmds = {}
+ self.top_up_cmds = {}
+ self.balance = None
+ logging.debug("New provider: %s, %s", country, name)
+
+ def add_fetch_balance_cmd(self, cmd):
+ self.fetch_balance_cmds.update(cmd)
+ logging.debug("Adding balance check %s" % cmd)
+
+ def add_top_up_cmd(self, cmd):
+ self.top_up_cmds.update(cmd)
+ logging.debug("Adding top up %s" % cmd)
+
+ def has_fetch_balance_cmd(self):
+ # Only USSD for now
+ if self.fetch_balance_cmds.has_key('ussd'):
+ return True
+ else:
+ return False
+
+ def has_top_up_cmd(self):
+ # Only USSD for now
+ if self.top_up_cmds.has_key('ussd'):
+ return True
+ else:
+ return False
+
+ def fetch_balance(self, mm, reply_func=None, error_func=None):
+ if self.has_fetch_balance_cmd():
+ mm.ussd_initiate (self.fetch_balance_cmds['ussd'],
+ reply_func=reply_func,
+ error_func=error_func)
+ return True
+ else:
+ return False
+
+ def top_up(self, mm, code, reply_func=None, error_func=None):
+ if self.has_top_up_cmd():
+ cmd = self.top_up_cmds['ussd'][0].replace(
+ self.top_up_cmds['ussd'][1],
+ code)
+ logging.debug("Top up cmd: %s", cmd)
+ mm.ussd_initiate (cmd, reply_func=reply_func, error_func=error_func)
+ return True
+ else:
+ return False
diff --git a/src/ppm/providerdb.py b/src/ppm/providerdb.py
new file mode 100644
index 0000000..a1bc394
--- /dev/null
+++ b/src/ppm/providerdb.py
@@ -0,0 +1,140 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2010 Guido Guenther <agx@sigxcpu.org>
+# 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 3 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 os
+import logging
+from lxml import etree
+
+from provider import Provider
+
+class ProviderDB(object):
+ """Proxy to mobile broadband provider database"""
+
+ provider_info = os.getenv('PPM_PROVIDER_DB',
+ '/usr/share/mobile-broadband-provider-info/'
+ 'serviceproviders.xml')
+ country_codes = '/usr/share/zoneinfo/iso3166.tab'
+
+ def __init__(self):
+ self.__tree = None
+ self.__countries = {}
+
+ @property
+ def tree(self):
+ if self.__tree:
+ return self.__tree
+ else:
+ self.__tree = etree.parse(self.provider_info)
+ return self.__tree
+
+ @property
+ def countries(self):
+ if self.__countries:
+ return self.__countries
+ else:
+ self._tree = self._load_countries()
+ return self.__countries
+
+ def _load_countries(self):
+ try:
+ for line in file(self.country_codes, 'r'):
+ if line[0] != '#':
+ (code, country) = line.split('\t',2)
+ self.__countries[code.lower()] = country.strip()
+ except IOError as msg:
+ logging.warning("Loading country code database failed: %s" % msg)
+
+ def _fill_provider_info(self, provider_elem):
+ """Fill a provider object with data from the XML"""
+ name = provider_elem.xpath("./name")[0].text
+ country = provider_elem.getparent().attrib['code']
+ gsm_elem = provider_elem.xpath("./gsm")
+ provider = Provider(country = country,
+ name = name)
+ if gsm_elem:
+ self._fill_balance_check_cmd(gsm_elem[0], provider)
+ self._fill_top_up_cmd(gsm_elem[0], provider)
+ return provider
+
+ def _fill_balance_check_cmd(self, xmlelemnt, provider):
+ """Fetch balance check method from XML and add it to the provider"""
+ for child in xmlelemnt.iter(tag='balance-check'):
+ check_types = child.getchildren()
+ for t in check_types:
+ if t.tag == 'ussd':
+ sequence = t.text
+ provider.add_fetch_balance_cmd({'ussd': sequence})
+ if t.tag == 'sms':
+ number = t.text
+ text = t.attrib['text']
+ provider.add_fetch_balance_cmd({'sms': (number, text)})
+
+ def _fill_top_up_cmd(self, xmlelement, provider):
+ for child in xmlelement.iter(tag='balance-top-up'):
+ check_types = child.getchildren()
+ for t in check_types:
+ if t.tag == 'ussd':
+ sequence = t.text
+ replacement = t.attrib['replacement']
+ provider.add_top_up_cmd({'ussd': [sequence, replacement]})
+ if t.tag == 'sms':
+ number = t.text
+ text = t.attrib['text']
+ provicer.add_top_up_cmd({'sms': (number, text)})
+
+ def get_providers(self, mcc, mnc):
+ """
+ Get possible providers for the current mcc and mnc from the database
+ """
+ path = "//network-id[@mcc='%s' and @mnc='%s']" % (mcc, mnc)
+ searcher = etree.ETXPath(path)
+ providers = []
+
+ for r in searcher(self.tree):
+ provider_elem = r.getparent().getparent()
+ providers.append(self._fill_provider_info(provider_elem))
+ return providers
+
+ def get_provider(self, country_code, name):
+ path = "//country[@code='%s']/provider[name='%s']" % (country_code, name)
+ searcher = etree.ETXPath(path)
+
+ for r in searcher(self.tree):
+ return self._fill_provider_info(r)
+ return None
+
+ def get_country_codes(self):
+ path = "/serviceproviders/country"
+ searcher = etree.ETXPath(path)
+
+ for r in searcher(self.tree):
+ yield r.attrib['code']
+
+ def get_countries(self):
+ for code in self.get_country_codes():
+ try:
+ yield (self.countries[code], code)
+ except KeyError:
+ yield (None, code)
+
+ def get_providers_by_code(self, country_code):
+ path = ("/serviceproviders/country[@code='%s']/provider/name" %
+ country_code)
+ searcher = etree.ETXPath(path)
+
+ for r in searcher(self.tree):
+ yield r.text
diff --git a/src/prepaid-manager-applet.desktop.in b/src/prepaid-manager-applet.desktop.in
new file mode 100644
index 0000000..4b568c1
--- /dev/null
+++ b/src/prepaid-manager-applet.desktop.in
@@ -0,0 +1,6 @@
+[Desktop Entry]
+_Name=Prepaid Manager
+_Comment=Prepaid Manager Applet for GSM
+Exec=prepaid-manager-applet
+Terminal=false
+Type=Application
diff --git a/src/prepaid-manager-applet.in b/src/prepaid-manager-applet.in
new file mode 100755
index 0000000..192cc22
--- /dev/null
+++ b/src/prepaid-manager-applet.in
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+exec python "::PYTHONDIR::/::PACKAGE::.py" "$@"
diff --git a/src/prepaid-manager-applet.py b/src/prepaid-manager-applet.py
new file mode 100755
index 0000000..e3957cb
--- /dev/null
+++ b/src/prepaid-manager-applet.py
@@ -0,0 +1,563 @@
+#!/usr/bin/python
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2010 Guido Guenther <agx@sigxcpu.org>
+# 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 3 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 dbus
+import dbus.mainloop.glib
+import gettext
+import gobject
+import glib
+import gtk
+import locale
+import logging
+import os
+import time
+
+import ppm
+from ppm.modemproxy import (ModemManagerProxy, ModemError)
+from ppm.provider import Provider
+from ppm.providerdb import ProviderDB
+
+# The controller receives input and initiates a response by making calls on model
+# objects. A controller accepts input from the user and instructs the model and
+# view to perform actions based on that input.
+class PPMController(gobject.GObject):
+ """
+ @ivar providers: the possible providers
+ @ivar provider: current provider
+ """
+
+ __gsignals__ = {
+ # Emitted when we got the new account balance from the provider
+ 'balance-info-fetched': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ [object]),
+ }
+
+ def _connect_mm_signals(self):
+ self.mm.connect('request-started', self.on_mm_request_started)
+ self.mm.connect('request-finished', self.on_mm_request_finished)
+
+ def __init__(self):
+ self.__gobject_init__()
+ self.mm = None
+ self.provider = None
+ self.view = None
+ self.providerdb = ProviderDB()
+
+ def get_account_balance(self):
+ return provider.balance
+
+ def fetch_balance_info(self):
+ """Fetch the current account balance from the network"""
+ if not self.provider.fetch_balance(self.mm,
+ reply_func=self.on_balance_info_fetched,
+ error_func=self.on_modem_error):
+ self.view.show_provider_balance_info_missing(self.provider)
+ logging.error("No idea how to fetch account information for "
+ "%s in %s.", self.provider.name, self.provider.country)
+
+ def top_up_balance(self):
+ code = self.view.get_top_up_code()
+ if not self.provider.top_up(self.mm, code,
+ reply_func=self.on_balance_topped_up,
+ error_func=self.on_modem_error):
+ self.view.show_provider_top_up_info_missing(self.provider)
+ logging.error("No idea how to top up balance for "
+ "%s in %s.", self.provider.name, self.provider.country)
+
+ def set_provider(self, provider=None, country_code=None, name=None):
+ """Change the current provider to provider and inform the view"""
+ if name and country_code:
+ provider = self.providerdb.get_provider(country_code, name)
+
+ if not provider:
+ raise Exception("No valid provider")
+
+ self.provider = provider
+ self.view.update_provider_name(provider.name)
+ # FIXME: fetch last stored balance from db
+
+ def _get_provider_from_mcc_mnc(self):
+ mcc, mnc = self.mm.get_network_id()
+ self.providers = self.providerdb.get_providers(mcc, mnc)
+ if self.providers:
+ if len(self.providers) > 1:
+ # More than one provider matching mcc/mnc, let user select
+ self.view.show_provider_assistant(self.providers)
+ else:
+ self.set_provider(self.providers[0])
+ else:
+ self.view.show_provider_unknown(mcc, mnc)
+
+ def init_current_provider(self):
+ try:
+ self._get_provider_from_mcc_mnc()
+ except ModemError as me:
+ logging.warning("Can't get network id: %s", me.msg)
+ if me.is_forbidden():
+ # FIXME: fetch last provider from GSettings
+ self.view.show_provider_assistant()
+ return False
+ if not me.is_disabled():
+ self.view.show_modem_error(me.msg)
+ return False
+ logging.info("modem not enabled")
+ if self.view.show_modem_enable() != gtk.RESPONSE_YES:
+ return False
+ else:
+ self.mm.modem_enable(reply_func=self.init_current_provider)
+ return True
+
+ def setup(self):
+ logging.debug("Setting up")
+
+ self.mm = ModemManagerProxy()
+ self._connect_mm_signals()
+
+ modems = self.mm.get_modems()
+ if modems:
+ modem = modems[0] # FIXME: handle multiple modems
+ logging.debug("Using modem %s" % modem)
+ self.mm.set_modem(modem)
+ glib.timeout_add(500, self.init_current_provider)
+ else:
+ self.view.show_error("No modem found.")
+ self.quit()
+ return False
+
+ def quit(self):
+ """Clean up"""
+ logging.debug("Quitting...")
+ self.view.close()
+ gtk.main_quit()
+
+ def get_provider_countries(self):
+ return self.providerdb.get_countries()
+
+ def get_provider_providers(self, country_code):
+ return self.providerdb.get_providers_by_code(country_code)
+
+ def on_mm_request_started(self, obj, mm_proxy):
+ logging.debug("Started modem request: %s", mm_proxy.request)
+ self.view.show_modem_response()
+
+ def on_mm_request_finished(self, obj, mm_proxy):
+ logging.debug("Finished modem request")
+ self.view.close_modem_response()
+
+ def on_balance_info_fetched(self, balance, *args):
+ self.provider.balance = balance
+ self.emit('balance-info-fetched', self.provider.balance)
+ self.view.update_account_balance_information(self.provider.balance,
+ time.asctime())
+
+ def on_balance_topped_up(self, reply):
+ self.view.update_top_up_information(reply)
+
+ def on_modem_error(self, e):
+ self.view.show_modem_error(e.msg)
+ logging.error(e.msg)
+
+gobject.type_register(PPMController)
+
+
+class PPMObject(object):
+ """Dialog or window constructed via a GtkBuilder"""
+ def __init__(self, view, ui):
+ if view:
+ self.controller = view.controller
+ else:
+ self.controller = None
+ self._load_ui(ui)
+
+ def _load_ui(self, ui):
+ """Load the user interfade description"""
+ self.builder = gtk.Builder()
+ self.builder.set_translation_domain(ppm.gettext_app)
+ self.builder.add_from_file(os.path.join(ppm.ui_dir, '%s.ui' % ui))
+ self.builder.connect_signals(self)
+
+ def _add_elem(self, name):
+ self.__dict__[name] = self.builder.get_object(name)
+
+ def _add_elements(self, *args):
+ for name in args:
+ self._add_elem(name)
+
+# View
+class PPMDialog(gobject.GObject, PPMObject):
+
+ def _init_subdialogs(self):
+ self.provider_info_missing_dialog = PPMProviderInfoMissingDialog(self)
+ self.provider_assistant = PPMProviderAssistant(self)
+ self.modem_response = PPMModemResponse(self)
+
+ def _setup_ui(self):
+ self.dialog = self.builder.get_object("ppm_dialog")
+ self._add_elements("label_balance_provider_name",
+ "label_topup_provider_name",
+ "label_balance_info",
+ "label_balance_timestamp",
+ "label_balance_from",
+ "entry_code",
+ "button_top_up",
+ "label_top_up_reply")
+ self._init_subdialogs()
+
+ def __init__(self, controller):
+ self.__gobject_init__()
+ PPMObject.__init__(self, None, "ppm")
+ self.controller = controller
+ # Register ourself to the controller
+ self.controller.view = self
+
+ self._setup_ui()
+ self.dialog.show()
+
+ def close(self):
+ self.dialog.hide()
+ self.dialog.destroy()
+
+ def get_top_up_code(self):
+ return self.entry_code.get_text().strip()
+
+ def on_close_clicked(self, dummy):
+ self.controller.quit()
+
+ def on_balance_top_up_clicked(self, dummy):
+ self.clear_top_up_information()
+ self.controller.top_up_balance()
+
+ def on_balance_info_renew_clicked(self, dummy):
+ self.controller.fetch_balance_info()
+
+ def on_provider_change_clicked(self, dummy):
+ # FIXME: allow to select provider
+ # and communicate the change to the controller
+ raise NotImplementedError
+
+ def on_entry_code_insert(self, *args):
+ if self.entry_code.get_text():
+ self.button_top_up.set_sensitive(True)
+ else:
+ self.button_top_up.set_sensitive(False)
+
+ def update_provider_name(self, provider_name):
+ self.label_balance_provider_name.set_text(provider_name)
+ self.label_topup_provider_name.set_text(provider_name)
+
+ def update_account_balance_information(self, balance_text, timestamp):
+ self.label_balance_info.set_text(balance_text)
+ self.label_balance_timestamp.set_text(timestamp)
+ self.label_balance_timestamp.show()
+ self.label_balance_from.show()
+
+ def update_top_up_information(self, reply):
+ self.label_top_up_reply.set_text(reply)
+
+ def clear_top_up_information(self):
+ self.label_top_up_reply.set_text("")
+
+ def show_provider_balance_info_missing(self, provider):
+ self.provider_info_missing_dialog.balance_info_missing(provider)
+
+ def show_provider_top_up_info_missing(self, provider):
+ self.provider_info_missing_dialog.top_up_info_missing(provider)
+
+ def show_provider_unknown(self, mcc, mnc):
+ self.provider_info_missing_dialog.provider_unknown(mcc, mnc)
+
+ def show_modem_error(self, msg):
+ dialog = gtk.MessageDialog(parent=self.dialog,
+ flags=gtk.DIALOG_MODAL |
+ gtk.DIALOG_DESTROY_WITH_PARENT,
+ type=gtk.MESSAGE_ERROR,
+ buttons=gtk.BUTTONS_OK)
+ dialog.set_markup("Modem error: %s" % msg)
+ dialog.run()
+ dialog.hide()
+
+ def show_modem_enable(self):
+ """Show dialog that asks if we should enable the modem"""
+ dialog = gtk.MessageDialog(parent=self.dialog,
+ flags=gtk.DIALOG_MODAL |
+ gtk.DIALOG_DESTROY_WITH_PARENT,
+ type=gtk.MESSAGE_QUESTION,
+ buttons=gtk.BUTTONS_YES_NO)
+ dialog.set_markup(_("Enable Modem?"))
+ ret = dialog.run()
+ dialog.hide()
+ return ret
+
+ def show_provider_assistant(self, providers=None):
+ self.provider_assistant.show(providers)
+
+ def show_error(self, msg):
+ """show generic error"""
+ logging.debug(msg)
+ error = gtk.MessageDialog(parent=self.dialog,
+ flags=gtk.DIALOG_MODAL |
+ gtk.DIALOG_DESTROY_WITH_PARENT,
+ type=gtk.MESSAGE_ERROR,
+ buttons=gtk.BUTTONS_OK)
+ error.set_markup(msg)
+ error.run()
+ error.hide()
+
+ def show_modem_response(self):
+ self.modem_response.show()
+
+ def close_modem_response(self):
+ self.modem_response.close()
+
+
+gobject.type_register(PPMDialog)
+
+class PPMProviderAssistant(PPMObject):
+
+ PAGE_INTRO, PAGE_COUNTRIES, PAGE_PROVIDERS, PAGE_CONFIRM = range(0, 4)
+
+ def __init__(self, main_dialog):
+ PPMObject.__init__(self, main_dialog, "ppm-provider-assistant")
+ self.assistant = self.builder.get_object("ppm_provider_assistant")
+ self._add_elements("vbox_countries",
+ "treeview_countries",
+ "vbox_providers",
+ "treeview_providers",
+ "liststore_providers",
+ "label_country",
+ "label_provider")
+ self.assistant.set_transient_for(main_dialog.dialog)
+ self.liststore_countries = None
+ self.country_code = None
+ self.provider = None
+ self.possible_providers = None
+
+ def _get_current_country_from_locale(self):
+ (l, enc) = locale.getlocale()
+ code = l.lower().split('_')[0]
+ logging.debug("Assuming your in country %s" % code)
+ return code
+
+ def _select_country_row(self, iter):
+ path = self.liststore_countries.get_path(iter)[0]
+ treeselection = self.treeview_countries.get_selection()
+ treeselection.select_path(path)
+ self.treeview_countries.scroll_to_cell(path)
+ self.assistant.set_page_complete(self.vbox_countries, True)
+
+ def _fill_liststore_countries(self):
+ """Fille the countries liststore with all known countries"""
+ lcode = self._get_current_country_from_locale()
+ if not self.liststore_countries:
+ self.liststore_countries = self.builder.get_object(
+ "liststore_countries")
+ for (country, code) in self.controller.get_provider_countries():
+ if country is None:
+ country = code
+ iter = self.liststore_countries.append()
+ self.liststore_countries.set_value(iter, 0, country)
+ self.liststore_countries.set_value(iter, 1, code)
+ if code == lcode:
+ self.country_code = code
+ self._select_country_row(iter)
+
+ def _providers_only_page_func(self, current_page):
+ if current_page < self.PAGE_PROVIDERS:
+ return self.PAGE_PROVIDERS
+ else:
+ return current_page+1
+
+ def _all_pages_func(self, current_page):
+ return current_page+1
+
+ def show(self, providers=None):
+ self.possible_providers = providers
+ self.provider = None
+
+ if not self.possible_providers:
+ # No list of possible providers so allow to select the country first
+ self._fill_liststore_countries()
+ self.assistant.set_forward_page_func(self._all_pages_func)
+ else:
+ # List of possible providers given, all from the same country
+ self.country_code = self.possible_providers[0].country
+ self.assistant.set_forward_page_func(self._providers_only_page_func)
+ self.assistant.show()
+
+ def close(self):
+ self.assistant.hide()
+
+ def on_ppm_provider_assistant_cancel(self, obj):
+ logging.debug("Assistant canceled.")
+ self.close()
+
+ def _fill_provider_liststore_by_country_code(self, country_code):
+ self.liststore_providers.clear()
+ for provider in self.controller.get_provider_providers(country_code):
+ iter = self.liststore_providers.append()
+ self.liststore_providers.set_value(iter, 0, provider)
+
+ def _fill_provider_liststore_by_providers(self):
+ self.liststore_providers.clear()
+ for provider in self.possible_providers:
+ iter = self.liststore_providers.append()
+ self.liststore_providers.set_value(iter,
+ 0,
+ provider.name)
+
+ def on_ppm_provider_assistant_prepare(self, obj, page):
+ if self.assistant.get_current_page() == self.PAGE_PROVIDERS:
+ if self.possible_providers:
+ self._fill_provider_liststore_by_providers()
+ else:
+ self._fill_provider_liststore_by_country_code(self.country_code)
+ elif self.assistant.get_current_page() == self.PAGE_CONFIRM:
+ self.label_country.set_text(self.country_code)
+ self.label_provider.set_text(self.provider)
+
+ def on_treeview_countries_changed(self, obj):
+ self.assistant.set_page_complete(self.vbox_countries, True)
+ selection = self.treeview_countries.get_selection()
+ (model, iter) = selection.get_selected()
+ if not iter:
+ return
+ self.country_code = model.get_value(iter, 1)
+
+ def on_treeview_providers_changed(self, obj):
+ self.assistant.set_page_complete(self.vbox_providers, True)
+ selection = self.treeview_providers.get_selection()
+ (model, iter) = selection.get_selected()
+ if not iter:
+ return
+ self.provider = model.get_value(iter, 0)
+
+ def on_ppm_provider_assistant_close(self, obj):
+ logging.debug("Selected: %s %s", self.provider, self.country_code)
+ self.close()
+ self.controller.set_provider(name=self.provider,
+ country_code=self.country_code)
+
+
+class PPMProviderInfoMissingDialog(object):
+ """
+ If information about the provider is missing redirect the user to a webpage
+ that explains howto provide that information
+ """
+
+ wiki_url = ('<a href = \"http://live.gnome.org/NetworkManager/'
+ 'MobileBroadband/ServiceProviders\">website</a>')
+
+ def __init__(self, main_dialog):
+ self.dialog = gtk.MessageDialog(parent=main_dialog.dialog,
+ flags=gtk.DIALOG_MODAL |
+ gtk.DIALOG_DESTROY_WITH_PARENT,
+ type=gtk.MESSAGE_INFO,
+ buttons=gtk.BUTTONS_OK)
+ self.messages = {
+ 'balance_info_missing':
+ _("We can't find the information on how to query the "
+ "account balance from your provider '%s' in our database."),
+ 'top_up_info_missing':
+ _("We can't find the information on how to top up the "
+ "balance for your provider '%s' in our database."),
+ 'provider_unknown':
+ _("We can't find any information about your provider with "
+ "mcc '%s' and mnc '%s'.")
+ }
+ self.common_msg = _("\n\nYou can go to %s to learn how to provide that "
+ "information.")
+
+ def _run(self, msg):
+ msg += self.common_msg % self.wiki_url
+ self.dialog.set_markup(msg)
+ self.dialog.run()
+ self.dialog.hide()
+
+ def balance_info_missing(self, provider):
+ msg = self.messages['balance_info_missing'] % provider.name
+ self._run(msg)
+
+ def top_up_info_missing(self, provider):
+ msg = self.messages['top_up_info_missing'] % provider.name
+ self._run(msg)
+
+ def provider_unknown(self, mcc, mnc):
+ msg = self.messages['provider_unknown'] % (mcc, mnc)
+ self._run(msg)
+
+
+class PPMModemResponse(PPMObject):
+ """Dialog shown while waiting for a response from the modem"""
+ def __init__(self, main_dialog):
+ PPMObject.__init__(self, main_dialog, "ppm-modem-request")
+ self.dialog = self.builder.get_object("ppm_modem_request")
+ self._add_elements("progressbar")
+ self.dialog.set_transient_for(main_dialog.dialog)
+ self.timer = None
+
+ def show(self):
+ self.timer = glib.timeout_add(50, self.do_progress,
+ priority=glib.PRIORITY_HIGH)
+ self.dialog.show()
+
+ def close(self):
+ if self.timer:
+ glib.source_remove(self.timer)
+ self.timer = None
+ self.dialog.hide()
+
+ def do_progress(self):
+ self.progressbar.pulse()
+ return True
+
+
+def setup_i18n():
+ locale.setlocale(locale.LC_ALL, '')
+ gettext.install(ppm.gettext_app, ppm.gettext_dir)
+ gettext.bindtextdomain(ppm.gettext_app, ppm.gettext_dir)
+ locale.bindtextdomain(ppm.gettext_app, ppm.gettext_dir)
+ _ = gettext.gettext
+ logging.debug('Using locale: %s', locale.getlocale())
+
+
+def setup_dbus():
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+
+def main():
+ logging.basicConfig(level=logging.DEBUG,
+ format='ppm: %(levelname)s: %(message)s')
+
+ setup_dbus()
+ setup_i18n()
+
+ controller = PPMController()
+ main_dialog = PPMDialog(controller)
+ glib.timeout_add(1, controller.setup)
+
+ gtk.main()
+
+if __name__ == "__main__":
+ try:
+ main()
+ except KeyboardInterrupt:
+ logging.debug("Received KeyboardInterrupt. Exiting application.")
+ except SystemExit:
+ raise