Chicago95/Plus/Chicago95_Plus.py

1217 lines
46 KiB
Python
Executable File

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Tool to parse and generate new Chicago95 themes based on Microsoft Theme files
# Parses ANI and ICO files, installs Icons/Theme and fonts
# Auto changes your theme
# Thanks to png2svg for icon converter and Fierelier for recoloring script
# Requires:
# - Inkscape
# - Imagemagick
# - xcursorgen
# - Chicago95 icons, theme and cursors installed
# TODO:
# - Add sound support
# - Add better themeing color support
# - Add a gui
# - Better font identification
import sys
import os
import collections
import PIL.Image
import svgwrite
import pathlib
import shutil
import subprocess
import argparse
import logging
import configparser
import xml.etree.ElementTree as ET
from pathlib import Path
from PIL import Image
from configparser import ConfigParser
plus = '''
██████╗██╗ ██╗██╗ ██████╗ █████╗ ██████╗ ██████╗ █████╗ ███████╗
██╔════╝██║ ██║██║██╔════╝██╔══██╗██╔════╝ ██╔═══██╗██╔══██╗██╔════╝
██║ ███████║██║██║ ███████║██║ ███╗██║ ██║╚██████║███████╗
██║ ██╔══██║██║██║ ██╔══██║██║ ██║██║ ██║ ╚═══██║╚════██║
╚██████╗██║ ██║██║╚██████╗██║ ██║╚██████╔╝╚██████╔╝ █████╔╝███████║
╚═════╝╚═╝ ╚═╝╚═╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚════╝ ╚══════╝
___
.---. .'/ \
_________ _...._ | | / / \
\ |.' '-. | | | | |
\ .'```'. '. | | | | |
\ | \ \| | |/`. .'
| | | || | _ _ _ `.| |
| \ / . | | | ' / | .' | ||___|
| |\`'-.-' .' | | .' | .' | . | /|/___/
| | '-....-'` | | / | / | .'.'| |//.'.--.
.' '. '---'| `'. | .'.'.-' /| | |
'-----------' ' .'| '/.' \_.' \_\ /
`-' `--' `''--' '''
def hexToRGB(h):
return tuple(int(h.lstrip('#')[i:i+2], 16) for i in (0, 2, 4))
def rgbaToRGB(tup):
return (tup[0],tup[1],tup[2])
def convert_icon_files(icon_filename, output_file_name):
convert_path = subprocess.check_output(["which", "convert"]).strip()
logging.info("\t{:<21} {}".format(os.path.split(icon_filename)[1], os.path.split(output_file_name)[1]))
args = [
convert_path,
icon_filename,
output_file_name
]
subprocess.check_call(args)
if os.path.isfile(output_file_name[:-4]+"-0.png"):
shutil.move(output_file_name[:-4]+"-0.png", output_file_name[:-4]+".png")
def make_x11_cursors(icons_file, seq=False, rate=False, in_filename="tmp_file", out_folder="tmp_file.png"):
logging.debug("[x11_cursors]")
icon_num = 1
icon_file_names = []
for icon in icons_file:
if len(icon) == 0:
# Skip empty icons
continue
icon_type = int.from_bytes(icon[2:4],"little")
number_of_images = int.from_bytes(icon[4:6],"little")
if icon_type == 1:
ext = ".ico"
else:
ext = ".cur"
path_to_ani, ani_file_name = os.path.split(in_filename)
icon_name = os.path.splitext(ani_file_name)[0].replace(" ","_")
if seq:
num = "_{:02}".format(seq[icon_num-1])
else:
num = "_{:02}".format(icon_num)
if rate and type(rate) is list:
jif = "_{}".format(rate[icon_num-1])
elif rate:
jif = "_{}".format(rate)
else:
jif = "_{}".format(10)
icon_file = icon_name + jif + num + ext
logging.debug("\t{:<21} {}".format(icon_file, out_folder))
icon_num += 1
icon_file_names.append(out_folder+icon_file)
f = open(out_folder+icon_file,"wb")
f.write(icon)
f.close()
return icon_file_names
def build_cursors(cursor_folder):
#xcursors defs
xcursors_conf = {
"01_AngleNW.conf" : "ul_angle",
"02_AngleNW.conf" : "dnd-none",
"03_AngleNW.conf" : "dnd-move",
"04_AngleNE.conf" : "ur_angle",
"05_AngleNE.conf" : "ll_angle",
"06_AngleNW.conf" : "lr_angle",
"07_AppStarting.conf" : "left_ptr_watch",
"08_AppStarting.conf" : "08e8e1c95fe2fc01f976f1e063a24ccd",
"09_AppStarting.conf" : "3ecb610c1bf2410f44200f48c40d3599",
"10_Arrow.conf" : "arrow",
"11_Arrow.conf" : "draft_large",
"12_Arrow.conf" : "draft_small",
"13_Arrow.conf" : "left_ptr",
"14_Arrow.conf" : "right_ptr",
"15_Arrow.conf" : "top_left_arrow",
"16_ArrowRight.conf" : "right_ptr",
"17_BaseN.conf" : "base_arrow_up",
"18_BaseN.conf" : "based_arrow_up",
"19_BaseN.conf" : "base_arrow_down",
"20_BaseN.conf" : "based_arrow_down",
"21_Circle.conf" : "circle",
"22_Copy.conf" : "copy",
"23_Copy.conf" : "1081e37283d90000800003c07f3ef6bf",
"24_Copy.conf" : "6407b0e94181790501fd1e167b474872",
"25_Copy.conf" : "08ffe1cb5fe6fc01f906f1c063814ccf",
"26_Cross.conf" : "cross",
"27_Cross.conf" : "cross_reverse",
"28_Cross.conf" : "tcross",
"29_Crosshair.conf" : "crosshair",
"30_DND-ask.conf" : "dnd-ask",
"31_DND-copy.conf" : "dnd-copy",
"32_DND-link.conf" : "dnd-link",
"33_Hand.conf" : "hand",
"34_Hand.conf" : "hand1",
"35_Hand.conf" : "hand2",
"36_Hand.conf" : "e29285e634086352946a0e7090d73106",
"37_Handgrab.conf" : "HandGrab",
"38_Handgrab.conf" : "9d800788f1b08800ae810202380a0822",
"39_Handgrab.conf" : "5aca4d189052212118709018842178c0",
"40_Handsqueezed.conf" : "HandSqueezed",
"41_Handsqueezed.conf" : "208530c400c041818281048008011002",
"42_Handwriting.conf" : "pencil",
"43_Help.conf" : "question_arrow",
"44_Help.conf" : "d9ce0ab605698f320427677b458ad60b",
"45_Help.conf" : "5c6cd98b3f3ebcb1f9c7f1c204630408",
"46_IBeam.conf" : "xterm",
"47_IBeam.conf" : "ibeam",
"48_Link.conf" : "link",
"49_Link.conf" : "3085a0e285430894940527032f8b26df",
"50_Link.conf" : "640fb0e74195791501fd1ed57b41487f",
"51_Link.conf" : "0876e1c15ff2fc01f906f1c363074c0f",
"52_NO.conf" : "crossed_circle",
"53_NO.conf" : "dnd-none",
"54_NO.conf" : "03b6e0fcb3499374a867c041f52298f0",
"55_Move.conf" : "move",
"56_Move.conf" : "plus",
"57_Move.conf" : "4498f0e0c1937ffe01fd06f973665830",
"58_Move.conf" : "9081237383d90e509aa00f00170e968f",
"59_SizeAll.conf" : "fleur",
"60_AngleNE.conf" : "bottom_left_corner",
"61_AngleNE.conf" : "fd_double_arrow",
"62_AngleNE.conf" : "top_right_corner",
"63_AngleNE.conf" : "fcf1c3c7cd4491d801f1e1c78f100000",
"64_BaseN.conf" : "bottom_side",
"65_BaseN.conf" : "double_arrow",
"66_BaseN.conf" : "top_side",
"67_BaseN.conf" : "00008160000006810000408080010102",
"68_AngleNW.conf" : "bd_double_arrow",
"69_AngleNW.conf" : "bottom_right_corner",
"70_AngleNW.conf" : "top_left_corner",
"71_AngleNW.conf" : "c7088f0f3e6c8088236ef8e1e3e70000",
"72_SizeWE.conf" : "left_side",
"73_SizeWE.conf" : "right_side",
"74_SizeWE.conf" : "028006030e0e7ebffc7f7070c0600140",
"75_UpArrow.conf" : "center_ptr",
"76_UpArrow.conf" : "sb_up_arrow",
"77_DownArrow.conf" : "sb_down_arrow",
"78_LeftArrow.conf" : "sb_left_arrow",
"79_RightArrow.conf" : "sb_right_arrow",
"80_HDoubleArrow.conf" : "h_double_arrow",
"81_HDoubleArrow.conf" : "sb_h_double_arrow",
"82_HDoubleArrow.conf" : "14fef782d02440884392942c11205230",
"83_VDoubleArrow.conf" : "v_double_arrow",
"84_VDoubleArrow.conf" : "sb_v_double_arrow",
"85_VDoubleArrow.conf" : "2870a09082c103050810ffdffffe0204",
"86_Wait.conf" : "watch",
"87_X.conf" : "X_cursor",
"88_X.conf" : "X-cursor",
"89_ZoomIn.conf" : "zoomIn",
"90_ZoomIn.conf" : "f41c0e382c94c0958e07017e42b00462",
"91_ZoomOut.conf" : "zoomOut",
"92_ZoomOut.conf" : "f41c0e382c97c0938e07017e42800402"
}
logging.debug("\n\t[Building Cursors]")
xcursorgen_path = subprocess.check_output(["which", "xcursorgen"]).strip()
src_folder = cursor_folder + "src/"
build_folder = cursor_folder + "cursors/"
shutil.rmtree(build_folder)
os.mkdir(build_folder)
for gen in xcursors_conf:
conf_file = src_folder + gen[3:]
cursor_file_output = build_folder + xcursors_conf[gen]
logging.info("\t{:<21} {}".format(os.path.split(conf_file)[1], os.path.split(cursor_file_output)[1]))
args = [
xcursorgen_path,
"-p",
src_folder,
conf_file,
cursor_file_output
]
subprocess.check_call(args, stdout=subprocess.DEVNULL)
def parse_ani(file_name):
# convert an ani file to a list of bytearray icons
# input: ani file location/name
f = open(file_name,'rb')
ani_file = f.read()
f.close()
ani_bytes = bytearray(ani_file)
loc = ani_bytes.find("anih".encode()) + 8
anih = {
"size" : 0,
"num_frames" : 0,
"num_steps" : 0,
"width" : 0,
"height" : 0,
"bit_count" : 0,
"num_planes" : 0,
"jif_rate" : 0,
"flags" : 0
}
for i in anih:
anih[i] = int.from_bytes(ani_bytes[loc:loc+4],"little")
loc +=4
logging.debug("[ANI Header]")
for i in anih:
logging.debug("\t{:<30} {}".format(i,anih[i]))
rate_length = anih["jif_rate"]
if ani_bytes.find("rate".encode()) > -1:
rate_length = []
logging.debug("\n[Rate]")
loc = ani_bytes.find("rate".encode()) + 4
rate_size = int.from_bytes(ani_bytes[loc:loc+4],"little")
loc += 4
for i in range(anih["num_steps"]):
rate_length.append(int.from_bytes(ani_bytes[loc:loc+4],"little"))
loc += 4
logging.debug("\t{}".format(rate_length))
seq_length = False
if ani_bytes.find("seq ".encode()) > -1:
seq_length = []
logging.debug("\n[Seq]")
loc = ani_bytes.find("seq ".encode()) + 4
seq_size = int.from_bytes(ani_bytes[loc:loc+4],"little")
loc += 4
for i in range(anih["num_steps"]):
seq_length.append(int.from_bytes(ani_bytes[loc:loc+4],"little"))
loc += 4
logging.debug("\t{}".format(seq_length))
# Now find the icons
loc = ani_bytes.find("LIST".encode()) + 4
num_icons = int.from_bytes(ani_bytes[loc:loc+4],"little")
loc = ani_bytes.find("fram".encode()) + 8
icons = []
count = 0
## At first icon
for i in range(anih["num_steps"]):
icon_size = int.from_bytes(ani_bytes[loc:loc+4],"little")
icon = ani_bytes[loc+4:(loc+4)+icon_size]
icons.append(icon)
loc = loc + icon_size + 8
count = count + 1
return icons, seq_length, rate_length
def convert_icon(folder, ico_file_name, theme_folder, squaresize = 20, overlap = 2, tmp_file="./chicago95_tmp_file.svg", max_colors=25 ):
## Converts Icons to PNG
# Input:
# folder: svg file destination folder
# ico_file_name: theme icon file to be processed
# theme_folder: dict of the folder for case insensitivty
# squaresize: how big svg 'pixels'
# overlap: do the squares overlap
# tmp_file: tmp working file for inkscape
# max_colors = max colors to try and merge in svg
# Lots of code lifted from pixel2svg
path_to_icon, icon_file_name = os.path.split(ico_file_name)
icon_name, icon_ext = os.path.splitext(icon_file_name)
svg_name = icon_name+".svg"
if not os.path.exists(ico_file_name):
for lower_file in theme_folder:
if ico_file_name in lower_file:
ico_file_name = theme_folder[lower_file]
# Open the icon file
image = Image.open(ico_file_name)
image = image.convert("RGBA")
(width, height) = image.size
rgb_values = list(image.getdata())
rgb_values = list(image.getdata())
svgdoc = svgwrite.Drawing(filename = folder + svg_name,
size = ("{0}px".format(width * squaresize),
"{0}px".format(height * squaresize)))
# If --overlap is given, use a slight overlap to prevent inaccurate SVG rendering
rectangle_size = ("{0}px".format(squaresize + overlap),
"{0}px".format(squaresize + overlap))
rowcount = 0
while rowcount < height:
colcount = 0
while colcount < width:
rgb_tuple = rgb_values.pop(0)
# Omit transparent pixels
if rgb_tuple[3] > 0:
rectangle_posn = ("{0}px".format(colcount * squaresize),
"{0}px".format(rowcount * squaresize))
rectangle_fill = svgwrite.rgb(rgb_tuple[0], rgb_tuple[1], rgb_tuple[2])
alpha = rgb_tuple[3];
if alpha == 255:
svgdoc.add(svgdoc.rect(insert = rectangle_posn,
size = rectangle_size,
fill = rectangle_fill))
else:
svgdoc.add(svgdoc.rect(insert = rectangle_posn,
size = rectangle_size,
fill = rectangle_fill,
opacity = alpha/float(255)))
colcount = colcount + 1
rowcount = rowcount + 1
svgdoc.save()
convert_to_proper_svg_with_inkscape(tmp_file, svgdoc.filename)
SVG_NS = "http://www.w3.org/2000/svg"
svg = ET.parse(tmp_file)
rects = svg.findall('.//{%s}rect' % SVG_NS)
rgbs = {}
for rect in rects:
rect_id = rect.attrib['id']
rgb = rect.attrib['fill']
if rgb not in rgbs:
rgbs[rgb] = rect_id
if len(rgbs) < max_colors:
print("\tinkscape will open {} times to process {}. (use option -v for more information)".format(len(rgbs), svg_name))
else:
logging.info("{:<21} too many colors ({}>{}), skipping".format(svg_name, len(rgbs), max_colors))
count = 0
for rgb in rgbs:
count = count + 1
if len(rgbs) >= max_colors:
break
#if count % 10 == 0:
logging.info("{:<21} [{:<3} / {:<3} {:<5}] Converting {}".format(' ',count, len(rgbs),str(round((float(count)/float(len(rgbs))*100),0)), rgb ))
fix_with_inkscape( rgbs[rgb] , tmp_file )
shutil.move(tmp_file, svgdoc.filename)
return(svgdoc.filename)
def fix_with_inkscape(color, tmpfile):
inkscape_path = subprocess.check_output(["which", "inkscape"]).strip()
args = [
inkscape_path,
"--select="+color,
"--verb", "EditSelectSameFillColor",
"--verb", "SelectionCombine",
"--verb", "SelectionUnion",
"--verb", "FileSave",
"--verb", "FileQuit",
tmpfile
]
subprocess.check_call(args, stderr=subprocess.DEVNULL ,stdout=subprocess.DEVNULL)
def convert_to_png_with_inkscape(svg_in, size, png_out):
inkscape_path = subprocess.check_output(["which", "inkscape"]).strip()
size = str(size)
args = [
inkscape_path,
"--without-gui",
"-f", svg_in,
"--export-area-page",
"-w", size,
"-h", size,
"--export-png=" + png_out
]
subprocess.check_call(args, stdout=subprocess.DEVNULL)
def convert_to_proper_svg_with_inkscape(svg_out, svg_in):
inkscape_path = subprocess.check_output(["which", "inkscape"]).strip()
args = [
inkscape_path,
"-l", svg_out, svg_in
]
subprocess.check_call(args, stdout=subprocess.DEVNULL)
def xfconf_query(svg_out, svg_in):
inkscape_path = subprocess.check_output(["which", "inkscape"]).strip()
args = [
inkscape_path,
"-l", svg_out, svg_in
]
subprocess.check_call(args, stdout=subprocess.DEVNULL)
def get_file_name(config, section, key):
#input:
# config = parsed .theme file
# section = the section in the config file
# key = key in theme file
# Returns: filename
# To Do: incorporate icon number
reg_key = "Software\\Classes\\"
file_name = ''
icon_number = 0
if section in config and key in config[section]:
file_name = config[section][key].lower()
elif reg_key+section in config and key in config[reg_key+section]:
file_name = config[reg_key+section][key].lower()
else:
return False
if file_name == '':
# The key was here but its empty
return False
if "%windir%" in file_name:
# we dont bother changing system icons
return False
if "%ThemeDir%".lower() in file_name:
file_name = file_name.replace("%ThemeDir%".lower(),'')
if "," in file_name:
file_name, icon_number = file_name.split(",")
file_name = file_name.split("\\")[-1]
return file_name
def null_string(data):
data = bytearray(data)
return data[:data.find(0)].decode('cp1252')
def parse_IconMetrics(IconMetrics):
font_weight = {
"0":"FW_DONTCARE",
"100":"FW_THIN",
"200":"FW_EXTRALIGHT",
"200":"FW_ULTRALIGHT",
"300":"FW_LIGHT",
"400":"FW_NORMAL",
"400":"FW_REGULAR",
"500":"FW_MEDIUM",
"600":"FW_SEMIBOLD",
"600":"FW_DEMIBOLD",
"700":"FW_BOLD",
"800":"FW_EXTRABOLD",
"800":"FW_ULTRABOLD",
"900":"FW_HEAVY",
"900":"FW_BLACK"
}
x = []
for i in IconMetrics.split():
x.append(int(i))
iconmetrics = {
"cbSize" : int.from_bytes(x[0:4],"little"),
"iHorzSpacing" : int.from_bytes(x[4:8],"little"),
"iVertSpacing" : int.from_bytes(x[8:12],"little"),
"iTitleWrap" : int.from_bytes(x[12:16],"little")
}
lfFont = {
"Name:" : "lfFont",
"lfHeight" : int.from_bytes(x[16:20],"little"),
"lfWidth" : int.from_bytes(x[20:24],"little"),
"lfEscapement" : int.from_bytes(x[24:28],"little"),
"lfOrientation" : int.from_bytes(x[28:32],"little"),
"lfWeight" : font_weight[str(int.from_bytes(x[32:36],"little"))],
"lfItalic" : x[37],
"lfUnderline" : x[38],
"lfStrikeOut" : x[39],
"lfCharSet" : x[40],
"lfOutPrecision" : x[41],
"lfClipPrecision" : x[42],
"lfQuality" : x[43],
"lfPitchAndFamily" : x[44],
"lfFaceName[32]" : null_string(x[44:44+32])
}
logging.info("[iconmetrics]")
for i in iconmetrics:
logging.info("\t{:<21} {}".format(i, iconmetrics[i]))
logging.info("[lfFont]")
for i in lfFont:
logging.info("\t{:<21} {}".format(i, lfFont[i]))
def parse_NONCLIENTMETRICS(NONCLIENTMETRICSA):
font_weight = {
"0":"FW_DONTCARE",
"100":"FW_THIN",
"200":"FW_EXTRALIGHT",
"200":"FW_ULTRALIGHT",
"300":"FW_LIGHT",
"400":"FW_NORMAL",
"400":"FW_REGULAR",
"500":"FW_MEDIUM",
"600":"FW_SEMIBOLD",
"600":"FW_DEMIBOLD",
"700":"FW_BOLD",
"800":"FW_EXTRABOLD",
"800":"FW_ULTRABOLD",
"900":"FW_HEAVY",
"900":"FW_BLACK"
}
x = []
for i in NONCLIENTMETRICSA.split():
x.append(int(i))
nonclientmetrics = {
"cbSize" : int.from_bytes(x[0:4],"little"),
"iBorderWidth" : int.from_bytes(x[4:8],"little"),
"iScrollWidth" : int.from_bytes(x[8:12],"little"),
"iScrollHeight" : int.from_bytes(x[12:16],"little"),
"iCaptionWidth" : int.from_bytes(x[16:20],"little"),
"iCaptionHeight": int.from_bytes(x[20:24],"little")
}
lfcaptionfont = {
"Name:" : "lfcaptionfont",
"lfHeight" : int.from_bytes(x[24:28],"little"),
"lfWidth" : int.from_bytes(x[28:32],"little"),
"lfEscapement" : int.from_bytes(x[32:36],"little"),
"lfOrientation" : int.from_bytes(x[36:40],"little"),
"lfWeight" : font_weight[str(int.from_bytes(x[40:44],"little"))],
"lfItalic" : x[44],
"lfUnderline" : x[45],
"lfStrikeOut" : x[46],
"lfCharSet" : x[47],
"lfOutPrecision" : x[48],
"lfClipPrecision" : x[49],
"lfQuality" : x[50],
"lfPitchAndFamily" : x[51],
"lfFaceName[32]" : null_string(x[52:52+32])
}
nonclientmetrics["iSmCaptionWidth"] = int.from_bytes(x[84:88],"little")
nonclientmetrics["iSmCaptionHeight"] = int.from_bytes(x[88:92],"little")
lfSmCaptionFont = {
"Name" : "lfSmCaptionFont",
"lfHeight" : int.from_bytes(x[92:96],"little"),
"lfWidth" : int.from_bytes(x[96:100],"little"),
"lfEscapement" : int.from_bytes(x[100:104],"little"),
"lfOrientation" : int.from_bytes(x[104:108],"little"),
"lfWeight" : font_weight[str(int.from_bytes(x[108:112],"little"))],
"lfItalic" : x[112],
"lfUnderline" : x[113],
"lfStrikeOut" : x[114],
"lfCharSet" : x[115],
"lfOutPrecision" : x[116],
"lfClipPrecision" : x[117],
"lfQuality" : x[118],
"lfPitchAndFamily" : x[119],
"lfFaceName[32]" : null_string(x[120:120+32])
}
nonclientmetrics["iMenuWidth"] = int.from_bytes(x[152:156],"little")
nonclientmetrics["iMenuHeight"] = int.from_bytes(x[156:160],"little")
lfMenuFont = {
"Name" : "lfMenuFont",
"lfHeight" : int.from_bytes(x[160:164],"little"),
"lfWidth" : int.from_bytes(x[164:168],"little"),
"lfEscapement" : int.from_bytes(x[168:172],"little"),
"lfOrientation" : int.from_bytes(x[172:176],"little"),
"lfWeight" : font_weight[str(int.from_bytes(x[176:180],"little"))],
"lfItalic" : x[180],
"lfUnderline" : x[181],
"lfStrikeOut" : x[182],
"lfCharSet" : x[183],
"lfOutPrecision" : x[184],
"lfClipPrecision" : x[185],
"lfQuality" : x[186],
"lfPitchAndFamily" : x[187],
"lfFaceName[32]" : null_string(x[188:188+32])
}
lfStatusFont = {
"Name" : "lfStatusFont",
"lfHeight" : int.from_bytes(x[220:224],"little"),
"lfWidth" : int.from_bytes(x[224:228],"little"),
"lfEscapement" : int.from_bytes(x[228:232],"little"),
"lfOrientation" : int.from_bytes(x[232:236],"little"),
"lfWeight" : font_weight[str(int.from_bytes(x[236:240],"little"))],
"lfItalic" : x[240],
"lfUnderline" : x[241],
"lfStrikeOut" : x[242],
"lfCharSet" : x[243],
"lfOutPrecision" : x[244],
"lfClipPrecision" : x[245],
"lfQuality" : x[246],
"lfPitchAndFamily" : x[247],
"lfFaceName[32]" : null_string(x[248:248+32])
}
lfMessageFont = {
"Name" : "lfMessageFont",
"lfHeight" : int.from_bytes(x[280:284],"little"),
"lfWidth" : int.from_bytes(x[284:288],"little"),
"lfEscapement" : int.from_bytes(x[288:292],"little"),
"lfOrientation" : int.from_bytes(x[292:296],"little"),
"lfWeight" : font_weight[str(int.from_bytes(x[296:300],"little"))],
"lfItalic" : x[300],
"lfUnderline" : x[301],
"lfStrikeOut" : x[302],
"lfCharSet" : x[303],
"lfOutPrecision" : x[304],
"lfClipPrecision" : x[305],
"lfQuality" : x[306],
"lfPitchAndFamily" : x[307],
"lfFaceName[32]" : null_string(x[308:308+32])
}
logging.info("[nonclientmetrics]")
for i in nonclientmetrics:
logging.info("\t{:<21} {}".format(i, nonclientmetrics[i]))
logging.info("[lfcaptionfont]")
for i in lfcaptionfont:
logging.info("\t{:<21} {}".format(i, lfcaptionfont[i]))
logging.info("[lfSmCaptionFont]")
for i in lfSmCaptionFont:
logging.info("\t{:<21} {}".format(i, lfSmCaptionFont[i]))
logging.info("[lfMenuFont]")
for i in lfMenuFont:
logging.info("\t{:<21} {}".format(i, lfMenuFont[i]))
logging.info("[lfStatusFont]")
for i in lfStatusFont:
logging.info("\t{:<21} {}".format(i, lfStatusFont[i]))
logging.info("[lfMessageFont]")
for i in lfMessageFont:
logging.info("\t{:<21} {}".format(i, lfMessageFont[i]))
return lfcaptionfont["lfFaceName[32]"], lfcaptionfont["lfWeight"]
def main():
print(plus)
print("Microsoft Theme file parser")
desc = '''Chicago95 Plus! is a python script that can parse Windows 95/98/ME/XP theme files and create new Chicago95 themes. Chicago95 Plus! supports Icons, Cursors, Fonts, Wallpapers and Theme colors! Use this against themes you can find on ThemeWorld or any site that have Windows Plus! Themes!\nThis script can be called from any folder, execute it and provide it the path to a theme file (e.g. %(prog)s /home/bgates/Wicked/Wicked.theme) and your new theme will be created!'''
arg_parser = argparse.ArgumentParser(description=desc,
usage='%(prog)s [options] MS_Theme_File',
epilog="Part of the Chicago95 theme project",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
arg_parser.add_argument('-d', '--debug', help="Print lots of debugging statements", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.WARNING)
arg_parser.add_argument('-v', '--verbose', help="Be verbose", action="store_const", dest="loglevel", const=logging.INFO)
arg_parser.add_argument("--noinstall", help="Do not place folders nor change theme", action="store_true")
arg_parser.add_argument("--info", help="Does nothing except give information about the theme file", action="store_true")
arg_parser.add_argument('-c', '--colors', help='How many colors before skipping Inkscape fix/merge for SVGs. Set to 1 to speed up conversion. WARNING: This may result in transparent icons!', default=32, type=int)
arg_parser.add_argument('-o', '--overlap', help='Pixel overlap for SVG icons', default=1, type=int)
arg_parser.add_argument('-s', '--squaresize', help='Square size for SVG icons', default=20, type=int)
arg_parser.add_argument("--installdir", help="Folder to create new theme in, default is current working directory", default=os.getcwd())
arg_parser.add_argument("theme_file", help="Microsoft Windows 95/98/ME .theme file")
args = arg_parser.parse_args()
if args.info:
args.loglevel = logging.INFO
logging.basicConfig(level=args.loglevel, format="\t%(message)s")
error = False
if not os.path.exists(str(Path.home())+"/.icons/Chicago95") and not os.path.exists(str(Path.home())+"/.icons/Chicago95_tux"):
print("\nERROR: Either the Chicago95 or Chicago95_tux icon theme must be installed to {} to use this script\n".format(str(Path.home())+"/.icons/"))
error = True
if not os.path.exists(str(Path.home())+"/.icons/Chicago95_Cursor_Black"):
print("\nERROR: The Chicago95 cursor Chicago95_Cursor_Black must be installed to {} to use this script\n".format(str(Path.home())+"/.icons/"))
error = True
if not os.path.exists(str(Path.home())+"/.themes/Chicago95"):
print("\nERROR: The Chicago95 theme must be installed to {} to use this script\n".format(str(Path.home())+"/.themes/"))
error = True
try:
inkscape_path = subprocess.check_output(["which", "inkscape"]).strip()
except subprocess.CalledProcessError:
print("\nERROR: You need inkscape installed to use this script.\n")
error = True
try:
convert_path = subprocess.check_output(["which", "convert"]).strip()
except subprocess.CalledProcessError:
print("\nERROR: You need imagemagick installed to use this script.\n")
error = True
try:
convert_path = subprocess.check_output(["which", "xcursorgen"]).strip()
except subprocess.CalledProcessError:
print("\nERROR: You need xcursorgen installed to use this script.\n")
error = True
if error:
sys.exit(1)
# Get the file name and extions in to useable names
theme_file = args.theme_file
path_to_theme, theme_file_name = os.path.split(theme_file)
if len(path_to_theme) != 0:
path_to_theme = path_to_theme + "/"
else:
path_to_theme = "./"
theme_name_spaces, theme_ext = os.path.splitext(theme_file_name)
index_theme_name = theme_name_spaces + "(Chicago 95 Variant)" # For various Index.theme files
theme_name = theme_name_spaces.capitalize().replace(" ", "_")
if args.installdir[-1] != "/":
new_theme_folder = args.installdir + "/" + theme_name + "_Chicago95/"
else:
new_theme_folder = args.installdir + theme_name + "_Chicago95/"
print("[Parser] Parsing Theme File:", theme_file)
#config = ConfigParser(dict_type=CaseInsensitiveDict,interpolation=None)
config = ConfigParser(interpolation=None)
config.read(theme_file)
# Themes have a weird structure we use thise dict to remove case but keep the filename
theme_files = {}
for root, dirs, files in os.walk(path_to_theme, topdown=False):
for name in files:
theme_files[os.path.join(root, name).lower()] = os.path.join(root, name)
## Get the icons
print("[Parser] Parsing Icons")
icons = {}
icons["my_computer"] = get_file_name(config,"CLSID\\{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\DefaultIcon","DefaultValue")
if get_file_name(config,"CLSID\\{450D8FBA-AD25-11D0-98A8-0800361B1103}\\DefaultIcon","DefaultValue"):
icons["my_documents"] = get_file_name(config,"CLSID\\{450D8FBA-AD25-11D0-98A8-0800361B1103}\\DefaultIcon","DefaultValue")
elif get_file_name(config,"CLSID\\{59031A47-3F72-44A7-89C5-5595FE6B30EE}\\DefaultIcon","DefaultValue"):
icons["my_documents"] = get_file_name(config,"CLSID\\{59031A47-3F72-44A7-89C5-5595FE6B30EE}\\DefaultIcon","DefaultValue")
else:
icons["my_documents"] = False
if get_file_name(config,"CLSID\\{208D2C60-3AEA-1069-A2D7-08002B30309D}\\DefaultIcon","DefaultValue"):
icons["network_neighborhood"] = get_file_name(config,"CLSID\\{208D2C60-3AEA-1069-A2D7-08002B30309D}\\DefaultIcon","DefaultValue")
elif get_file_name(config,"CLSID\\{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}\\DefaultIcon","DefaultValue"):
icons["network_neighborhood"] = get_file_name(config,"CLSID\\{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}\\DefaultIcon","DefaultValue")
else:
icons["network_neighborhood"] = False
icons["recycle_bin_full"] = get_file_name(config,"CLSID\\{645FF040-5081-101B-9F08-00AA002F954E}\\DefaultIcon","Full")
icons["recycle_bin_empty"] = get_file_name(config,"CLSID\\{645FF040-5081-101B-9F08-00AA002F954E}\\DefaultIcon","Empty")
for i in icons:
logging.info("{:<21} {}".format(i,icons[i]))
print("[Parser] Parsing Colors")
colors = {}
if "Control Panel\Colors" in config:
for color_name in config["Control Panel\Colors"]:
r, g, b = config["Control Panel\Colors"][color_name].split()
colors[color_name] = '#{:02x}{:02x}{:02x}'.format(int(r),int(g),int(b))
logging.info("{:<21} {:<7} ({:<15})".format(color_name, colors[color_name], config["Control Panel\Colors"][color_name]))
print("[Parser] Parsing Cursors")
cursors = {}
if "Control Panel\Cursors" in config:
for cursor_name in config["Control Panel\Cursors"]:
cursors[cursor_name] = get_file_name(config,"Control Panel\Cursors",cursor_name)
logging.info("{:<21} {}".format(cursor_name, cursors[cursor_name]))
## Get Sound files
print("[Parser] Parsing Sounds")
sound_names = [
"AppEvents\\Schemes\\Apps\\.Default\\AppGPFault\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\Close\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\.Default\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\MailBeep\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\Maximize\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\MenuCommand\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\MenuPopup\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\Minimize\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\Open\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\RestoreDown\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\RestoreUp\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\RingIn\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\Ringout\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\SystemAsterisk\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\SystemDefault\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\SystemExclamation\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\SystemExit\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\SystemHand\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\SystemQuestion\\.Current",
"AppEvents\\Schemes\\Apps\\.Default\\SystemStart\\.Current",
"AppEvents\\Schemes\\Apps\\Explorer\\EmptyRecycleBin\\.Current"
]
sounds = {}
for i in sound_names:
sound_name = i.split("\\")[-2]
if get_file_name(config,i,"DefaultValue"):
wav_file = get_file_name(config,i,"DefaultValue")
sounds[sound_name] = wav_file
logging.info("{:<21} {}".format(sound_name, wav_file))
#Screen Saver
screensaver = False
if "boot" in config:
print("[Parser] Parsing Boot (screensaver)")
screensaver = get_file_name(config,"boot","SCRNSAVE.EXE")
logging.info("{:<21} {}".format("screensaver",screensaver))
## Get the wallpaper
wallpaper = False
if "Control Panel\Desktop" in config:
print("[Parser] Parsing Wallpaper")
wallpaper = get_file_name(config,"Control Panel\Desktop","Wallpaper")
logging.info("{:<21} {}".format("wallpaper",wallpaper))
#NonClientMetrics/IconMetrics
print("[Parser] Parsing NonClientMetrics")
NonclientMetrics = config["Metrics"]["nonclientmetrics"]
(lfcaptionfont, weight) = parse_NONCLIENTMETRICS(NonclientMetrics)
print("[Parser] Parsing IconMetrics")
IconMetrics = config["Metrics"]["iconmetrics"]
parse_IconMetrics(IconMetrics)
print("[Parser] Parsing Complete!\n\n", "=" * 80)
if args.info:
print("\nThank you for using Chicago95 Plus!\n")
sys.exit()
print("\n[Theme Builder]\tMaking folders for theme: {}".format(theme_name))
chicago95_icons_folder = str(Path.home())+"/.icons/Chicago95" if os.path.exists(str(Path.home())+"/.icons/Chicago95") else str(Path.home())+"/.icons/Chicago95_tux"
chicago95_cursors_folder = str(Path.home())+"/.icons/Chicago95_Cursor_Black"
chicago95_theme_folder = str(Path.home())+"/.themes/Chicago95"
folder_names = {
"root" : new_theme_folder,
"icons" : new_theme_folder + theme_name + "_Icons/",
"theme" : new_theme_folder + theme_name + "_Theme/",
"cursors" : new_theme_folder + theme_name + "_Cursors/",
"sounds" : new_theme_folder + theme_name + "_Sounds/"
}
for i in folder_names:
logging.info("{:<21} {}".format(i,folder_names[i]))
shutil.rmtree(folder_names[i], ignore_errors=True)
if i == "icons":
shutil.copytree(chicago95_icons_folder,folder_names[i],symlinks=True,ignore_dangling_symlinks=True)
elif i == "cursors":
shutil.copytree(chicago95_cursors_folder,folder_names[i],symlinks=True,ignore_dangling_symlinks=True)
#elif i == "theme":
# shutil.copytree(chicago95_theme_folder,folder_names[i],symlinks=True,ignore_dangling_symlinks=True)
else:
os.mkdir(folder_names[i])
print("[Icons]\t\tCreating new icons in {}".format(folder_names['icons']))
icon_sizes = [16,22,24,32,48]
svg_file_names = {}
png_file_names = {
"my_computer" : "user-home.png",
"my_documents" : "folder-documents.png",
"network_neighborhood" : "network-server.png",
"recycle_bin_empty" : "user-trash.png",
"recycle_bin_full" : "user-trash-full.png"
}
for i in png_file_names:
svg_file_names[i] = png_file_names[i].replace(".png",".svg")
for i in icons:
if not icons[i]: #Skip ithe icon if it doesn't exist in the theme
continue
svg_icon_file = convert_icon(folder_names['icons'],icons[i], theme_files, squaresize = args.squaresize, overlap = args.overlap, max_colors=args.colors)
for size in icon_sizes:
if size <= 32 and i == "documents_ico":
continue
sized_target = folder_names['icons']+"places/"+str(size)+"/"+png_file_names[i]
logging.info("Size: {:<21} {} {}".format(size, svg_icon_file, sized_target))
convert_to_png_with_inkscape( svg_icon_file, size, sized_target)
scaled_target = folder_names['icons']+"places/scalable/"+svg_file_names[i]
shutil.copy(svg_icon_file, scaled_target)
# Now replace Icons
icon_theme_config = configparser.RawConfigParser(interpolation=None)
icon_theme_config.optionxform = str
icon_theme_config.read(folder_names['icons']+"/index.theme")
icon_theme_config.set("Icon Theme","Name",index_theme_name)
with open(folder_names['icons']+"/index.theme", 'w') as configfile:
icon_theme_config.write(configfile, space_around_delimiters=False)
# Cursors
print("[Cursors]\tGenerating New cursors in {}".format(folder_names['cursors']))
pointers = {
"arrow" : "Arrow",
"help" : "Help",
"appstarting" : "AppStarting",
"wait" : "Wait",
"nwpen" : "Handwriting",
"no" : "NO",
"sizens" : "BaseN",
"sizewe" : "SizeWE",
"crosshair" : "Crosshair",
"ibeam" : "IBeam",
"sizenwse" : "AngleNE",
"sizenesw" : "AngleNW",
"sizeall" : "SizeAll",
"uparrow" : "UpArrow"
}
cursor_src_folder = folder_names['cursors'] + "src/"
for current_cursor in pointers:
if current_cursor not in cursors or not cursors[current_cursor]:
continue
if not os.path.exists(path_to_theme+cursors[current_cursor]):
for lower_file in theme_files:
if cursors[current_cursor] in lower_file:
theme_cursor_file_name = theme_files[lower_file]
else:
theme_cursor_file_name = cursors[current_cursor]
x11_cursor_file_name = cursor_src_folder+pointers[current_cursor]+".png"
os.remove(x11_cursor_file_name)
logging.info("{:<21} {}".format(os.path.split(theme_cursor_file_name)[1],os.path.split(x11_cursor_file_name)[1]))
if os.path.splitext(cursors[current_cursor])[1] in ".ani":
icon_cur_files, seq, rate = parse_ani(theme_cursor_file_name)
icon_file_names = make_x11_cursors(icon_cur_files, seq, rate, theme_cursor_file_name, cursor_src_folder)
with open(cursor_src_folder+pointers[current_cursor]+".conf") as f:
(g1, g2, g3, cursor_n) = f.readline().strip().split(" ")
write_conf = open(cursor_src_folder+pointers[current_cursor]+".conf", 'w')
logging.debug("\n[Convert]")
for icon_cur in icon_file_names:
x11_cursor_file_name = cursor_src_folder+pointers[current_cursor]+".png"
path_to_src, png_file_name = os.path.split(x11_cursor_file_name)
cur_name = os.path.splitext(png_file_name)[0]
path_to_icon, icon_file_name = os.path.split(icon_cur)
orig_icon_name = os.path.splitext(icon_file_name)[0]
seq = orig_icon_name.split("_")[-1]
rate = orig_icon_name.split("_")[-2]
x11_cursor_file_name = x11_cursor_file_name[:-4] + "_{}_{}.png".format(rate, seq)
convert_icon_files(icon_cur,x11_cursor_file_name)
# Xcursorgen conf file format: <size> <xhot> <yhot> <filename> <ms-delay>
# Ani to png file format: <filename> <jiffie> <sequence>
cursor_conf_string = "{} {} {} {} {}\n".format(g1, g2, g3, os.path.split(x11_cursor_file_name)[1],int(rate) * 17 )
write_conf.write(cursor_conf_string)
write_conf.close()
else:
convert_icon_files(theme_cursor_file_name,x11_cursor_file_name)
# Cursors are all done now we need to generate X11 cursors with xcursorgen
build_cursors(folder_names['cursors'])
cur_theme_config = configparser.RawConfigParser(interpolation=None)
cur_theme_config.optionxform = str
cur_theme_config.read(folder_names['cursors']+"index.theme")
cur_theme_config.set("Icon Theme","Name",index_theme_name)
with open(folder_names['cursors']+"index.theme", 'w') as configfile:
cur_theme_config.write(configfile, space_around_delimiters=False)
print("[Colors]\tChanging theme colors in {}".format(folder_names['theme']))
original_theme_folder = os.path.expanduser("~/.themes/Chicago95")
target_theme_folder = folder_names["theme"]
remapColors = {
"#000080": colors['activetitle'], #Active Window and Text Highlight - RED
"#dfdfdf": colors['menu'], #highlight? - Does Nothing - Yellow
"#c0c0c0": colors['menu'], #main window outline/buttons/bars color and inactive text - Green
"#ffffff": colors['window'], #main window color inner and main text color - Blue
"#808080": colors['inactivetitle'], #shadow window color (Inactive?) - turqoise
"#000000": colors['windowtext'], #Inactive window text color - Purple
}
#Make sure none of them overlap
for x in remapColors:
if remapColors[x] in remapColors:
if x.lower() == remapColors[x].lower():
continue
elif remapColors[x][-1].lower() == 'f':
remapColors[x] = remapColors[x][:6] + 'e'
else:
remapColors[x] = remapColors[x][:6] + str(int(remapColors[x][-1]) + 1)
for i in remapColors:
logging.info("\tCurrent: {:<12} New Color: {}".format(i,remapColors[i]))
if (os.path.isdir(target_theme_folder)):
shutil.rmtree(target_theme_folder)
os.makedirs(target_theme_folder)
for root,dirs,files in os.walk(original_theme_folder):
for dir in dirs:
fpath = os.path.join(root,dir)
nfpath = fpath.replace(original_theme_folder,target_theme_folder)
if not (os.path.isdir(nfpath)):
os.makedirs(nfpath)
for file in files:
fpath = os.path.join(root,file)
nfpath = fpath.replace(original_theme_folder,target_theme_folder)
lpath = fpath.replace(original_theme_folder + "/","")
ext = os.path.splitext(fpath)[1].lower()
if (ext == ".css") or (ext == ".scss") or (ext == ".xpm") or (ext == ".svg") or (ext == ".rc")\
or (lpath == "gtk-2.0/gtkrc") or (lpath == "xfwm4/hidpi/themerc") or (lpath == "xfwm4/themerc"):
fileh = open(fpath,"r")
nfileh = open(nfpath,"w")
for line in fileh:
for color in remapColors:
if color.lower() == remapColors[color].lower():
continue
if color.upper() in line:
logging.debug("\t{:<30} from: {} to: {}".format( os.path.split(fpath)[1],color,remapColors[color]))
line = line.replace(color.upper(),remapColors[color].upper())
elif color.lower() in line:
logging.debug("\t{:<30} from: {} to: {}".format( os.path.split(fpath)[1],color,remapColors[color]))
line = line.replace(color.lower(),remapColors[color].lower())
nfileh.write(line)
fileh.close()
nfileh.close()
if (ext == ".png"):
img = Image.open(fpath)
img = img.convert("RGBA")
pixels = img.load()
width, height = img.size
for y in range(height):
for x in range(width):
pixel = pixels[x,y]
for color in remapColors:
if color.lower() == remapColors[color].lower():
continue
colorV = remapColors[color]
rgbColor = hexToRGB(color)
rgbColorV = hexToRGB(colorV)
if (rgbaToRGB(pixel) == rgbColor):
logging.debug("\t{:<30} from: {} to: {}".format( os.path.split(fpath)[1],color,remapColors[color]))
pixels[x,y] = (rgbColorV[0],rgbColorV[1],rgbColorV[2],pixel[3])
break
img.save(nfpath)
img.close()
if not (os.path.isfile(nfpath)):
shutil.copy(fpath,nfpath)
cur_theme_config = configparser.RawConfigParser(interpolation=None)
cur_theme_config.optionxform = str
cur_theme_config.read(folder_names['theme']+"index.theme")
cur_theme_config.set("Desktop Entry","Name",index_theme_name)
cur_theme_config.set("X-GNOME-Metatheme","GtkTheme",index_theme_name)
cur_theme_config.set("X-GNOME-Metatheme","MetacityTheme",index_theme_name)
cur_theme_config.set("X-GNOME-Metatheme","IconTheme",index_theme_name)
cur_theme_config.set("X-GNOME-Metatheme","CursorTheme",index_theme_name)
with open(folder_names['theme']+"index.theme", 'w') as configfile:
cur_theme_config.write(configfile, space_around_delimiters=False)
if wallpaper:
print("[Desktop]\tWallpaper: {}".format(wallpaper))
theme_wallpaper = False
if not os.path.exists(path_to_theme+wallpaper):
for lower_file in theme_files:
if wallpaper.lower() in lower_file:
theme_wallpaper = theme_files[lower_file]
else:
theme_wallpaper = wallpaper
if theme_wallpaper:
logging.info("{}".format(os.path.split(wallpaper)[1]))
shutil.copy(theme_wallpaper,folder_names['root'])
theme_wallpaper = folder_names['root'] + os.path.split(theme_wallpaper)[1]
else:
logging.info("Could not copy wallpaper {} to {}".format(path_to_theme+wallpaper,theme_screensaver))
if screensaver:
print("[Screensaver]\t{0} copied to {1} (usage: wine {0} /s)".format(path_to_theme+screensaver, folder_names['root']))
theme_screensaver = False
if not os.path.exists(path_to_theme+screensaver):
for lower_file in theme_files:
if screensaver.lower() in lower_file:
theme_screensaver = theme_files[lower_file]
else:
theme_screensaver = screensaver
if theme_screensaver:
logging.info("{}".format(os.path.split(theme_screensaver)[1]))
shutil.copy(theme_screensaver,folder_names['root'])
theme_screensaver = folder_names['root'] + os.path.split(theme_screensaver)[1]
else:
logging.info("Could not copy screensaver {} to {}".format(path_to_theme+screensaver,theme_screensaver))
print("[Fonts]\t\tGetting any font files")
fonts = []
for files in theme_files:
if ".ttf" in files:
logging.info("{}".format(os.path.split(files)[1]))
shutil.copy(theme_files[files],folder_names['root']+os.path.split(files)[1])
fonts.append(folder_names['root']+os.path.split(files)[1])
print("[Colors]\tCopying Sounds to {}".format(folder_names['sounds']))
for sound in sounds:
if not sound:
continue
if not os.path.exists(path_to_theme+sounds[sound]):
for lower_file in theme_files:
if sounds[sound] in lower_file:
theme_sound_file_name = theme_files[lower_file]
else:
theme_sound_file_name = sounds[sound]
new_sound_file_name = "{}_{}".format(sound,sounds[sound].replace(" ","_"))
logging.info("Sound: {} copying {} to {}".format(sound,theme_sound_file_name,folder_names['sounds']+new_sound_file_name))
shutil.copy(theme_sound_file_name,folder_names['sounds']+new_sound_file_name)
print("[Theme Builder]\tCompleted!\n")
print("-" * 80)
install_icons_dir = str(Path.home())+"/.icons/"+folder_names['icons'].split("/")[-2]
install_cursors_dir = str(Path.home())+"/.icons/"+folder_names['cursors'].split("/")[-2]
install_themes_dir = str(Path.home())+"/.themes/"+folder_names['theme'].split("/")[-2]
if not args.noinstall:
print("\n[Installing]")
shutil.rmtree(install_icons_dir, ignore_errors=True)
shutil.rmtree(install_cursors_dir, ignore_errors=True)
shutil.rmtree(install_themes_dir, ignore_errors=True)
logging.info("\tCopying {} to {}".format(folder_names['icons'], install_icons_dir))
shutil.copytree(folder_names['icons'],install_icons_dir,symlinks=True,ignore_dangling_symlinks=True)
logging.info("\tCopying {} to {}".format(folder_names['cursors'], install_cursors_dir))
shutil.copytree(folder_names['cursors'],install_cursors_dir,symlinks=True,ignore_dangling_symlinks=True)
logging.info("\tCopying {} to {}".format(folder_names['theme'], install_themes_dir))
shutil.copytree(folder_names['theme'],install_themes_dir,symlinks=True,ignore_dangling_symlinks=True)
logging.info("\tCopying {} to {}".format(theme_wallpaper, str(Path.home())+"/Pictures/" ))
shutil.copy(theme_wallpaper, str(Path.home())+"/Pictures/")
for i in fonts:
if not os.path.exists( str(Path.home())+"/.fonts/"+os.path.split(i)[1]):
logging.info("\tCopying {} to {}".format(i, str(Path.home())+"/.fonts/" ))
shutil.copy(i, str(Path.home())+"/.fonts/")
font = lfcaptionfont
if font == "MS Sans Serif":
font = "Sans Serif"
font = font + " " + weight[3:].lower().capitalize() + " 8"
print("[Updating XFCE]")
xfconf_item = [
["xsettings","/Gtk/CursorThemeName", theme_name+"_Cursors", "Cursors" ],
["xsettings","/Net/IconThemeName", theme_name+"_Icons", "Icons" ],
["xsettings","/Net/ThemeName", theme_name+"_Theme", "Theme" ],
["xfwm4","/general/theme", theme_name+"_Theme", "Windows Manager" ],
["xfwm4","/general/title_font", font, "Font" ]
]
xfconf_query_path = subprocess.check_output(["which", "xfconf-query"]).strip()
for i in xfconf_item:
logging.info("\tChanging {} to {}".format(i[3], i[2]))
args = [
xfconf_query_path,
"-c", i[0],
"-p", i[1],
"-s", i[2]
]
logging.debug(args)
subprocess.check_call(args, stdout=subprocess.DEVNULL)
print("Done!")
print("\n{}\n".format("=-" * 40))
print("The Microsoft theme {} is installed!\n".format(theme_name))
else:
font = lfcaptionfont
print("\n[Skipped Install]\n\tTo install the theme {} run the following commands:\n".format(theme_name))
print("\tcp -rav {} {}".format(folder_names['icons'], install_icons_dir))
print("\tcp -rav {} {}".format(folder_names['cursors'], install_cursors_dir))
print("\tcp -rav {} {}".format(folder_names['theme'], install_themes_dir))
print("\tcp -rav {} {}".format(theme_wallpaper, str(Path.home())+"/Pictures/" ))
for i in fonts:
print("\tcp {} {}".format(i, str(Path.home())+"/.fonts/" ))
print("\n\tThen change the icons, cursors, appearance and window manager to {}.\n\tThe theme font for window titles is: {}\n".format(theme_name, font))
print("Thank you for using Chicago95 Plus!\n")
main()