Blob Blame History Raw
commit d545084d092fa049932e2ec1f9adc92c900eb0e7
Author: Thomas Woerner <twoerner@redhat.com>
Date:   Wed Feb 19 13:35:01 2014 +0100

    firewall-config: Use left button menu of -applet in Option menu
    
    nm-connection-editor is not used anymore to change zones of connections in the
    Option menu "Change Zones of Connections...". The -applet left mouse menu is
    attached as a submenu. Zone bindings of connections, interfaces and sources can
    now be changes easily.

diff --git a/src/firewall-config b/src/firewall-config
index a00a794..f963cbc 100755
--- a/src/firewall-config
+++ b/src/firewall-config
@@ -58,14 +58,11 @@ if not datadir:
     sys.path.insert(0, datadir)
 from gtk3_chooserbutton import ChooserButton
 
-NM_CONNECTION_EDITOR = ""
-for binary in [ "/usr/bin/nm-connection-editor",
-                    "/bin/nm-connection-editor",
-                "/usr/bin/kde-nm-connection-editor",
-                    "/bin/kde-nm-connection-editor" ]:
-    if os.path.exists(binary):
-        NM_CONNECTION_EDITOR = binary
-        break
+def escape(text):
+    text = text.replace('&', '&amp;')
+    text = text.replace('>', '&gt;')
+    text = text.replace('<', '&lt;')
+    return text
 
 FIREWALL_CONFIG_SCHEMA = "org.fedoraproject.FirewallConfig"
 
@@ -87,6 +84,10 @@ class FirewallConfig(object):
         self.settings = Gio.Settings.new(FIREWALL_CONFIG_SCHEMA)
         self.modified_timer = None
 
+        self.zone_connection_editors = { }
+        self.zone_interface_editors = { }
+        self.zone_source_editors = { }
+
         # get icon and logo
         (foo, width, height) = Gtk.icon_size_lookup(Gtk.IconSize.BUTTON)
         size = min(width, height)
@@ -127,12 +128,13 @@ class FirewallConfig(object):
 
         self.changeZonesConnectionMenuitem = \
             builder.get_object("changeZonesConnectionMenuitem")
-        if NM_CONNECTION_EDITOR != "":
-            self.changeZonesConnectionMenuitem.set_sensitive(True)
-        else:
-            self.changeZonesConnectionMenuitem.set_tooltip_markup(\
-                _("NetworkManager connection editor is missing."))
-            self.changeZonesConnectionMenuitem.set_sensitive(False)
+
+        self.left_menu = Gtk.Menu.new()
+        self.left_menu.set_reserve_toggle_size(False)
+        self.changeZonesConnectionMenuitem.set_submenu(self.left_menu)
+        self.changeZonesConnectionMenuitem.connect(
+            "activate", self.left_menu_cb, self.left_menu)
+        self.active_zones = { }
 
         self.panicMenuitem = builder.get_object("panicMenuitem")
         self.panic_check_id = \
@@ -963,6 +965,61 @@ class FirewallConfig(object):
         except KeyboardInterrupt:
             self.onQuit()
 
+    def left_menu_cb(self, widget, menu):
+        menu.show_all()
+
+    def no_select(self, item):
+        item.deselect()
+
+    def change_zone_interface_editor(self, item, interface, zone):
+        if interface in self.zone_interface_editors:
+            return self.zone_interface_editors[interface].present()
+
+        editor = ZoneInterfaceEditor(self.fw, interface, zone)
+        editor.set_icon(self.icon)
+        editor.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
+        editor.set_transient_for(self.mainWindow)
+        self.zone_interface_editors[interface] = editor
+
+        editor.show_all()
+        result = editor.run()
+        editor.hide()
+        if result == 2:
+            self.fw.changeZoneOfInterface(editor.get_zone(), interface)
+        del self.zone_interface_editors[interface]
+
+    def change_zone_connection_editor(self, item, connection, zone):
+        if connection in self.zone_connection_editors:
+            return self.zone_connection_editors[connection].present()
+
+        editor = ZoneConnectionEditor(self.fw, connection, zone)
+        editor.set_icon(self.icon)
+        editor.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
+        editor.set_transient_for(self.mainWindow)
+        self.zone_connection_editors[connection] = editor
+
+        editor.show_all()
+        editor.run()
+        editor.hide()
+        del self.zone_connection_editors[connection]
+
+    def change_zone_source_editor(self, item, source, zone):
+        if source in self.zone_source_editors:
+            return self.zone_source_editors[source].present()
+
+        editor = ZoneSourceEditor(self.fw, source, zone)
+        editor.set_icon(self.icon)
+        editor.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
+        editor.set_transient_for(self.mainWindow)
+        self.zone_source_editors[source] = editor
+
+        editor.show_all()
+        result = editor.run()
+        editor.hide()
+        if result == 2:
+            self.fw.changeZoneOfSource(editor.get_zone(), source)
+        del self.zone_source_editors[source]
+
     def onViewICMPTypes_toggled(self, button):
         self.settings.set_boolean("show-icmp-types", button.get_active())
 
