167c4c8fd888e44e00b5a6fbab29af762af5d086
[jalview.git] / utils / macos_dmg / jalview_customise_dsstore.py
1 #!/usr/bin/env python3
2 # Adapted from custom_dsstore.py by the Jalview team 2024
3 # Copyright (c) 2013-2017 The Bitcoin Core developers
4 # Distributed under the MIT software license, see the accompanying
5 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
6 import ds_store
7 import pprint
8 import mac_alias
9 import argparse
10 import json
11
12 parser = argparse.ArgumentParser(
13   prog = "jalview_custom_dsstore.py",
14   description = "Take an existing DS_Store file and change the volume name and add path to background image"
15   )
16 parser.add_argument("-i", "--input", help="The existing styled DS_Store file", dest="input")
17 parser.add_argument("-o", "--output", help="The output DS_Store file", dest="output")
18 parser.add_argument("-v", "--volumename", help="The name of the output DS_Store volume", dest="volumename")
19 parser.add_argument("-b", "--backgroundfile", help="The background image filename to use", dest="backgroundfile", default="background.png")
20 parser.add_argument("-d", "--backgrounddir", help="The background image hidden dirname to use", dest="backgrounddir", default=".background")
21 parser.add_argument("-c", "--config", help="YAML configuration for window and icon sizes and positions", dest="config")
22 parser.add_argument("-m", "--dmg", help="The filename of the DMG file", dest="dmg", default="Jalview_macos.dmg")
23 parser.add_argument("--dump", help="Display contents of the input DS_Store to stdout", action="store_true", dest="dump")
24 parser.add_argument("-q", "--quiet", help="Don't print messages to stdout", action="store_true", dest="quiet")
25 parser.add_argument("--appname", help="Filename of the application on the DMG (including '.app')", dest="appname")
26 parser.add_argument("--appxpos", help="x position of the application icon", dest="appxpos", type=int)
27 parser.add_argument("--appypos", help="y position of the application icon", dest="appypos", type=int)
28
29 args = parser.parse_args()
30
31 def myprint(string):
32   if (not args.quiet):
33     print(string)
34
35 def mypprint(string):
36   if (not args.quiet):
37     pprint.pprint(string)
38
39 if (args.dump and not args.input):
40   exit("When --dump used, an --input must be given")
41
42 if (args.dump):
43   with ds_store.DSStore.open(args.input, 'r') as d:
44     for key in ["bwsp", "icvp","Iloc"]:
45       try:
46         print(f"d['.']['{key}'] =\n" + pprint.pformat(d['.'][key], width=4))
47       except:
48         print(f"No info for d['.']['{key}']")
49
50     a = mac_alias.Alias.from_bytes( d['.']['icvp']['backgroundImageAlias'] )
51     print("backgroundImageAlias:")
52     print("alias.volume="+pprint.pformat(a.volume))
53     print("alias.target="+pprint.pformat(a.target))
54
55     for data in d:
56       try:
57         data = str(data)
58         pprint.pprint(f"d['{data}']="+d[data], width=1)
59       except:
60         print(f"No info for d['{data}']")
61   exit(0)
62
63 if (args.output and not (args.input or args.config)):
64   exit("Need --input FILENAME or --config FILENAME to produce an --output")
65
66 if (not args.output):
67   exit("Provide --output FILENAME to output DS_Store")
68
69 if (not args.volumename):
70   exit("Provide a volume name with --volumename NAME")
71
72 if (not args.backgroundfile):
73   exit("Provide a background image filename (just the file, no path) with --backgroundfile FILENAME")
74
75 package_name_ns = args.volumename
76 configfilename = args.config
77 config = None
78 if (configfilename):
79   configfile = open(configfilename, 'r')
80   config = json.load(configfile)
81
82 inputds = ds_store.DSStore.open(args.input)
83 outputds = ds_store.DSStore.open(args.output, 'w+')
84
85
86 bwsp = {}
87
88 for key in "ShowStatusBar SidebarWidthTenElevenOrLater ShowToolbar ShowTabView ContainerShowSidebar WindowBounds ShowSidebar ShowPathbar".split():
89   if config and 'bwsp' in config and key in config['bwsp']:
90     bwsp[key] = config['bwsp'][key]
91     myprint(f"Setting bwsp['{key}'] to '"+str(bwsp[key])+"' from config")
92   else:
93     if key in inputds['.']['bwsp']:
94       bwsp[key] = inputds['.']['bwsp'][key]
95       myprint(f"Setting bwsp['{key}'] to '"+str(bwsp[key])+"' from input DS_Store")
96     else:
97       myprint(f"Did not find '{key}' in input bwsp")
98
99 outputds['.']['bwsp'] = bwsp
100
101 icvp = {}
102
103 alias = mac_alias.Alias.from_bytes(inputds['.']['icvp']['backgroundImageAlias'])
104
105 alias.volume.name = args.volumename
106 alias.volume.posix_path = "/Volumes/" + args.volumename
107 alias.volume.disk_image_alias = None
108 alias.target.filename = args.backgroundfile
109 alias.target.folder_name = args.backgrounddir
110 alias.target.carbon_path = f'{args.volumename}:{args.backgrounddir}:{args.backgroundfile}'
111 alias.target.posix_path = f'/{args.backgrounddir}/{args.backgroundfile}'
112
113 icvp['backgroundImageAlias'] = alias.to_bytes()
114 myprint(f"Setting icvp['backgroundImageAlias']")
115
116 for key in "backgroundColorRed backgroundColorBlue backgroundColorGreen gridSpacing gridOffsetX gridOffsetY showItemInfo viewOptionsVersion arrangeBy textSize labelOnBottom backgroundType showIconPreview iconSize".split():
117   if config and 'icvp' in config and key in config['icvp']:
118     icvp[key] = config['icvp'][key]
119     myprint(f"Setting icvp['{key}'] to '"+str(icvp[key])+"' from config")
120   else:
121     if key in inputds['.']['icvp']:
122       icvp[key] = inputds['.']['icvp'][key]
123       myprint(f"Setting icvp['{key}'] to '"+str(icvp[key])+"' from input DS_Store")
124     else:
125       myprint(f"Did not find '{key}' in input icvp")
126
127 outputds['.']['icvp'] = icvp
128 outputds['.']['vSrn'] = ('long', 1)
129
130 if config:
131   for fileinfo in config['files']:
132     outputds[fileinfo['name']]['Iloc'] = (fileinfo['xpos'], fileinfo['ypos'])
133     myprint("Setting icon location for filename '" + fileinfo['name'] + "' to ( " + str(fileinfo['xpos']) + ", " + str(fileinfo['ypos']) + " )")
134
135 if args.appname:
136
137   xpos = args.appxpos
138   if (not xpos) and 'app' in config and 'xpos' in config['app']:
139     xpos = config['app']['xpos']
140
141   ypos = args.appypos
142   if (not ypos) and 'app' in config and 'ypos' in config['app']:
143     ypos = config['app']['ypos']
144
145   if xpos != None and ypos != None:
146     outputds[args.appname]['Iloc'] = (xpos, ypos)
147     myprint("Setting icon location for filename '" + args.appname + "' to ( " + str(xpos) + ", " + str(ypos) + " )")
148
149 outputds.flush()
150 outputds.close()
151 inputds.close()