#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2010-2012 Red Hat, Inc.
#
# Authors:
# Thomas Woerner <twoerner@redhat.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import sys

try:
    from gi.repository import Gtk
except RuntimeError as e:
    print ("firewall-applet: %s" % e)
    print ("This is a graphical application and requires DISPLAY to be set.")
    sys.exit (1)

from gi.repository import GLib, GObject, Gio, Notify, NetworkManager

# force use of pygobject3 in python-slip
sys.modules['gobject'] = GObject

import os
import dbus.mainloop.glib
import slip.dbus

from firewall.config import *
from firewall.config.dbus import *
from firewall.client import FirewallClient
from firewall.dbus_utils import dbus_to_python
import dbus


PATH = [ ]
for p in os.getenv("PATH").split(":"):
    if p not in PATH:
        PATH.append(p)

def search_app(app):
    for p in PATH:
        _app = "%s/%s" % (p, app)
        if os.path.exists(_app):
            return _app
    return None

APPLET_SCHEMA = "org.fedoraproject.FirewallApplet"
NM_CONNECTION_EDITOR = "/usr/bin/nm-connection-editor"

def combobox_select_text(combobox, value):
    model = combobox.get_model()
    iter = model.get_iter_first()
    while iter:
        if model.get_value(iter, 0) == value:
            combobox.set_active_iter(iter)
            return True
        iter = model.iter_next(iter)
    combobox.set_active(0)
    return False

class ZoneEditor(Gtk.Dialog):
    def __init__(self, fw, interface, zone):
        self.fw = fw
        self.interface = interface
        self.zone = zone
        self.title = _("Select zone for interface '%s'") % self.interface

        super(ZoneEditor, self).__init__(self.title)

        self.set_property("width-request", 100)
        self.resize_to_geometry(100, 50)
        self.set_resizable(True)

        self.add_button("gtk-close", 1)
        self.ok_button = self.add_button("gtk-ok", 2)
        self.ok_button.set_sensitive(False)

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        vbox.set_border_width(12)
        vbox.set_homogeneous(False)

        label = Gtk.Label()
        label.set_markup(self.title)
        label.set_line_wrap(True)
        label.set_justify(Gtk.Justification.LEFT)
        label.set_alignment(0, 0.5)
        vbox.pack_start(label, True, True, 0)

        self.combo = Gtk.ComboBoxText()
        zones = self.fw.getZones()
        for zone in zones:
            self.combo.append_text(zone)
        vbox.pack_start(self.combo, True, True, 0)

        box = self.get_content_area()
        box.set_border_width(6)
        box.set_homogeneous(False)
        box.pack_start(vbox, False, True, 0)

        combobox_select_text(self.combo, self.zone)
        self.combo.connect("changed", self.combo_changed)

    def combo_changed(self, combo):
        self.ok_button.set_sensitive(self.combo.get_active_text() != self.zone)

    def set_zone(self, zone):
        old_zone = self.zone
        self.zone = zone
        if self.combo.get_active_text() == old_zone:
            combobox_select_text(self.combo, self.zone)
        else:
            self.combo_changed(None)

    def get_zone(self):
        return self.combo.get_active_text()