@@ -1013,6 +1070,8 @@ class FirewallConfig(object):
 
     def nm_signal_receiver(self, *args, **kwargs):
         #print("nm_signal_receiver", args, kwargs)
+        self.update_active_zones()
+
         self.connections.clear()
         self.connections_uuid.clear()
 
@@ -1134,6 +1193,7 @@ class FirewallConfig(object):
             self.lockdownLabel.set_text("-")
             self.panicLabel.set_text("-")
 
+        self.update_active_zones()
         self.mainToolbar.set_sensitive(self.fw.connected)
         self.mainVBox.set_sensitive(self.fw.connected)
         self.optionMenuitem.get_submenu().set_sensitive(self.fw.connected)
@@ -1507,6 +1567,7 @@ class FirewallConfig(object):
         self.interfaceStore.append([interface, comment])
 
     def interface_added_cb(self, zone, interface):
+        self.update_active_zones()
         if not self.runtime_view or zone != self.get_active_zone():
             return
         iter = self.interfaceStore.get_iter_first()
@@ -1527,8 +1588,10 @@ class FirewallConfig(object):
                 self.interfaceStore.remove(iter)
                 break
             iter = self.interfaceStore.iter_next(iter)
+        self.update_active_zones()
 
     def zone_of_interface_changed_cb(self, zone, interface):
+        self.update_active_zones()
         if not self.runtime_view:
             return
         iter = self.interfaceStore.get_iter_first()
@@ -1542,6 +1605,7 @@ class FirewallConfig(object):
             self._add_interface(interface)
 
     def source_added_cb(self, zone, source):
+        self.update_active_zones()
         if not self.runtime_view or zone != self.get_active_zone():
             return
         iter = self.sourceStore.get_iter_first()
@@ -1554,6 +1618,7 @@ class FirewallConfig(object):
         self.sourceStore.append([source])
 
     def source_removed_cb(self, zone, source):
+        self.update_active_zones()
         if not self.runtime_view or zone != self.get_active_zone():
             return
         iter = self.sourceStore.get_iter_first()
@@ -1564,6 +1629,7 @@ class FirewallConfig(object):
             iter = self.sourceStore.iter_next(iter)
 
     def zone_of_source_changed_cb(self, zone, source):
+        self.update_active_zones()
         if not self.runtime_view:
             return
         iter = self.sourceStore.get_iter_first()
@@ -1673,18 +1739,119 @@ class FirewallConfig(object):
         self.load_direct()
         self.load_lockdown_whitelist()
 
-    def nm_connection_editor(self, item, uuid=None):
-        if NM_CONNECTION_EDITOR == "":
-            self._warning("NetworkManager connection editor is missing.")
+    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>"+escape(_("Connections"))+"</b>")
+        label.set_markup(escape(_("Connections")))
+        label.set_alignment(0, 0.5)
+        item.add(label)
+        item.connect("select", self.no_select)
+        self.left_menu.append(item)
+
+        if not self.fw.connected:
             return
 
-        if uuid:
-            if "kde-" in NM_CONNECTION_EDITOR:
-                os.system("%s %s &" % (NM_CONNECTION_EDITOR, uuid))
-            else:
-                os.system("%s --edit=%s &" % (NM_CONNECTION_EDITOR, uuid))
-        else:
-            os.system("%s &" % NM_CONNECTION_EDITOR)
+        active_zones = self.fw.getActiveZones()
+        if active_zones:
+            self.active_zones = active_zones
+
+        # get all active connections (NM) and interfaces
+        connections = { }
+        interfaces = { }
+        sources = { }
+        for zone in sorted(self.active_zones):
+            if "interfaces" in self.active_zones[zone]:
+                for interface in sorted(self.active_zones[zone]["interfaces"]):
+                    if interface not in self.connections:
+                        interfaces[interface] = zone
+                    else:
+                        # NM controlled
+                        connection = self.connections[interface]
+                        if connection not in connections:
+                            connections[connection] = [ zone, [ interface, ] ]
+                        else:
+                            connections[connection][1].append(interface)
+            if "sources" in self.active_zones[zone]:
+                for source in sorted(self.active_zones[zone]["sources"]):
+                    sources[source] = zone
+
+        # add NM controlled entries
+        for connection in sorted(connections):
+            [ zone, _interfaces ] = connections[connection]
+
+            item = Gtk.MenuItem.new()
+            hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
+            label = Gtk.Label()
+            label.set_markup("%s (%s)\n<small>%s: %s</small>" % \
+                             (connection, ",".join(_interfaces), escape(_("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_connection_editor, connection, zone)
+            self.left_menu.append(item)
+
+        if len(interfaces) > 0:
+            item = Gtk.MenuItem.new()
+            label = Gtk.Label()
+#            label.set_markup("<b>"+escape(_("Interfaces"))+"</b>")
+            label.set_markup(escape(_("Interfaces")))
+            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, escape(_("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_interface_editor, interface, zone)
+                self.left_menu.append(item)
+
+        if len(sources) > 0:
+            item = Gtk.MenuItem.new()
+            label = Gtk.Label()
+#            label.set_markup("<b>"+escape(_("Sources"))+"</b>")
+            label.set_markup(escape(_("Sources")))
+            label.set_alignment(0, 0.5)
+            item.add(label)
+            item.connect("select", self.no_select)
+            self.left_menu.append(item)
+
+            for source in sorted(sources):
+                zone = sources[source]
+
+                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>" % \
+                                     (source, escape(_("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_source_editor, source, zone)
+                self.left_menu.append(item)
 
     def onChangeDefaultZone(self, *args):
         self.defaultZoneStore.clear()
@@ -5020,6 +5187,7 @@ class ZoneConnectionEditor(Gtk.Dialog):
 
         self.combo = Gtk.ComboBoxText()
         zones = self.fw.getZones()
+        self.combo.append_text(_("Default Zone"))
         for zone in zones:
             self.combo.append_text(zone)
         vbox.pack_start(self.combo, True, True, 0)
@@ -5044,7 +5212,10 @@ class ZoneConnectionEditor(Gtk.Dialog):
             self.combo_changed(None)
 
     def get_zone(self):
-        return self.combo.get_active_text()
+        text = self.combo.get_active_text()
+        if text == _("Default Zone"):
+            text = ""
+        return text
 
     def run(self):
         result = super(ZoneConnectionEditor, self).run()
@@ -5068,6 +5239,119 @@ class ZoneConnectionEditor(Gtk.Dialog):
         if settings and connection_obj:
             settings["connection"]["zone"] = self.get_zone()
             connection_obj.Update(settings)
+
+class ZoneInterfaceEditor(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(ZoneInterfaceEditor, 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_text(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 ZoneSourceEditor(Gtk.Dialog):
+    def __init__(self, fw, source, zone):
+        self.fw = fw
+        self.source = source
+        self.zone = zone
+        self.title = _("Select zone for source %s") % self.source
+
+        super(ZoneSourceEditor, 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_text(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()
+
 # MAIN
 
 if len(sys.argv) > 1:
diff --git a/src/firewall-config.glade b/src/firewall-config.glade
index 2cc33d2..872748e 100755
--- a/src/firewall-config.glade
+++ b/src/firewall-config.glade
@@ -2073,14 +2073,11 @@
                       </object>
                     </child>
                     <child>
-                      <object class="GtkImageMenuItem" id="changeZonesConnectionMenuitem">
+                      <object class="GtkMenuItem" id="changeZonesConnectionMenuitem">
                         <property name="label" translatable="yes">Change Zones of Connections...</property>
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
                         <property name="tooltip_text" translatable="yes">Change which zone a network connection belongs to.</property>
-                        <property name="image">image2</property>
-                        <property name="use_stock">False</property>
-                        <signal name="activate" handler="nm_connection_editor" swapped="no"/>
                       </object>
                     </child>
                     <child>