virt-manager/virtManager/createnet.py

775 lines
28 KiB
Python

#
# Copyright (C) 2006-2007, 2013 Red Hat, Inc.
# Copyright (C) 2006 Hugh O. Brock <hbrock@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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301 USA.
#
import logging
import re
import ipaddr
# pylint: disable=E0611
from gi.repository import Gtk
from gi.repository import Gdk
# pylint: enable=E0611
from virtinst import Network
from virtManager import uihelpers
from virtManager.asyncjob import vmmAsyncJob
from virtManager.baseclass import vmmGObjectUI
(PAGE_NAME,
PAGE_IPV4,
PAGE_IPV6,
PAGE_MISC) = range(4)
_green = Gdk.Color.parse("#c0ffc0")[1]
_red = Gdk.Color.parse("#ffc0c0")[1]
_black = Gdk.Color.parse("#000000")[1]
_white = Gdk.Color.parse("#f0f0f0")[1]
def _make_ipaddr(addrstr):
if addrstr is None:
return None
try:
return ipaddr.IPNetwork(addrstr)
except:
return None
class vmmCreateNetwork(vmmGObjectUI):
def __init__(self, conn):
vmmGObjectUI.__init__(self, "createnet.ui", "vmm-create-net")
self.conn = conn
self.builder.connect_signals({
"on_create_pages_switch_page" : self.page_changed,
"on_create_cancel_clicked" : self.close,
"on_vmm_create_delete_event" : self.close,
"on_create_forward_clicked" : self.forward,
"on_create_back_clicked" : self.back,
"on_create_finish_clicked" : self.finish,
"on_net_name_activate": self.forward,
"on_net_forward_toggled" : self.change_forward_type,
"on_net-ipv4-enable_toggled" : self.change_ipv4_enable,
"on_net-ipv4-network_changed": self.change_ipv4_network,
"on_net-dhcpv4-enable_toggled": self.change_dhcpv4_enable,
"on_net-dhcpv4-start_changed": self.change_dhcpv4_start,
"on_net-dhcpv4-end_changed": self.change_dhcpv4_end,
"on_net-ipv6-enable_toggled" : self.change_ipv6_enable,
"on_net-ipv6-network_changed": self.change_ipv6_network,
"on_net-dhcpv6-enable_toggled": self.change_dhcpv6_enable,
"on_net-dhcpv6-start_changed": self.change_dhcpv6_start,
"on_net-dhcpv6-end_changed": self.change_dhcpv6_end,
"on_net-routev4-enable_toggled": self.change_routev4_enable,
"on_net-routev4-network_changed": self.change_routev4_network,
"on_net-routev4-gateway_changed": self.change_routev4_gateway,
"on_net-routev6-enable_toggled": self.change_routev6_enable,
"on_net-routev6-network_changed": self.change_routev6_network,
"on_net-routev6-gateway_changed": self.change_routev6_gateway,
})
self.bind_escape_key_close()
self.set_initial_state()
####################
# Standard methods #
####################
def show(self, parent):
logging.debug("Showing new network wizard")
self.reset_state()
self.topwin.set_transient_for(parent)
self.topwin.present()
def close(self, ignore1=None, ignore2=None):
logging.debug("Closing new network wizard")
self.topwin.hide()
return 1
def _cleanup(self):
self.conn = None
def set_initial_state(self):
notebook = self.widget("create-pages")
notebook.set_show_tabs(False)
blue = Gdk.Color.parse("#0072A8")[1]
self.widget("header").modify_bg(Gtk.StateType.NORMAL, blue)
# [ label, dev name ]
fw_list = self.widget("net-forward")
fw_model = Gtk.ListStore(str, str)
fw_list.set_model(fw_model)
text = Gtk.CellRendererText()
fw_list.pack_start(text, True)
fw_list.add_attribute(text, 'text', 0)
# [ label, mode ]
mode_list = self.widget("net-forward-mode")
mode_model = Gtk.ListStore(str, str)
mode_list.set_model(mode_model)
text = Gtk.CellRendererText()
mode_list.pack_start(text, True)
mode_list.add_attribute(text, 'text', 0)
mode_model.append([_("NAT"), "nat"])
mode_model.append([_("Routed"), "route"])
def reset_state(self):
notebook = self.widget("create-pages")
notebook.set_current_page(0)
self.page_changed(None, None, 0)
self.widget("net-name").set_text("")
self.widget("net-domain-name").set_text("")
self.widget("net-ipv4-enable").set_active(True)
self.widget("net-ipv4-network").set_text("192.168.100.0/24")
self.widget("net-dhcpv4-enable").set_active(True)
self.widget("net-dhcpv4-start").set_text("192.168.100.128")
self.widget("net-dhcpv4-end").set_text("192.168.100.254")
self.widget("net-routev4-enable").set_active(False)
self.widget("net-routev4-enable").toggled()
self.widget("net-routev4-network").set_text("")
self.widget("net-routev4-gateway").set_text("")
self.widget("net-ipv6-enable").set_active(False)
self.widget("net-ipv6-enable").toggled()
self.widget("net-ipv6-network").set_text("")
self.widget("net-dhcpv6-enable").set_active(False)
self.widget("net-dhcpv6-enable").toggled()
self.widget("net-dhcpv6-start").set_text("")
self.widget("net-dhcpv6-end").set_text("")
self.widget("net-routev6-enable").set_active(False)
self.widget("net-routev6-enable").toggled()
self.widget("net-routev6-network").set_text("")
self.widget("net-routev6-gateway").set_text("")
self.widget("net-enable-ipv6-networking").set_active(False)
fw_model = self.widget("net-forward").get_model()
fw_model.clear()
fw_model.append([_("Any physical device"), None])
for path in self.conn.list_net_device_paths():
net = self.conn.get_net_device(path)
fw_model.append([_("Physical device %s") % (net.get_name()),
net.get_name()])
self.widget("net-forward").set_active(0)
self.widget("net-forward-mode").set_active(0)
self.widget("net-forward-none").set_active(True)
##################
# UI get helpers #
##################
def get_config_ipv4_enable(self):
return self.widget("net-ipv4-enable").get_active()
def get_config_ipv6_enable(self):
return self.widget("net-ipv6-enable").get_active()
def get_config_dhcpv4_enable(self):
return self.widget("net-dhcpv4-enable").get_active()
def get_config_dhcpv6_enable(self):
return self.widget("net-dhcpv6-enable").get_active()
def get_config_routev4_enable(self):
return self.widget("net-routev4-enable").get_active()
def get_config_routev6_enable(self):
return self.widget("net-routev6-enable").get_active()
def _get_network_helper(self, widgetname):
widget = self.widget(widgetname)
if not widget.is_visible() or not widget.is_sensitive():
return None
return _make_ipaddr(widget.get_text())
def get_config_ip4(self):
return self._get_network_helper("net-ipv4-network")
def get_config_dhcpv4_start(self):
return self._get_network_helper("net-dhcpv4-start")
def get_config_dhcpv4_end(self):
return self._get_network_helper("net-dhcpv4-end")
def get_config_ip6(self):
return self._get_network_helper("net-ipv6-network")
def get_config_dhcpv6_start(self):
return self._get_network_helper("net-dhcpv6-start")
def get_config_dhcpv6_end(self):
return self._get_network_helper("net-dhcpv6-end")
def get_config_forwarding(self):
if self.widget("net-forward-none").get_active():
return [None, None]
dev = self.widget("net-forward")
model = dev.get_model()
active = dev.get_active()
name = model[active][1]
mode_w = self.widget("net-forward-mode")
mode = mode_w.get_model()[mode_w.get_active()][1]
return [name, mode]
def get_config_routev4_network(self):
if not self.get_config_routev4_enable():
return None
return self.widget("net-routev4-network").get_text()
def get_config_routev4_gateway(self):
if not self.get_config_routev4_enable():
return None
return self.widget("net-routev4-gateway").get_text()
def get_config_routev6_network(self):
if not self.get_config_routev6_enable():
return None
return self.widget("net-routev6-network").get_text()
def get_config_routev6_gateway(self):
if not self.get_config_routev6_enable():
return None
return self.widget("net-routev6-gateway").get_text()
###################
# Page validation #
###################
def validate_name(self):
try:
net = self._build_xmlstub()
net.name = self.widget("net-name").get_text()
except Exception, e:
return self.err.val_err(_("Invalid network name"), str(e))
return True
def validate_ipv4(self):
if not self.get_config_ipv4_enable():
return True
ip = self.get_config_ip4()
if ip is None:
return self.err.val_err(_("Invalid Network Address"),
_("The network address could not be understood"))
if ip.version != 4:
return self.err.val_err(_("Invalid Network Address"),
_("The network must be an IPv4 address"))
if ip.numhosts < 16:
return self.err.val_err(_("Invalid Network Address"),
_("The network must address at least 16 addresses."))
if not ip.is_private:
res = self.err.yes_no(_("Check Network Address"),
_("The network should normally use a private IPv4 "
"address. Use this non-private address anyway?"))
if not res:
return False
enabled = self.get_config_dhcpv4_enable()
if enabled:
start = self.get_config_dhcpv4_start()
end = self.get_config_dhcpv4_end()
if start is None:
return self.err.val_err(_("Invalid DHCP Address"),
_("The DHCP start address could not be understood"))
if end is None:
return self.err.val_err(_("Invalid DHCP Address"),
_("The DHCP end address could not be understood"))
if not ip.overlaps(start):
return self.err.val_err(_("Invalid DHCP Address"),
(_("The DHCP start address is not with the network %s") %
(str(ip))))
if not ip.overlaps(end):
return self.err.val_err(_("Invalid DHCP Address"),
(_("The DHCP end address is not with the network %s") %
(str(ip))))
enabled = self.get_config_routev4_enable()
if enabled:
ntwk = self.get_config_routev4_network()
ntwkbad = False
gway = self.get_config_routev4_gateway()
gwaybad = False
if ntwk is None or gway is None:
return True
if ntwk == "" and gway == "":
return True
naddr = _make_ipaddr(ntwk)
if naddr is None:
ntwkbad = True
else:
if naddr.version != 4:
ntwkbad = True
if naddr.prefixlen > 28:
ntwkbad = True
gaddr = _make_ipaddr(gway)
if gaddr is None:
gwaybad = True
else:
if gaddr.version != 4:
gwaybad = True
if gaddr.prefixlen != 32:
gwaybad = True
if not ip.overlaps(gaddr):
gwaybad = True
if ntwkbad:
return self.err.val_err(_("Invalid static route"),
_("The network address is incorrect."))
if gwaybad:
return self.err.val_err(_("Invalid static route"),
_("The gateway address is incorrect."))
return True
def validate_ipv6(self):
if not self.get_config_ipv6_enable():
return True
ip = self.get_config_ip6()
if ip is None:
return self.err.val_err(_("Invalid Network Address"),
_("The network address could not be understood"))
if ip.version != 6:
return self.err.val_err(_("Invalid Network Address"),
_("The network must be an IPv6 address"))
if ip.prefixlen != 64:
return self.err.val_err(_("Invalid Network Address"),
_("For libvirt, the IPv6 network prefix must be /64"))
if not ip.is_private:
res = self.err.yes_no(_("Check Network Address"),
_("The network should normally use a private IPv4 "
"address. Use this non-private address anyway?"))
if not res:
return False
enabled = self.get_config_dhcpv6_enable()
if enabled:
start = self.get_config_dhcpv6_start()
end = self.get_config_dhcpv6_end()
if start is None:
return self.err.val_err(_("Invalid DHCPv6 Address"),
_("The DHCPv6 start address could not be understood"))
if end is None:
return self.err.val_err(_("Invalid DHCPv6 Address"),
_("The DHCPv6 end address could not be understood"))
if not ip.overlaps(start):
return self.err.val_err(_("Invalid DHCPv6 Address"),
(_("The DHCPv6 start address is not with the network %s") %
(str(ip))))
if not ip.overlaps(end):
return self.err.val_err(_("Invalid DHCPv6 Address"),
(_("The DHCPv6 end address is not with the network %s") %
(str(ip))))
enabled = self.get_config_routev6_enable()
if enabled:
ntwk = self.get_config_routev6_network()
ntwkbad = False
gway = self.get_config_routev6_gateway()
gwaybad = False
if ntwk is None or gway is None:
return True
if ntwk == "" and gway == "":
return True
naddr = _make_ipaddr(ntwk)
if naddr is None:
ntwkbad = True
else:
if naddr.version != 6:
ntwkbad = True
if naddr.prefixlen > 64:
ntwkbad = True
gaddr = _make_ipaddr(gway)
if gaddr is None:
gwaybad = True
else:
if gaddr.version != 6:
gwaybad = True
if gaddr.prefixlen != 128:
gwaybad = True
if not ip.overlaps(gaddr):
gwaybad = True
if ntwkbad:
return self.err.val_err(_("Invalid static route"),
_("The network address is incorrect."))
if gwaybad:
return self.err.val_err(_("Invalid static route"),
_("The gateway address is incorrect."))
return True
def validate_miscellaneous(self):
domain_name = self.widget("net-domain-name").get_text()
if len(domain_name) > 0:
if len(domain_name) > 16:
return self.err.val_err(_("Invalid Domain Name"),
_("Domain name must be less than 17 characters"))
if re.match("^[a-zA-Z0-9_]*$", domain_name) is None:
return self.err.val_err(_("Invalid Domain Name"),
_("Domain name may contain alphanumeric and '_' "
"characters only"))
return True
def validate(self, page_num):
if page_num == PAGE_NAME:
return self.validate_name()
elif page_num == PAGE_IPV4:
return self.validate_ipv4()
elif page_num == PAGE_IPV6:
return self.validate_ipv6()
elif page_num == PAGE_MISC:
return self.validate_miscellaneous()
return True
#############
# Listeners #
#############
def forward(self, ignore=None):
notebook = self.widget("create-pages")
if self.validate(notebook.get_current_page()) is not True:
return
self.widget("create-forward").grab_focus()
notebook.next_page()
def back(self, ignore=None):
notebook = self.widget("create-pages")
notebook.prev_page()
def page_changed(self, ignore1, ignore2, page_number):
page_lbl = ("<span color='#59B0E2'>%s</span>" %
_("Step %(current_page)d of %(max_page)d") %
{'current_page': page_number + 1,
'max_page': PAGE_MISC + 1})
self.widget("header-pagenum").set_markup(page_lbl)
if page_number == PAGE_NAME:
name_widget = self.widget("net-name")
name_widget.set_sensitive(True)
name_widget.grab_focus()
elif page_number == PAGE_MISC:
name = self.widget("net-name").get_text()
if self.widget("net-domain-name").get_text() == "":
self.widget("net-domain-name").set_text(name)
self.widget("create-back").set_sensitive(page_number != 0)
is_last_page = (
page_number == (self.widget("create-pages").get_n_pages() - 1))
self.widget("create-forward").set_visible(not is_last_page)
self.widget("create-finish").set_visible(is_last_page)
if is_last_page:
self.widget("create-finish").grab_focus()
def change_forward_type(self, ignore):
skip_fwd = self.widget("net-forward-none").get_active()
self.widget("net-forward-mode").set_sensitive(not skip_fwd)
self.widget("net-forward").set_sensitive(not skip_fwd)
def change_ipv4_enable(self, ignore):
enabled = self.get_config_ipv4_enable()
self.widget("net-ipv4-box").set_visible(enabled)
def change_ipv6_enable(self, ignore):
enabled = self.get_config_ipv6_enable()
self.widget("net-ipv6-box").set_visible(enabled)
def change_routev4_enable(self, ignore):
enabled = self.get_config_routev4_enable()
ntwk = self.widget("net-routev4-network")
gway = self.widget("net-routev4-gateway")
uihelpers.set_grid_row_visible(ntwk, enabled)
uihelpers.set_grid_row_visible(gway, enabled)
def change_routev6_enable(self, ignore):
enabled = self.get_config_routev6_enable()
ntwk = self.widget("net-routev6-network")
gway = self.widget("net-routev6-gateway")
uihelpers.set_grid_row_visible(ntwk, enabled)
uihelpers.set_grid_row_visible(gway, enabled)
def change_dhcpv4_enable(self, ignore):
enabled = self.get_config_dhcpv4_enable()
start = self.widget("net-dhcpv4-start")
end = self.widget("net-dhcpv4-end")
uihelpers.set_grid_row_visible(start, enabled)
uihelpers.set_grid_row_visible(end, enabled)
def change_dhcpv6_enable(self, ignore):
enabled = self.get_config_dhcpv6_enable()
start = self.widget("net-dhcpv6-start")
end = self.widget("net-dhcpv6-end")
uihelpers.set_grid_row_visible(start, enabled)
uihelpers.set_grid_row_visible(end, enabled)
def change_dhcpv4_start(self, src):
start = self.get_config_dhcpv4_start()
self.change_dhcpv4(src, start)
def change_dhcpv4_end(self, src):
end = self.get_config_dhcpv4_end()
self.change_dhcpv4(src, end)
def change_dhcpv4(self, src, addr):
ip = self.get_config_ip4()
if ip is None or addr is None:
src.modify_bg(Gtk.StateType.NORMAL, _white)
return
if addr.version != 4 or not ip.overlaps(addr):
src.modify_bg(Gtk.StateType.NORMAL, _red)
else:
src.modify_bg(Gtk.StateType.NORMAL, _green)
def change_dhcpv6_start(self, src):
start = self.get_config_dhcpv6_start()
self.change_dhcpv6(src, start)
def change_dhcpv6_end(self, src):
end = self.get_config_dhcpv6_end()
self.change_dhcpv6(src, end)
def change_dhcpv6(self, src, addr):
ip = self.get_config_ip6()
if ip is None or addr is None:
src.modify_bg(Gtk.StateType.NORMAL, _white)
return
if addr.version != 6 or not ip.overlaps(addr):
src.modify_bg(Gtk.StateType.NORMAL, _red)
else:
src.modify_bg(Gtk.StateType.NORMAL, _green)
def change_ipv4_network(self, src):
ip = self.get_config_ip4()
# No IP specified or invalid IP
if ip is None or ip.version != 4:
src.modify_bg(Gtk.StateType.NORMAL, _red)
return
valid_ip = (ip.numhosts >= 16 and ip.is_private)
gateway = (ip.prefixlen != 32 and str(ip.network + 1) or "")
info = (ip.is_private and _("Private") or _("Other/Public"))
start = int(ip.numhosts / 2)
end = int(ip.numhosts - 2)
src.modify_bg(Gtk.StateType.NORMAL, valid_ip and _green or _red)
self.widget("net-info-gateway").set_text(gateway)
self.widget("net-info-type").set_text(info)
self.widget("net-dhcpv4-start").set_text(str(ip.network + start))
self.widget("net-dhcpv4-end").set_text(str(ip.network + end))
def change_routev4_network(self, src):
ntwk = self.get_config_routev4_network()
ipAddr = self.get_config_ip4()
if ipAddr is None or ntwk is None:
src.modify_bg(Gtk.StateType.NORMAL, _white)
return
addr = _make_ipaddr(ntwk)
color = _green
if (addr is None or
addr.version != 4 or
addr.prefixlen > 28):
color = _red
src.modify_bg(Gtk.StateType.NORMAL, color)
def change_routev4_gateway(self, src):
gway = self.get_config_routev4_gateway()
ipAddr = self.get_config_ip4()
if ipAddr is None or gway is None:
src.modify_bg(Gtk.StateType.NORMAL, _white)
return
addr = _make_ipaddr(gway)
color = _green
if (addr is None or
addr.version != 4 or
not ipAddr.overlaps(addr) or
addr.prefixlen != 32):
color = _red
src.modify_bg(Gtk.StateType.NORMAL, color)
def change_ipv6_network(self, src):
ip = self.get_config_ip6()
if ip is None or ip.version != 6:
src.modify_bg(Gtk.StateType.NORMAL, _red)
return
valid_ip = (ip.numhosts == 64 and ip.is_private)
gateway = (ip.prefixlen != 64 and str(ip.network + 1) or "")
start = 256
end = 512 - 1
if ip.is_private:
info = _("Private")
elif ip.is_reserved:
info = _("Reserved")
elif ip.is_unspecified:
info = _("Unspecified")
else:
info = _("Other/Public")
src.modify_bg(Gtk.StateType.NORMAL, valid_ip and _green or _red)
self.widget("net-info-gateway-ip6").set_text(gateway)
self.widget("net-info-type-ip6").set_text(info)
self.widget("net-dhcpv6-start").set_text(str(ip.network + start))
self.widget("net-dhcpv6-end").set_text(str(ip.network + end))
def change_routev6_network(self, src):
ntwk = self.get_config_routev6_network()
ip = self.get_config_ip6()
if ip is None or ntwk is None:
src.modify_bg(Gtk.StateType.NORMAL, _white)
return
addr = _make_ipaddr(ntwk)
color = _green
if (addr is None or
addr.version != 6 or
addr.prefixlen > 64):
color = _red
src.modify_bg(Gtk.StateType.NORMAL, color)
def change_routev6_gateway(self, src):
gway = self.get_config_routev6_gateway()
ip = self.get_config_ip6()
if ip is None or gway is None:
src.modify_bg(Gtk.StateType.NORMAL, _white)
return
addr = _make_ipaddr(gway)
color = _green
if (addr is None or
addr.version != 6 or
ip.overlaps(addr) or
addr.prefixlen != 128):
color = _red
src.modify_bg(Gtk.StateType.NORMAL, color)
#########################
# XML build and install #
#########################
def _build_xmlstub(self):
return Network(self.conn.get_backend())
def _build_xmlobj(self):
net = self._build_xmlstub()
net.name = self.widget("net-name").get_text()
net.domain_name = self.widget("net-domain-name").get_text() or None
if self.widget("net-enable-ipv6-networking").get_active():
net.ipv6 = True
dev, mode = self.get_config_forwarding()
if mode:
net.forward.mode = mode
net.forward.dev = dev or None
if self.get_config_ipv4_enable():
ip = self.get_config_ip4()
ipobj = net.add_ip()
ipobj.address = str(ip.network + 1)
ipobj.netmask = str(ip.netmask)
if self.get_config_dhcpv4_enable():
dhcpobj = ipobj.add_range()
dhcpobj.start = str(self.get_config_dhcpv4_start().network)
dhcpobj.end = str(self.get_config_dhcpv4_end().network)
if self.get_config_ipv6_enable():
ip = self.get_config_ip6()
ipobj = net.add_ip()
ipobj.family = "ipv6"
ipobj.address = str(ip.network + 1)
ipobj.prefix = str(ip.prefixlen)
if self.get_config_dhcpv6_enable():
dhcpobj = ipobj.add_range()
dhcpobj.start = str(self.get_config_dhcpv6_start().network)
dhcpobj.end = str(self.get_config_dhcpv6_end().network)
netaddr = _make_ipaddr(self.get_config_routev4_network())
gwaddr = _make_ipaddr(self.get_config_routev4_gateway())
if netaddr and gwaddr:
route = net.add_route()
route.family = "ipv4"
route.address = netaddr.network
route.prefix = netaddr.prefixlen
route.gateway = gwaddr.network
netaddr = _make_ipaddr(self.get_config_routev6_network())
gwaddr = _make_ipaddr(self.get_config_routev6_gateway())
if netaddr and gwaddr:
route = net.add_route()
route.family = "ipv6"
route.address = netaddr.network
route.prefix = netaddr.prefixlen
route.gateway = gwaddr.network
return net
def _finish_cb(self, error, details):
self.topwin.set_sensitive(True)
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.TOP_LEFT_ARROW))
if error:
error = _("Error creating virtual network: %s") % str(error)
self.err.show_err(error, details=details)
else:
self.conn.schedule_priority_tick(pollnet=True)
self.close()
def _async_net_create(self, asyncjob, net):
ignore = asyncjob
net.install()
def finish(self, ignore):
try:
net = self._build_xmlobj()
except Exception, e:
self.err.show_erro(_("Error generating network xml: %s" % str(e)))
return
self.topwin.set_sensitive(False)
self.topwin.get_window().set_cursor(
Gdk.Cursor.new(Gdk.CursorType.WATCH))
progWin = vmmAsyncJob(self._async_net_create, [net],
self._finish_cb, [],
_("Creating virtual network..."),
_("Creating the virtual network may take a "
"while..."),
self.topwin)
progWin.run()