|
|
|
#!/usr/bin/python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
# TODO:
|
|
|
|
# - IPv6
|
|
|
|
# - Proxy
|
|
|
|
# - Fix connman's PropertyChanged when changing off/manual -> dhcp,
|
|
|
|
# gateway is not updated.
|
|
|
|
|
|
|
|
|
|
|
|
import dbus
|
|
|
|
import dbus.service
|
|
|
|
import logging
|
|
|
|
import argparse
|
|
|
|
import os.path
|
|
|
|
|
|
|
|
# For python2 backwards compatibility
|
|
|
|
try:
|
|
|
|
import configparser
|
|
|
|
except ImportError:
|
|
|
|
import ConfigParser as configparser
|
|
|
|
|
|
|
|
try:
|
|
|
|
import efl.evas as evas
|
|
|
|
import efl.ecore as ecore
|
|
|
|
import efl.edje
|
|
|
|
from efl.dbus_mainloop import DBusEcoreMainLoop
|
|
|
|
import efl.elementary as elm
|
|
|
|
from efl.elementary import ELM_POLICY_QUIT, \
|
|
|
|
ELM_POLICY_QUIT_LAST_WINDOW_CLOSED
|
|
|
|
from efl.elementary.window import Window, ELM_WIN_BASIC, \
|
|
|
|
ELM_WIN_DIALOG_BASIC
|
|
|
|
from efl.elementary.background import Background
|
|
|
|
from efl.elementary.box import Box
|
|
|
|
from efl.elementary.label import Label
|
|
|
|
from efl.elementary.naviframe import Naviframe
|
|
|
|
from efl.elementary.popup import Popup
|
|
|
|
from efl.elementary.button import Button
|
|
|
|
from efl.elementary.scroller import Scroller, ELM_SCROLLER_POLICY_OFF, \
|
|
|
|
ELM_SCROLLER_POLICY_AUTO
|
|
|
|
from efl.elementary.check import Check
|
|
|
|
from efl.elementary.progressbar import Progressbar
|
|
|
|
from efl.elementary.genlist import Genlist, GenlistItemClass
|
|
|
|
from efl.elementary.segment_control import SegmentControl
|
|
|
|
from efl.elementary.frame import Frame
|
|
|
|
from efl.elementary.entry import Entry
|
|
|
|
from efl.elementary.icon import Icon
|
|
|
|
from efl.elementary.layout import Layout
|
|
|
|
from efl.elementary.theme import Theme
|
|
|
|
except:
|
|
|
|
import elementary as elm
|
|
|
|
import evas
|
|
|
|
import ecore
|
|
|
|
import edje
|
|
|
|
from e_dbus import DBusEcoreMainLoop
|
|
|
|
from elementary import Window, Background, Box, Label, Naviframe, Popup, \
|
|
|
|
Button, Scroller, Check, Progressbar, Genlist, GenlistItemClass, \
|
|
|
|
SegmentControl, Frame, Entry, Icon, Layout, Theme, ELM_WIN_BASIC, \
|
|
|
|
ELM_WIN_DIALOG_BASIC, ELM_POLICY_QUIT, ELM_SCROLLER_POLICY_OFF, \
|
|
|
|
ELM_SCROLLER_POLICY_AUTO, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED
|
|
|
|
|
|
|
|
|
|
|
|
dbus_ml = DBusEcoreMainLoop()
|
|
|
|
bus = dbus.SystemBus(mainloop=dbus_ml)
|
|
|
|
log = logging.getLogger("econnman")
|
|
|
|
log_handler = logging.StreamHandler()
|
|
|
|
log_formatter = logging.Formatter(
|
|
|
|
"%(relativeCreated)d %(levelname)s %(name)s: %(message)s"
|
|
|
|
)
|
|
|
|
log_handler.setFormatter(log_formatter)
|
|
|
|
log.addHandler(log_handler)
|
|
|
|
|
|
|
|
manager = None
|
|
|
|
|
|
|
|
CONF_FILE = "/var/lib/connman/econnman.config"
|
|
|
|
configs = None
|
|
|
|
|
|
|
|
EXPAND_BOTH = (evas.EVAS_HINT_EXPAND, evas.EVAS_HINT_EXPAND)
|
|
|
|
EXPAND_HORIZ = (evas.EVAS_HINT_EXPAND, 0.0)
|
|
|
|
|
|
|
|
FILL_BOTH = (evas.EVAS_HINT_FILL, evas.EVAS_HINT_FILL)
|
|
|
|
|
|
|
|
|
|
|
|
########################################################################
|
|
|
|
# Debug helpers:
|
|
|
|
def dbus_variant_to_str(v):
|
|
|
|
if isinstance(v, dbus.String):
|
|
|
|
v = '"%s"' % (str(v),)
|
|
|
|
elif isinstance(v, dbus.Boolean):
|
|
|
|
v = str(bool(v))
|
|
|
|
elif isinstance(v, (dbus.Dictionary, dbus.Struct)):
|
|
|
|
v = "{%s}" % (dbus_dict_to_str(v),)
|
|
|
|
elif isinstance(v, dbus.Array):
|
|
|
|
v = "[%s]" % (dbus_array_to_str(v),)
|
|
|
|
elif isinstance(v, dbus.ObjectPath):
|
|
|
|
v = str(v)
|
|
|
|
elif isinstance(v, (dbus.Byte, dbus.Int16, dbus.Int32, dbus.Int64,
|
|
|
|
dbus.UInt16, dbus.UInt32, dbus.UInt64)):
|
|
|
|
v = int(v)
|
|
|
|
elif isinstance(v, dbus.Double):
|
|
|
|
v = float(v)
|
|
|
|
else:
|
|
|
|
v = repr(v)
|
|
|
|
return v
|
|
|
|
|
|
|
|
|
|
|
|
def dbus_dict_to_str(d):
|
|
|
|
"""Help debug by converting a dbus.Dictionary to a string in a shorter
|
|
|
|
form.
|
|
|
|
"""
|
|
|
|
s = []
|
|
|
|
for k, v in d.items():
|
|
|
|
s.append("%s=%s" % (k, dbus_variant_to_str(v)))
|
|
|
|
return ", ".join(s)
|
|
|
|
|
|
|
|
|
|
|
|
def dbus_array_to_str(a):
|
|
|
|
"""Help debug by converting a complex structure to a string in shorter
|
|
|
|
form.
|
|
|
|
"""
|
|
|
|
return ", ".join(dbus_variant_to_str(x) for x in a)
|
|
|
|
|
|
|
|
|
|
|
|
def dbus_array_of_dict_to_str(a):
|
|
|
|
"""Help debug by converting a complex structure to a string in a
|
|
|
|
shorter form with only the keys, not the value.
|
|
|
|
"""
|
|
|
|
s = []
|
|
|
|
for k, v in a:
|
|
|
|
s.append(str(k))
|
|
|
|
return ", ".join(s)
|
|
|
|
|
|
|
|
|
|
|
|
class ObjectView(object):
|
|
|
|
"""Base for viewing a complex object.
|
|
|
|
|
|
|
|
Implementors must set:
|
|
|
|
- bus_interface: to assign to self.bus_obj
|
|
|
|
- create_view(properties): to create the specific view widgets
|
|
|
|
- on_property_changed(name, value): to update view widgets
|
|
|
|
|
|
|
|
Provided automatically by this class:
|
|
|
|
- path: object path
|
|
|
|
- bus_obj: proxy object with specific interface to remote bus object
|
|
|
|
- obj: main toplevel view object
|
|
|
|
- box: main toplevel view box
|
|
|
|
"""
|
|
|
|
bus_interface = None
|
|
|
|
|
|
|
|
def __init__(self, parent, path, properties):
|
|
|
|
self.path = path
|
|
|
|
self.bus_obj = dbus.Interface(bus.get_object("net.connman", path),
|
|
|
|
self.bus_interface)
|
|
|
|
self.sig_ch = self.bus_obj.connect_to_signal("PropertyChanged",
|
|
|
|
self.on_property_changed)
|
|
|
|
|
|
|
|
self.obj = Scroller(parent)
|
|
|
|
self.obj.on_del_add(self._deleted)
|
|
|
|
self.obj.size_hint_weight = EXPAND_BOTH
|
|
|
|
self.obj.policy_set(ELM_SCROLLER_POLICY_OFF,
|
|
|
|
ELM_SCROLLER_POLICY_AUTO)
|
|
|
|
self.obj.bounce_set(False, True)
|
|
|
|
self.obj.content_min_limit(True, False)
|
|
|
|
|
|
|
|
self.box = Box(self.obj)
|
|
|
|
self.box.size_hint_weight = EXPAND_HORIZ
|
|
|
|
self.box.horizontal = False
|
|
|
|
|
|
|
|
self.create_view(properties)
|
|
|
|
|
|
|
|
self.obj.content = self.box
|
|
|
|
for k in properties:
|
|
|
|
self.on_property_changed(k, properties[k])
|
|
|
|
|
|
|
|
def _deleted(self, obj):
|
|
|
|
log.debug("View deleted %s (%s)", self.__class__.__name__, self.path)
|
|
|
|
self.sig_ch.remove()
|
|
|
|
self.bus_obj = None
|
|
|
|
self.sig_ch = None
|
|
|
|
self.obj = None
|
|
|
|
|
|
|
|
def create_view(self, properties):
|
|
|
|
log.critical("must be implemented!")
|
|
|
|
pass
|
|
|
|
|
|
|
|
def on_property_changed(self, name, value):
|
|
|
|
log.critical("must be implemented!")
|
|
|
|
|
|
|
|
def add_check(self, box, label, callback=None):
|
|
|
|
obj = Check(box)
|
|
|
|
obj.size_hint_weight = EXPAND_HORIZ
|
|
|
|
obj.size_hint_align = FILL_BOTH
|
|
|
|
obj.text = label
|
|
|
|
obj.show()
|
|
|
|
box.pack_end(obj)
|
|
|
|
if callback:
|
|
|
|
obj.callback_changed_add(callback)
|
|
|
|
return obj
|
|
|
|
|
|
|
|
def add_button(self, box, label, callback):
|
|
|
|
obj = Button(box)
|
|
|
|
obj.size_hint_weight = EXPAND_HORIZ
|
|
|
|
obj.size_hint_align = FILL_BOTH
|
|
|
|
obj.text = label
|
|
|
|
obj.show()
|
|
|
|
obj.callback_clicked_add(callback)
|
|
|
|
box.pack_end(obj)
|
|
|
|
return obj
|
|
|
|
|
|
|
|
def add_label(self, box, label):
|
|
|
|
lb = Label(box)
|
|
|
|
lb.size_hint_weight = EXPAND_HORIZ
|
|
|
|
lb.size_hint_align = FILL_BOTH
|
|
|
|
lb.text = label
|
|
|
|
lb.show()
|
|
|
|
box.pack_end(lb)
|
|
|
|
return lb
|
|
|
|
|
|
|
|
def add_progress(self, box, label):
|
|
|
|
pb = Progressbar(box)
|
|
|
|
pb.size_hint_weight = EXPAND_HORIZ
|
|
|
|
pb.size_hint_align = FILL_BOTH
|
|
|
|
pb.text = label
|
|
|
|
pb.show()
|
|
|
|
box.pack_end(pb)
|
|
|
|
return pb
|
|
|
|
|
|
|
|
def add_segment_control(self, box, options, callback):
|
|
|
|
sc = SegmentControl(box)
|
|
|
|
sc.size_hint_weight = EXPAND_HORIZ
|
|
|
|
sc.size_hint_align = FILL_BOTH
|
|
|
|
items = {}
|
|
|
|
for o in options:
|
|
|
|
items[o] = sc.item_add(None, o)
|
|
|
|
sc.show()
|
|
|
|
box.pack_end(sc)
|
|
|
|
sc.callback_changed_add(callback)
|
|
|
|
return sc, items
|
|
|
|
|
|
|
|
def add_frame_and_box(self, box, label):
|
|
|
|
fr = Frame(box)
|
|
|
|
fr.size_hint_weight = EXPAND_HORIZ
|
|
|
|
fr.size_hint_align = FILL_BOTH
|
|
|
|
fr.text = label
|
|
|
|
fr.show()
|
|
|
|
box.pack_end(fr)
|
|
|
|
|
|
|
|
bx = Box(fr)
|
|
|
|
bx.size_hint_weight = EXPAND_HORIZ
|
|
|
|
bx.size_hint_align = FILL_BOTH
|
|
|
|
bx.horizontal = False
|
|
|
|
bx.show()
|
|
|
|
fr.content = bx
|
|
|
|
return fr, bx
|
|
|
|
|
|
|
|
def add_label_and_entry(self, box, label, callback=None):
|
|
|
|
lb = self.add_label(box, label)
|
|
|
|
|
|
|
|
en = Entry(box)
|
|
|
|
en.size_hint_weight = EXPAND_HORIZ
|
|
|
|
en.size_hint_align = FILL_BOTH
|
|
|
|
en.single_line = True
|
|
|
|
en.scrollable = True
|
|
|
|
en.show()
|
|
|
|
box.pack_end(en)
|
|
|
|
if callback:
|
|
|
|
en.callback_activated_add(callback)
|
|
|
|
return lb, en
|
|
|
|
|
|
|
|
|
|
|
|
#######################################################################
|
|
|
|
# Config Files Helper:
|
|
|
|
def config_file_setup():
|
|
|
|
global configs
|
|
|
|
configs = configparser.RawConfigParser()
|
|
|
|
configs.optionxform = str
|
|
|
|
try:
|
|
|
|
fd = open(CONF_FILE, 'r', encoding='utf8')
|
|
|
|
configs.readfp(fd)
|
|
|
|
fd.close()
|
|
|
|
except IOError:
|
|
|
|
popup_error(
|
|
|
|
win,
|
|
|
|
"Cannot read configuration file",
|
|
|
|
"Econnman cannot read the coniguration file \"" + CONF_FILE +
|
|
|
|
"\", used by connman to configure your ieee802.1x networks. "
|
|
|
|
"Make sure the user running connman is able to read/write it."
|
|
|
|
)
|
|
|
|
configs = None
|
|
|
|
raise IOError
|
|
|
|
|
|
|
|
|
|
|
|
def config_del(name):
|
|
|
|
global configs
|
|
|
|
secname = 'service_' + name
|
|
|
|
if configs is None:
|
|
|
|
try:
|
|
|
|
config_file_setup()
|
|
|
|
except IOError:
|
|
|
|
return
|
|
|
|
if configs.has_section(secname):
|
|
|
|
configs.remove_section(secname)
|
|
|
|
config_write(name)
|
|
|
|
|
|
|
|
|
|
|
|
def config_set(name, key, value):
|
|
|
|
global configs
|
|
|
|
secname = 'service_' + name
|
|
|
|
if configs is None:
|
|
|
|
try:
|
|
|
|
config_file_setup()
|
|
|
|
except IOError:
|
|
|
|
return
|
|
|
|
if not configs.has_section(secname):
|
|
|
|
configs.add_section(secname)
|
|
|
|
configs.set(secname, 'Type', 'wifi')
|
|
|
|
configs.set(secname, 'Name', name)
|
|
|
|
if value is not None:
|
|
|
|
configs.set(secname, key, value)
|
|
|
|
elif configs.has_option(secname, key):
|
|
|
|
configs.remove_option(secname, key)
|
|
|
|
config_write(name)
|
|
|
|
|
|
|
|
|
|
|
|
def config_get(name):
|
|
|
|
global configs
|
|
|
|
if configs is None:
|
|
|
|
try:
|
|
|
|
config_file_setup()
|
|
|
|
except IOError:
|
|
|
|
return None
|
|
|
|
for sec in configs.sections():
|
|
|
|
if configs.has_option(sec, 'Name') and \
|
|
|
|
configs.get(sec, 'Name') == name:
|
|
|
|
return sec
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def config_option_get(secname, key):
|
|
|
|
if configs.has_option(secname, key):
|
|
|
|
return configs.get(secname, key)
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def config_exists(name):
|
|
|
|
if config_get(name):
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def config_write(name):
|
|
|
|
global configs
|
|
|
|
try:
|
|
|
|
with open(CONF_FILE, 'w', encoding='utf8') as configfile:
|
|
|
|
configs.write(configfile)
|
|
|
|
except IOError:
|
|
|
|
popup_error(
|
|
|
|
win,
|
|
|
|
"Cannot write configuration file",
|
|
|
|
"Econnman cannot write the coniguration file \"" + CONF_FILE +
|
|
|
|
"\", used by connman to configure your ieee802.1x networks. "
|
|
|
|
"Make sure the user running connman is able to read/write it."
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
########################################################################
|
|
|
|
# Views:
|
|
|
|
class OfflineModeMonitor(object):
|
|
|
|
"""Monitors the Manager's OfflineMode property as a Toggle.
|
|
|
|
|
|
|
|
The toggle reflects the server state but can be changed by the
|
|
|
|
user to set the property remotely.
|
|
|
|
"""
|
|
|
|
def __init__(self, win):
|
|
|
|
self.obj = Check(win)
|
|
|
|
self.obj.style = "toggle"
|
|
|
|
self.obj.part_text_set("on", "Offline")
|
|
|
|
self.obj.part_text_set("off", "Online")
|
|
|
|
self.obj.callback_changed_add(self._on_user_changed)
|
|
|
|
self.obj.on_del_add(self._deleted)
|
|
|
|
|
|
|
|
def on_reply(properties):
|
|
|
|
for name, value in properties.items():
|
|
|
|
log.debug("property %s: %s", name, value)
|
|
|
|
self._property_changed(name, value)
|
|
|
|
|
|
|
|
def on_error(exc):
|
|
|
|
popup_fatal(win, "Failed to get ConnMan Properties", str(exc))
|
|
|
|
|
|
|
|
manager.GetProperties(reply_handler=on_reply,
|
|
|
|
error_handler=on_error)
|
|
|
|
self.sig_ch = manager.connect_to_signal("PropertyChanged",
|
|
|
|
self._property_changed)
|
|
|
|
|
|
|
|
def _deleted(self, obj):
|
|
|
|
self.sig_ch.remove()
|
|
|
|
self.obj = None
|
|
|
|
self.sig_ch = None
|
|
|
|
|
|
|
|
def _property_changed(self, name, value):
|
|
|
|
log.debug("property %s: %s", name, value)
|
|
|
|
if name == "OfflineMode":
|
|
|
|
self.obj.state = bool(value)
|
|
|
|
|
|
|
|
def _on_user_changed(self, obj):
|
|
|
|
state = obj.state
|
|
|
|
|
|
|
|
def on_reply():
|
|
|
|
log.info("Set OfflineMode=%s", state)
|
|
|
|
|
|
|
|
def on_error(exc):
|
|
|
|
log.error("Failed to set OfflineMode=%s: %s", state, exc)
|
|
|
|
obj.state = not state
|
|
|
|
popup_error(self.obj, "Failed to Apply Offline Mode",
|
|
|
|
exc.get_dbus_message())
|
|
|
|
|
|
|
|
manager.SetProperty("OfflineMode", dbus.Boolean(state),
|
|
|
|
reply_handler=on_reply, error_handler=on_error)
|
|
|
|
|
|
|
|
|
|
|
|
class TechList(object):
|
|
|
|
"""Provides a Genlist with the Technologies supported.
|
|
|
|
|
|
|
|
It will call manager's GetTechnologies() and then keep it updated
|
|
|
|
with TechnologyAdded and TechnologyRemoved signals, as well as the
|
|
|
|
technologies properties with PropertyChanged.
|
|
|
|
|
|
|
|
Selecting an item will call C{on_selected(path, tech_properties)}.
|
|
|
|
"""
|
|
|
|
def __init__(self, parent, on_selected=None):
|
|
|
|
self.techs = {}
|
|
|
|
self.items = {}
|
|
|
|
self.obj = Genlist(parent)
|
|
|
|
self.obj.on_del_add(self._deleted)
|
|
|
|
self.on_selected = on_selected
|
|
|
|
self.obj.callback_selected_add(self._tech_selected)
|
|
|
|
self.sig_added = manager.connect_to_signal(
|
|
|
|
"TechnologyAdded",
|
|
|
|
self._tech_added
|
|
|
|
)
|
|
|
|
self.sig_removed = manager.connect_to_signal(
|
|
|
|
"TechnologyRemoved",
|
|
|
|
self._tech_removed
|
|
|
|
)
|
|
|
|
self.sig_propch = bus.add_signal_receiver(
|
|
|
|
self._tech_changed,
|
|
|
|
"PropertyChanged",
|
|
|
|
"net.connman.Technology",
|
|
|
|
"net.connman",
|
|
|
|
path_keyword='path'
|
|
|
|
)
|
|
|
|
self.itc = GenlistItemClass(
|
|
|
|
item_style="default",
|
|
|
|
text_get_func=self._item_text_get,
|
|
|
|
content_get_func=self._item_content_get
|
|
|
|
)
|
|
|
|
|
|
|
|
manager.GetTechnologies(
|
|
|
|
reply_handler=self._get_techs_reply,
|
|
|
|
error_handler=self._get_techs_error
|
|
|
|
)
|
|
|
|
|
|
|
|
def _deleted(self, lst):
|
|
|
|
self.sig_added.remove()
|
|
|
|
self.sig_removed.remove()
|
|
|
|
self.sig_propch.remove()
|
|
|
|
|
|
|
|
self.obj = None
|
|
|
|
self.sig_added = None
|
|
|
|
self.sig_removed = None
|
|
|
|
self.sig_propch = None
|
|
|
|
self.techs.clear()
|
|
|
|
self.items.clear()
|
|
|
|
|
|
|
|
def _get_techs_reply(self, techs):
|
|
|
|
log.debug("Got technologies: %s", dbus_array_of_dict_to_str(techs))
|
|
|
|
for path, properties in techs:
|
|
|
|
self._tech_added(path, properties)
|
|
|
|
|
|
|
|
def _get_techs_error(self, exc):
|
|
|
|
log.error("Failed to GetTechnologies(): %s", exc)
|
|
|
|
popup_error(self.obj, "Failed to get Technologies",
|
|
|
|
exc.get_dbus_message())
|
|
|
|
|
|
|
|
def _tech_added(self, path, properties):
|
|
|
|
path = str(path)
|
|
|
|
log.debug("Added %s: %s", path, dbus_dict_to_str(properties))
|
|
|
|
self.techs[path] = properties
|
|
|
|
self.items[path] = self.obj.item_append(self.itc, path)
|
|
|
|
|
|
|
|
def _tech_changed(self, name, value, path):
|
|
|
|
path = str(path)
|
|
|
|
log.debug("Changed %s: %s=%s", path, name, value)
|
|
|
|
t = self.techs.get(path)
|
|
|
|
if not t:
|
|
|
|
return
|
|
|
|
t[name] = value
|
|
|
|
it = self.items.get(path)
|
|
|
|
if not it:
|
|
|
|
return
|
|
|
|
it.update()
|
|
|
|
|
|
|
|
def _tech_removed(self, path):
|
|
|
|
path = str(path)
|
|
|
|
log.debug("Removed %s", path)
|
|
|
|
try:
|
|
|
|
del self.techs[path]
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
try:
|
|
|
|
it = self.items.pop(path)
|
|
|
|
it.delete()
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
def _tech_selected(self, lst, item):
|
|
|
|
item.selected = False
|
|
|
|
if not self.on_selected:
|
|
|
|
return
|
|
|
|
path = item.data
|
|
|
|
t = self.techs.get(path)
|
|
|
|
if t:
|
|
|
|
self.on_selected(path, t)
|
|
|
|
|
|
|
|
def _item_text_get(self, obj, part, item_data):
|
|
|
|
if part != "elm.text":
|
|
|
|
return None
|
|
|
|
t = self.techs.get(item_data)
|
|
|
|
if not t:
|
|
|
|
return "Unknown"
|
|
|
|
return t.get("Name", item_data[len("/net/connman/technology/"):])
|
|
|
|
|
|
|
|
def _item_content_get(self, obj, part, item_data):
|
|
|
|
if part == "elm.swallow.end":
|
|
|
|
ic = Icon(obj)
|
|
|
|
ic.standard = "arrow_right"
|
|
|
|
return ic
|
|
|
|
|
|
|
|
if part != "elm.swallow.icon":
|
|
|
|
return
|
|
|
|
t = self.techs.get(item_data)
|
|
|
|
if not t:
|
|
|
|
return None
|
|
|
|
|
|
|
|
ic = Icon(obj)
|
|
|
|
if t.get("Connected", False):
|
|
|
|
ic.standard = "connman-tech-connected"
|
|
|
|
elif t.get("Powered", False):
|
|
|
|
ic.standard = "connman-tech-powered"
|
|
|
|
else:
|
|
|
|
ic.standard = "connman-tech-offline"
|
|
|
|
return ic
|
|
|
|
|
|
|
|
|
|
|
|
class TechView(ObjectView):
|
|
|
|
"""Provides a detailed view of the technology given by C{path}.
|
|
|
|
|
|
|
|
The C{properties} argument is used to populate the current state,
|
|
|
|
which will be updated with net.connman.Technology.PropertyChanged
|
|
|
|
signal that it will listen.
|
|
|
|
|
|
|
|
User updates will be automatically applied to the server.
|
|
|
|
"""
|
|
|
|
bus_interface = "net.connman.Technology"
|
|
|
|
|
|
|
|
def create_view(self, properties):
|
|
|
|
self.powered = self.add_check(self.box, "Powered",
|
|
|
|
self._on_user_powered)
|
|
|
|
|
|
|
|
self.connected = self.add_check(self.box, "Connected")
|
|
|
|
self.connected.disabled = True
|
|
|
|
|
|
|
|
self.scan = self.add_button(self.box, "Scan", self._scan)
|
|
|
|
|
|
|
|
fr, bx = self.add_frame_and_box(self.box, "Tethering")
|
|
|
|
|
|
|
|
self.tethering = self.add_check(bx, "Enabled",
|
|
|
|
self._on_user_tethering)
|
|
|
|
|
|
|
|
lb, self.identifier = self.add_label_and_entry(bx, "Identifier:")
|
|
|
|
lb, self.passphrase = self.add_label_and_entry(bx, "Passphrase:")
|
|
|
|
self.tethering_apply = self.add_button(bx, "Apply Tethering",
|
|
|
|
self._tethering_apply)
|
|
|
|
|
|
|
|
def _on_user_powered(self, obj):
|
|
|
|
state = bool(self.powered.state)
|
|
|
|
|
|
|
|
def on_reply():
|
|
|
|
log.info("Set %s Powered=%s", self.path, state)
|
|
|
|
|
|
|
|
def on_error(exc):
|
|
|
|
log.error("Could not set %s Powered=%s: %s", self.path, state, exc)
|
|
|
|
obj.state = not state
|
|
|
|
popup_error(self.obj, "Failed to Apply Powered",
|
|
|
|
exc.get_dbus_message())
|
|
|
|
|
|
|
|
self.bus_obj.SetProperty(
|
|
|
|
"Powered", dbus.Boolean(state),
|
|
|
|
reply_handler=on_reply, error_handler=on_error
|
|
|
|
)
|
|
|
|
|
|
|
|
def _scan(self, obj):
|
|
|
|
def on_reply():
|
|
|
|
log.debug("Scanned %s", self.path)
|
|
|
|
self.scan.disabled = False
|
|
|
|
self.scan.text = "Scan"
|
|
|
|
|
|
|
|
def on_error(exc):
|
|
|
|
log.error("Could not scan %s", exc)
|
|
|
|
self.scan.disabled = False
|
|
|
|
self.scan.text = "Scan"
|
|
|
|
|
|
|
|
self.bus_obj.Scan(reply_handler=on_reply, error_handler=on_error)
|
|
|
|
self.scan.disabled = True
|
|
|
|
self.scan.text = "Scanning..."
|
|
|
|
|
|
|
|
def _on_user_tethering(self, obj):
|
|
|
|
state = bool(obj.state)
|
|
|
|
self.identifier.disabled = not state
|
|
|
|
self.passphrase.disabled = not state
|
|
|
|
|
|
|
|
def _tethering_apply(self, obj):
|
|
|
|
self.to_apply = [
|
|
|
|
("TetheringIdentifier", self.identifier.text),
|
|
|
|
("TetheringPassphrase", self.passphrase.text),
|
|
|
|
("Tethering", dbus.Boolean(self.tethering.state)),
|
|
|
|
]
|
|
|
|
|
|
|
|
def apply_next():
|
|
|
|
if not self.to_apply:
|
|
|
|
return
|
|
|
|
name, value = self.to_apply.pop(0)
|
|
|
|
self.bus_obj.SetProperty(name, value,
|
|
|
|
reply_handler=on_reply,
|
|
|
|
error_handler=on_error)
|
|
|
|
|
|
|
|
def on_reply():
|
|
|
|
log.debug("Applied tethering %s", self.path)
|
|
|
|
self.tethering_apply.disabled = False
|
|
|
|
self.tethering_apply.text = "Apply Tethering"
|
|
|
|
apply_next()
|
|
|
|
|
|
|
|
def on_error(exc):
|
|
|
|
log.error("Could not apply tethering %s", exc)
|
|
|
|
self.tethering_apply.disabled = False
|
|
|
|
self.tethering_apply.text = "Apply Tethering"
|
|
|
|
popup_error(self.obj, "Failed to Apply Tethering",
|
|
|
|
exc.get_dbus_message())
|
|
|
|
|
|
|
|
apply_next()
|
|
|
|
self.tethering_apply.disabled = True
|
|
|
|
self.tethering_apply.text = "Applying Tethering..."
|
|
|
|
|
|
|
|
def on_property_changed(self, name, value):
|
|
|
|
log.debug("Changed %s: %s=%s", self.path, name, value)
|
|
|
|
if name == "Powered":
|
|
|
|
self.powered.state = bool(value)
|
|
|
|
elif name == "Connected":
|
|
|
|
self.connected.state = bool(value)
|
|
|
|
elif name == "Tethering":
|
|
|
|
state = bool(value)
|
|
|
|
self.tethering.state = state
|
|
|
|
self.identifier.disabled = not state
|
|
|
|
self.passphrase.disabled = not state
|
|
|
|
elif name == "TetheringIdentifier":
|
|
|
|
self.identifier.text = str(value)
|
|
|
|
elif name == "TetheringPassphrase":
|
|
|
|
self.passphrase.text = str(value)
|
|
|
|
|
|
|
|
|
|
|
|
class ServicesList(object):
|
|
|
|
"""Provides a Genlist with the known Services.
|
|
|
|
|
|
|
|
It will call manager's GetServices() and then keep it updated with
|
|
|
|
ServicesChanged signal.
|
|
|
|
|
|
|
|
Selecting an item will call C{on_selected(path, service_properties)}.
|
|
|
|
"""
|
|
|
|
def __init__(self, parent, on_selected=None, on_disclosure=None):
|
|
|
|
self.services = {}
|
|
|
|
self.items = {}
|
|
|
|
self.obj = Genlist(parent)
|
|
|
|
self.on_selected = on_selected
|
|
|
|
self.on_disclosure = on_disclosure
|
|
|
|
self.obj.callback_selected_add(self._item_selected)
|
|
|
|
self.obj.on_del_add(self._deleted)
|
|
|
|
self.sig_ch = manager.connect_to_signal(
|
|
|
|
"ServicesChanged",
|
|
|
|
self._services_changed
|
|
|
|
)
|
|
|
|
self.sig_propch = bus.add_signal_receiver(
|
|
|
|
self._service_prop_changed,
|
|
|
|
"PropertyChanged",
|
|
|
|
"net.connman.Service",
|
|
|
|
"net.connman",
|
|
|
|
path_keyword='path'
|
|
|
|
)
|
|
|
|
manager.GetServices(
|
|
|
|
reply_handler=self._get_services_reply,
|
|
|
|
error_handler=self._get_services_error
|
|
|
|
)
|
|
|
|
self.itc = GenlistItemClass(
|
|
|
|
item_style="default",
|
|
|
|
text_get_func=self._item_text_get,
|
|
|
|
content_get_func=self._item_content_get
|
|
|
|
)
|
|
|
|
|
|
|
|
def _deleted(self, obj):
|
|
|
|
self.sig_ch.remove()
|
|
|
|
|
|
|
|
self.obj = None
|
|
|
|
self.sig_ch = None
|
|
|
|
self.services.clear()
|
|
|
|
self.items.clear()
|
|
|
|
|
|
|
|
def _items_repopulate(self, paths):
|
|
|
|
for path in paths:
|
|
|
|
self.items[path] = self.obj.item_append(self.itc, path)
|
|
|
|
|
|
|
|
def _get_services_reply(self, services):
|
|
|
|
log.debug("Got services: %s", dbus_array_of_dict_to_str(services))
|
|
|
|
for path, properties in services:
|
|
|
|
self._service_added(path, properties)
|
|
|
|
self._items_repopulate(str(path) for path, properties in services)
|
|
|
|
|
|
|
|
def _get_services_error(self, exc):
|
|
|
|
log.critical("Failed to GetServices(): %s", exc)
|
|
|
|
popup_fatal(self.obj, "Failed to get Services",
|
|
|
|
exc.get_dbus_message())
|
|
|
|
|
|
|
|
def _service_added(self, path, properties):
|
|
|
|
log.debug("Added %s: %s", path, dbus_dict_to_str(properties))
|
|
|
|
self.services[path] = properties
|
|
|
|
|
|
|
|
def _service_prop_changed(self, name, value, path):
|
|
|
|
path = str(path)
|
|
|
|
log.debug("Changed %s: %s=%s", path, name, value)
|
|
|
|
s = self.services.get(path)
|
|
|
|
if not s:
|
|
|
|
return
|
|
|
|
s[name] = value
|
|
|
|
it = self.items.get(path)
|
|
|
|
if not it:
|
|
|
|
return
|
|
|
|
it.update()
|
|
|
|
|
|
|
|
def _service_changed(self, path, properties):
|
|
|
|
log.debug("Changed %s: %s", path, dbus_dict_to_str(properties))
|
|
|
|
d = self.services[path]
|
|
|
|
for k, v in properties.items():
|
|
|
|
d[k] = v
|
|
|
|
|
|
|
|
def _services_changed(self, changed, removed):
|
|
|
|
log.debug("Changed: %s, Removed: %s",
|
|
|
|
dbus_array_of_dict_to_str(changed),
|
|
|
|
removed)
|
|
|
|
|
|
|
|
self.items.clear()
|
|
|
|
self.obj.clear()
|
|
|
|
|
|
|
|
for path in removed:
|
|
|
|
self._service_removed(path)
|
|
|
|
for path, properties in changed:
|
|
|
|
path = str(path)
|
|
|
|
if path in self.services:
|
|
|
|
self._service_changed(path, properties)
|
|
|
|
else:
|
|
|
|
self._service_added(path, properties)
|
|
|
|
self._items_repopulate(str(path) for path, properties in changed)
|
|
|
|
|
|
|
|
def _service_removed(self, path):
|
|
|
|
path = str(path)
|
|
|
|
log.debug("Removed %s", path)
|
|
|
|
try:
|
|
|
|
del self.services[path]
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
def _item_selected(self, lst, item):
|
|
|
|
item.selected = False
|
|
|
|
if not self.on_selected:
|
|
|
|
return
|
|
|
|
path = item.data
|
|
|
|
s = self.services.get(path)
|
|
|
|
if s:
|
|
|
|
self.on_selected(path, s)
|
|
|
|
|
|
|
|
def _item_disclosure(self, bt, path):
|
|
|
|
if not self.on_disclosure:
|
|
|
|
return
|
|
|
|
s = self.services.get(path)
|
|
|
|
if s:
|
|
|
|
self.on_disclosure(path, s)
|
|
|
|
|
|
|
|
def _item_text_get(self, obj, part, item_data):
|
|
|
|
if part != "elm.text":
|
|
|
|
return None
|
|
|
|
t = self.services.get(item_data)
|
|
|
|
if not t:
|
|
|
|
return "Unknown"
|
|
|
|
return t.get("Name", item_data[len("/net/connman/service/"):])
|
|
|
|
|
|
|
|
def _item_content_get(self, obj, part, item_data):
|
|
|
|
s = self.services.get(item_data)
|
|
|
|
if not s:
|
|
|
|
return None
|
|
|
|
type = s.get("Type")
|
|
|
|
state = s.get("State")
|
|
|
|
error = s.get("Error")
|
|
|
|
security = s.get("Security")
|
|
|
|
strength = s.get("Strength")
|
|
|
|
favorite = s.get("Favorite")
|
|
|
|
roaming = s.get("Roaming")
|
|
|
|
auto_connect = s.get("AutoConnect")
|
|
|
|
connected = (str(state) not in ("idle", "failure"))
|
|
|
|
|
|
|
|
if security:
|
|
|
|
security = [str(x) for x in security]
|
|
|
|
if "none" in security:
|
|
|
|
security.remove("none")
|
|
|
|
|
|
|
|
if part == "elm.swallow.end":
|
|
|
|
bx = Box(obj)
|
|
|
|
bx.horizontal = True
|
|
|
|
bx.homogeneous = True
|
|
|
|
bx.padding = (2, 0)
|
|
|
|
bx.align = (1.0, 0.5)
|
|
|
|
|
|
|
|
if connected:
|
|
|
|
ic = Icon(obj)
|
|
|
|
ic.standard = "connman-connected"
|
|
|
|
ic.size_hint_min = ic.size_hint_max = (32, 32)
|
|
|
|
ic.show()
|
|
|
|
bx.pack_end(ic)
|
|
|
|
|
|
|
|
if security and favorite:
|
|
|
|
ic = Icon(obj)
|
|
|
|
ic.standard = "connman-security-favorite"
|
|
|
|
ic.size_hint_min = ic.size_hint_max = (32, 32)
|
|
|
|
ic.show()
|
|
|
|
bx.pack_end(ic)
|
|
|
|
elif security:
|
|
|
|
ic = Icon(obj)
|
|
|
|
ic.standard = "connman-security"
|
|
|
|
ic.size_hint_min = ic.size_hint_max = (32, 32)
|
|
|
|
ic.show()
|
|
|
|
bx.pack_end(ic)
|
|
|
|
|
|
|
|
ic = Icon(obj)
|
|
|
|
ic.standard = "arrow_right"
|
|
|
|
bt = Button(obj)
|
|
|
|
bt.content = ic
|
|
|
|
bt.callback_clicked_add(self._item_disclosure, item_data)
|
|
|
|
bt.propagate_events = False
|
|
|
|
bt.show()
|
|
|
|
bt.size_hint_min = bt.size_hint_max = (32, 32)
|
|
|
|
|
|
|
|
bx.pack_end(bt)
|
|
|
|
return bx
|
|
|
|
|
|
|
|
if part != "elm.swallow.icon":
|
|
|
|
return
|
|
|
|
|
|
|
|
ly = Layout(obj)
|
|
|
|
ly.theme_set("icon", type, "default")
|
|
|
|
ly.size_hint_min_set(32, 32)
|
|
|
|
|
|
|
|
def yesno(val):
|
|
|
|
return ("no", "yes")[bool(val)]
|
|
|
|
|
|
|
|
def ornone(val):
|
|
|
|
return val or "none"
|
|
|
|
|
|
|
|
ly.signal_emit("elm,state," + state, "elm")
|
|
|
|
ly.signal_emit("elm,error," + ornone(error), "elm")
|
|
|
|
ly.signal_emit("elm,favorite," + yesno(favorite), "elm")
|
|
|
|
ly.signal_emit("elm,roaming," + yesno(roaming), "elm")
|
|
|
|
ly.signal_emit("elm,auto_connect," + yesno(auto_connect), "elm")
|
|
|
|
ly.signal_emit("elm,connected," + yesno(connected), "elm")
|
|
|
|
|
|
|
|
for s in security:
|
|
|
|
ly.signal_emit("elm,security," + s, "elm")
|
|
|
|
if security:
|
|
|
|
ly.signal_emit("elm,security,yes", "elm")
|
|
|
|
else:
|
|
|
|
ly.signal_emit("elm,security,none", "elm")
|
|
|
|
|
|
|
|
if strength:
|
|
|
|
ly.edje.message_send(1, strength)
|
|
|
|
return ly
|
|
|
|
|
|
|
|
def service_name_get(self, path):
|
|
|
|
s = self.services.get(path)
|
|
|
|
if not s:
|
|
|
|
return None
|
|
|
|
n = s.get("Name")
|
|
|
|
if not n:
|
|
|
|
return None
|
|
|
|
return str(n)
|
|
|
|
|
|
|
|
|
|
|
|
class ServiceView(ObjectView):
|
|
|
|
"""Provides a detailed view of the service given by C{path}.
|
|
|
|
|
|
|
|
The C{properties} argument is used to populate the current state,
|
|
|
|
which will be updated with net.connman.Service.PropertyChanged
|
|
|
|
signal that it will listen.
|
|
|
|
|
|
|
|
User updates will be automatically applied to the server.
|
|
|
|
"""
|
|
|
|
bus_interface = "net.connman.Service"
|
|
|
|
|
|
|
|
eth_fields = (("Method", "eth_method"),
|
|
|
|
("Interface", "eth_iface"),
|
|
|
|
("Address", "eth_addr"),
|
|
|
|
("MTU", "eth_mtu"),
|
|
|
|
("Speed", "eth_speed"),
|
|
|
|
("Duplex", "eth_duplex"),
|
|
|
|
)
|
|
|
|
vpn_fields = (("Host", "vpn_host"),
|
|
|
|
("Domain", "vpn_domain"),
|
|
|
|
("Name", "vpn_name"),
|
|