Index: _package.epm ================================================================== --- _package.epm +++ _package.epm @@ -35,11 +35,11 @@ f 644 root root /usr/share/streamtuner2/gtk3.xml ./gtk3.xml f 644 root root /usr/share/streamtuner2/pson.py ./pson.py #f 644 root root /usr/share/streamtuner2/processing.py ./processing.py f 644 root root /usr/share/streamtuner2/action.py ./action.py f 644 root root /usr/share/streamtuner2/config.py ./config.py -f 644 root root /usr/share/streamtuner2/http.py ./http.py +f 644 root root /usr/share/streamtuner2/ahttp.py ./ahttp.py f 644 root root /usr/share/streamtuner2/cli.py ./cli.py f 644 root root /usr/share/streamtuner2/mygtk.py ./mygtk.py f 644 root root /usr/share/streamtuner2/favicon.py ./favicon.py f 644 root root /usr/share/streamtuner2/kronos.py ./kronos.py f 644 root root /usr/share/streamtuner2/pq.py ./pq.py Index: action.py ================================================================== --- action.py +++ action.py @@ -21,11 +21,11 @@ # import re import os -import http +import ahttp as http from config import conf, __print__, dbg import platform main = None @@ -79,12 +79,12 @@ # exec wrapper @staticmethod def run(cmd): if conf.windows: - os.system("start \"%s\"") - else: + os.system("start \"%s\"") + else: os.system(cmd + " &") # streamripper @staticmethod ADDED ahttp.py Index: ahttp.py ================================================================== --- ahttp.py +++ ahttp.py @@ -0,0 +1,231 @@ +# +# encoding: UTF-8 +# api: streamtuner2 +# type: functions +# title: http download / methods +# description: http utility +# version: 1.3 +# +# Provides a http GET method with gtk.statusbar() callback. +# And a function to add trailings slashes on http URLs. +# +# The latter code is pretty much unreadable. But let's put the +# blame on urllib2, the most braindamaged code in the Python +# standard library. +# + + +# Python 2.x +try: + import urllib2 + from urllib import urlencode + import urlparse + import cookielib + from StringIO import StringIO +# Python 3.x +except: + import urllib.request as urllib2 + from urllib.parse import urlencode + import urllib.parse as urlparse + from http import cookiejar as cookielib + from io import StringIO + +from gzip import GzipFile + +from config import conf, __print__, dbg + + +#-- url download --------------------------------------------- + + + +#-- chains to progress meter and status bar in main window +feedback = None + +# sets either text or percentage, so may take two parameters +def progress_feedback(*args): + + # use reset values if none given + if not args: + args = ["", 1.0] + + # send to main win + if feedback: + try: [feedback(d) for d in args] + except: pass + + + + +#-- GET +def get(url, maxsize=1<<19, feedback="old"): + __print__("GET", url) + + # statusbar info + progress_feedback(url, 0.0) + + # read + content = "" + f = urllib2.urlopen(url) + max = 222000 # mostly it's 200K, but we don't get any real information + read_size = 1 + + # multiple steps + while (read_size and len(content) < maxsize): + + # partial read + add = f.read(8192) + content = content + add + read_size = len(add) + + # set progress meter + progress_feedback(float(len(content)) / float(max)) + + # done + + # clean statusbar + progress_feedback() + + # fin + __print__(len(content)) + return content + + + + + +#-- fix invalid URLs +def fix_url(url): + if url is None: + url = "" + if len(url): + # remove whitespace + url = url.strip() + # add scheme + if (url.find("://") < 0): + url = "http://" + url + # add mandatory path + if (url.find("/", 10) < 0): + url = url + "/" + return url + + + + +# default HTTP headers for AJAX/POST request +default_headers = { + "User-Agent": "streamtuner2/2.1 (X11; U; Linux AMD64; en; rv:1.5.0.1) like WinAmp/2.1 but not like Googlebot/2.1", #"Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6", + "Accept": "*/*;q=0.5, audio/*, url/*", + "Accept-Language": "en-US,en,de,es,fr,it,*;q=0.1", + "Accept-Encoding": "gzip,deflate", + "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.1", + "Keep-Alive": "115", + "Connection": "keep-alive", + #"Content-Length", "56", + #"Cookie": "s_pers=%20s_getnr%3D1278607170446-Repeat%7C1341679170446%3B%20s_nrgvo%3DRepeat%7C1341679170447%3B; s_sess=%20s_cc%3Dtrue%3B%20s_sq%3Daolshtcst%252Caolsvc%253D%252526pid%25253Dsht%25252520%2525253A%25252520SHOUTcast%25252520Radio%25252520%2525257C%25252520Search%25252520Results%252526pidt%25253D1%252526oid%25253Dfunctiononclick%25252528event%25252529%2525257BshowMoreGenre%25252528%25252529%2525253B%2525257D%252526oidt%25253D2%252526ot%25253DDIV%3B; aolDemoChecked=1.849061", + "Pragma": "no-cache", + "Cache-Control": "no-cache", +} + + + +# simulate ajax calls +def ajax(url, post, referer=""): + + # request + headers = default_headers + headers.update({ + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + "X-Requested-With": "XMLHttpRequest", + "Referer": (referer if referer else url), + }) + if type(post) == dict: + post = urlencode(post) + request = urllib2.Request(url, post, headers) + + # open url + __print__( vars(request) ) + progress_feedback(url, 0.2) + r = urllib2.urlopen(request) + + # get data + __print__( r.info() ) + progress_feedback(0.5) + data = r.read() + progress_feedback() + return data + + + +# http://techknack.net/python-urllib2-handlers/ +class ContentEncodingProcessor(urllib2.BaseHandler): + """A handler to add gzip capabilities to urllib2 requests """ + + # add headers to requests + def http_request(self, req): + req.add_header("Accept-Encoding", "gzip, deflate") + return req + + # decode + def http_response(self, req, resp): + old_resp = resp + # gzip + if resp.headers.get("content-encoding") == "gzip": + gz = GzipFile( + fileobj=StringIO(resp.read()), + mode="r" + ) + resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code) + resp.msg = old_resp.msg + # deflate + if resp.headers.get("content-encoding") == "deflate": + gz = StringIO( deflate(resp.read()) ) + resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code) # 'class to add info() and geturl() methods to an open file.' + resp.msg = old_resp.msg + return resp + +# deflate support +import zlib +def deflate(data): # zlib only provides the zlib compress format, not the deflate format; + try: # so on top of all there's this workaround: + return zlib.decompress(data, -zlib.MAX_WBITS) + except zlib.error: + return zlib.decompress(data) + + + + + + + +#-- init for later use +if urllib2: + + # config 1 + handlers = [None, None, None] + + # base + handlers[0] = urllib2.HTTPHandler() + if conf.debug: + handlers[0].set_http_debuglevel(3) + + # content-encoding + handlers[1] = ContentEncodingProcessor() + + # store cookies at runtime + cj = cookielib.CookieJar() + handlers[2] = urllib2.HTTPCookieProcessor( cj ) + + # inject into urllib2 + urllib2.install_opener( urllib2.build_opener(*handlers) ) + + + + +# alternative function names +AJAX=ajax +POST=ajax +GET=get +URL=fix_url + + Index: channels/_generic.py ================================================================== --- channels/_generic.py +++ channels/_generic.py @@ -21,11 +21,11 @@ import gtk from mygtk import mygtk from config import conf, __print__, dbg -import http +import ahttp as http import action import favicon import os.path import xml.sax.saxutils import re @@ -147,17 +147,17 @@ # category tree self.display_categories() #mygtk.tree(self.gtk_cat, self.categories, title="Category", icon=gtk.STOCK_OPEN); # update column names - for field,title in self.titles.iteritems(): + for field,title in list(self.titles.items()): self.update_datamap(field, title=title) # prepare stream list if (not self.rowmap): for row in self.datamap: - for x in xrange(2, len(row)): + for x in range(2, len(row)): self.rowmap.append(row[x][0]) # load default category if (self.current): self.load(self.current) @@ -249,11 +249,11 @@ # oh my, at least it's working # at start the bookmarks module isn't fully registered at instantiation in parent.channels{} - might want to do that step by step rather # then display() is called too early to take effect - load() & co should actually be postponed to when a notebook tab gets selected first # => might be fixed now, 1.9.8 # state icon: bookmark star - if (conf.show_bookmarks and self.parent.channels.has_key("bookmarks") and self.parent.bookmarks.is_in(streams[i].get("url", "file:///tmp/none"))): + if (conf.show_bookmarks and "bookmarks" in self.parent.channels and self.parent.bookmarks.is_in(streams[i].get("url", "file:///tmp/none"))): streams[i]["favourite"] = 1 # state icon: INFO or DELETE if (not row.get("state")): if row.get("favourite"): @@ -309,15 +309,14 @@ # if category tree is empty, initialize it if not self.categories: __print__(dbg.PROC, "first_show: reload_categories"); #self.parent.thread(self.reload_categories) - print("reload categories"); self.reload_categories() self.display_categories() self.current = self.categories.keys()[0] - print self.current + __print__(dbg.STAT, self.current) self.load(self.current) # load current category else: __print__(dbg.STAT, "first_show: load current category"); Index: channels/basicch.py ================================================================== --- channels/basicch.py +++ channels/basicch.py @@ -10,11 +10,11 @@ # Needs manual initialisation of categories first. # import re -import http +import ahttp as http from config import conf from channels import * from xml.sax.saxutils import unescape Index: channels/google.py ================================================================== --- channels/google.py +++ channels/google.py @@ -49,11 +49,11 @@ import re, os, gtk from channels import * from xml.sax.saxutils import unescape as entity_decode, escape as xmlentities -import http +import ahttp as http ### constants ################################################################# Index: channels/internet_radio_org_uk.py ================================================================== --- channels/internet_radio_org_uk.py +++ channels/internet_radio_org_uk.py @@ -13,11 +13,11 @@ from channels import * import re from config import conf, __print__, dbg -import http +import ahttp as http from pq import pq Index: channels/jamendo.py ================================================================== --- channels/jamendo.py +++ channels/jamendo.py @@ -6,11 +6,11 @@ # Requires more rework of streamtuner2 list display to show album covers. # import re -import http +import ahttp as http from config import conf from channels import * from xml.sax.saxutils import unescape Index: channels/live365.py ================================================================== --- channels/live365.py +++ channels/live365.py @@ -8,11 +8,11 @@ # streamtuner2 modules from config import conf from mygtk import mygtk -import http +import ahttp as http from channels import * from config import __print__, dbg # python modules import re Index: channels/modarchive.py ================================================================== --- channels/modarchive.py +++ channels/modarchive.py @@ -10,11 +10,11 @@ # VLC in */* seems to work fine however. # import re -import http +import ahttp as http from config import conf from channels import * from config import __print__, dbg from xml.sax.saxutils import unescape Index: channels/musicgoal.py ================================================================== --- channels/musicgoal.py +++ channels/musicgoal.py @@ -14,11 +14,11 @@ # st2 modules from config import conf from mygtk import mygtk -import http +import ahttp as http from channels import * # python modules import re import json Index: channels/punkcast.py ================================================================== --- channels/punkcast.py +++ channels/punkcast.py @@ -7,11 +7,11 @@ # ST1 looked prettier with random images within. # import re -import http +import ahttp as http from config import conf import action from channels import * from config import __print__, dbg Index: channels/shoutcast.py ================================================================== --- channels/shoutcast.py +++ channels/shoutcast.py @@ -16,11 +16,11 @@ # # # -import http +import ahttp as http import urllib import re from config import conf, __print__, dbg from pq import pq #from channels import * # works everywhere but in this plugin(???!) Index: channels/tv.py ================================================================== --- channels/tv.py +++ channels/tv.py @@ -16,11 +16,11 @@ # stored in .streams["all"] pseudo-category. # # icon: http://cemagraphics.deviantart.com/art/Little-Tv-Icon-96461135 from channels import * -import http +import ahttp as http import lxml.etree Index: channels/xiph.py ================================================================== --- channels/xiph.py +++ channels/xiph.py @@ -17,11 +17,11 @@ # streamtuner2 modules from config import conf from mygtk import mygtk -import http +import ahttp as http from channels import * from config import __print__, dbg # python modules import re Index: cli.py ================================================================== --- cli.py +++ cli.py @@ -15,11 +15,11 @@ # import sys #from channels import * -import http +import ahttp import action from config import conf import json Index: favicon.py ================================================================== --- favicon.py +++ favicon.py @@ -28,15 +28,14 @@ import os, os.path import urllib import re -import urlparse from config import conf try: from processing import Process as Thread except: from threading import Thread -import http +import ahttp # ensure that we don't try to download a single favicon twice per session, # if it's not available the first time, we won't get it after switching stations back and forth @@ -87,16 +86,16 @@ title = rx_t.search(row["title"]) if title: title = title.group(0).replace(" ", "%20") # do a google search - html = http.ajax("http://www.google.de/search?hl=de&q="+title, None) + html = ahttp.ajax("http://www.google.de/search?hl=de&q="+title, None) # find first URL hit url = rx_u.search(html) if url: - row["homepage"] = http.fix_url(url.group(1)) + row["homepage"] = ahttp.fix_url(url.group(1)) pass #----------------- @@ -193,13 +192,13 @@ r = urllib.urlopen(favicon) headers = r.info() # abort on if r.getcode() >= 300: - raise "HTTP error", r.getcode() + raise Error("HTTP error" + r.getcode()) if not headers["Content-Type"].lower().find("image/"): - raise "can't use text/* content" + raise Error("can't use text/* content") # save file fn_tmp = fn+".tmp" f = open(fn_tmp, "wb") f.write(r.read(32768)) @@ -234,11 +233,11 @@ # url or if favicon.startswith("http://"): None # just /pathname else: - favicon = urlparse.urljoin(url, favicon) + favicon = ahttp.urlparse.urljoin(url, favicon) #favicon = "http://" + domain(url) + "/" + favicon # download direct_download(favicon, file(url)) @@ -264,11 +263,11 @@ import operator import struct try: from PIL import BmpImagePlugin, PngImagePlugin, Image -except Exception, e: +except Exception as e: print("no PIL", e) always_google = 1 only_google = 1 DELETED http.py Index: http.py ================================================================== --- http.py +++ http.py @@ -1,224 +0,0 @@ -# -# encoding: UTF-8 -# api: streamtuner2 -# type: functions -# title: http download / methods -# description: http utility -# version: 1.3 -# -# Provides a http GET method with gtk.statusbar() callback. -# And a function to add trailings slashes on http URLs. -# -# The latter code is pretty much unreadable. But let's put the -# blame on urllib2, the most braindamaged code in the Python -# standard library. -# - -try: - import urllib2 - from urllib import urlencode -except: - import urllib.request as urllib2 - import urllib.parse.urlencode as urlencode -import config -from config import __print__, dbg - - - -#-- url download --------------------------------------------- - - - -#-- chains to progress meter and status bar in main window -feedback = None - -# sets either text or percentage, so may take two parameters -def progress_feedback(*args): - - # use reset values if none given - if not args: - args = ["", 1.0] - - # send to main win - if feedback: - try: [feedback(d) for d in args] - except: pass - - - - -#-- GET -def get(url, maxsize=1<<19, feedback="old"): - __print__("GET", url) - - # statusbar info - progress_feedback(url, 0.0) - - # read - content = "" - f = urllib2.urlopen(url) - max = 222000 # mostly it's 200K, but we don't get any real information - read_size = 1 - - # multiple steps - while (read_size and len(content) < maxsize): - - # partial read - add = f.read(8192) - content = content + add - read_size = len(add) - - # set progress meter - progress_feedback(float(len(content)) / float(max)) - - # done - - # clean statusbar - progress_feedback() - - # fin - __print__(len(content)) - return content - - - - - -#-- fix invalid URLs -def fix_url(url): - if url is None: - url = "" - if len(url): - # remove whitespace - url = url.strip() - # add scheme - if (url.find("://") < 0): - url = "http://" + url - # add mandatory path - if (url.find("/", 10) < 0): - url = url + "/" - return url - - - - -# default HTTP headers for AJAX/POST request -default_headers = { - "User-Agent": "streamtuner2/2.1 (X11; U; Linux AMD64; en; rv:1.5.0.1) like WinAmp/2.1 but not like Googlebot/2.1", #"Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6", - "Accept": "*/*;q=0.5, audio/*, url/*", - "Accept-Language": "en-US,en,de,es,fr,it,*;q=0.1", - "Accept-Encoding": "gzip,deflate", - "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.1", - "Keep-Alive": "115", - "Connection": "keep-alive", - #"Content-Length", "56", - #"Cookie": "s_pers=%20s_getnr%3D1278607170446-Repeat%7C1341679170446%3B%20s_nrgvo%3DRepeat%7C1341679170447%3B; s_sess=%20s_cc%3Dtrue%3B%20s_sq%3Daolshtcst%252Caolsvc%253D%252526pid%25253Dsht%25252520%2525253A%25252520SHOUTcast%25252520Radio%25252520%2525257C%25252520Search%25252520Results%252526pidt%25253D1%252526oid%25253Dfunctiononclick%25252528event%25252529%2525257BshowMoreGenre%25252528%25252529%2525253B%2525257D%252526oidt%25253D2%252526ot%25253DDIV%3B; aolDemoChecked=1.849061", - "Pragma": "no-cache", - "Cache-Control": "no-cache", -} - - - -# simulate ajax calls -def ajax(url, post, referer=""): - - # request - headers = default_headers - headers.update({ - "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", - "X-Requested-With": "XMLHttpRequest", - "Referer": (referer if referer else url), - }) - if type(post) == dict: - post = urlencode(post) - request = urllib2.Request(url, post, headers) - - # open url - __print__( vars(request) ) - progress_feedback(url, 0.2) - r = urllib2.urlopen(request) - - # get data - __print__( r.info() ) - progress_feedback(0.5) - data = r.read() - progress_feedback() - return data - - - -# http://techknack.net/python-urllib2-handlers/ -from gzip import GzipFile -from StringIO import StringIO -class ContentEncodingProcessor(urllib2.BaseHandler): - """A handler to add gzip capabilities to urllib2 requests """ - - # add headers to requests - def http_request(self, req): - req.add_header("Accept-Encoding", "gzip, deflate") - return req - - # decode - def http_response(self, req, resp): - old_resp = resp - # gzip - if resp.headers.get("content-encoding") == "gzip": - gz = GzipFile( - fileobj=StringIO(resp.read()), - mode="r" - ) - resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code) - resp.msg = old_resp.msg - # deflate - if resp.headers.get("content-encoding") == "deflate": - gz = StringIO( deflate(resp.read()) ) - resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code) # 'class to add info() and geturl() methods to an open file.' - resp.msg = old_resp.msg - return resp - -# deflate support -import zlib -def deflate(data): # zlib only provides the zlib compress format, not the deflate format; - try: # so on top of all there's this workaround: - return zlib.decompress(data, -zlib.MAX_WBITS) - except zlib.error: - return zlib.decompress(data) - - - - - - - -#-- init for later use -if urllib2: - - # config 1 - handlers = [None, None, None] - - # base - handlers[0] = urllib2.HTTPHandler() - if config.conf.debug: - handlers[0].set_http_debuglevel(3) - - # content-encoding - handlers[1] = ContentEncodingProcessor() - - # store cookies at runtime - import cookielib - cj = cookielib.CookieJar() - handlers[2] = urllib2.HTTPCookieProcessor( cj ) - - # inject into urllib2 - urllib2.install_opener( urllib2.build_opener(*handlers) ) - - - - -# alternative function names -AJAX=ajax -POST=ajax -GET=get -URL=fix_url - - Index: mygtk.py ================================================================== --- mygtk.py +++ mygtk.py @@ -2,11 +2,11 @@ # encoding: UTF-8 # api: python # type: functions # title: mygtk helper functions # description: simplify usage of some gtk widgets -# version: 1.6 +# version: 1.7 # author: mario # license: public domain # # # Wrappers around gtk methods. The TreeView method .columns() allows @@ -27,33 +27,42 @@ # debug from config import __print__, dbg +# filesystem +import os.path +import copy +import sys + +if sys.version_info[0] >= 3: + unicode = str + -# gtk modules -gtk = 0 # 0=gtk2, else gtk3 -if gtk: +# gtk version +ver = 2 # 2=gtk2, 3=gtk3 +if "--gtk3" in sys.argv: + ver = 3 +if sys.version_info >= (3, 0): + ver = 3 +# load gtk modules +if ver==3: from gi import pygtkcompat as pygtk pygtk.enable() pygtk.enable_gtk(version='3.0') from gi.repository import Gtk as gtk from gi.repository import GObject as gobject from gi.repository import GdkPixbuf ui_file = "gtk3.xml" - __print__(gtk) - __print__(gobject) -if not gtk: + __print__(dbg.PROC, gtk) + __print__(dbg.PROC, gobject) +else: import pygtk import gtk import gobject ui_file = "gtk2.xml" -# filesystem -import os.path -import copy - try: empty_pixbuf = gtk.gdk.pixbuf_new_from_data(b"\0\0\0\0",gtk.gdk.COLORSPACE_RGB,True,8,1,1,4) except: empty_pixbuf = GdkPixbuf.Pixbuf.new_from_data(b"\0\0\0\0", GdkPixbuf.Colorspace.RGB, True, 8, 1, 1, 4, None, None) @@ -105,11 +114,11 @@ if (desc[1] > 0): col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) col.set_fixed_width(desc[1]) # loop through cells - for var in xrange(2, len(desc)): + for var in range(2, len(desc)): cell = desc[var] # cell renderer if (cell[2] == "pixbuf"): rend = gtk.CellRendererPixbuf() # img cell if (cell[1] == str): @@ -124,11 +133,11 @@ #col.set_sort_column_id(datapos) # only on textual cells # attach cell to column col.pack_end(rend, expand=cell[3].get("expand",True)) # apply attributes - for attr,val in cell[3].iteritems(): + for attr,val in list(cell[3].items()): col.add_attribute(rend, attr, val) # next datapos += 1 __print__(cell) @@ -147,11 +156,11 @@ #- expand datamap vartypes = [] #(str, str, bool, str, int, int, gtk.gdk.Pixbuf, str, int) rowmap = [] #["title", "desc", "bookmarked", "name", "count", "max", "img", ...] if (not rowmap): for desc in datamap: - for var in xrange(2, len(desc)): + for var in range(2, len(desc)): vartypes.append(desc[var][1]) # content types rowmap.append(desc[var][0]) # dict{} column keys in entries[] list # create gtk array storage ls = gtk.ListStore(*vartypes) # could be a TreeStore, too __print__(vartypes) Index: st2.py ================================================================== --- st2.py +++ st2.py @@ -3,11 +3,11 @@ # api: python # type: application # title: streamtuner2 # description: directory browser for internet radio / audio streams # depends: gtk, pygtk, xml.dom.minidom, threading, lxml, pyquery, kronos -# version: 2.0.9.5 +# version: 2.0.9.6 # author: mario salzer # license: public domain # url: http://freshmeat.net/projects/streamtuner2 # config: # category: multimedia @@ -98,11 +98,11 @@ from mygtk import pygtk, gtk, gobject, ui_file, mygtk # custom modules from config import conf # initializes itself, so all conf.vars are available right away from config import __print__, dbg -import http +import ahttp import action # needs workaround... (action.main=main) from channels import * import favicon #from pq import pq @@ -235,19 +235,19 @@ #-- Shortcut for glade.get_widget() # Allows access to widgets as direct attributes instead of using .get_widget() # Also looks in self.channels[] for the named channel plugins def __getattr__(self, name): - if (self.channels.has_key(name)): + if (name in self.channels): return self.channels[name] # like self.shoutcast else: return self.get_object(name) # or gives an error if neither exists # custom-named widgets are available from .widgets{} not via .get_widget() def get_widget(self, name): - if self.widgets.has_key(name): + if name in self.widgets: return self.widgets[name] else: return gtk.Builder.get_object(self, name) @@ -523,11 +523,14 @@ # end application and gtk+ main loop def gtk_main_quit(self, widget, *x): if conf.auto_save_appstate: - self.app_state(widget) + try: # doesn't work with gtk3 yet + self.app_state(widget) + except: + None gtk.main_quit() @@ -1151,11 +1154,11 @@ #-- global configuration settings "conf = Config()" # already happened with "from config import conf" # graphical - if len(sys.argv) < 2: + if len(sys.argv) < 2 or "--gtk3" in sys.argv: # prepare for threading in Gtk+ callbacks gobject.threads_init() gui_startup(1/100.0) @@ -1164,11 +1167,11 @@ main = StreamTunerTwo() # module coupling action.main = main # action (play/record) module needs a reference to main window for gtk interaction and some URL/URI callbacks action = action.action # shorter name - http.feedback = main.status # http module gives status feedbacks too + ahttp.feedback = main.status # http module gives status feedbacks too # first invocation if (conf.get("firstrun")): config_dialog.open(None) del conf.firstrun