From b317319cc0a2c0838647fcba4a03c1142502bafc Mon Sep 17 00:00:00 2001 From: Joey Payne Date: Mon, 8 Feb 2016 08:37:25 -0700 Subject: [PATCH] Added electron support with no asar packing. --- command_line.py | 302 ++++++++++++++---------- files/images/web_prefs.png | Bin 0 -> 2811 bytes files/images/web_prefs.svg | 189 +++++++++++++++ files/mainjstemplate.js | 46 ++++ files/settings.cfg | 469 +++++++++++++++++++++++-------------- files/version.txt | 2 +- main.py | 201 +++++++++++----- utils.py | 4 + 8 files changed, 850 insertions(+), 363 deletions(-) create mode 100644 files/images/web_prefs.png create mode 100644 files/images/web_prefs.svg create mode 100644 files/mainjstemplate.js diff --git a/command_line.py b/command_line.py index 8ac3af0..f61d1f4 100644 --- a/command_line.py +++ b/command_line.py @@ -30,6 +30,7 @@ import logging import logging.handlers as lh import plistlib import codecs +import requests from pprint import pprint from utils import get_data_path, get_data_file_path @@ -128,20 +129,42 @@ class Setting(object): self.scope = kwargs.pop('scope', 'local') self.default_value = kwargs.pop('default_value', None) + self.label_suffix = kwargs.pop('label_suffix', '') self.button = kwargs.pop('button', None) self.button_callback = kwargs.pop('button_callback', None) self.description = kwargs.pop('description', u'') self.values = kwargs.pop('values', []) + self.exists = kwargs.pop('exists', True) self.filter = kwargs.pop('filter', '.*') + self.factor = kwargs.pop('factor', 1) self.filter_action = kwargs.pop('filter_action', 'None') self.check_action = kwargs.pop('check_action', 'None') self.action = kwargs.pop('action', None) + convert = kwargs.pop('convert', lambda x: x) + if not callable(convert): + convert = eval(convert) + + def conv(x): + try: + x = convert(x) + except ValueError: + x = convert(float(x)) + + if isinstance(x, (int, float)) and not isinstance(x, bool): + x = x/self.factor + return convert(x) + + self.convert = conv + self.set_extra_attributes_from_keyword_args(**kwargs) if self.value is None: self.value = self.default_value + if self.value: + self.value = self.convert(self.value) + self.save_path = kwargs.pop('save_path', u'') self.get_file_information_from_url() @@ -187,11 +210,6 @@ class Setting(object): return '' - def extract_file_path(self, version): - if self.extract_file: - return self.extract_file.format(version) - return u'' - def set_extra_attributes_from_keyword_args(self, **kwargs): for undefined_key, undefined_value in kwargs.items(): setattr(self, undefined_key, undefined_value) @@ -229,47 +247,6 @@ class Setting(object): utils.move(abs_file, ex_path) utils.rmtree(dir_name, ignore_errors=True) - def get_file_bytes(self, version): - fbytes = [] - - path = self.save_file_path(version) - - file = self.extract_class(path, - *self.extract_args) - for extract_path, dest_path in zip(self.extract_files, - self.dest_files): - new_bytes = None - try: - extract_p = extract_path.format(version) - - versions = re.findall('(\d+)\.(\d+)\.(\d+)', version)[0] - - minor = int(versions[1]) - if minor >= 12: - extract_p = extract_p.replace('node-webkit', 'nwjs') - - if self.file_ext == '.gz': - new_bytes = file.extractfile(extract_p).read() - elif self.file_ext == '.zip': - new_bytes = file.read(extract_p) - except KeyError as e: - logger.error(str(e)) - # dirty hack to support old versions of nw - if 'no item named' in str(e): - extract_path = '/'.join(extract_path.split('/')[1:]) - try: - if self.file_ext == '.gz': - new_bytes = file.extractfile(extract_path).read() - elif self.file_ext == '.zip': - new_bytes = file.read(extract_path) - except KeyError as e: - logger.error(str(e)) - - if new_bytes is not None: - fbytes.append((dest_path, new_bytes)) - - return fbytes - def __repr__(self): url = '' if hasattr(self, 'url'): @@ -291,6 +268,8 @@ class CommandBase(object): self.logger = None self.output_package_json = True self.settings = self.get_settings() + self.js_cmd_args = '' + self.js_window_init = '' self._project_dir = '' self._output_dir = '' self._progress_text = '' @@ -301,28 +280,28 @@ class CommandBase(object): def init(self): self.logger = logging.getLogger('CMD logger') - self.update_nw_versions(None) - self.setup_nw_versions() + self.update_electron_versions(None) + self.setup_electron_versions() - def update_nw_versions(self, button): + def update_electron_versions(self, button): self.progress_text = 'Updating nw versions...' self.get_versions() self.progress_text = '\nDone.\n' - def setup_nw_versions(self): - nw_version = self.get_setting('nw_version') - nw_version.values = [] + def setup_electron_versions(self): + electron_version = self.get_setting('electron_version') + electron_version.values = [] try: - f = codecs.open(get_data_file_path('files/nw-versions.txt'), encoding='utf-8') + f = codecs.open(get_data_file_path('files/electron-versions.txt'), encoding='utf-8') for line in f: - nw_version.values.append(line.strip()) + electron_version.values.append(line.strip()) f.close() except IOError: - nw_version.values.append(nw_version.default_value) + electron_version.values.append(electron_version.default_value) - def get_nw_versions(self): - nw_version = self.get_setting('nw_version') - return nw_version.values[:] + def get_electron_versions(self): + electron_version = self.get_setting('electron_version') + return electron_version.values[:] def get_settings(self): config_file = get_file('files/settings.cfg') @@ -401,30 +380,30 @@ class CommandBase(object): union_versions = set() - for url in self.settings['version_info']['urls']: - response = request.urlopen(url) - html = response.read().decode('utf-8') + url = self.settings['version_info']['electron_url'] - nw_version = self.get_setting('nw_version') + req = requests.get(url) - old_versions = set(nw_version.values) - old_versions = old_versions.union(union_versions) - new_versions = set(re.findall('(\S+) / \S+', html)) + data = json.loads(req.text) - union_versions = old_versions.union(new_versions) + versions = [d['name'].split(' ')[-1][1:] for d in data] + + electron_version = self.get_setting('electron_version') + + old_versions = set(electron_version.values) + old_versions = old_versions.union(union_versions) + new_versions = set(versions) + + union_versions = old_versions.union(new_versions) versions = sorted(union_versions, key=Version, reverse=True) - if len(versions) > 19: - #Cut off old versions - versions = versions[:-19] - - nw_version.values = versions + electron_version.values = versions f = None try: - f = codecs.open(get_data_file_path('files/nw-versions.txt'), 'w', encoding='utf-8') - for v in nw_version.values: + f = codecs.open(get_data_file_path('files/electron-versions.txt'), 'w', encoding='utf-8') + for v in electron_version.values: f.write(v+os.linesep) f.close() except IOError: @@ -444,10 +423,6 @@ class CommandBase(object): path = setting.url.format(version, version) versions = re.findall('(\d+)\.(\d+)\.(\d+)', version)[0] - minor = int(versions[1]) - if minor >= 12: - path = path.replace('node-webkit', 'nwjs') - try: return self.download_file(setting.url.format(version, version), setting) @@ -467,7 +442,7 @@ class CommandBase(object): p_json = [json_path] else: p_json = glob.glob(utils.path_join(self.project_dir(), - 'package.json')) + 'package.json')) setting_list = [] if p_json: json_str = '' @@ -486,10 +461,11 @@ class CommandBase(object): def generate_json(self, global_json=False): self.logger.info('Generating package.json...') - dic = {'webexe_settings': {}} + dic = {'webexe_settings': {}, + 'web_preferences': {}} if not global_json: - dic.update({'webkit': {}, 'window': {}}) + dic.update({'webkit': {}, 'window': {}, 'web': {}}) dic.update(self.original_packagejson) for setting_name, setting in self.settings['app_settings'].items(): if setting.value is not None: @@ -499,18 +475,16 @@ class CommandBase(object): for setting_name, setting in self.settings['window_settings'].items(): if setting.value is not None: - if 'height' in setting.name or 'width' in setting.name: - try: - dic['window'][setting_name] = int(setting.value) - except ValueError: - pass - else: - dic['window'][setting_name] = setting.value + dic['window'][setting_name] = setting.value for setting_name, setting in self.settings['webkit_settings'].items(): if setting.value is not None: dic['webkit'][setting_name] = setting.value + for setting_name, setting in self.settings['web_preferences'].items(): + if setting.value is not None: + dic['web'][setting_name] = setting.value + if not global_json: dl_export_items = (list(self.settings['download_settings'].items()) + list(self.settings['export_settings'].items()) + @@ -525,6 +499,33 @@ class CommandBase(object): if setting.value is not None: dic['webexe_settings'][setting_name] = setting.value + dic['main'] = 'main.js' + + js_settings = {'webPreferences': {}} + + for setting_name, setting in self.settings['window_settings'].items(): + js_settings[utils.to_camel_case(setting_name)] = setting.convert(setting.value) + + for setting_name, setting in self.settings['web_preferences'].items(): + js_settings['webPreferences'][utils.to_camel_case(setting_name)] = setting.convert(setting.value) + + ignored_app_settings = set(['main_html', 'app_name', 'description', 'version', 'user_agent']) + + self.js_cmd_args = '' + + for setting_name, setting in self.settings['app_settings'].items(): + if setting_name not in ignored_app_settings: + if setting.value: + js_name = setting_name.replace('_', '-') + if setting.type == 'check': + self.js_cmd_args += 'app.commandLine.appendSwitch("'+js_name+'");\n' + else: + value = setting.value + self.js_cmd_args += 'app.commandLine.appendSwitch("'+js_name+'", "'+value+'");\n' + + + self.js_window_init = 'var mainWindow = new BrowserWindow('+json.dumps(js_settings, indent=4)+');' + s = json.dumps(dic, indent=4) return s @@ -577,7 +578,7 @@ class CommandBase(object): setting.type == 'string' or setting.type == 'folder'): val_str = self.convert_val_to_str(new_dic[item]) - setting.value = val_str + setting.value = setting.convert(val_str) if setting.type == 'strings': strs = self.convert_val_to_str(new_dic[item]).split(',') setting.value = strs @@ -588,12 +589,14 @@ class CommandBase(object): setting.value = val_str if setting.type == 'range': setting.value = new_dic[item] + if setting.type == 'color': + setting.value = new_dic[item] if isinstance(new_dic[item], dict): stack.append((item, new_dic[item])) return setting_list def selected_version(self): - return self.get_setting('nw_version').value + return self.get_setting('electron_version').value def extract_files(self): self.extract_error = None @@ -607,14 +610,6 @@ class CommandBase(object): extract_path = get_data_path('files/'+setting.name) setting.extract(extract_path, version) - #if os.path.exists(save_file_path): - # setting_fbytes = setting.get_file_bytes(version) - # for dest_file, fbytes in setting_fbytes: - # path = utils.path_join(extract_path, dest_file) - # with open(path, 'wb+') as d: - # d.write(fbytes) - # self.progress_text += '.' - self.progress_text += '.' except (tarfile.ReadError, zipfile.BadZipfile) as e: @@ -679,23 +674,60 @@ class CommandBase(object): json_file = utils.path_join(self.project_dir(), 'package.json') global_json = utils.get_data_file_path('files/global.json') + js_template = get_file('files/mainjstemplate.js') + + main_js_file = utils.path_join(self.project_dir(), 'main.js') if self.output_package_json: with codecs.open(json_file, 'w+', encoding='utf-8') as f: f.write(self.generate_json()) - with codecs.open(global_json, 'w+', encoding='utf-8') as f: f.write(self.generate_json(global_json=True)) + js_string = codecs.open(js_template, encoding='utf-8').read() + + inject_js_start = '' + inject_js_end = '' + + inject_start_file = self.get_setting('inject_js_start').value + inject_start_file = utils.path_join(self.project_dir(), + inject_start_file) + + if os.path.exists(inject_start_file): + inject_js_start = codecs.open(inject_start_file, encoding='utf-8').read() + + inject_end_file = self.get_setting('inject_js_end').value + inject_end_file = utils.path_join(self.project_dir(), + inject_end_file) + + if os.path.exists(inject_end_file): + inject_js_end = codecs.open(inject_end_file, encoding='utf-8').read() + + main_html = self.get_setting('main_html') + user_agent = self.get_setting('user_agent') + + js_string = js_string.replace('{{command_line_switches}}', self.js_cmd_args) + js_string = js_string.replace('{{user_agent}}', '"'+user_agent.value+'"') + js_string = js_string.replace('{{main_html}}', main_html.value) + js_string = js_string.replace('{{main_window_line}}', self.js_window_init) + js_string = js_string.replace('{{app_name}}', self.project_name()) + js_string = js_string.replace('{{inject_js_start}}', inject_js_start) + js_string = js_string.replace('{{inject_js_end}}', inject_js_end) + + with codecs.open(main_js_file, 'w+', encoding='utf-8') as f: + f.write(js_string) + zip_file = utils.path_join(temp_dir, self.project_name()+'.nw') app_nw_folder = utils.path_join(temp_dir, self.project_name()+'.nwf') - utils.copytree(self.project_dir(), app_nw_folder, - ignore=shutil.ignore_patterns(output_dir)) + if self.project_dir() in self.output_dir(): + utils.copytree(self.project_dir(), app_nw_folder, + ignore=shutil.ignore_patterns(os.path.basename(self.output_dir()))) + else: + utils.copytree(self.project_dir(), app_nw_folder) - zip_files(zip_file, self.project_dir(), exclude_paths=[output_dir]) for ex_setting in self.settings['export_settings'].values(): if ex_setting.value: self.progress_text = '\n' @@ -724,14 +756,9 @@ class CommandBase(object): app_path = utils.path_join(export_dest, self.project_name()+'.app') - try: - utils.move(utils.path_join(export_dest, - 'nwjs.app'), - app_path) - except IOError: - utils.move(utils.path_join(export_dest, - 'node-webkit.app'), - app_path) + utils.move(utils.path_join(export_dest, + 'Electron.app'), + app_path) plist_path = utils.path_join(app_path, 'Contents', 'Info.plist') @@ -751,16 +778,18 @@ class CommandBase(object): app_nw_res = utils.path_join(app_path, 'Contents', 'Resources', - 'app.nw') + 'default_app') + + + if os.path.exists(app_nw_res): + utils.rmtree(app_nw_res) + + utils.copytree(app_nw_folder, app_nw_res) - if uncompressed: - utils.copytree(app_nw_folder, app_nw_res) - else: - utils.copy(zip_file, app_nw_res) self.create_icns_for_app(utils.path_join(app_path, - 'Contents', - 'Resources', - 'nw.icns')) + 'Contents', + 'Resources', + 'atom.icns')) self.progress_text += '.' else: @@ -771,7 +800,7 @@ class CommandBase(object): windows = True nw_path = utils.path_join(export_dest, - ex_setting.dest_files[0]) + ex_setting.binary_location) if windows: self.replace_icon_in_exe(nw_path) @@ -779,18 +808,28 @@ class CommandBase(object): self.compress_nw(nw_path) dest_binary_path = utils.path_join(export_dest, - self.project_name() + - ext) + self.project_name() + + ext) if 'linux' in ex_setting.name: self.make_desktop_file(dest_binary_path, export_dest) - join_files(dest_binary_path, nw_path, zip_file) + utils.move(nw_path, dest_binary_path) + + app_nw_res = utils.path_join(export_dest, + 'resources', + 'default_app') + + if os.path.exists(app_nw_res): + utils.rmtree(app_nw_res) + + utils.copytree(app_nw_folder, app_nw_res) sevenfivefive = (stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) + os.chmod(dest_binary_path, sevenfivefive) self.progress_text += '.' @@ -1047,6 +1086,8 @@ class CommandBase(object): os.path.sep, self.project_name()) self.delete_files() + else: + print('Export failed. Check the log at {} or run again with --verbose.'.format(LOG_FILENAME)) def get_export_options(self): options = [] @@ -1073,6 +1114,11 @@ class CommandBase(object): self.progress_text = 'Extracting files.' return self.extract_files() + def get_redirected_url(self, url): + opener = request.build_opener(request.HTTPRedirectHandler) + req = opener.open(url) + return req.url + def download_file(self, path, setting): self.logger.info(u'Downloading file {}.'.format(path)) @@ -1080,9 +1126,7 @@ class CommandBase(object): versions = re.findall('v(\d+)\.(\d+)\.(\d+)', path)[0] - minor = int(versions[1]) - if minor >= 12: - path = path.replace('node-webkit', 'nwjs') + path = self.get_redirected_url(path) url = path file_name = setting.save_file_path(self.selected_version(), location) @@ -1099,6 +1143,7 @@ class CommandBase(object): forced = self.get_setting('force_download').value if (archive_exists or dest_files_exist) and not forced: + print('File exists.') self.logger.info(u'File {} already downloaded. Continuing...'.format(path)) return self.continue_downloading_or_extract() elif tmp_exists and (os.stat(tmp_file).st_size > 0): @@ -1109,7 +1154,7 @@ class CommandBase(object): web_file = request.urlopen(url) f = open(tmp_file, 'ab') meta = web_file.info() - file_size = tmp_size + int(meta.getheaders("Content-Length")[0]) + file_size = tmp_size + int(meta["Content-Length"]) version = self.selected_version() version_file = self.settings['base_url'].format(version) @@ -1155,10 +1200,9 @@ class CommandBase(object): def delete_files(self): for ex_setting in self.settings['export_settings'].values(): - for dest_file in ex_setting.dest_files: - f_path = get_data_file_path('files/{}/{}'.format(ex_setting.name, dest_file)) - if os.path.exists(f_path): - os.remove(f_path) + f_path = get_data_file_path('files/{}/'.format(ex_setting.name)) + if os.path.exists(f_path): + utils.rmtree(f_path) class ArgParser(argparse.ArgumentParser): diff --git a/files/images/web_prefs.png b/files/images/web_prefs.png new file mode 100644 index 0000000000000000000000000000000000000000..c7e59a0e4cac9414b43b0b2839e4c72ef8751eb8 GIT binary patch literal 2811 zcmVk8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H13V2CGK~#90?ORK899I?o&h6J|W+dAqMUiBY#7YPcJ9gqq7O5yk zBvowKx{61jiYgXR#Uj6fUw{{cVnYE%Re)ka6&s2TOyooXLJ=oULK#O!k@fcIIj^4S z?t56wbgR3kU!$>|1avlX_r2e_=R1Ax>2pqxfnzzAWBGp;$~y6d7hX8+IL<@N{6LoZ z%$!+wx*bYp-9qrD5MsGpF0Tx{@Z8*7QA+tefG#uhOaeBn$Gav1nB&I>#3tzCMIw4t z2=Pj}T&^KTVjb|@b8}BCiu@Ts4@P)AlM#av005@Ytv+h@Fh!@M{@-@VPTzU4{XNOc(y-GyWM~ zj`aS$q$uKIxm-5=6GaFJRaL(`DESDvkK|(m4VrsE&V=kvDYKa*2&m zMKN^5tPchtTWzr2X8ij{`>^&T05S|+oIihlL{Svd^#nXKTCEmL)0Ee*zh9HmApm%w z*ruO+{eyz85(%vkkCzUlUB~KPPQLuR&$le6H-2!qT^5@OdbGO85RH_ zQjG7+Q9xioD|os!O(OuQ+_`ZM=xJ;afOCL81y>(t-RyY)Z*8_S~mA zAhNOX61aE{=tQuTXg&+*uMlch*LgxHc;bn(#au3zCG*sFc6RKWH*dCG@;X%fz#Kv>(wF2}37`A1Zeu>DsQp+xfToX+2zSI&+i zFPH#5i97%Zf2Ox@f;jJRLe%(~Ko z($VYjPflU=@fSk_%01l)Szb$@W=VBk)|S{kHR2v&GwDj}2mt`OX-|e?xD!R$>&Urv zLl$Hg?v~EJ4AV6tiyA(}_#|5*6!#T)1MDz>re&qB-*!4#>?+8!q1kLo0H{{0FpT8Z zcCFU=lIrG^AK(xtNtvJr+ycpYp!FroGO{?~2FQL7 z@KVuAW!o7Y9di)=r9+IYt*zOnX}Zsw-c{D*ML8`W|0q3XF&JgBC@X06WPwp{ro>{% zfBi?mdR&*1(B>P;g1sr00y}N|94k{7ZH1KNSAo+aEH4PTx1{)5V(}(t3WB<>(PNK2mNyIo z#bR<((lkv>PoLJS)v7!=IB)=<#FOfy&eK{kmq)<}e}6p`24lZ@KwoS>ukB*9-4;z| zW*RvSarzls8OrC7D;NoT9jF~N@yMmNcyCiUR-`Wt{Rgy(3QMw641T0TFd_+jBx(=<)FzP{ns^WJElPte1BRvk+Cyd?pu z(KdgiTz4$A0`rU$Oyg%AVT^=p0k7#q0MOVbY+YAiw<4E#_v+R83;r7s?xf!U{KKI3 z&M{bCUbfKv+|^w*skRRcG-x0Hq6OPMbP+{L(pja}KH>I)DqSqllkB9H#zgp4_+C#C zwC(m!0o;yI(o-7u@w)73PNa~Jbz}_rEOz`y2=U|a%^nB5`R1EVB76&g|9AEMl22?- zq*Q4AG&Ztgn*d)9|6c(h<~2A!KmRtsUj?ucZA4$>gOh`aR$tf-zHjnt%<{`uuU`FU z)L?9PVtILab!O(w&zWT#plJZ7x(38<&$R2%)*l2L9*-h_lL%L&biVz@8*ki-7>@-; z%B4$}KEuop2_d|nc@2{691W|cVikp3%)B%|KYu%J&aoWJu^fvl{{^qTOA#^kf-wL9 N002ovPDHLkV1g0IHz@!B literal 0 HcmV?d00001 diff --git a/files/images/web_prefs.svg b/files/images/web_prefs.svg new file mode 100644 index 0000000..b7bcc0c --- /dev/null +++ b/files/images/web_prefs.svg @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/files/mainjstemplate.js b/files/mainjstemplate.js new file mode 100644 index 0000000..6851c1a --- /dev/null +++ b/files/mainjstemplate.js @@ -0,0 +1,46 @@ +var app = require('app'); // Module to control application life. +var BrowserWindow = require('browser-window'); // Module to create native browser window. + +{{command_line_switches}} + +{{inject_js_start}} + +// Report crashes to our server. +require('crash-reporter').start(); + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +var mainWindow = null; + +// Quit when all windows are closed. +app.on('window-all-closed', function() { + // On OS X it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform != 'darwin') { + app.quit(); + } +}); + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +app.on('ready', function() { + // Create the browser window. + {{main_window_line}} + // and load the index.html of the app. + mainWindow.loadUrl('file://' + __dirname + '/{{main_html}}'); + + mainWindow.webContents.setUserAgent({{user_agent}}); + + mainWindow.webContents.on('did-finish-load',function(){ + mainWindow.setTitle("{{app_name}}"); + {{inject_js_end}} + }); + + // Emitted when the window is closed. + mainWindow.on('closed', function() { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null; + }); +}); diff --git a/files/settings.cfg b/files/settings.cfg index 94f4335..1b94736 100644 --- a/files/settings.cfg +++ b/files/settings.cfg @@ -1,27 +1,22 @@ -base_url='http://dl.nwjs.io/v{}/' -win_32_dir_prefix = 'node-webkit-v{}-win-ia32' -mac_32_dir_prefix = 'node-webkit-v{}-osx-ia32' -linux_32_dir_prefix = 'node-webkit-v{}-linux-ia32' +base_url='https://github.com/atom/electron/releases/download/v{}/' +win_32_dir_prefix = 'electron-v{}-win32-ia32' +mac_32_dir_prefix = 'electron-v{}-darwin-ia32' +linux_32_dir_prefix = 'electron-v{}-linux-ia32' + +win_64_dir_prefix = 'electron-v{}-win32-x64' +mac_64_dir_prefix = 'electron-v{}-darwin-x64' +linux_64_dir_prefix = 'electron-v{}-linux-x64' + -win_64_dir_prefix = 'node-webkit-v{}-win-x64' -mac_64_dir_prefix = 'node-webkit-v{}-osx-x64' -linux_64_dir_prefix = 'node-webkit-v{}-linux-x64' [setting_groups] [[app_settings]] - [[[main]]] + [[[main_html]]] display_name='Main html file' required=True type='file' file_types='*.html *.php *.htm' description='Main html file relative to the project directory.' - [[[name]]] - display_name='Name' - required=True - type='string' - description='The name in the internal package.json. Must be alpha-numeric with no spaces.' - filter='[a-z0-9_\-\.]+' - filter_action='lower' [[[app_name]]] display_name='App Name' required=False @@ -32,67 +27,121 @@ linux_64_dir_prefix = 'node-webkit-v{}-linux-x64' type='string' [[[version]]] default_value='0.1.0' + filter='[a-z0-9\.]+' type='string' - [[[keywords]]] - default_value='' - type='string' - [[[nodejs]]] - display_name='Include Nodejs' - default_value=True - type='check' - [[[node-main]]] - display_name='Node Main' - default_value='' - type='file' - file_types='*.js' - description='A path to a nodejs script file that will be executed on startup.' - [[[single-instance]]] - display_name='Single Instance' - default_value=True - type='check' - description='Restrict the app to run with only a single instance allowed at a time.' - [[[user-agent]]] + description='Version of your application.' + [[[user_agent]]] display_name='User Agent' default_value='' type='string' - description='Overrides the User-Agent header in http requests.' - [[[node-remote]]] - display_name='Node Remote' - default_value='' - type='string' - description='Enable calling node in remote pages. See the node-webkit manifest format for more info.' - [[[chromium-args]]] + description='Overrides the User-Agent header in http requests.\n\nExample:\nMozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.99 Safari/537.36' + [[[chromium_args]]] display_name='Chromium Args' default_value='' type='string' - description='Specify chromium command line arguments. Example value: "--disable-accelerated-video --force-cpu-draw"' - [[[js-flags]]] + description='Specify chromium command line arguments.\nExample value: "--disable-accelerated-video --force-cpu-draw"' + [[[js_flags]]] display_name='JS Flags' default_value='' type='string' - description='Specify flags passed to the js engine. Example value: "--harmony_proxies --harmony_collecions"' - [[[inject-js-start]]] + description='Specify flags passed to the js engine.\nExample value: "--harmony_proxies --harmony_collections"' + [[[inject_js_start]]] display_name='Inject JS Start' default_value='' type='file' file_types='*.js' - description='A path to a js file that will be executed before any other script is run.' - [[[inject-js-end]]] + description='A path to a js file that will be executed before any other\nscript is run. Will be inserted into generated main.js.' + [[[inject_js_end]]] display_name='Inject JS End' default_value='' type='file' file_types='*.js' - description='A path to a js file that will be executed after the DOM is loaded.' - [[[snapshot]]] + description='A path to a js file that will be executed after the DOM is\nloaded. Will be inserted into generated main.js.' + [[[client_certificate]]] + display_name='Client Certificate' default_value='' type='file' - file_types='*.bin' - description='A path to a binary file compiled with snapshot. Used if you don\'t want your code to be exposed.' - [[[additional_trust_anchors]]] - display_name='Trust Anchors' + description='Sets the path of client certificate file.' + [[[ignore_certificate_errors]]] + display_name='Ignore Cert. Errors' + default_value=False + type='check' + description='Ignores certificate related errors.' + [[[remote_debugging_port]]] + display_name='Remote Debug Port' default_value='' - type='strings' - description='A list of PEM-encoded certificates. Used as additional root certificates for validation to allow connecting to services using a self-signed certificate.' + type='string' + filter='[0-9]+' + description='Enables remote debugging over HTTP on the port.' + [[[ignore_connections_limit]]] + display_name='Ignore Conn. Limit' + default_value='' + type='string' + description='Ignore the connection limit for domains list separated by a comma.\nExample: www.google.ca,www.bing.com' + [[[disable_http_cache]]] + display_name='Disable Http Cache' + default_value=False + type='check' + description='Disables the disk cache for HTTP requests.' + [[[proxy_server]]] + default_value='' + type='string' + description='Use a specified proxy server, which overrides the system\nsetting. This switch only affects requests with HTTP protocol,\nincluding HTTPS and WebSocket requests. It is also noteworthy\nthat not all proxy servers support HTTPS and WebSocket requests.\n\nExamples:\n231.114.156.200:4554\nmyserver.com:3423' + [[[proxy_bypass_list]]] + display_name='Proxy Bypass' + default_value='' + type='string' + description='Instructs Electron to bypass the proxy server for the given\nsemi-colon-separated list of hosts. This flag has an effect only\nif used in tandem with Proxy Server.\n\nExample:\n;*.google.com;*foo.com;1.2.3.4:5678\n\nThe example will use the proxy server for all hosts except for\nlocal addresses (localhost, 127.0.0.1 etc.), google.com subdomains, hosts that\ncontain the suffix foo.com and anything at 1.2.3.4:5678.' + [[[proxy_pac_url]]] + display_name='Proxy Pac URL' + default_value='' + type='string' + description='Uses the PAC script at the specified URL.' + [[[no_proxy_server]]] + display_name='No Proxy' + default_value=False + type='check' + description="Don't use a proxy server and always make direct connections.\nOverrides any other proxy server settings." + [[[host_rules]]] + default_value='' + type='string' + description='A comma-separated list of rules that control how hostnames are mapped.\n\nFor example:\n\n- "MAP * 127.0.0.1" Forces all hostnames to be mapped to 127.0.0.1\n- "MAP *.google.com proxy" Forces all google.com subdomains to be resolved to "proxy".\n- "MAP test.com [::1]:77" Forces "test.com" to resolve to IPv6 loopback. Will also force the\nport of the resulting socket address to be 77.\n- "MAP * baz, EXCLUDE www.google.com" Remaps everything to "baz", except for "www.google.com".\n\nThese mappings apply to the endpoint host in a net request (the TCP connect\nand host resolver in a direct connection, and the CONNECT in an HTTP proxy\nconnection, and the endpoint host in a SOCKS proxy connection).' + [[[host_resolver_rules]]] + default_value='' + type='string' + description='Like Host Rules but these rules only apply to the host resolver.' + [[[ppapi_flash_path]]] + display_name='Pepper API' + default_value='' + type='file' + description='Sets the path of the pepper flash plugin.' + [[[ppapi_flash_version]]] + display_name='Pepper API Version' + default_value='' + type='string' + description='Sets the version of the Pepper API field.' + [[[log_net_log]]] + display_name='Net Log Path' + default_value='' + type='file' + exists=False + description='Enables net log events to be saved and writes them to this path.' + [[[disable_renderer_backgrounding]]] + display_name='Disable Renderer Backgrounding' + default_value=False + type='check' + description="Prevents Chromium from lowering the priority of invisible pages'\nrenderer processes. This flag is global to all renderer processes" + [[[ssl_version_fallback_min]]] + display_name='Min SSL Version Fallback' + default_value='' + type='string' + description='Sets the minimum SSL/TLS version ("tls1", "tls1.1" or "tls1.2")\nthat TLS fallback will accept.' + [[[cipher_suite_blacklist]]] + display_name='Cipher Blacklist' + default_value='' + type='string' + description='Specifies comma-separated list of SSL cipher suites to disable.' + [[webkit_settings]] [[[plugin]]] @@ -117,6 +166,85 @@ linux_64_dir_prefix = 'node-webkit-v{}-linux-x64' copy=False type='file' + [[web_preferences]] + [[[node_integration]]] + type='check' + default_value=True + description='Whether node integration is enabled.' + [[[preload]]] + type='file' + default_value='' + description='Specifies a script that will be loaded before other scripts run in\nthe page. This script will always have access to node APIs no matter whether\nnode integration is turned on or off. When node integration is turned off,\nthe preload script can reintroduce Node global symbols back to the global scope.' + [[[partition]]] + type='string' + description='Sets the session used by the page. If partition starts with "persist:",\nthe page will use a persistent session available to all pages in the app with\nthe same partition. if there is no "persist:" prefix, the page will use\nan in-memory session. By assigning the same partition, multiple pages\ncan share the same session. If the partition is unset then default\nsession of the app will be used.' + [[[zoom_factor]]] + default_value=100 + min=1 + max=1000 + factor=100 + label_suffix='%' + type='range' + description='The default zoom factor of the page.' + [[[javascript]]] + type='check' + default_value=True + description='Enables JavaScript support.' + [[[web_security]]] + type='check' + default_value=True + description='When setting false, it will disable the same-origin policy (Usually\nusing testing websites by people), and set allowDisplayingInsecureContent\nand allowRunningInsecureContent to true if these two options are not set by user.' + [[[allow_displaying_insecure_content]]] + type='check' + default_value=False + display_name='Display Insecure' + description='Allow an https page to display content like images from http URLs.' + [[[allow_running_insecure_content]]] + type='check' + default_value=False + display_name='Run Insecure' + description='Allow a https page to run JavaScript, CSS or plugins from http URLs.' + [[[images]]] + type='check' + default_value=True + display_name='Display Images' + description='Enables image support.' + [[[text_areas_are_resizable]]] + type='check' + default_value=True + display_name='Resizable TextAreas' + description='Make TextArea elements resizable.' + [[[webgl]]] + type='check' + default_value=True + description='Enables WebGL support.' + [[[webaudio]]] + type='check' + default_value=True + description='Enables WebAudio support.' + [[[plugins]]] + type='check' + default_value=False + description='Whether plugins should be enabled.' + [[[experimental_features]]] + type='check' + display_name='Exp. Features' + default_value=False + description="Enables Chromium's experimental features." + [[[experimental_canvas_features]]] + type='check' + display_name='Exp. Canvas Features' + default_value=False + description="Enables Chromium's experimental canvas features." + [[[direct_write]]] + type='check' + default_value=True + description='Enables DirectWrite font rendering system on Windows.' + [[[blink_features]]] + type='string' + default_value='' + description='A list of feature strings separated by ",", like "CSSVariables,KeyboardEventKey".\nThe full list of supported feature strings can be found in the\nsetFeatureEnabledFromString function of chromium.' + [[window_settings]] [[[title]]] default_value='' @@ -140,11 +268,52 @@ linux_64_dir_prefix = 'node-webkit-v{}-linux-x64' file_types='*.png *.jpg *.jpeg' description='This icon to be displayed for the windows exe of the app. Defaults to Window icon.' [[[width]]] - default_value='640' + default_value='800' type='string' + convert='int' [[[height]]] - default_value='480' + default_value='600' type='string' + convert='int' + [[[x]]] + default_value='800' + type='string' + convert='int' + description="Window's left offset from screen. Default is to center the window" + [[[y]]] + default_value='600' + convert='int' + type='string' + description="Window's top offset from screen. Default is to center the window." + [[[use_content_size]]] + default_value=False + type='check' + description="The width and height would be used as web page's size, which\nmeans the actual window's size will include window frame's size\nand be slightly larger. Default is false." + [[[accept_first_mouse]]] + type='check' + default_value=False + description='Whether the web view accepts a single mouse-down event that\nsimultaneously activates the window.' + [[[auto_hide_menu_bar]]] + type='check' + default_value=False + description='Auto hide the menu bar unless the Alt key is pressed.' + [[[disable_auto_hide_cursor]]] + type='check' + default_value=False + description='Whether to hide cursor when typing.' + [[[enable_larger_than_screen]]] + display_name='Resize Larger Than Screen' + type='check' + default_value=False + description='Enable the window to be resized larger than screen.' + [[[background_color]]] + type='color' + default_value='#000' + description="Window's background color as Hexadecimal value, like #66CD00 or #FFF.\nThis is only implemented on Linux and Windows." + [[[dark_theme]]] + type='check' + default_value=False + description='Forces using dark theme for the window, only works on some GTK+3\ndesktop environments.' [[[min_width]]] default_value=None type='string' @@ -157,75 +326,67 @@ linux_64_dir_prefix = 'node-webkit-v{}-linux-x64' [[[max_height]]] default_value=None type='string' - [[[toolbar]]] - display_name='Show Toolbar' - default_value=False - type='check' - description='' - [[[always-on-top]]] + [[[always_on_top]]] display_name='Keep on Top' default_value=False type='check' - description='' + description='Whether the window should always stay on top of other windows.' [[[frame]]] display_name='Window Frame' default_value=True type='check' - description='Hide the frame of the window' - [[[show_in_taskbar]]] - display_name='Taskbar' - default_value=True + description='Specify false to create a Frameless Window.' + [[[skip_taskbar]]] + display_name='Skip Taskbar' + default_value=False type='check' - description='Hide the app running in the taskbar' + description='Whether to show the window in the taskbar.' [[[show]]] display_name='Show' default_value=True type='check' description='Uncheck to make your app hidden on startup.' - [[[visible]]] - default_value=True - type='check' - description='' [[[resizable]]] default_value=False type='check' - description='' + description='Whether window is resizable.' [[[fullscreen]]] default_value=False type='check' - description='' - [[[position]]] - display_name='Position by' - default_value=None - values=[None, 'mouse', 'center'] - type='list' - description='The position to place the window when it opens.' - [[[as_desktop]]] - default_value=False + description='Whether the window should show in fullscreen. When set to false the\nfullscreen button will be hidden or disabled on OS X.' + [[[center]]] + display_name='Center Window' + default_value=True type='check' - description='Tries to render the app to the desktop background' + description='Show window in the center of the screen.' [[[transparent]]] default_value=False type='check' description='Allows window tranparency.' + [[[type]]] + display_name='Window Type' + default_value=None + values=[None, 'desktop', 'dock', 'toolbar', 'splash', 'notification', 'textured'] + type='list' + description='The type of window to draw. Note that "textured" is Mac OSX only.' + [[[title_bar_style]]] + default_value='default' + values=['default', 'hidden', 'hidden-inset'] + type='list' + description='The title bar style. Mac OSX > 10.10 only.' [[[kiosk]]] default_value=False type='check' description='Puts the application is kiosk mode.' - [[[kiosk_emulation]]] - default_value=False - type='check' - description='Puts the application is kiosk emulation mode. Will automatically check off required settings that will emulate kiosk.' - check_action='set_kiosk_emulation_options' [[download_settings]] - [[[nw_version]]] - display_name='Node-webkit version' - default_value='0.12.0' + [[[electron_version]]] + display_name='Electron version' + default_value='0.36.7' values=[] type='list' button='Update' - button_callback='update_nw_versions' + button_callback='update_electron_versions' [[[force_download]]] default_value=False type='check' @@ -239,99 +400,37 @@ linux_64_dir_prefix = 'node-webkit-v{}-linux-x64' default_value=False type='check' url='%(base_url)s%(win_32_dir_prefix)s.zip' - extract_files="""['%(win_32_dir_prefix)s/nw.exe', - '%(win_32_dir_prefix)s/nw.pak', - '%(win_32_dir_prefix)s/icudtl.dat', - '%(win_32_dir_prefix)s/libEGL.dll', - '%(win_32_dir_prefix)s/ffmpegsumo.dll', - '%(win_32_dir_prefix)s/libGLESv2.dll', - '%(win_32_dir_prefix)s/d3dcompiler_46.dll', - '%(win_32_dir_prefix)s/d3dcompiler_47.dll', - '%(win_32_dir_prefix)s/pdf.dll' - ]""" - dest_files="""['nw.exe', - 'nw.pak', - 'icudtl.dat', - 'libEGL.dll', - 'ffmpegsumo.dll', - 'libGLESv2.dll', - 'd3dcompiler_46.dll', - 'd3dcompiler_47.dll', - 'pdf.dll' - ]""" + binary_location='electron.exe' + [[windows-x64]] default_value=False type='check' url='%(base_url)s%(win_64_dir_prefix)s.zip' - extract_files="""['%(win_64_dir_prefix)s/nw.exe', - '%(win_64_dir_prefix)s/nw.pak', - '%(win_64_dir_prefix)s/icudtl.dat', - '%(win_64_dir_prefix)s/libEGL.dll', - '%(win_64_dir_prefix)s/ffmpegsumo.dll', - '%(win_64_dir_prefix)s/libGLESv2.dll', - '%(win_64_dir_prefix)s/d3dcompiler_46.dll', - '%(win_64_dir_prefix)s/d3dcompiler_47.dll', - '%(win_64_dir_prefix)s/pdf.dll' - ]""" - dest_files="""['nw.exe', - 'nw.pak', - 'icudtl.dat', - 'libEGL.dll', - 'ffmpegsumo.dll', - 'libGLESv2.dll', - 'd3dcompiler_46.dll', - 'd3dcompiler_47.dll', - 'pdf.dll' - ]""" + binary_location='electron.exe' [[mac-x32]] default_value=False type='check' url='%(base_url)s%(mac_32_dir_prefix)s.zip' - extract_file='%(mac_32_dir_prefix)s/node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/node-webkit Framework' - extract_files="""['%(mac_32_dir_prefix)s/node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/node-webkit Framework', - '%(mac_32_dir_prefix)s/node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/Resources/nw.pak', - '%(mac_32_dir_prefix)s/node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/Libraries/ffmpegsumo.so', - '%(mac_32_dir_prefix)s/node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/Resources/icudtl.dat']""" - dest_files="""['node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/node-webkit Framework', - 'node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/Resources/nw.pak', - 'node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/Libraries/ffmpegsumo.so', - 'node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/Resources/icudtl.dat']""" + binary_location='Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/Current/Electron Framework' + [[mac-x64]] default_value=False type='check' url='%(base_url)s%(mac_64_dir_prefix)s.zip' - extract_file='%(mac_64_dir_prefix)s/node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/node-webkit Framework' - extract_files="""['%(mac_64_dir_prefix)s/node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/node-webkit Framework', - '%(mac_64_dir_prefix)s/node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/Resources/nw.pak', - '%(mac_64_dir_prefix)s/node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/Libraries/ffmpegsumo.so', - '%(mac_64_dir_prefix)s/node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/Resources/icudtl.dat']""" - dest_files="""['node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/node-webkit Framework', - 'node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/Resources/nw.pak', - 'node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/Libraries/ffmpegsumo.so', - 'node-webkit.app/Contents/Frameworks/node-webkit Framework.framework/Resources/icudtl.dat']""" - + binary_location='Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/Current/Electron Framework' [[linux-x64]] default_value=False type='check' - url='%(base_url)s%(linux_64_dir_prefix)s.tar.gz' - extract_file='%(linux_64_dir_prefix)s/nw' - extract_files="""['%(linux_64_dir_prefix)s/nw', - '%(linux_64_dir_prefix)s/nw.pak', - '%(linux_64_dir_prefix)s/libffmpegsumo.so', - '%(linux_64_dir_prefix)s/icudtl.dat']""" - dest_files=['nw', 'nw.pak', 'libffmpegsumo.so', 'icudtl.dat'] + url='%(base_url)s%(linux_64_dir_prefix)s.zip' + binary_location='electron' + [[linux-x32]] default_value=False type='check' - url='%(base_url)s%(linux_32_dir_prefix)s.tar.gz' - extract_file='%(linux_32_dir_prefix)s/nw' - extract_files="""['%(linux_32_dir_prefix)s/nw', - '%(linux_32_dir_prefix)s/nw.pak', - '%(linux_32_dir_prefix)s/libffmpegsumo.so', - '%(linux_32_dir_prefix)s/icudtl.dat']""" - dest_files=['nw', 'nw.pak', 'libffmpegsumo.so', 'icudtl.dat'] + url='%(base_url)s%(linux_32_dir_prefix)s.zip' + binary_location='electron' [compression] [[nw_compression_level]] @@ -340,31 +439,43 @@ linux_64_dir_prefix = 'node-webkit-v{}-linux-x64' min=0 max=9 type='range' - description='Compression to be applied to the executable\'s nwjs binary. 0 is no compression, 9 is maximum. They all use lzma.' + description='Compression to be applied to the executable\'s nwjs binary.\n0 is no compression, 9 is maximum. They all use lzma.' [[uncompressed_folder]] display_name='Uncompressed Folder' type='check' default_value=False - description='Mac OSX Only! This option makes the resulting app.nw inside the app just a plain folder. This is useful to mitigate startup times and to modify files.' + description='Mac OSX Only! This option makes the resulting app.nw inside\nthe app just a plain folder. This is useful to mitigate\nstartup times and to modify files.' [order] - application_setting_order="""['main', 'name', 'app_name', 'node-main', 'description', 'version', 'keywords', - 'user-agent', 'chromium-args', - 'node-remote', 'js-flags', 'inject-js-start', 'inject-js-end', - 'additional_trust_anchors', 'snapshot', - 'nodejs', 'single-instance', 'plugin', 'java', 'page-cache']""" - window_setting_order = """['title', 'icon', 'mac_icon', 'exe_icon', 'position', 'width', 'height', - 'min_width', 'min_height', - 'max_width', 'max_height', 'toolbar', 'always-on-top', 'frame', - 'show_in_taskbar', 'show', 'visible', 'resizable', 'fullscreen', 'as_desktop', - 'kiosk', 'kiosk_emulation', 'transparent']""" + application_setting_order="""['main_html', 'app_name', 'description', 'version', + 'user_agent', 'chromium_args', 'js_flags', 'inject_js_start', + 'inject_js_end', 'remote_debugging_port', 'client_certificate', + 'ignore_connections_limit', 'proxy_server', 'proxy_bypass_list', + 'host_resolver_rules', 'log_net_log', 'ppapi_flash_path', + 'ppapi_flash_version', 'ssl_version_fallback_min', + 'cipher_suite_blacklist', + 'ignore_certificate_errors', 'disable_http_cache', + 'disable_renderer_backgrounding']""" + window_setting_order = """['title', 'icon', 'mac_icon', 'exe_icon', + 'width', 'height', 'x', 'y', 'min_width', 'min_height', + 'max_width', 'max_height', 'center', 'use_content_size', 'always_on_top', + 'frame', 'skip_taskbar', 'show', 'resizable', 'fullscreen', + 'kiosk', 'transparent', 'accept_first_mouse', 'disable_auto_hide_cursor', + 'auto_hide_menu_bar', 'enable_larger_than_screen', 'background_color', + 'dark_theme', 'type', 'title_bar_style']""" + web_prefs_order = """['preload', 'partition', 'blink_features', 'zoom_factor', 'node_integration', + 'javascript', 'web_security', 'allow_displaying_insecure_content', + 'allow_running_insecure_content', 'images', 'text_areas_are_resizable', + 'webaudio', 'plugins', 'experimental_features', 'experimental_canvas_features', + 'direct_write']""" export_setting_order = """['windows-x32', 'windows-x64', 'mac-x32', 'mac-x64', 'linux-x64', 'linux-x32']""" compression_setting_order = """['nw_compression_level', 'uncompressed_folder']""" - download_setting_order = """['nw_version', 'download_dir', + download_setting_order = """['electron_version', 'download_dir', 'force_download']""" [version_info] urls="""['https://raw.githubusercontent.com/nwjs/nw.js/master/CHANGELOG.md']""" + electron_url='https://api.github.com/repos/atom/electron/releases' diff --git a/files/version.txt b/files/version.txt index cd4cc25..52a01ab 100644 --- a/files/version.txt +++ b/files/version.txt @@ -1 +1 @@ -v0.4.0b +v0.4.1b diff --git a/main.py b/main.py index e5b5ee6..b54b53d 100644 --- a/main.py +++ b/main.py @@ -11,7 +11,7 @@ import validators from PySide import QtGui, QtCore from PySide.QtGui import QApplication, QHBoxLayout, QVBoxLayout -from PySide.QtNetwork import QHttp +from PySide.QtNetwork import QHttp, QHttpRequestHeader from PySide.QtCore import QUrl, QFile, QIODevice, QCoreApplication from pycns import pngs_from_icns @@ -30,6 +30,41 @@ def url_exists(path): return True return False +class ColorDisplay(QtGui.QWidget): + colorChanged = QtCore.Signal(QtGui.QColor) + + def __init__(self, initial, parent=None): + super(ColorDisplay, self).__init__(parent) + + self.color = None + self.setColor(initial, False) + + def setColor(self, color, emit=True): + if isinstance(color, (str, bytes)): + self.color = QtGui.QColor(color) + else: + self.color = color + + self.update() + + if emit: + self.colorChanged.emit(self.color) + + def paintEvent(self, event=None): + painter = QtGui.QPainter(self) + if self.color is not None: + painter.setBrush(QtGui.QBrush(self.color)) + rect = self.rect() + margins = self.contentsMargins() + new_rect = QtCore.QRect(rect.left()+margins.left(), + rect.top()+margins.top(), + rect.width()-margins.right()*2, + rect.height()-margins.bottom()*2) + painter.drawRect(new_rect) + + def getColorName(self): + return str(self.color.name()) + class ExistingProjectDialog(QtGui.QDialog): def __init__(self, recent_projects, directory_callback, parent=None): super(ExistingProjectDialog, self).__init__(parent) @@ -132,7 +167,7 @@ class BackgroundThread(QtCore.QThread): class MainWindow(QtGui.QMainWindow, CommandBase): - def update_nw_versions(self, button): + def update_electron_versions(self, button): self.get_versions_in_background() def load_recent_projects(self): @@ -252,7 +287,7 @@ class MainWindow(QtGui.QMainWindow, CommandBase): self.setWindowIcon(QtGui.QIcon(get_file('files/images/icon.png'))) self.update_json = False - self.setup_nw_versions() + self.setup_electron_versions() self.thread = None self.original_packagejson = {} @@ -266,22 +301,22 @@ class MainWindow(QtGui.QMainWindow, CommandBase): self.option_settings_enabled(False) self.setWindowTitle(u"Web2Executable {}".format(__gui_version__)) - self.update_nw_versions(None) + self.update_electron_versions(None) def open_recent_file(self): action = self.sender() if action: self.load_project(action.data()) - def setup_nw_versions(self): - nw_version = self.get_setting('nw_version') + def setup_electron_versions(self): + electron_version = self.get_setting('electron_version') try: - f = codecs.open(get_data_file_path('files/nw-versions.txt'), encoding='utf-8') + f = codecs.open(get_data_file_path('files/electron-versions.txt'), encoding='utf-8') for line in f: - nw_version.values.append(line.strip()) + electron_version.values.append(line.strip()) f.close() except IOError: - nw_version.values.append(nw_version.default_value) + electron_version.values.append(electron_version.default_value) def create_application_layout(self): self.main_layout = QtGui.QVBoxLayout() @@ -303,12 +338,14 @@ class MainWindow(QtGui.QMainWindow, CommandBase): self.comp_settings_widget = self.create_compression_settings() self.win_settings_widget = self.create_window_settings() self.ex_settings_widget = self.create_export_settings() + self.web_prefs_widget = self.create_web_prefs_settings() self.dl_settings_widget = self.create_download_settings() self.directory_chooser_widget = self.create_directory_choose() def addWidgets_to_main_layout(self): self.warning_settings_icon = QtGui.QIcon(get_file('files/images/warning.png')) self.app_settings_icon = QtGui.QIcon(get_file('files/images/app_settings.png')) + self.web_prefs_icon = QtGui.QIcon(get_file('files/images/web_prefs.png')) self.win_settings_icon = QtGui.QIcon(get_file('files/images/window_settings.png')) self.ex_settings_icon = QtGui.QIcon(get_file('files/images/export_settings.png')) self.comp_settings_icon = QtGui.QIcon(get_file('files/images/compress_settings.png')) @@ -316,22 +353,32 @@ class MainWindow(QtGui.QMainWindow, CommandBase): self.tab_icons = [self.app_settings_icon, self.win_settings_icon, + self.web_prefs_icon, self.ex_settings_icon, self.comp_settings_icon, self.download_settings_icon] self.main_layout.addWidget(self.directory_chooser_widget) + self.tab_widget.addTab(self.app_settings_widget, self.app_settings_icon, 'App Settings') + self.tab_widget.addTab(self.win_settings_widget, self.win_settings_icon, 'Window Settings') + + self.tab_widget.addTab(self.web_prefs_widget, + self.web_prefs_icon, + 'Web Preferences') + self.tab_widget.addTab(self.ex_settings_widget, self.ex_settings_icon, 'Export Settings') + self.tab_widget.addTab(self.comp_settings_widget, self.comp_settings_icon, 'Compression Settings') + self.tab_widget.addTab(self.dl_settings_widget, self.download_settings_icon, 'Download Settings') @@ -344,6 +391,7 @@ class MainWindow(QtGui.QMainWindow, CommandBase): self.app_settings_widget.setEnabled(is_enabled) self.win_settings_widget.setEnabled(is_enabled) self.ex_settings_widget.setEnabled(is_enabled) + self.web_prefs_widget.setEnabled(is_enabled) self.comp_settings_widget.setEnabled(is_enabled) self.dl_settings_widget.setEnabled(is_enabled) self.options_enabled = is_enabled @@ -372,7 +420,7 @@ class MainWindow(QtGui.QMainWindow, CommandBase): 'the export options!')) def selected_version(self): - return self.get_setting('nw_version').value + return self.get_setting('electron_version').value def enable_ui_after_error(self): self.enable_ui() @@ -395,10 +443,11 @@ class MainWindow(QtGui.QMainWindow, CommandBase): options_dict = {'app_settings': 0, 'webkit_settings': 0, 'window_settings': 1, - 'export_settings': 2, - 'web2exe_settings': 2, - 'compression': 3, - 'download_settings': 4} + 'web_preferences': 2, + 'export_settings': 3, + 'web2exe_settings': 3, + 'compression': 4, + 'download_settings': 5} for setting_group_name, setting_group in self._setting_items: if name in setting_group: return options_dict.get(setting_group_name, None) @@ -428,7 +477,7 @@ class MainWindow(QtGui.QMainWindow, CommandBase): self.tab_widget.setTabIcon(tab, self.warning_settings_icon) if (setting.type == 'file' and - setting.value): + setting.value and setting.exists): setting_path_invalid = not os.path.exists(setting_path) setting_url_invalid = not url_exists(setting.value) if setting_path_invalid and setting_url_invalid: @@ -487,9 +536,6 @@ class MainWindow(QtGui.QMainWindow, CommandBase): def project_dir(self): return self.project_path - if hasattr(self, 'input_line'): - return self.input_line.text() - return '' def output_dir(self): if hasattr(self, 'output_line'): @@ -549,6 +595,7 @@ class MainWindow(QtGui.QMainWindow, CommandBase): self.open_export_button = open_export_button http = QHttp(self) + http.sslErrors.connect(self.https_error) http.requestFinished.connect(self.http_request_finished) http.dataReadProgress.connect(self.update_progress_bar) http.responseHeaderReceived.connect(self.read_response_header) @@ -557,6 +604,9 @@ class MainWindow(QtGui.QMainWindow, CommandBase): return hlayout + def https_error(self, errors): + print(errors) + def read_response_header(self, response_header): # Check for genuine error conditions. if response_header.statusCode() not in (200, 300, 301, 302, 303, 307): @@ -620,11 +670,11 @@ class MainWindow(QtGui.QMainWindow, CommandBase): self.ex_button.setEnabled(self.required_settings_filled()) self.progress_text = 'Done retrieving versions.' - nw_version = self.get_setting('nw_version') - combo = self.find_child_by_name(nw_version.name) + electron_version = self.get_setting('electron_version') + combo = self.find_child_by_name(electron_version.name) combo.clear() - combo.addItems(nw_version.values) + combo.addItems(electron_version.values) def make_output_files_in_background(self): self.ex_button.setEnabled(False) @@ -696,23 +746,15 @@ class MainWindow(QtGui.QMainWindow, CommandBase): versions = re.findall('v(\d+)\.(\d+)\.(\d+)', path)[0] - minor = int(versions[1]) - if minor >= 12: - path = path.replace('node-webkit', 'nwjs') - self.progress_text = u'Downloading {}'.format(path.replace(version_file, '')) + path = self.get_redirected_url(path) + url = QUrl(path) file_name = setting.save_file_path(self.selected_version(), location) archive_exists = QFile.exists(file_name) - #dest_files_exist = False - - # for dest_file in setting.dest_files: - # dest_file_path = utils.path_join('files', setting.name, dest_file) - # dest_files_exist &= QFile.exists(dest_file_path) - forced = self.get_setting('force_download').value if archive_exists and not forced: @@ -728,21 +770,17 @@ class MainWindow(QtGui.QMainWindow, CommandBase): self.enable_ui() return - mode = QHttp.ConnectionModeHttp + mode = QHttp.ConnectionModeHttps port = url.port() + if port == -1: port = 0 + self.http.setHost(url.host(), mode, port) self.http_request_aborted = False - path = QUrl.toPercentEncoding(url.path(), "!$&'()*+,;=:@/") - if path: - path = str(path) - else: - path = u'/' - # Download the file. - self.http_get_id = self.http.get(path, self.out_file) + self.http_get_id = self.http.get(path, to=self.out_file) def create_icon_box(self, name, text): style = 'width:48px;height:48px;background-color:white;border-radius:5px;border:1px solid rgb(50,50,50);' @@ -889,7 +927,7 @@ class MainWindow(QtGui.QMainWindow, CommandBase): proj_name = os.path.basename(directory) self.title_label.setText(proj_name) - setting_input = self.find_child_by_name('main') + setting_input = self.find_child_by_name('main_html') files = (glob.glob(utils.path_join(directory, 'index.html')) + glob.glob(utils.path_join(directory, 'index.php')) + glob.glob(utils.path_join(directory, 'index.htm'))) @@ -898,13 +936,8 @@ class MainWindow(QtGui.QMainWindow, CommandBase): setting_input.setText(files[0].replace(self.project_dir() + os.path.sep, '')) app_name_input = self.find_child_by_name('app_name') - name_input = self.find_child_by_name('name') - name_setting = self.get_setting('name') title_input = self.find_child_by_name('title') - if not name_input.text(): - name_input.setText(name_setting.filter_name(proj_name)) - if not app_name_input.text(): app_name_input.setText(proj_name) @@ -948,6 +981,12 @@ class MainWindow(QtGui.QMainWindow, CommandBase): text_obj.setText(file_path) setting.last_value = file_path + def get_color(self, obj, color_disp, initial, setting, *args, **kwargs): + color = QtGui.QColorDialog.getColor(QtGui.QColor(initial), obj, 'Choose Color') + if color: + color_disp.setColor(color) + setting.last_value = color.name() + def get_file_reg(self, obj, text_obj, setting, file_types, *args, **kwargs): file_path, _ = QtGui.QFileDialog.getOpenFileName(self, 'Choose File', (setting.last_value or @@ -1002,6 +1041,11 @@ class MainWindow(QtGui.QMainWindow, CommandBase): return self.create_list_setting(name) elif setting.type == 'range': return self.create_range_setting(name) + elif setting.type == 'color': + return self.create_color_setting(name) + else: + print('Setting "{}" type "{}" not defined.'.format(name, setting.type)) + return QtGui.QHBoxLayout() def create_window_settings(self): group_box = QtGui.QWidget() @@ -1010,6 +1054,13 @@ class MainWindow(QtGui.QMainWindow, CommandBase): group_box.setLayout(vlayout) return group_box + def create_web_prefs_settings(self): + group_box = QtGui.QWidget() + vlayout = self.create_layout(self.settings['order']['web_prefs_order'], cols=3) + + group_box.setLayout(vlayout) + return group_box + def create_export_settings(self): group_box = QtGui.QWidget() vlayout = self.create_layout(self.settings['order']['export_setting_order'], cols=4) @@ -1097,8 +1148,8 @@ class MainWindow(QtGui.QMainWindow, CommandBase): setting_label.setToolTip(setting.description) setting_label.setStatusTip(setting.description) glayout.addWidget(setting_label, row, col) - glayout.addLayout(self.create_setting(setting_name), - row, col+1) + sett = self.create_setting(setting_name) + glayout.addLayout(sett, row, col+1) col += 2 return glayout @@ -1115,7 +1166,7 @@ class MainWindow(QtGui.QMainWindow, CommandBase): text.textChanged.connect(self.call_with_object('setting_changed', text, setting)) if setting.value: - text.setText(setting.value) + text.setText(str(setting.value)) text.setStatusTip(setting.description) text.setToolTip(setting.description) @@ -1151,6 +1202,37 @@ class MainWindow(QtGui.QMainWindow, CommandBase): return hlayout + def create_color_setting(self, name): + hlayout = QtGui.QHBoxLayout() + + setting = self.get_setting(name) + + color_disp = ColorDisplay(setting.value or setting.default_value) + color_disp.setObjectName(setting.name) + color_disp.setContentsMargins(5, 5, 5, 5) + + button = QtGui.QPushButton('Choose...') + button.setMaximumWidth(100) + button.setMaximumHeight(26) + + button.clicked.connect(self.call_with_object('get_color', button, + color_disp, + setting.value or setting.default_value, + setting)) + + if setting.value: + color_disp.setColor(setting.value) + color_disp.setStatusTip(setting.description) + color_disp.setToolTip(setting.description) + + color_disp.colorChanged.connect(self.call_with_object('setting_changed', + color_disp, setting)) + + hlayout.addWidget(color_disp) + hlayout.addWidget(button) + + return hlayout + def create_text_input_with_folder_setting(self, name): hlayout = QtGui.QHBoxLayout() @@ -1218,6 +1300,12 @@ class MainWindow(QtGui.QMainWindow, CommandBase): old_val = setting.default_value setting.value = old_val widget.setValue(old_val) + elif setting.type == 'color': + old_val = '' + if setting.default_value is not None: + old_val = setting.default_value + setting.value = old_val + widget.setColor(setting.value) def set_kiosk_emulation_options(self, is_checked): if is_checked: @@ -1252,7 +1340,7 @@ class MainWindow(QtGui.QMainWindow, CommandBase): if (setting.type == 'string' or setting.type == 'file' or setting.type == 'folder'): - setting.value = args[0] + setting.value = setting.convert(args[0]) elif setting.type == 'strings': setting.value = args[0].split(',') elif setting.type == 'check': @@ -1264,6 +1352,8 @@ class MainWindow(QtGui.QMainWindow, CommandBase): setting.value = obj.currentText() elif setting.type == 'range': setting.value = obj.value() + elif setting.type == 'color': + setting.value = args[0].name() if setting.action is not None: action = getattr(self, setting.action, None) @@ -1362,16 +1452,17 @@ class MainWindow(QtGui.QMainWindow, CommandBase): slider.valueChanged.connect(self.call_with_object('setting_changed', slider, setting)) + slider.setMinimumWidth(60) slider.setObjectName(setting.name) slider.setValue(setting.default_value) slider.setStatusTip(setting.description) slider.setToolTip(setting.description) range_label = QtGui.QLabel(str(setting.default_value)) - range_label.setMaximumWidth(30) + range_label.setMaximumWidth(45) slider.valueChanged.connect(self.call_with_object('_update_range_label', - range_label)) + range_label, setting)) w = QtGui.QWidget() whlayout = QtGui.QHBoxLayout() @@ -1383,8 +1474,8 @@ class MainWindow(QtGui.QMainWindow, CommandBase): return hlayout - def _update_range_label(self, label, value): - label.setText(str(value)) + def _update_range_label(self, label, setting, value): + label.setText(str(value)+setting.label_suffix) def load_package_json(self, json_path=None): setting_list = super(MainWindow, self).load_package_json(json_path) @@ -1408,6 +1499,8 @@ class MainWindow(QtGui.QMainWindow, CommandBase): setting_field.setCurrentIndex(index) if setting.type == 'range': setting_field.setValue(int(setting.value)) + if setting.type == 'color': + setting_field.setColor(setting.value) self.ex_button.setEnabled(self.required_settings_filled()) def show_and_raise(self): @@ -1425,7 +1518,7 @@ if __name__ == '__main__': QCoreApplication.setOrganizationName("SimplyPixelated") QCoreApplication.setOrganizationDomain("simplypixelated.com") - frame = MainWindow(900, 500, app) + frame = MainWindow(1000, 500, app) frame.show_and_raise() sys.exit(app.exec_()) diff --git a/utils.py b/utils.py index 8672e9b..84e53a3 100644 --- a/utils.py +++ b/utils.py @@ -19,6 +19,10 @@ def is_windows(): def get_temp_dir(): return tempfile.gettempdir() +def to_camel_case(underscore): + res = ''.join(x.capitalize() or '_' for x in underscore.split('_')) + return res[:1].lower() + res[1:] + def path_join(base, *rest): new_rest = [] for i in range(len(rest)):