Advertisement
Guest User

python_rt

a guest
Oct 2nd, 2011
124
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 19.33 KB | None | 0 0
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3.  
  4. # ***************************************************************************
  5. # *   Copyright (C) 2011, Paul Lutus                                        *
  6. # *                                                                         *
  7. # *   This program is free software; you can redistribute it and/or modify  *
  8. # *   it under the terms of the GNU General Public License as published by  *
  9. # *   the Free Software Foundation; either version 2 of the License, or     *
  10. # *   (at your option) any later version.                                   *
  11. # *                                                                         *
  12. # *   This program is distributed in the hope that it will be useful,       *
  13. # *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
  14. # *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
  15. # *   GNU General Public License for more details.                          *
  16. # *                                                                         *
  17. # *   You should have received a copy of the GNU General Public License     *
  18. # *   along with this program; if not, write to the                         *
  19. # *   Free Software Foundation, Inc.,                                       *
  20. # *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
  21. # ***************************************************************************
  22.  
  23. # version date 01-12-2011
  24.  
  25. VERSION = '1.1'
  26.  
  27. import re, sys, os
  28.  
  29. import gobject
  30. gobject.threads_init()
  31. import gst
  32. import gtk
  33. gtk.gdk.threads_init()
  34. import time
  35. import struct
  36. import math
  37. import random
  38. import signal
  39. import webbrowser
  40.  
  41. class Icon:
  42.   icon = [
  43.     "32 32 17 1",
  44.     "   c None",
  45.     ".  c #2A2E30",
  46.     "+  c #333739",
  47.     "@  c #464A4C",
  48.     "#  c #855023",
  49.     "$  c #575A59",
  50.     "%  c #676A69",
  51.     "&  c #CC5B00",
  52.     "*  c #777A78",
  53.     "=  c #DB731A",
  54.     "-  c #8A8C8A",
  55.     ";  c #969895",
  56.     ">  c #F68C22",
  57.     ",  c #A5A7A4",
  58.     "'  c #F49D4A",
  59.     ")  c #B3B5B2",
  60.     "!  c #DEE0DD",
  61.     "                        &&&&&&& ",
  62.     "                  &&&===='''''& ",
  63.     "                  &'''''====&'& ",
  64.     "             +++++&'&&&&&   &'& ",
  65.     "          +@$%****&'&+      &'& ",
  66.     "        +@**%$@++@&'&*@+    &'& ",
  67.     "      +@**@+++++++&'&@**@+  &'& ",
  68.     "     +$*$+++++++++&'&++$*$+ &'& ",
  69.     "     @*@++++++++++&'&+++@#&&&'& ",
  70.     "    +*@++++++++#&&&'&+++#=''''& ",
  71.     "   +*$++++++++#=''''&+++&'>>>'& ",
  72.     "   @*+++++++++&'>>>'&+++#='''=  ",
  73.     "  +%$++++++++@#='''=#@@++#&&&#  ",
  74.     "  +*@+++++++@@@#&&&#@@@@@++@*+  ",
  75.     "  +*+++++++@@@@++@$%$$@@@@++*+  ",
  76.     "  +*++++++@@+@;,,*@@*$$$@@@+*+  ",
  77.     "  +*@++++@@@%!!!!,;@$*$$$@@@*+  ",
  78.     "  +%$++++@@+)!!!),-*+-%$$$@$%+  ",
  79.     "  +@*+++@@@+-!!!,;-%@;%%$$+*@+  ",
  80.     "   +*@++@@@@+$*-*%@+*-%%$@@*+   ",
  81.     "   ++*@+@@@$$%@++@%;;*%%$@-$+   ",
  82.     "    +@%+@@@$$%*;;;;-*%%%@**+    ",
  83.     "    .+$%@@@$$$*******%$$*-+.    ",
  84.     "     .+@%%@@$$*@*@%%%$%-%+.     ",
  85.     "      .++@%$$$$$$%%%%--@+.      ",
  86.     "        +++@@$%*****%+++        ",
  87.     "         +++++++++++++@.        ",
  88.     "          @--%@++@$*-%+         ",
  89.     "           +%,))),;%+.          ",
  90.     "             ++++++.            ",
  91.     "                                ",
  92.     "                                "
  93.   ]
  94.  
  95. # this should be a temporary hack
  96.  
  97. class WidgetFinder:
  98.   def localize_widgets(self,parent,xmlfile):
  99.     # an unbelievable hack made necessary by
  100.     # someone unwilling to fix a year-old bug
  101.     with open(xmlfile) as f:
  102.       for name in re.findall('(?s) id="(.*?)"',f.read()):
  103.         if re.search('^k_',name):
  104.           obj = parent.builder.get_object(name)
  105.           setattr(parent,name,obj)
  106.  
  107. class ConfigManager:
  108.   def __init__(self,path,dic):
  109.     self.path = path
  110.     self.dic = dic
  111.  
  112.   def read_config(self):
  113.     if os.path.exists(self.path):
  114.       with open(self.path) as f:
  115.         for record in f.readlines():
  116.           se = re.search('(.*?)\s*=\s*(.*)',record.strip())
  117.           if(se):
  118.             key,value = se.groups()
  119.             if (key in self.dic):
  120.               widget = self.dic[key]
  121.               typ = type(widget)
  122.               if(typ == list):
  123.                 widget[0] = value
  124.               elif(typ == gtk.Entry):
  125.                 widget.set_text(value)
  126.               elif(typ == gtk.HScale):
  127.                 widget.set_value(float(value))
  128.               elif(typ == gtk.Window):
  129.                 w,h = value.split(',')
  130.                 widget.resize(int(w),int(h))
  131.               elif(typ == gtk.CheckButton or typ == gtk.RadioButton or typ == gtk.ToggleButton):
  132.                 widget.set_active(value == 'True')
  133.               elif(typ == gtk.ComboBox):
  134.                 if(value in widget.datalist):
  135.                   i = widget.datalist.index(value)
  136.                   widget.set_active(i)
  137.               else:
  138.                 print "ERROR: reading, cannot identify key %s with type %s" % (key,type(widget))
  139.  
  140.   def write_config(self):
  141.     with open(self.path,'w') as f:
  142.       for key,widget in sorted(self.dic.iteritems()):
  143.         typ = type(widget)
  144.         if(typ == list):
  145.           value = widget[0]
  146.         elif(typ == gtk.Entry):
  147.           value = widget.get_text()
  148.         elif(typ == gtk.HScale):
  149.           value = str(widget.get_value())
  150.         elif(typ == gtk.Window):
  151.           _,_,w,h = widget.get_allocation()
  152.           value = "%d,%d" % (w,h)
  153.         elif(typ == gtk.CheckButton or typ == gtk.RadioButton or typ == gtk.ToggleButton):
  154.           value = ('False','True')[widget.get_active()]
  155.         elif(typ == gtk.ComboBox):
  156.           value = widget.get_active_text()
  157.         else:
  158.           print "ERROR: writing, cannot identify key %s with type %s" % (key,type(widget))
  159.           value = "Error"
  160.         f.write("%s = %s\n" % (key,value))
  161.  
  162.   def preset_combobox(self,box,v):
  163.     if(v in box.datalist):
  164.       i = box.datalist.index(v)
  165.       box.set_active(i)
  166.     else:
  167.       box.set_active(0)
  168.  
  169.   def load_combobox(self,obj,data):
  170.     if(len(obj.get_cells()) == 0):
  171.       # Create a text cell renderer
  172.       cell = gtk.CellRendererText ()
  173.       obj.pack_start(cell)
  174.       obj.add_attribute (cell, "text", 0)
  175.     obj.get_model().clear()
  176.     for s in data:
  177.       obj.append_text(s.strip())
  178.     setattr(obj,'datalist',data)
  179.  
  180. class TextEntryController:
  181.   def __init__(self,parent,widget):
  182.     self.par = parent
  183.     self.widget = widget
  184.     widget.connect('scroll-event',self.scroll_event)
  185.     widget.set_tooltip_text('Enter number or:\n\
  186.    Mouse wheel: increase,decrease\n\
  187.    Shift/Ctrl/Alt: faster change')
  188.  
  189.   def scroll_event(self,w,evt):
  190.     q = (-1,1)[evt.direction == gtk.gdk.SCROLL_UP]
  191.     # magnify change if shift,ctrl,alt pressed
  192.     for m in (1,2,4):
  193.       if(self.par.mod_key_val & m): q *= 10
  194.     s = self.widget.get_text()
  195.     v = float(s)
  196.     v += q
  197.     v = max(0,v)
  198.     s = self.par.format_num(v)
  199.     self.widget.set_text(s)
  200.  
  201. class SignalGen:
  202.   M_AM,M_FM = range(2)
  203.   W_SINE,W_TRIANGLE,W_SQUARE,W_SAWTOOTH,W_EQUATION_IMPORT = range(5)
  204.   waveform_strings = ('Sine','Triangle','Square','Sawtooth', 'Equation_Import')
  205.   R_48000,R_44100,R_22050,R_16000,R_11025,R_8000,R_4000 = range(7)
  206.   sample_rates = ('48000','44100','22050','16000', '11025', '8000', '4000')
  207.   def __init__(self):
  208.     self.restart = False
  209.     # exit correctly on system signals
  210.     signal.signal(signal.SIGTERM, self.close)
  211.     signal.signal(signal.SIGINT, self.close)
  212.     # precompile struct operator
  213.     self.struct_int = struct.Struct('i')
  214.     self.max_level = (2.0**31)-1
  215.     self.gen_functions = (
  216.       self.sine_function,
  217.       self.triangle_function,
  218.       self.square_function,
  219.       self.sawtooth_function,
  220.       self.equation_import_function
  221.     )
  222.     self.main_color = gtk.gdk.color_parse('#c04040')
  223.     self.sig_color = gtk.gdk.color_parse('#40c040')
  224.     self.mod_color = gtk.gdk.color_parse('#4040c0')
  225.     self.noise_color = gtk.gdk.color_parse('#c040c0')
  226.     self.pipeline = False
  227.     self.count = 0
  228.     self.imod = 0
  229.     self.rate = 1
  230.     self.mod_key_val = 0
  231.     self.sig_freq = 440
  232.     self.mod_freq = 3
  233.     self.sig_level = 100
  234.     self.mod_level = 100
  235.     self.noise_level = 100
  236.     self.enable = True
  237.     self.sig_waveform = SignalGen.W_SINE
  238.     self.sig_enable = True
  239.     self.sig_function = False
  240.     self.mod_waveform = SignalGen.W_SINE
  241.     self.mod_function = False
  242.     self.mod_mode = SignalGen.M_AM
  243.     self.mod_enable = False
  244.     self.noise_enable = False
  245.     self.sample_rate = SignalGen.R_22050
  246.     self.left_audio  = True
  247.     self.right_audio = True
  248.     self.program_name = self.__class__.__name__
  249.     self.config_file = os.path.expanduser("~/." + self.program_name)
  250.     self.builder = gtk.Builder()
  251.     self.xmlfile = 'signalgen_gui.glade'
  252.     self.builder.add_from_file(self.xmlfile)
  253.     WidgetFinder().localize_widgets(self,self.xmlfile)
  254.     self.k_quit_button.connect('clicked',self.close)
  255.     self.k_help_button.connect('clicked',self.launch_help)
  256.     self.k_mainwindow.connect('destroy',self.close)
  257.     self.k_mainwindow.set_icon(gtk.gdk.pixbuf_new_from_xpm_data(Icon.icon))
  258.     self.title = self.program_name + ' ' + VERSION
  259.     self.k_mainwindow.set_title(self.title)
  260.     self.tooltips = {
  261.       self.k_sample_rate_combobox : 'Change data sampling rate',
  262.       self.k_left_checkbutton : 'Enable left channel audio',
  263.       self.k_right_checkbutton : 'Enable right channel audio',
  264.       self.k_sig_waveform_combobox : 'Select signal waveform',
  265.       self.k_mod_waveform_combobox : 'Select modulation waveform',
  266.       self.k_mod_enable_checkbutton  : 'Enable modulation',
  267.       self.k_sig_enable_checkbutton  : 'Enable signal',
  268.       self.k_noise_enable_checkbutton  : 'Enable white noise',
  269.       self.k_mod_am_radiobutton : 'Enable amplitude modulation',
  270.       self.k_mod_fm_radiobutton : 'Enable frequency modulation',
  271.       self.k_quit_button : 'Quit %s' % self.title,
  272.       self.k_enable_checkbutton : 'Enable output',
  273.       self.k_help_button : 'Visit the %s Web page' % self.title,
  274.     }
  275.     for k,v in self.tooltips.iteritems():
  276.       k.set_tooltip_text(v)
  277.     self.config_data = {
  278.       'SampleRate' : self.k_sample_rate_combobox,
  279.       'LeftChannelEnabled' : self.k_left_checkbutton,
  280.       'RightChannelEnabled' : self.k_right_checkbutton,
  281.       'SignalWaveform' : self.k_sig_waveform_combobox,
  282.       'SignalFrequency' : self.k_sig_freq_entry,
  283.       'SignalLevel' : self.k_sig_level_entry,
  284.       'SignalEnabled' : self.k_sig_enable_checkbutton,
  285.       'ModulationWaveform' : self.k_mod_waveform_combobox,
  286.       'ModulationFrequency' : self.k_mod_freq_entry,
  287.       'ModulationLevel' : self.k_mod_level_entry,
  288.       'ModulationEnabled' : self.k_mod_enable_checkbutton,
  289.       'AmplitudeModulation' : self.k_mod_am_radiobutton,
  290.       'FrequencyModulation' : self.k_mod_fm_radiobutton,
  291.       'NoiseEnabled' : self.k_noise_enable_checkbutton,
  292.       'NoiseLevel' : self.k_noise_level_entry,
  293.       'OutputEnabled' : self.k_enable_checkbutton,
  294.     }
  295.     self.cm = ConfigManager(self.config_file,self.config_data)
  296.     self.cm.load_combobox(self.k_sig_waveform_combobox,self.waveform_strings)
  297.     self.k_sig_waveform_combobox.set_active(self.sig_waveform)
  298.     self.cm.load_combobox(self.k_mod_waveform_combobox,self.waveform_strings)
  299.     self.k_mod_waveform_combobox.set_active(self.mod_waveform)
  300.     self.cm.load_combobox(self.k_sample_rate_combobox,self.sample_rates)
  301.     self.k_sample_rate_combobox.set_active(self.sample_rate)
  302.     self.k_sig_freq_entry.set_text(self.format_num(self.sig_freq))
  303.     self.k_sig_level_entry.set_text(self.format_num(self.sig_level))
  304.     self.k_mod_freq_entry.set_text(self.format_num(self.mod_freq))
  305.     self.k_mod_level_entry.set_text(self.format_num(self.mod_level))
  306.     self.k_noise_level_entry.set_text(self.format_num(self.noise_level))
  307.     self.k_main_viewport_border.modify_bg(gtk.STATE_NORMAL,self.main_color)
  308.     self.k_sig_viewport_border.modify_bg(gtk.STATE_NORMAL,self.sig_color)
  309.     self.k_mod_viewport_border.modify_bg(gtk.STATE_NORMAL,self.mod_color)
  310.     self.k_noise_viewport_border.modify_bg(gtk.STATE_NORMAL,self.noise_color)
  311.     self.sig_freq_cont = TextEntryController(self,self.k_sig_freq_entry)
  312.     self.sig_level_cont = TextEntryController(self,self.k_sig_level_entry)
  313.     self.mod_freq_cont = TextEntryController(self,self.k_mod_freq_entry)
  314.     self.mod_level_cont = TextEntryController(self,self.k_mod_level_entry)
  315.     self.noise_level_cont = TextEntryController(self,self.k_noise_level_entry)
  316.     self.k_mainwindow.connect('key-press-event',self.key_event)
  317.     self.k_mainwindow.connect('key-release-event',self.key_event)
  318.     self.k_enable_checkbutton.connect('toggled',self.update_values)
  319.     self.k_sig_freq_entry.connect('changed',self.update_entry_values)
  320.     self.k_sig_level_entry.connect('changed',self.update_entry_values)
  321.     self.k_sig_enable_checkbutton.connect('toggled',self.update_checkbutton_values)
  322.     self.k_mod_freq_entry.connect('changed',self.update_entry_values)
  323.     self.k_mod_level_entry.connect('changed',self.update_entry_values)
  324.     self.k_noise_level_entry.connect('changed',self.update_entry_values)
  325.     self.k_sample_rate_combobox.connect('changed',self.update_values)
  326.     self.k_sig_waveform_combobox.connect('changed',self.update_values)
  327.     self.k_mod_waveform_combobox.connect('changed',self.update_values)
  328.     self.k_left_checkbutton.connect('toggled',self.update_checkbutton_values)
  329.     self.k_right_checkbutton.connect('toggled',self.update_checkbutton_values)
  330.     self.k_mod_enable_checkbutton.connect('toggled',self.update_checkbutton_values)
  331.     self.k_noise_enable_checkbutton.connect('toggled',self.update_checkbutton_values)
  332.     self.k_mod_am_radiobutton.connect('toggled',self.update_checkbutton_values)
  333.     self.cm.read_config()
  334.     self.update_entry_values()
  335.     self.update_checkbutton_values()
  336.     self.update_values()
  337.  
  338.   def format_num(self,v):
  339.     return "%.2f" % v
  340.  
  341.   def get_widget_text(self,w):
  342.     typ = type(w)
  343.     if(typ == gtk.ComboBox):
  344.       return w.get_active_text()
  345.     elif(typ == gtk.Entry):
  346.       return w.get_text()
  347.  
  348.   def get_widget_num(self,w):
  349.     try:
  350.       return float(self.get_widget_text(w))
  351.     except:
  352.       return 0.0
  353.  
  354.   def restart_test(self,w,pv):
  355.     nv = w.get_active()
  356.     self.restart |= (nv != pv)
  357.     return nv
  358.    
  359.   def update_entry_values(self,*args):
  360.     self.sig_freq = self.get_widget_num(self.k_sig_freq_entry)
  361.     self.sig_level = self.get_widget_num(self.k_sig_level_entry) / 100.0
  362.     self.mod_freq = self.get_widget_num(self.k_mod_freq_entry)
  363.     self.mod_level = self.get_widget_num(self.k_mod_level_entry) / 100.0
  364.     self.noise_level = self.get_widget_num(self.k_noise_level_entry) / 100.0
  365.    
  366.   def update_checkbutton_values(self,*args):
  367.     self.left_audio = self.k_left_checkbutton.get_active()
  368.     self.right_audio = self.k_right_checkbutton.get_active()
  369.     self.mod_enable = self.k_mod_enable_checkbutton.get_active()
  370.     self.sig_enable = self.k_sig_enable_checkbutton.get_active()
  371.     self.mod_mode = (SignalGen.M_FM,SignalGen.M_AM)[self.k_mod_am_radiobutton.get_active()]
  372.     self.noise_enable = self.k_noise_enable_checkbutton.get_active()
  373.    
  374.   def update_values(self,*args):
  375.     self.restart = (not self.sig_function)
  376.     self.sample_rate = self.restart_test(self.k_sample_rate_combobox, self.sample_rate)
  377.     self.enable = self.restart_test(self.k_enable_checkbutton,self.enable)
  378.     self.mod_waveform = self.k_mod_waveform_combobox.get_active()
  379.     self.mod_function = self.gen_functions[self.mod_waveform]
  380.     self.sig_waveform = self.k_sig_waveform_combobox.get_active()
  381.     self.sig_function = self.gen_functions[self.sig_waveform]
  382.     self.k_sample_rate_combobox.set_sensitive(not self.enable)
  383.     if(self.restart):
  384.       self.init_audio()
  385.      
  386.   def make_and_chain(self,name):
  387.     target = gst.element_factory_make(name)
  388.     self.chain.append(target)
  389.     return target
  390.  
  391.   def unlink_gst(self):
  392.     if(self.pipeline):
  393.       self.pipeline.set_state(gst.STATE_NULL)
  394.       self.pipeline.remove_many(*self.chain)
  395.       gst.element_unlink_many(*self.chain)
  396.       for item in self.chain:
  397.         item = False
  398.       self.pipeline = False
  399.       time.sleep(0.01)
  400.  
  401.   def init_audio(self):
  402.     self.unlink_gst()
  403.     if(self.enable):
  404.       self.chain = []
  405.       self.pipeline = gst.Pipeline("mypipeline")
  406.       self.source = self.make_and_chain("appsrc")
  407.       rs = SignalGen.sample_rates[self.sample_rate]
  408.       self.rate = float(rs)
  409.       self.interval = 1.0 / self.rate
  410.       caps = gst.Caps(
  411.       'audio/x-raw-int,'
  412.       'endianness=(int)1234,'
  413.       'channels=(int)2,'
  414.       'width=(int)32,'
  415.       'depth=(int)32,'
  416.       'signed=(boolean)true,'
  417.       'rate=(int)%s' % rs)
  418.       self.source.set_property('caps', caps)
  419.       self.sink = self.make_and_chain("autoaudiosink")
  420.       self.pipeline.add(*self.chain)
  421.       gst.element_link_many(*self.chain)
  422.       self.source.connect('need-data', self.need_data)
  423.       self.pipeline.set_state(gst.STATE_PLAYING)
  424.      
  425.   def key_event(self,w,evt):
  426.     cn = gtk.gdk.keyval_name(evt.keyval)
  427.     if(re.search('Shift',cn) != None):
  428.       mod = 1
  429.     elif(re.search('Control',cn) != None):
  430.       mod = 2
  431.     elif(re.search('Alt|Meta',cn) != None):
  432.       mod = 4
  433.     else:
  434.       return
  435.     if(evt.type == gtk.gdk.KEY_PRESS):
  436.       self.mod_key_val |= mod
  437.     else:
  438.       self.mod_key_val &= ~mod
  439.  
  440.   def sine_function(self,t,f):
  441.     return math.sin(2.0*math.pi*f*t)
  442.    
  443.  
  444.  
  445.   def triangle_function(self,t,f):
  446.     q = 4*math.fmod(t*f,1)
  447.     q = (q,2-q)[q > 1]
  448.     return (q,-2-q)[q < -1]
  449.  
  450.   def square_function(self,t,f):
  451.     if(f == 0): return 0
  452.     q = 0.5 - math.fmod(t*f,1)
  453.     return (-1,1)[q > 0]
  454.  
  455.   def sawtooth_function(self,t,f):
  456.     return 2.0*math.fmod((t*f)+0.5,1.0)-1.0
  457.    
  458.   def equation_import_function(self,t,f):
  459.     fileobj=open("/home/rat/eq1.txt","r")
  460.     eqdata =fileobj.read() #read whole file
  461.     fileobj.close()
  462.     #return math.tan(2.0*math.pi*f*t)
  463.     return eqdata
  464.    
  465.   def need_data(self,src,length):
  466.     bytes = ""
  467.     # sending two channels, so divide requested length by 2
  468.     ld2 = length / 2
  469.     for tt in range(ld2):
  470.       t = (self.count + tt) * self.interval
  471.       if(not self.mod_enable):
  472.         datum = self.sig_function(t,self.sig_freq)
  473.       else:
  474.         mod = self.mod_function(t,self.mod_freq)
  475.         # AM mode
  476.         if(self.mod_mode == SignalGen.M_AM):
  477.           datum = 0.5 * self.sig_function(t,self.sig_freq) * (1.0 + (mod * self.mod_level))
  478.         # FM mode
  479.         else:
  480.           self.imod += (mod * self.mod_level * self.interval)
  481.           datum = self.sig_function(t+self.imod,self.sig_freq)
  482.       v = 0
  483.       if(self.sig_enable):
  484.         v += (datum * self.sig_level)
  485.       if(self.noise_enable):
  486.         noise = ((2.0 * random.random()) - 1.0)
  487.         v += noise * self.noise_level
  488.       v *= self.max_level
  489.       v = max(-self.max_level,v)
  490.       v = min(self.max_level,v)
  491.       left  = (0,v)[self.left_audio]
  492.       right = (0,v)[self.right_audio]
  493.       bytes += self.struct_int.pack(left)
  494.       bytes += self.struct_int.pack(right)
  495.     self.count += ld2
  496.     src.emit('push-buffer', gst.Buffer(bytes))
  497.    
  498.   def launch_help(self,*args):
  499.     webbrowser.open("http://arachnoid.com/python/signalgen_program.html")
  500.  
  501.   def close(self,*args):
  502.     self.unlink_gst()
  503.     self.cm.write_config()
  504.     gtk.main_quit()
  505.  
  506. app=SignalGen()
  507. gtk.main()
  508.  
  509.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement