1217 lines
46 KiB
Python
Executable File
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()
|