Index: channels/favicon.py ================================================================== --- channels/favicon.py +++ channels/favicon.py @@ -323,11 +323,11 @@ except Exception as e: #traceback.print_exc() return log.ERR("favicon/logo conversion error:", e) and False else: - log.WARN("Couldn't detect valig image type from its raw content") + log.WARN("Couldn't detect valid image type from its raw content") # PNG already? if re.match(b"^.(PNG)", imgdata): try: with open(fn, "wb") as f: Index: contrib/gtk_theme.py ================================================================== --- contrib/gtk_theme.py +++ contrib/gtk_theme.py @@ -58,10 +58,11 @@ log.GTK_THEME_FILE(fn) # .GTKRC/Gtk2 if uikit.ver == 2: uikit.gtk.rc_parse(fn) if now or conf.theme_instant: + uikit.gtk.reset_styles(uikit.gtk.settings_get_for_screen(uikit.gtk.gdk.screen_get_default())) uikit.gtk.rc_reparse_all() # .CSS/Gtk3 elif now or conf.theme_instant: #ctx = uikit.gtk.StyleContext # global ctx = self.parent.win_streamtuner2.get_style_context() # main window ADDED contrib/theme_installer.py Index: contrib/theme_installer.py ================================================================== --- contrib/theme_installer.py +++ contrib/theme_installer.py @@ -0,0 +1,179 @@ +# encoding: UTF-8 +# api: streamtuner2 +# title: Gtk2 theme installer +# description: Shows themes in the bookmarks pane for installation +# type: feature +# category: ui +# version: 0.3 +# priority: experimental +# +# Downloads a list of Gtk themes and presents it in the bookmarks +# tab under... »themes«. Double clicking will download and install +# a theme right away. +# +# Note that this is primarily meant for Windows, as it unpacks +# *.dll engines if found. Should work on BSD/Linux still, but would +# require setting up .gtkrc, and writeable module_path for engines. +# It only handles Gtk2 themes currently. +# +# Reuses the `conf.theme` setting from the `gtk_theme` plugin, and +# should work in conjunction to it. +# +# The theme repository is a CSV file of: +# themepkg.zip, theme.png, Title, Author, http://homepage/ +# with the packages residing next to it. +# +# Using a repo.json works better however, and would allow to +# integrate it with the regular plugin manager somewhen. +# The bookmark/themes channel provides the nicer UI however. +# +# A theme.zip should contain a structure like: +# --------- ---------- ----- ---- +# 62937 2016-12-12 16:39 librezlooks.dll +# 0 2016-12-12 16:40 Rezlooks-dark/ +# 0 2016-12-03 20:58 Rezlooks-dark/gtk-2.0/ +# 5332 2006-06-30 05:28 Rezlooks-dark/gtk-2.0/gtkrc +# --------- ---------- ----- ---- +# With the dll in the root, and theme files in a named subdir. +# Zips are extracted into the config dir ../streamtuner2/themes/ +# and copies left there even. + + +import os, shutil +import csv +import zipfile +import re +import json +import ahttp +from config import * +import uikit +from compat2and3 import * +import action +import channels.favicon as fi + + +# register a key +class theme_installer(object): + + # plugin info + module = "theme_installer" + meta = plugin_meta() + category = "themes" + theme_dir = conf.dir + "/themes/" + themes_url = "http://milki.include-once.org/streamtuner2/themes/" + themes_csv = "themes.json" + mime = "zip/gtk-theme" + parent = None + bm = None + + # register + def __init__(self, parent): + if not parent: + return + if not uikit.ver == 2: + return + if not "theme" in conf: + conf.theme = "default" + if not os.path.exists(self.theme_dir): + os.mkdir(self.theme_dir) + self.parent = parent + self.bm = parent.bookmarks + + # register hooks + action.handler[self.mime] = self.install_handler # zip/gtk-theme downloader + self.bm.add_category(self.category) # add subcategory + self.bm.category_plugins[self.category] = self # reloading theme list + parent.hooks["init"].append(self.apply_theme) # load gtk theme on start + + # gtk.rc_parse() called on configwin.save and ST2 startup + def apply_theme(self, now=True): + if conf.theme == "default": + return + # look if theme exists + fn = "%s%s/%s" % (self.theme_dir, conf.theme, "gtk-2.0/gtkrc") + if not os.path.exists(fn): + return + log.GTK_THEME_FILE(fn) + # .GTKRC/Gtk2 + uikit.gtk.rc_parse_string("module_path \"%s:%s\"\n" % (uikit.gtk.rc_get_module_dir(), self.theme_dir)) + uikit.gtk.rc_parse(fn) + uikit.gtk.rc_reset_styles(uikit.gtk.settings_get_for_screen(uikit.gtk.gdk.screen_get_default())) + + # download list of themes + def update_streams(self, cat): + r = [] + data = ahttp.get(self.themes_url + self.themes_csv) + + #-- repo.JSON + if re.match("^\s*\[\s*\{", data): + r = json.loads(data) + # can contain a literal rows-list + repo meta data + + #-- themes.CSV + else: + for row in re.findall("^(?!#)\s*(.+?),\s*(.+?),\s*(.+?),\s*(.+?),\s*(.+)", data, re.M): + # prepare row + d = dict( + genre = "gtk2", + url = row[0], + img = row[1], + title = row[2], + playing = row[3], + homepage = row[4], + state = "gtk-zoom-fit", + format = self.mime, + listformat = "href" + ) + # add + r.append(d) + + # convert relative references + for d in r: + for field in ("url", "img", '$file'): + v = str(d.get(field)) + if v and v.find("://") < 0: + d[field] = self.themes_url + v + d["title"] = "\n%s\n" % d.get("title", "-") + + # predownload favicons + for d in r: + d["favicon"] = fi.row_to_fn(d) + if not os.path.exists(d["favicon"]): + fi.banner_localcopy(d["img"], d["favicon"], 64) + log.COPY( d["img"], d["favicon"] ) + + return r + + # invoked by action. module when encounterin a zip/gtk-theme links + def install_handler(self, row, audioformat, source, assoc): + if not "url" in row: + return + + # download + log.THEME_INSTALL(row["url"]) + zip = self.theme_dir + os.path.basename(row["url"]) + #if not os.path.exists(zip): + with open(zip, "wb") as f: + f.write(ahttp.get(row["url"], binary=True)) + # extract + z = zipfile.ZipFile(zip) + z.extractall(self.theme_dir) + ls = z.namelist() + dll = [fn for fn in ls if re.search("\w+\.(dll|so)$", fn)] + base = [m.group(1) for fn in ls for m in [re.match("^([\w\s\-\.]+)/gtk-2.0/.+", fn)] if m] + + # move *.dll / *.so + for gtk_dir in uikit.gtk.rc_get_module_dir().split(":"): + if os.path.exists(gtk_dir) and os.access(gtk_dir, os.W_OK): + for fn in dll: + if fn.find("/") > 0: # create lib/engines/.../ if given + try: os.makedirs(self.theme_dir + os.path.basename(fn)) + except: pass # copy file + if shutil.copy(self.theme_dir + fn, gtk_dir): + break + + # enable + conf.theme = base[0] + self.apply_theme(True) + conf.save() + Index: dev/install_python_gtk.ps1 ================================================================== --- dev/install_python_gtk.ps1 +++ dev/install_python_gtk.ps1 @@ -20,11 +20,11 @@ [string]$StartMenu = "$env:USERPROFILE\AppData\Roaming\Microsoft\Windows\Start Menu\Programs", #[string]$UsrFolder = $MyInvocation.MyCommand.Path -replace ("([\\/][^\\/]+){4}$",""), [string]$ProgramFiles = "%ProgramFiles(x86)%", [string]$AboutLink = "http://freshcode.club/projects/streamtuner2", [string]$VERSION = "2.2.0" -) +) #-- paths $UsrFolder = $MyInvocation.MyCommand.Path -replace ("([\\/][^\\/]+){4}$","") $UninstallPath = "$UsrFolder\share\streamtuner2\dev\uninstall.cmd" $ResetPrefsPath = $UninstallPath -replace ("uninstall", "resetprefs") @@ -191,10 +191,11 @@ $MyWindow.Width = (80) $MyBuffer.Height = (9999) #$MyBuffer.Width = (80) $host.UI.RawUI.set_bufferSize($MyBuffer) $host.UI.RawUI.set_windowSize($MyWindow) + $host.ui.RawUI.BackgroundColor = ($bckgrnd = 'Black') } } #-- create Desktop/Startmenu shortcuts function Make-Shortcut { @@ -325,11 +326,11 @@ $t.testpth = $t.testpth -replace "{PYTHON}","$PYTHON" if (!$t.regkey -and !$t.testpth) { continue } if ($t.prescn) { # expression for e.g. registry → path lookup - Invoke-Expression $t.prescn # should set $.found + global $PLACEHOLDER variable + Invoke-Expression $t.prescn # should set $t.found + global $PLACEHOLDER variable } elseif ($t.testpth) { if (Test-Path $t.testpth) { $t.found = $t.testpth } @@ -363,19 +364,19 @@ #-- Operates on parameters of each hash from `$tasks` # # · $title → print current step # · $url → download from given link, keep as local `$file` # · $cmd → instead of running file, run a custom command -# · $iargs → used for MSI installation +# · $iargs → used for MSI installation # · $testpth → check for exisiting dir/file # · $regkey → set registry key if successful # · $is_opt → run as expression # · $prescn → used in check-prereq() # filter Run-Task { # extract flags/vars from $tasks pipe - $title=""; $cmd=""; $url=""; $iargs=""; $testpth=""; $regkey=""; $is_opt=""; $prescn=""; $_found="" + $title=""; $cmd=""; $url=""; $iargs=""; $testpth=""; $regkey=""; $is_opt=""; $prescn=""; $found="" ($task = $_).GetEnumerator() | % { Set-Variable -Scope Local -Name $_.key -Value ([regex]::Replace($_.value, "[#{](\w+)[}#]", { param($m) Invoke-Expression ("$"+$m.Groups[1].Value) })) } # skip optionals if ($is_opt -AND !(Invoke-Expression $is_opt)) { return } @@ -431,11 +432,11 @@ Set-ItemProperty -Path "$regkey" -Name "WindowsInstaller" -Value "0" } } # exe elseif ($file -match ".+.exe$") { - write-host -f DarkGray " → $file $iargs" + Write-Host -f DarkGray " → $file $iargs" if ($iargs) { Start-Process -Wait "$TEMP\$file" -ArgumentList $iargs } else { Start-Process -Wait "$TEMP\$file" @@ -450,15 +451,14 @@ #-- run through tasks Clear-Host -$host.ui.RawUI.BackgroundColor = ($bckgrnd = 'Black') Console-MaxHeight Display-Logo Check-Package $PYTHON = Check-PythonInstall $reuseCachedFiles, $optionalInstall = Ask-First Check-Prerequisites $tasks | Run-Task # <-- main loop: process all commands Any-Key Green