class TrayApplet(object):
    @staticmethod
    def position_function(menu, icon):
        return (Gtk.StatusIcon.position_menu(menu, icon))

    def __init__(self):
        self.name = _("Firewall Applet")
        self.icon_name = "firewall-applet"
        self.settings = Gio.Settings.new(APPLET_SCHEMA)

        self.icons = { "normal": None, "error": None, "panic": None, }
        self.timer = None
        self.mode = None
        self._blink = False
        self.blink_count = 0

        self.connected = False

        self.active_zones = { }
        self.connections = { }
        self.connections_uuid = { }
        self.default_zone = None
        self.zone_editors = { }

        if os.path.isfile(NM_CONNECTION_EDITOR):
            self.has_nm_connection_editor = True
        else:
            self.has_nm_connection_editor = False

        self.statusicon = Gtk.StatusIcon.new()
        self.statusicon.set_from_icon_name(self.icon_name)

        theme = Gtk.IconTheme.get_default()
        # Gtk.IconSize.MENU
        size = 24

        info = theme.lookup_icon("firewall-applet", size, 0)
        if not info:
            print("Icon 'firewall-applet' could not be loaded")
        else:
            self.icons["normal"] = info.load_icon()

        for _type in [ "error", "panic" ]:
            info = theme.lookup_icon("firewall-applet-%s" % _type, size, 0)
            if not info:
                print("Icon 'firewall-applet-%s' could not be loaded" % _type)
            else:
                self.icons[_type] = info.load_icon()

        self.left_menu = Gtk.Menu.new()
        self.left_menu.set_reserve_toggle_size(False)

        self.right_menu = Gtk.Menu.new()

        self.shieldsup_check = Gtk.CheckMenuItem.new_with_mnemonic(
            _("Shields Up"))        
        self.shieldsup_check.set_active(False)
        self.shieldsup_check_id = \
            self.shieldsup_check.connect('toggled',
                                         self.shieldsup_check_toggled)
        self.right_menu.append(self.shieldsup_check)

        self.settings.connect("changed::shields-up",
                              self.settings_shields_up_changed)
        self.settings.connect("changed::shields-down",
                              self.settings_shields_down_changed)

        self.notification_check = Gtk.CheckMenuItem.new_with_mnemonic(
            _("Enable Notifications"))
        self.notification_check.set_active(self.settings.get_boolean(
                "notifications"))
        self.settings.connect("changed::notifications",
                              self.settings_check_changed,
                              self.notification_check)
        self.notification_check.connect('toggled',
                                        self.notification_check_toggled,
                                        self.settings, "notifications")
        self.right_menu.append(self.notification_check)

        self.right_menu.append(Gtk.SeparatorMenuItem.new())

        item = Gtk.ImageMenuItem.new_with_mnemonic(_("Edit Firewall Settings..."))
        item.set_image(Gtk.Image.new_from_stock("gtk-preferences",
                                                Gtk.IconSize.MENU))
        item.connect("activate", self.configure_cb)
        if not search_app("firewall-config"):
            item.set_sensitive(False)
        self.right_menu.append(item)
        
        item = Gtk.ImageMenuItem.new_with_mnemonic(_("Change Zones of Connections..."))
        item.set_image(Gtk.Image.new_from_stock("gtk-preferences",
                                                Gtk.IconSize.MENU))
        item.connect("activate", self.nm_connection_editor)
        item.set_sensitive(self.has_nm_connection_editor)
        self.right_menu.append(item)

        item = Gtk.ImageMenuItem.new_with_mnemonic(_("Configure Shields UP/Down Zones..."))
        item.set_image(Gtk.Image.new_from_stock("gtk-preferences",
                                                Gtk.IconSize.MENU))
        item.connect("activate", self.configure_shields_cb)
        self.right_menu.append(item)

        self.right_menu.append(Gtk.SeparatorMenuItem.new())

        self.panic_check = Gtk.CheckMenuItem.new_with_mnemonic(\
            _("Block all network traffic"))
        self.panic_check.set_active(False)
        self.panic_check_id = self.panic_check.connect("toggled",
                                                       self.panic_mode_cb)
        self.right_menu.append(self.panic_check)

        self.right_menu.append(Gtk.SeparatorMenuItem.new())

        item = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_ABOUT, None)
        item.connect('activate', self.about_cb)
        self.right_menu.append(item)

        self.statusicon.connect("activate", self.left_menu_cb,
                                self.left_menu)
        self.statusicon.connect("popup-menu", self.right_menu_cb,
                                self.right_menu)

        self.about_dialog = Gtk.AboutDialog.new()
        self.about_dialog.set_name(self.name)
        self.about_dialog.set_version(VERSION)
        self.about_dialog.set_license(LICENSE)
        self.about_dialog.set_wrap_license(True)
        self.about_dialog.set_copyright(COPYRIGHT)
        self.about_dialog.set_authors(AUTHORS)
        self.about_dialog.set_logo_icon_name(self.icon_name)

        # shields up/down configure dialog

        self.shields_dialog = Gtk.Dialog(_("Configure Shields Up/Down Zones"))
        self.shields_dialog.set_property("width-request", 400)
        self.shields_dialog.resize_to_geometry(400, 100)
        self.shields_dialog.add_buttons("gtk-close", 1)
        self.shields_dialog.set_modal(True)
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
        vbox.set_border_width(12)
        vbox.set_homogeneous(False)

        label = Gtk.Label()
        label.set_markup(_("Here you can select the zones used for Shields "
                           "Up and Shields Down."))
        label.set_line_wrap(True)
        label.set_justify(Gtk.Justification.LEFT)
        label.set_alignment(0, 0.5)
        vbox.pack_start(label, True, True, 0)

        label = Gtk.Label()
        label.set_markup(_("This feature is useful for people using the "
                           "default zones mostly. For users, that are "
                           "changing zones of connections, it might be of "
                           "limited use."))
        label.set_line_wrap(True)
        label.set_justify(Gtk.Justification.LEFT)
        label.set_alignment(0, 0.5)
        vbox.pack_start(label, True, True, 0)

        grid = Gtk.Grid()
        grid.set_row_spacing(6)
        grid.set_column_spacing(6)
        grid.set_border_width(6)

        label = Gtk.Label()
        label.set_markup(_("Shields Up Zone:"))
        label.set_line_wrap(True)
        label.set_justify(Gtk.Justification.LEFT)
        label.set_alignment(1, 0.5)
        grid.attach(label, 0, 0, 1, 1)

        self.shields_up_combo = Gtk.ComboBoxText()
        self.shields_up_combo.connect("changed", self.shields_up_changed)
        grid.attach(self.shields_up_combo, 1, 0, 1, 1)

        label = Gtk.Label()
        label.set_markup(_("Shields Down Zone:"))
        label.set_line_wrap(True)
        label.set_justify(Gtk.Justification.LEFT)
        label.set_alignment(1, 0.5)
        grid.attach(label, 0, 1, 1, 1)

        self.shields_down_combo = Gtk.ComboBoxText()
        self.shields_down_combo.connect("changed", self.shields_down_changed)
        grid.attach(self.shields_down_combo, 1, 1, 1, 1)

        button = Gtk.Button("Set To Default")
        button.connect("clicked", self.reset_shields_up_zone)
        grid.attach(button, 2, 0, 1, 1)

        button = Gtk.Button("Set To Default")
        button.connect("clicked", self.reset_shields_down_zone)
        grid.attach(button, 2, 1, 1, 1)

        vbox.pack_start(grid, True, True, 0)

        box = self.shields_dialog.get_content_area()
        box.set_border_width(6)
        box.set_homogeneous(False)
        box.pack_start(vbox, False, True, 0)

        # init notification

        Notify.init(self.name)

        # status icon

        self.statusicon.set_visible(True)

        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
        try:
            self.bus = slip.dbus.SystemBus()
            self.bus.default_timeout = None
        except Exception as msg:
            print(_("Not using slip"), msg)
            self.bus = dbus.SystemBus()

        self.fw = FirewallClient(self.bus, wait=1)
        self.set_mode("error")

        self.fw.connect("connection-established", self.connection_established)
        self.fw.connect("connection-lost", self.connection_lost)
        self.fw.connect("reloaded", self.reloaded),
        self.fw.connect("default-zone-changed", self.default_zone_changed)
        self.fw.connect("panic-mode-enabled", self.panic_mode_enabled)
        self.fw.connect("panic-mode-disabled", self.panic_mode_disabled)
        self.fw.connect("interface-added", self.interface_added)
        self.fw.connect("interface-removed", self.interface_removed)
        self.fw.connect("zone-changed", self.zone_changed)

        self.bus.add_signal_receiver(
            self.nm_signal_receiver,
            dbus_interface=NetworkManager.DBUS_INTERFACE,
            signal_name='PropertiesChanged',
            member_keyword='member')
        self.nm_signal_receiver()

    def settings_check_changed(self, settings, key, button):
        button.set_active(settings.get_boolean(key))

    def shieldsup_check_toggled(self, button):
        if button.get_active():
            self.fw.setDefaultZone(self.settings.get_string("shields-up"))
        else:
            self.fw.setDefaultZone(self.settings.get_string("shields-down"))

    def notification_check_toggled(self, button, settings, key):
        settings.set_boolean(key, button.get_active())

    def notify(self, msg, sender=None, urgency=Notify.Urgency.NORMAL):
        n = Notify.Notification.new(self.name, msg, self.icon_name)
        n.set_urgency(urgency)
        try:
            n.show()
        except:
            pass

    def update_active_zones(self):
        self.active_zones.clear()

        # remove all entries for the left menu
        left_menu_children = self.left_menu.get_children()
        for child in left_menu_children:
            self.left_menu.remove(child)
            child.destroy()

        # add connecitons entry
        item = Gtk.MenuItem.new()
        label = Gtk.Label()
        label.set_markup("<b>"+_("Connections")+"</b>")
        label.set_alignment(0, 0.5)
        item.add(label)
        item.connect("select", self.no_select)
        self.left_menu.append(item)

        if not self.connected:
            return

        active_zones = self.fw.getActiveZones()
        if active_zones:
            self.active_zones = active_zones

        # get all active connections (NM) and interfaces
        connections = { }
        interfaces = { }
        for zone in sorted(self.active_zones):
            for interface in sorted(self.active_zones[zone]):
                if interface not in self.connections:
                    interfaces[interface] = zone
                else:
                    # NM controlled
                    connection = self.connections[interface]
                    if connection not in self.connections_uuid:
                        uuid = None
                    else:
                        uuid = self.connections_uuid[connection]
                    connections[connection] = [ zone, uuid ]

        # add NM controlled entries
        for connection in sorted(connections):
            [ zone, uuid ] = connections[connection]
            
            item = Gtk.MenuItem.new()
            hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
            label = Gtk.Label()
            label.set_markup("%s\n<small>%s: %s</small>" % \
                                 (connection, _("Zone"), zone))
            label.set_alignment(0, 0.5)
            label.set_padding(12, 0)
            hbox.pack_start(label, True, True, 0)
            # only sensitive if nm_connection_editor exists
            if not self.has_nm_connection_editor:
                item.set_sensitive(False)
            item.add(hbox)
            item.connect("activate", self.nm_connection_editor, uuid)
            self.left_menu.append(item)

        if len(interfaces) < 1:
            return

        item = Gtk.MenuItem.new()
        label = Gtk.Label()
        label.set_markup("<b>"+_("Interfaces")+"</b>")
        label.set_alignment(0, 0.5)
        item.add(label)
        item.connect("select", self.no_select)
        self.left_menu.append(item)

        # add other interfaces
        for interface in sorted(interfaces):
            zone = interfaces[interface]
            
            item = Gtk.MenuItem.new()
            hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
            label = Gtk.Label()
            label.set_markup("%s\n<small>%s: %s</small>" % \
                                 (interface, _("Zone"), zone))
            label.set_alignment(0, 0.5)
            label.set_padding(12, 0)
            hbox.pack_start(label, True, True, 0)
            item.add(hbox)
            item.connect("activate", self.change_zone_editor, interface, zone)
            self.left_menu.append(item)


    def nm_connection_editor(self, item, uuid=None):
        if uuid:
            os.system("%s --edit=%s &" % (NM_CONNECTION_EDITOR, uuid))
        else:
            os.system("%s &" % NM_CONNECTION_EDITOR)

    def change_zone_editor(self, item, interface, zone):
        if interface in self.zone_editors:
            return self.zone_editors[interface].present()

        editor = ZoneEditor(self.fw, interface, zone)
        editor.set_icon_name(self.icon_name)
        self.zone_editors[interface] = editor

        editor.show_all()
        result = editor.run()
        editor.hide()
        if result == 2:
            self.fw.changeZone(editor.get_zone(), interface)
        del self.zone_editors[interface]

    def connection_established(self, first=False):
        self.set_mode("normal")
        self.default_zone = self.fw.getDefaultZone()
        self.connected = True
        self.update_active_zones()

        self.shields_up_combo.get_model().clear()
        self.shields_down_combo.get_model().clear()
        zones = self.fw.getZones()
        for zone in zones:
            self.shields_up_combo.append_text(zone)
            self.shields_down_combo.append_text(zone)

        if self.settings.get_string("shields-up") not in zones:
            self.warning("Shields Up zone '%s' not found" % \
                             self.settings.get_string("shields-up"), 
                         "Resetting to default value...")
            self.settings.reset("shields-up")
        if self.settings.get_string("shields-down") not in zones:
            self.warning("Shields Down zone '%s' not found" % \
                             self.settings.get_string("shields-down"), 
                         "Resetting to default value...")
            self.settings.reset("shields-down")

        self.shieldsup_check.handler_block(self.shieldsup_check_id)
        if self.fw.getDefaultZone() == self.settings.get_string("shields-up"):
            self.shieldsup_check.set_active(True)
        else:
            self.shieldsup_check.set_active(False)
        self.shieldsup_check.handler_unblock(self.shieldsup_check_id)

        self.update_tooltip()
        if self.notification_check.get_active():
            self.notify(_("Connection to FirewallD established."),
                        urgency=Notify.Urgency.NORMAL)

    def connection_lost(self):
        self.connected = False
        self.default_zone = None
        self.set_mode("error")
        self.update_active_zones()
        self.update_tooltip()
        if self.notification_check.get_active():
            self.notify(_("Connection to FirewallD lost."),
                        urgency=Notify.Urgency.NORMAL)

    def panic_mode_cb(self, check):
        if not self.fw or not self.connected:
            return
        if check.get_active():
            self.fw.enablePanicMode()
        else:
            self.fw.disablePanicMode()
        
    def left_menu_cb(self, widget, menu):
        menu.show_all()
        menu.popup(None, None, self.position_function,
                   self.statusicon, 1, Gtk.get_current_event_time())

    def right_menu_cb(self, widget, button, time, menu):
        if button != 3:
            return
        menu.show_all()
        menu.popup(None, None, self.position_function,
                   self.statusicon, button, time)

    def no_select(self, item):
        item.deselect()

    def about_cb(self, widget):
        self.about_dialog.run()
        self.about_dialog.hide()

    def configure_cb(self, widget):
        os.system("firewall-config &")

    def combobox_select_text(self, combobox, value):
        model = combobox.get_model()
        iter = model.get_iter_first()
        while iter:
            if model.get_value(iter, 0) == value:
                combobox.set_active_iter(iter)
                return True
            iter = model.iter_next(iter)
        combobox.set_active(0)
        return False

    def reloaded(self):
        if self.notification_check.get_active():
            self.notify(_("FirewallD has been reloaded."))

    def shields_up_changed(self, *args):
        text = self.shields_up_combo.get_active_text()
        if text and text != self.settings.get_string("shields-up"):
            self.settings.set_string("shields-up", text)

    def shields_down_changed(self, *args):
        text = self.shields_down_combo.get_active_text()
        if text and text != self.settings.get_string("shields-down"):
            self.settings.set_string("shields-down", text)

    def settings_shields_up_changed(self, *args):
        self.combobox_select_text(self.shields_up_combo,
                                  self.settings.get_string("shields-up"))
        #TODO: apply zone if shields-up enabled

    def settings_shields_down_changed(self, *args):
        self.combobox_select_text(self.shields_down_combo,
                                  self.settings.get_string("shields-down"))
        #TODO: apply zone if shields-down enabled?

    def reset_shields_up_zone(self, *args):
        self.settings.reset("shields-up")

    def reset_shields_down_zone(self, *args):
        self.settings.reset("shields-down")

    def configure_shields_cb(self, widget):
        self.combobox_select_text(self.shields_up_combo,
                                  self.settings.get_string("shields-up"))
        self.combobox_select_text(self.shields_down_combo,
                                  self.settings.get_string("shields-down"))

        self.shields_dialog.show_all()
        self.shields_dialog.run()
        self.shields_dialog.hide()

    def __blink(self, arg=None):
        if self.blink_count != 0:
            if self.blink_count > 0 and self._blink:
                self.blink_count -= 1
            self._blink = not self._blink
            self.timer = GLib.timeout_add_seconds(1, self.__blink, None)

        if not self._blink:
            self.statusicon.set_from_pixbuf(self.icons[self.mode])
        else:
            self.statusicon.set_from_pixbuf(self.icons["normal"])

    def get_mode(self):
        return self.mode

    def set_mode(self, mode, count=5):
        if self.mode != mode:
            if self.timer:
                GLib.source_remove(self.timer)
                self.timer = None
                self._blink = False
            self.mode = mode

        elif self.mode == mode and self.timer:
            if self.blink_count == 0:
                self.blink_count += 1
            return

        if mode == "normal":
            self.statusicon.set_from_pixbuf(self.icons[mode])
            return

        if count != 0:
            self._blink = True
            self.blink_count = count
            self.__blink()

    def update_tooltip(self):
        if self.get_mode() == "error":
            self.tooltip = "<span color='#FF0000'>" + \
                _("No connection to firewall daemon") + "</span>"
            self.statusicon.set_tooltip_markup(self.tooltip)
            return

        if self.panic_check.get_active():
            self.tooltip = "<big><b><span color='#FF0000'>" + \
                _("PANIC MODE") + "</span></b></big>"
            self.statusicon.set_tooltip_markup(self.tooltip)
            return

        messages = [ ]

        if self.default_zone:
            messages.append(_("Default Zone: %s" % self.default_zone))

        if len(self.active_zones) > 0:
            for zone in sorted(self.active_zones):
                for interface in sorted(self.active_zones[zone]):
                    if interface in self.connections:
                        connection = self.connections[interface]
                        text = _("Zone '{zone}' active for connection "
                                 "'{connection}' on interface '{interface}'")
                    else:
                        text = _("Zone '{zone}' active for interface "
                                 "'{interface}'")
                        connection = None
                    messages.append(text.format(zone=zone,
                                                connection=connection,
                                                interface=interface))
        else:
            messages.append(_("No Active Zones."))

        self.tooltip = "\n".join(messages)

        self.statusicon.set_tooltip_markup(self.tooltip)

    def default_zone_changed(self, zone):
        self.default_zone = zone
        if self.notification_check.get_active():
            self.notify(_("Default zone changed to '%s'.") % zone)
        self.update_active_zones()
        self.update_tooltip()

    def _panic_mode(self, enable):
        self.panic_check.handler_block(self.panic_check_id)
        self.panic_check.set_active(enable)
        self.panic_check.handler_unblock(self.panic_check_id)

        self.update_tooltip()

        if enable:
            self.set_mode("panic")
        else:
            self.set_mode("normal")

        if self.notification_check.get_active():
            ed = { 1: _("All network traffic is blocked."),
                   0: _("Network traffic is not blocked anymore.") }
            self.notify(ed[enable], urgency=Notify.Urgency.NORMAL)

    def panic_mode_enabled(self):
        self._panic_mode(True)

    def panic_mode_disabled(self):
        self._panic_mode(False)

    def _interface(self, zone, interface, enable):
        self.update_active_zones()
        self.update_tooltip()

        # send notification if enabled
        if self.notification_check.get_active():
            ed = { 1: _("activated"),
                   0: _("deactivated") }
            if interface in self.connections:
                connection = self.connections[interface]
                text = _("Zone '{zone}' {activated_deactivated} for "
                         "connection '{connection}' on "
                         "interface '{interface}'")
            else:
                connection = None
                text = _("Zone '{zone}' {activated_deactivated} for "
                         "interface '{interface}'")
            self.notify(text.format(zone=zone,
                                    activated_deactivated=ed[enable],
                                    connection=connection,
                                    interface=interface))

    def interface_added(self, zone, interface):
        self._interface(zone, interface, True)

    def interface_removed(self, zone, interface):
        self._interface(zone, interface, False)

    def zone_changed(self, zone, interface):
        # update zone editor
        if interface in self.zone_editors:
            self.zone_editors[interface].set_zone(zone)

        self.update_active_zones()
        self.update_tooltip()

        if self.notification_check.get_active():
            self.notify(_("Zone '%s' activated for interface '%s'") % \
                            (zone, interface))

    def nm_signal_receiver(self, *args, **kwargs):
        #print("nm_signal_receiver", args, kwargs)
        self.connections.clear()
        self.connections_uuid.clear()

        # do not use NMClient could result in python core dump

        NM_IF = NetworkManager.DBUS_INTERFACE
        NM_IF_D = NetworkManager.DBUS_INTERFACE+".Device"
        NM_IF_C_A = NetworkManager.DBUS_INTERFACE+".Connection.Active"
        NM_IF_S_C = NetworkManager.DBUS_INTERFACE+".Settings.Connection"
        NM_PATH = NetworkManager.DBUS_PATH
        DBUS_PROP = 'org.freedesktop.DBus.Properties'

        try:
            # get active connections
            obj = self.bus.get_object(NM_IF, NM_PATH)
            props = dbus.Interface(obj, dbus_interface=DBUS_PROP)
            connections = dbus_to_python(props.Get(NM_IF, "ActiveConnections"))

            # for all active connections:
            for active in connections:
                # get connection and devices from active connection
                obj = self.bus.get_object(NM_IF, active)
                props = dbus.Interface(obj, dbus_interface=DBUS_PROP)
                connection = dbus_to_python(props.Get(NM_IF_C_A, "Connection"))
                devices = dbus_to_python(props.Get(NM_IF_C_A, "Devices"))

                # get name (id) from connection
                obj = self.bus.get_object(NM_IF, connection)
                iface = dbus.Interface(obj, dbus_interface=NM_IF_S_C)
                settings = dbus_to_python(iface.GetSettings())
                name = settings["connection"]["id"]
                uuid = settings["connection"]["uuid"]
                self.connections_uuid[name] = uuid

                # for all devices:
                for device in devices:
                    obj = self.bus.get_object(NM_IF, device)
                    props = dbus.Interface(obj, dbus_interface=DBUS_PROP)
                    # get interface from device (first try: IpInterface)
                    iface = dbus_to_python(props.Get(NM_IF_D, "IpInterface"))
                    if iface == "":
                        iface = dbus_to_python(props.Get(NM_IF_D, "Interface"))
                    self.connections[iface] = name

        except Exception as msg:
            print(msg)

        self.update_tooltip()

    def warning(self, text, secondary_text=None):
        self.message(text, secondary_text=secondary_text,
                     msg_type=Gtk.MessageType.WARNING)

    def error(self, text, secondary_text=None):
        self.message(text, secondary_text=secondary_text,
                     msg_type=Gtk.MessageType.ERROR)

    def message(self, text, secondary_text=None, msg_type=Gtk.MessageType.INFO,
                buttons=[ ("gtk-close", Gtk.ResponseType.CLOSE) ]):
        dialog = Gtk.MessageDialog(None, 0, msg_type, None, text)
        dialog.set_title(_("Firewall-applet"))
        for (button, response) in buttons:
            dialog.add_button(button, response)
        if secondary_text != None:
            dialog.format_secondary_text(secondary_text)
        dialog.show_all()
        result = dialog.run()
        dialog.hide()
        return result

# MAIN

if __name__ == "__main__":
    mainloop = GObject.MainLoop()
    applet = TrayApplet()
    mainloop.run()
