").append(st.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,a||[e.responseText,t,e])}),this},st.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){st.fn[t]=function(e){return this.on(t,e)}}),st.each(["get","post"],function(e,n){st[n]=function(e,r,i,o){return st.isFunction(r)&&(o=o||i,i=r,r=t),st.ajax({url:e,type:n,dataType:o,data:r,success:i})}}),st.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Dn,type:"GET",isLocal:Fn.test(jn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":In,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":st.parseJSON,"text xml":st.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?H(H(e,st.ajaxSettings),t):H(st.ajaxSettings,e)},ajaxPrefilter:D(Wn),ajaxTransport:D($n),ajax:function(e,n){function r(e,n,r,s){var l,f,v,b,T,N=n;2!==x&&(x=2,u&&clearTimeout(u),i=t,a=s||"",w.readyState=e>0?4:0,r&&(b=M(p,w,r)),e>=200&&300>e||304===e?(p.ifModified&&(T=w.getResponseHeader("Last-Modified"),T&&(st.lastModified[o]=T),T=w.getResponseHeader("etag"),T&&(st.etag[o]=T)),304===e?(l=!0,N="notmodified"):(l=q(p,b),N=l.state,f=l.data,v=l.error,l=!v)):(v=N,(e||!N)&&(N="error",0>e&&(e=0))),w.status=e,w.statusText=(n||N)+"",l?g.resolveWith(d,[f,N,w]):g.rejectWith(d,[w,N,v]),w.statusCode(y),y=t,c&&h.trigger(l?"ajaxSuccess":"ajaxError",[w,p,l?f:v]),m.fireWith(d,[w,N]),c&&(h.trigger("ajaxComplete",[w,p]),--st.active||st.event.trigger("ajaxStop")))}"object"==typeof e&&(n=e,e=t),n=n||{};var i,o,a,s,u,l,c,f,p=st.ajaxSetup({},n),d=p.context||p,h=p.context&&(d.nodeType||d.jquery)?st(d):st.event,g=st.Deferred(),m=st.Callbacks("once memory"),y=p.statusCode||{},v={},b={},x=0,T="canceled",w={readyState:0,getResponseHeader:function(e){var t;if(2===x){if(!s)for(s={};t=_n.exec(a);)s[t[1].toLowerCase()]=t[2];t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===x?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return x||(e=b[n]=b[n]||e,v[e]=t),this},overrideMimeType:function(e){return x||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>x)for(t in e)y[t]=[y[t],e[t]];else w.always(e[w.status]);return this},abort:function(e){var t=e||T;return i&&i.abort(t),r(0,t),this}};if(g.promise(w).complete=m.add,w.success=w.done,w.error=w.fail,p.url=((e||p.url||Dn)+"").replace(Mn,"").replace(Bn,jn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=st.trim(p.dataType||"*").toLowerCase().match(lt)||[""],null==p.crossDomain&&(l=Pn.exec(p.url.toLowerCase()),p.crossDomain=!(!l||l[1]===jn[1]&&l[2]===jn[2]&&(l[3]||("http:"===l[1]?80:443))==(jn[3]||("http:"===jn[1]?80:443)))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=st.param(p.data,p.traditional)),L(Wn,p,n,w),2===x)return w;c=p.global,c&&0===st.active++&&st.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!On.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(Hn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=qn.test(o)?o.replace(qn,"$1_="+Ln++):o+(Hn.test(o)?"&":"?")+"_="+Ln++)),p.ifModified&&(st.lastModified[o]&&w.setRequestHeader("If-Modified-Since",st.lastModified[o]),st.etag[o]&&w.setRequestHeader("If-None-Match",st.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&w.setRequestHeader("Content-Type",p.contentType),w.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+In+"; q=0.01":""):p.accepts["*"]);for(f in p.headers)w.setRequestHeader(f,p.headers[f]);if(p.beforeSend&&(p.beforeSend.call(d,w,p)===!1||2===x))return w.abort();T="abort";for(f in{success:1,error:1,complete:1})w[f](p[f]);if(i=L($n,p,n,w)){w.readyState=1,c&&h.trigger("ajaxSend",[w,p]),p.async&&p.timeout>0&&(u=setTimeout(function(){w.abort("timeout")},p.timeout));try{x=1,i.send(v,r)}catch(N){if(!(2>x))throw N;r(-1,N)}}else r(-1,"No Transport");return w},getScript:function(e,n){return st.get(e,t,n,"script")},getJSON:function(e,t,n){return st.get(e,t,n,"json")}}),st.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return st.globalEval(e),e}}}),st.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),st.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=V.head||st("head")[0]||V.documentElement;return{send:function(t,i){n=V.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Xn=[],Un=/(=)\?(?=&|$)|\?\?/;st.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xn.pop()||st.expando+"_"+Ln++;return this[e]=!0,e}}),st.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,u=n.jsonp!==!1&&(Un.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Un.test(n.data)&&"data");return u||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=st.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,u?n[u]=n[u].replace(Un,"$1"+o):n.jsonp!==!1&&(n.url+=(Hn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||st.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Xn.push(o)),s&&st.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Vn,Yn,Jn=0,Gn=e.ActiveXObject&&function(){var e;for(e in Vn)Vn[e](t,!0)};st.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&_()||F()}:_,Yn=st.ajaxSettings.xhr(),st.support.cors=!!Yn&&"withCredentials"in Yn,Yn=st.support.ajax=!!Yn,Yn&&st.ajaxTransport(function(n){if(!n.crossDomain||st.support.cors){var r;return{send:function(i,o){var a,s,u=n.xhr();if(n.username?u.open(n.type,n.url,n.async,n.username,n.password):u.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)u[s]=n.xhrFields[s];n.mimeType&&u.overrideMimeType&&u.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)u.setRequestHeader(s,i[s])}catch(l){}u.send(n.hasContent&&n.data||null),r=function(e,i){var s,l,c,f,p;try{if(r&&(i||4===u.readyState))if(r=t,a&&(u.onreadystatechange=st.noop,Gn&&delete Vn[a]),i)4!==u.readyState&&u.abort();else{f={},s=u.status,p=u.responseXML,c=u.getAllResponseHeaders(),p&&p.documentElement&&(f.xml=p),"string"==typeof u.responseText&&(f.text=u.responseText);try{l=u.statusText}catch(d){l=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=f.text?200:404}}catch(h){i||o(-1,h)}f&&o(s,l,f,c)},n.async?4===u.readyState?setTimeout(r):(a=++Jn,Gn&&(Vn||(Vn={},st(e).unload(Gn)),Vn[a]=r),u.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Qn,Kn,Zn=/^(?:toggle|show|hide)$/,er=RegExp("^(?:([+-])=|)("+ut+")([a-z%]*)$","i"),tr=/queueHooks$/,nr=[W],rr={"*":[function(e,t){var n,r,i=this.createTween(e,t),o=er.exec(t),a=i.cur(),s=+a||0,u=1,l=20;if(o){if(n=+o[2],r=o[3]||(st.cssNumber[e]?"":"px"),"px"!==r&&s){s=st.css(i.elem,e,!0)||n||1;do u=u||".5",s/=u,st.style(i.elem,e,s+r);while(u!==(u=i.cur()/a)&&1!==u&&--l)}i.unit=r,i.start=s,i.end=o[1]?s+(o[1]+1)*n:n}return i}]};st.Animation=st.extend(P,{tweener:function(e,t){st.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");for(var n,r=0,i=e.length;i>r;r++)n=e[r],rr[n]=rr[n]||[],rr[n].unshift(t)},prefilter:function(e,t){t?nr.unshift(e):nr.push(e)}}),st.Tween=$,$.prototype={constructor:$,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(st.cssNumber[n]?"":"px")},cur:function(){var e=$.propHooks[this.prop];return e&&e.get?e.get(this):$.propHooks._default.get(this)},run:function(e){var t,n=$.propHooks[this.prop];return this.pos=t=this.options.duration?st.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):$.propHooks._default.set(this),this}},$.prototype.init.prototype=$.prototype,$.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=st.css(e.elem,e.prop,"auto"),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){st.fx.step[e.prop]?st.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[st.cssProps[e.prop]]||st.cssHooks[e.prop])?st.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},$.propHooks.scrollTop=$.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},st.each(["toggle","show","hide"],function(e,t){var n=st.fn[t];st.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(I(t,!0),e,r,i)}}),st.fn.extend({fadeTo:function(e,t,n,r){return this.filter(w).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=st.isEmptyObject(e),o=st.speed(t,n,r),a=function(){var t=P(this,st.extend({},e),o);a.finish=function(){t.stop(!0)},(i||st._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=st.timers,a=st._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&tr.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&st.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=st._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=st.timers,a=r?r.length:0;for(n.finish=!0,st.queue(this,e,[]),i&&i.cur&&i.cur.finish&&i.cur.finish.call(this),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}}),st.each({slideDown:I("show"),slideUp:I("hide"),slideToggle:I("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){st.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),st.speed=function(e,t,n){var r=e&&"object"==typeof e?st.extend({},e):{complete:n||!n&&t||st.isFunction(e)&&e,duration:e,easing:n&&t||t&&!st.isFunction(t)&&t};return r.duration=st.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in st.fx.speeds?st.fx.speeds[r.duration]:st.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){st.isFunction(r.old)&&r.old.call(this),r.queue&&st.dequeue(this,r.queue)},r},st.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},st.timers=[],st.fx=$.prototype.init,st.fx.tick=function(){var e,n=st.timers,r=0;for(Qn=st.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||st.fx.stop(),Qn=t},st.fx.timer=function(e){e()&&st.timers.push(e)&&st.fx.start()},st.fx.interval=13,st.fx.start=function(){Kn||(Kn=setInterval(st.fx.tick,st.fx.interval))},st.fx.stop=function(){clearInterval(Kn),Kn=null},st.fx.speeds={slow:600,fast:200,_default:400},st.fx.step={},st.expr&&st.expr.filters&&(st.expr.filters.animated=function(e){return st.grep(st.timers,function(t){return e===t.elem}).length}),st.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){st.offset.setOffset(this,e,t)});var n,r,i={top:0,left:0},o=this[0],a=o&&o.ownerDocument;if(a)return n=a.documentElement,st.contains(n,o)?(o.getBoundingClientRect!==t&&(i=o.getBoundingClientRect()),r=z(a),{top:i.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:i.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):i},st.offset={setOffset:function(e,t,n){var r=st.css(e,"position");"static"===r&&(e.style.position="relative");var i,o,a=st(e),s=a.offset(),u=st.css(e,"top"),l=st.css(e,"left"),c=("absolute"===r||"fixed"===r)&&st.inArray("auto",[u,l])>-1,f={},p={};c?(p=a.position(),i=p.top,o=p.left):(i=parseFloat(u)||0,o=parseFloat(l)||0),st.isFunction(t)&&(t=t.call(e,n,s)),null!=t.top&&(f.top=t.top-s.top+i),null!=t.left&&(f.left=t.left-s.left+o),"using"in t?t.using.call(e,f):a.css(f)}},st.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===st.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),st.nodeName(e[0],"html")||(n=e.offset()),n.top+=st.css(e[0],"borderTopWidth",!0),n.left+=st.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-st.css(r,"marginTop",!0),left:t.left-n.left-st.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var e=this.offsetParent||V.documentElement;e&&!st.nodeName(e,"html")&&"static"===st.css(e,"position");)e=e.offsetParent;return e||V.documentElement})}}),st.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);st.fn[e]=function(i){return st.access(this,function(e,i,o){var a=z(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?st(a).scrollLeft():o,r?o:st(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}}),st.each({Height:"height",Width:"width"},function(e,n){st.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){st.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return st.access(this,function(n,r,i){var o;return st.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?st.css(n,r,s):st.style(n,r,i,s)},n,a?i:t,a,null)}})}),e.jQuery=e.$=st,"function"==typeof define&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return st})})(window);
4 | //@ sourceMappingURL=jquery.min.map
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | // Generated by CoffeeScript 1.7.1
2 | (function() {
3 | var __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
4 |
5 | $(function() {
6 | var addConfig, addServer, args, chooseServer, deleteConfig, divWarning, divWarningShown, gui, isRestarting, load, local, menu, os, publicConfig, quit, reloadServerList, restartServer, save, serverHistory, show, tray, update, util, win;
7 | os = require('os');
8 | gui = require('nw.gui');
9 | divWarning = $('#divWarning');
10 | divWarningShown = false;
11 | serverHistory = function() {
12 | return (localStorage['server_history'] || '').split('|');
13 | };
14 | util = require('util');
15 | util.log = function(s) {
16 | console.log(new Date().toLocaleString() + (" - " + s));
17 | if (!divWarningShown) {
18 | divWarning.show();
19 | divWarningShown = true;
20 | }
21 | return divWarning.text(s);
22 | };
23 | args = require('./args');
24 | local = require('shadowsocks');
25 | update = require('./update');
26 | update.checkUpdate(function(url, version) {
27 | var divNewVersion, span;
28 | divNewVersion = $('#divNewVersion');
29 | span = $("
New version " + version + " found, click here to download");
30 | span.click(function() {
31 | return gui.Shell.openExternal(url);
32 | });
33 | divNewVersion.find('.msg').append(span);
34 | return divNewVersion.fadeIn();
35 | });
36 | addServer = function(serverIP) {
37 | var newServers, server, servers, _i, _len;
38 | servers = (localStorage['server_history'] || '').split('|');
39 | servers.push(serverIP);
40 | newServers = [];
41 | for (_i = 0, _len = servers.length; _i < _len; _i++) {
42 | server = servers[_i];
43 | if (server && __indexOf.call(newServers, server) < 0) {
44 | newServers.push(server);
45 | }
46 | }
47 | return localStorage['server_history'] = newServers.join('|');
48 | };
49 | $('#inputServerIP').typeahead({
50 | source: serverHistory
51 | });
52 | chooseServer = function() {
53 | var index;
54 | index = +$(this).attr('data-key');
55 | args.saveIndex(index);
56 | load(false);
57 | return reloadServerList();
58 | };
59 | reloadServerList = function() {
60 | var configName, configs, currentIndex, divider, i, menuItem, serverMenu, _results;
61 | currentIndex = args.loadIndex();
62 | configs = args.allConfigs();
63 | divider = $('#serverIPMenu .insert-point');
64 | serverMenu = $('#serverIPMenu .divider');
65 | $('#serverIPMenu li.server').remove();
66 | i = 0;
67 | _results = [];
68 | for (configName in configs) {
69 | if (i === currentIndex) {
70 | menuItem = $("
" + configs[configName] + " ");
71 | } else {
72 | menuItem = $("
" + configs[configName] + " ");
73 | }
74 | menuItem.find('a').click(chooseServer);
75 | menuItem.insertBefore(divider, serverMenu);
76 | _results.push(i++);
77 | }
78 | return _results;
79 | };
80 | addConfig = function() {
81 | args.saveIndex(NaN);
82 | reloadServerList();
83 | return load(false);
84 | };
85 | deleteConfig = function() {
86 | args.deleteConfig(args.loadIndex());
87 | args.saveIndex(NaN);
88 | reloadServerList();
89 | return load(false);
90 | };
91 | publicConfig = function() {
92 | args.saveIndex(-1);
93 | reloadServerList();
94 | return load(false);
95 | };
96 | save = function() {
97 | var config, index;
98 | config = {};
99 | $('input,select').each(function() {
100 | var key, val;
101 | key = $(this).attr('data-key');
102 | val = $(this).val();
103 | return config[key] = val;
104 | });
105 | index = args.saveConfig(args.loadIndex(), config);
106 | args.saveIndex(index);
107 | reloadServerList();
108 | util.log('config saved');
109 | restartServer(config);
110 | return false;
111 | };
112 | load = function(restart) {
113 | var config;
114 | config = args.loadConfig(args.loadIndex());
115 | $('input,select').each(function() {
116 | var key, val;
117 | key = $(this).attr('data-key');
118 | val = config[key] || '';
119 | $(this).val(val);
120 | return config[key] = this.value;
121 | });
122 | if (restart) {
123 | return restartServer(config);
124 | }
125 | };
126 | isRestarting = false;
127 | restartServer = function(config) {
128 | var e, start;
129 | if (config.server && +config.server_port && config.username && config.password && +config.local_port && config.method && +config.timeout) {
130 | if (isRestarting) {
131 | util.log("Already restarting");
132 | return;
133 | }
134 | isRestarting = true;
135 | start = function() {
136 | var e;
137 | try {
138 | isRestarting = false;
139 | util.log('Starting shadowsocks...');
140 | window.local = local.createServer(config.server, config.server_port, config.local_port, config.password, config.method, 1000 * (config.timeout || 600), '127.0.0.1', config.username);
141 | addServer(config.server);
142 | $('#divError').fadeOut();
143 | return gui.Window.get().hide();
144 | } catch (_error) {
145 | e = _error;
146 | return util.log(e);
147 | }
148 | };
149 | if (window.local != null) {
150 | try {
151 | util.log('Restarting shadowsocks');
152 | if (window.local.address()) {
153 | window.local.close();
154 | }
155 | return setTimeout(start, 1000);
156 | } catch (_error) {
157 | e = _error;
158 | isRestarting = false;
159 | return util.log(e);
160 | }
161 | } else {
162 | return start();
163 | }
164 | } else {
165 | return $('#divError').fadeIn();
166 | }
167 | };
168 | $('#buttonSave').on('click', save);
169 | $('#buttonNewProfile').on('click', addConfig);
170 | $('#buttonDeleteProfile').on('click', deleteConfig);
171 | $('#buttonPublicServer').on('click', publicConfig);
172 | $('#buttonConsole').on('click', function() {
173 | return gui.Window.get().showDevTools();
174 | });
175 | $('#buttonAbout').on('click', function() {
176 | return gui.Shell.openExternal('https://github.com/shadowsocks/shadowsocks-gui');
177 | });
178 | tray = new gui.Tray({
179 | icon: 'menu_icon@2x.png'
180 | });
181 | menu = new gui.Menu();
182 | tray.on('click', function() {
183 | return gui.Window.get().show();
184 | });
185 | show = new gui.MenuItem({
186 | type: 'normal',
187 | label: 'Show',
188 | click: function() {
189 | return gui.Window.get().show();
190 | }
191 | });
192 | quit = new gui.MenuItem({
193 | type: 'normal',
194 | label: 'Quit',
195 | click: function() {
196 | return gui.Window.get().close(true);
197 | }
198 | });
199 | show.add;
200 | menu.append(show);
201 | menu.append(quit);
202 | tray.menu = menu;
203 | window.tray = tray;
204 | win = gui.Window.get();
205 | win.on('minimize', function() {
206 | return this.hide();
207 | });
208 | win.on('close', function(quit) {
209 | if (os.platform() === 'darwin' && !quit) {
210 | return this.hide();
211 | } else {
212 | return this.close(true);
213 | }
214 | });
215 | reloadServerList();
216 | return load(true);
217 | });
218 |
219 | }).call(this);
220 |
--------------------------------------------------------------------------------
/menu_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lupino/shadowsocks-gui/8bc29e0d009d22194e438e4a14edc63c723447f1/menu_icon.png
--------------------------------------------------------------------------------
/menu_icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lupino/shadowsocks-gui/8bc29e0d009d22194e438e4a14edc63c723447f1/menu_icon@2x.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shadowsocks-gui",
3 | "main": "index.html",
4 | "description": "shadowsocks client with GUI",
5 | "version": "0.4.1",
6 | "keywords": [ "shadowsocks", "node-webkit" ],
7 | "single-instance": false,
8 | "dependencies": {
9 | "shadowsocks": "1.4.4"
10 | },
11 | "window": {
12 | "icon": "icon.png",
13 | "toolbar": false,
14 | "width": 500,
15 | "height": 450,
16 | "position": "mouse",
17 | "min_width": 500,
18 | "min_height": 450,
19 | "max_width": 500,
20 | "max_height": 450
21 | },
22 | "js-flags": "--expose-gc",
23 | "licenses": [
24 | {
25 | "type": "MIT",
26 | "url": "http://opensource.org/licenses/MIT"
27 | }
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lupino/shadowsocks-gui/8bc29e0d009d22194e438e4a14edc63c723447f1/screenshot.png
--------------------------------------------------------------------------------
/src/args.coffee:
--------------------------------------------------------------------------------
1 | localStorage = window.localStorage
2 | util = require 'util'
3 |
4 | fs = require 'fs'
5 | guiconfigFilename = fs.realpathSync(process.execPath + '/..') + '/gui-config.json'
6 |
7 | loadFromJSON = ->
8 | # Windows users are happy to see a config file within their shadowsocks-gui folder
9 | if process.platform == 'win32'
10 | try
11 | data = fs.readFileSync guiconfigFilename
12 | temp = JSON.parse data.toString('utf-8')
13 | # make config file easier to read
14 | if temp.configs
15 | temp.configs = JSON.stringify(temp.configs)
16 | localStorage = temp
17 | util.log 'reading config file'
18 | catch e
19 | console.log e
20 |
21 | loadFromJSON()
22 |
23 | saveToJSON = ->
24 | if process.platform == 'win32'
25 | util.log 'saving config file'
26 | # make config file easier to read
27 | temp = JSON.parse(JSON.stringify(localStorage))
28 | if temp.configs
29 | temp.configs = JSON.parse(temp.configs)
30 | data = JSON.stringify(temp, null, 2)
31 | try
32 | fs.writeFileSync guiconfigFilename, data, 'encoding': 'utf-8'
33 | catch e
34 | util.log e
35 |
36 | # This is a public server
37 | publicConfig =
38 | server: '209.141.36.62'
39 | server_port: 8348
40 | local_port: 1080
41 | password: '$#HAL9000!'
42 | method: 'aes-256-cfb'
43 | timeout: 600
44 |
45 | defaultConfig =
46 | server_port: 8388
47 | local_port: 1080
48 | method: 'aes-256-cfb'
49 | timeout: 600
50 |
51 |
52 | loadConfigs = ->
53 | try
54 | JSON.parse(localStorage['configs'] or '[]')
55 | catch e
56 | util.log e
57 | []
58 |
59 | allConfigs = ->
60 | if localStorage['configs']
61 | result = []
62 | try
63 | configs = loadConfigs()
64 | for i of configs
65 | c = configs[i]
66 | result.push "#{c.server}:#{c.server_port}"
67 | return result
68 | catch e
69 | []
70 |
71 | saveIndex = (index) ->
72 | localStorage['index'] = index
73 | saveToJSON()
74 |
75 | loadIndex = ->
76 | +localStorage['index']
77 |
78 | saveConfigs = (configs) ->
79 | localStorage['configs'] = JSON.stringify(configs)
80 | saveToJSON()
81 |
82 | saveConfig = (index, config) ->
83 | if index == -1
84 | # if modified based on public server, add a profile, not to modify public server
85 | index = NaN
86 | configs = loadConfigs()
87 | if isNaN(index)
88 | configs.push config
89 | index = configs.length - 1
90 | else
91 | configs[index] = config
92 | saveConfigs configs
93 | index
94 |
95 | loadConfig = (index) ->
96 | if isNaN(index)
97 | return defaultConfig
98 | if index == -1
99 | return publicConfig
100 | configs = loadConfigs()
101 | return configs[index] or defaultConfig
102 |
103 | deleteConfig = (index) ->
104 | if (not isNaN(index)) and not (index == -1)
105 | configs = loadConfigs()
106 | configs.splice index, 1
107 | saveConfigs configs
108 |
109 | exports.allConfigs = allConfigs
110 | exports.saveConfig = saveConfig
111 | exports.loadConfig = loadConfig
112 | exports.deleteConfig = deleteConfig
113 | exports.loadIndex = loadIndex
114 | exports.saveIndex = saveIndex
115 | exports.publicConfig = publicConfig
116 |
--------------------------------------------------------------------------------
/src/main.coffee:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2014 clowwindy
2 | #
3 | # Permission is hereby granted, free of charge, to any person obtaining a copy
4 | # of this software and associated documentation files (the "Software"), to deal
5 | # in the Software without restriction, including without limitation the rights
6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | # copies of the Software, and to permit persons to whom the Software is
8 | # furnished to do so, subject to the following conditions:
9 | #
10 | # The above copyright notice and this permission notice shall be included in
11 | # all copies or substantial portions of the Software.
12 | #
13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | # SOFTWARE.
20 |
21 | $ ->
22 | os = require 'os'
23 | gui = require 'nw.gui'
24 | # hack util.log
25 |
26 | divWarning = $('#divWarning')
27 | divWarningShown = false
28 | serverHistory = ->
29 | (localStorage['server_history'] || '').split('|')
30 |
31 | util = require 'util'
32 | util.log = (s) ->
33 | console.log new Date().toLocaleString() + " - #{s}"
34 | if not divWarningShown
35 | divWarning.show()
36 | divWarningShown = true
37 | divWarning.text(s)
38 |
39 | args = require './args'
40 | local = require 'shadowsocks'
41 | update = require './update'
42 |
43 | update.checkUpdate (url, version) ->
44 | divNewVersion = $('#divNewVersion')
45 | span = $("
New version #{version} found, click here to download")
46 | span.click ->
47 | gui.Shell.openExternal url
48 | divNewVersion.find('.msg').append span
49 | divNewVersion.fadeIn()
50 |
51 | addServer = (serverIP) ->
52 | servers = (localStorage['server_history'] || '').split('|')
53 | servers.push serverIP
54 | newServers = []
55 | for server in servers
56 | if server and server not in newServers
57 | newServers.push server
58 | localStorage['server_history'] = newServers.join '|'
59 |
60 | $('#inputServerIP').typeahead
61 | source: serverHistory
62 |
63 | chooseServer = ->
64 | index = +$(this).attr('data-key')
65 | args.saveIndex(index)
66 | load false
67 | reloadServerList()
68 |
69 | reloadServerList = ->
70 | currentIndex = args.loadIndex()
71 | configs = args.allConfigs()
72 | divider = $('#serverIPMenu .insert-point')
73 | serverMenu = $('#serverIPMenu .divider')
74 | $('#serverIPMenu li.server').remove()
75 | i = 0
76 | for configName of configs
77 | if i == currentIndex
78 | menuItem = $("
#{configs[configName]} ")
79 | else
80 | menuItem = $("
#{configs[configName]} ")
81 | menuItem.find('a').click chooseServer
82 | menuItem.insertBefore(divider, serverMenu)
83 | i++
84 |
85 | addConfig = ->
86 | args.saveIndex(NaN)
87 | reloadServerList()
88 | load false
89 |
90 | deleteConfig = ->
91 | args.deleteConfig(args.loadIndex())
92 | args.saveIndex(NaN)
93 | reloadServerList()
94 | load false
95 |
96 | publicConfig = ->
97 | args.saveIndex(-1)
98 | reloadServerList()
99 | load false
100 |
101 | save = ->
102 | config = {}
103 | $('input,select').each ->
104 | key = $(this).attr 'data-key'
105 | val = $(this).val()
106 | config[key] = val
107 | index = args.saveConfig(args.loadIndex(), config)
108 | args.saveIndex(index)
109 | reloadServerList()
110 | util.log 'config saved'
111 | restartServer config
112 | false
113 |
114 | load = (restart)->
115 | config = args.loadConfig(args.loadIndex())
116 | $('input,select').each ->
117 | key = $(this).attr 'data-key'
118 | val = config[key] or ''
119 | $(this).val(val)
120 | config[key] = this.value
121 | if restart
122 | restartServer config
123 |
124 | isRestarting = false
125 |
126 | restartServer = (config) ->
127 | if config.server and +config.server_port and config.username and config.password and +config.local_port and config.method and +config.timeout
128 | if isRestarting
129 | util.log "Already restarting"
130 | return
131 | isRestarting = true
132 | start = ->
133 | try
134 | isRestarting = false
135 | util.log 'Starting shadowsocks...'
136 | window.local = local.createServer config.server, config.server_port, config.local_port, config.password, config.method, 1000 * (config.timeout or 600), '127.0.0.1', config.username
137 | addServer config.server
138 | $('#divError').fadeOut()
139 | gui.Window.get().hide()
140 | catch e
141 | util.log e
142 | if window.local?
143 | try
144 | util.log 'Restarting shadowsocks'
145 | if window.local.address()
146 | window.local.close()
147 | setTimeout start, 1000
148 | catch e
149 | isRestarting = false
150 | util.log e
151 | else
152 | start()
153 | else
154 | $('#divError').fadeIn()
155 |
156 | $('#buttonSave').on 'click', save
157 | $('#buttonNewProfile').on 'click', addConfig
158 | $('#buttonDeleteProfile').on 'click', deleteConfig
159 | $('#buttonPublicServer').on 'click', publicConfig
160 | $('#buttonConsole').on 'click', ->
161 | gui.Window.get().showDevTools()
162 | $('#buttonAbout').on 'click', ->
163 | gui.Shell.openExternal 'https://github.com/shadowsocks/shadowsocks-gui'
164 |
165 | tray = new gui.Tray icon: 'menu_icon@2x.png'
166 | menu = new gui.Menu()
167 |
168 | tray.on 'click', ->
169 | gui.Window.get().show()
170 |
171 | show = new gui.MenuItem
172 | type: 'normal'
173 | label: 'Show'
174 | click: ->
175 | gui.Window.get().show()
176 |
177 | quit = new gui.MenuItem
178 | type: 'normal'
179 | label: 'Quit'
180 | click: ->
181 | gui.Window.get().close(true)
182 |
183 | hide = new gui.MenuItem
184 | type: 'normal'
185 | label: 'Hide'
186 | click: ->
187 | gui.Window.get().hide()
188 |
189 | menu.append show
190 | menu.append hide
191 | menu.append quit
192 | tray.menu = menu
193 | window.tray = tray
194 |
195 | win = gui.Window.get()
196 |
197 | win.on 'minimize', ->
198 | this.hide()
199 |
200 | win.on 'close', (quit) ->
201 | if os.platform() == 'darwin' and not quit
202 | this.hide()
203 | else
204 | this.close true
205 |
206 | reloadServerList()
207 | load true
208 |
--------------------------------------------------------------------------------
/src/update.coffee:
--------------------------------------------------------------------------------
1 | util = require 'util'
2 |
3 | platformMap =
4 | 'win32': 'win'
5 | 'darwin': 'osx'
6 | 'linux': 'linux'
7 |
8 | compareVersion = (l, r) ->
9 | # compare two version numbers
10 | ls = l.split '.'
11 | rs = r.split '.'
12 | for i in [0..Math.min(ls.length, rs.length)]
13 | lp = ls[i]
14 | rp = rs[i]
15 | if lp != rp
16 | return lp - rp
17 | return ls.length - rs.length
18 |
19 | checkUpdate = (callback) ->
20 | if callback?
21 | try
22 | packageInfo = require('./package.json')
23 | catch e
24 | util.log e
25 | return
26 | version = packageInfo.version
27 | arch = process.arch
28 | platform = platformMap[process.platform]
29 | # jQuery works well with node-webkit
30 | $ = window.$;
31 | re = /^.*shadowsocks-gui-([\d\.]+)-(\w+)-(\w+)\..*$/
32 | $.get('https://sourceforge.net/api/file/index/project-id/1817190/path/dist/mtime/desc/limit/4/rss',(data) ->
33 | results = []
34 | $(data).find('content').each ->
35 | url = $(this).attr('url')
36 | g = re.exec(url)
37 | if g?
38 | results.push g
39 | # sort versions desc
40 | results.sort (l, r) ->
41 | -compareVersion(l[1], r[1])
42 | # pick latest version
43 | for r in results
44 | if (r[2] == platform) and (r[3] == arch)
45 | if compareVersion(r[1], version) > 0
46 | callback r[0], r[1]
47 | return
48 | ).fail(->
49 | alert("error")
50 | )
51 |
52 | exports.checkUpdate = checkUpdate
53 |
--------------------------------------------------------------------------------
/update.js:
--------------------------------------------------------------------------------
1 | // Generated by CoffeeScript 1.7.1
2 | (function() {
3 | var checkUpdate, compareVersion, platformMap, util;
4 |
5 | util = require('util');
6 |
7 | platformMap = {
8 | 'win32': 'win',
9 | 'darwin': 'osx',
10 | 'linux': 'linux'
11 | };
12 |
13 | compareVersion = function(l, r) {
14 | var i, lp, ls, rp, rs, _i, _ref;
15 | ls = l.split('.');
16 | rs = r.split('.');
17 | for (i = _i = 0, _ref = Math.min(ls.length, rs.length); 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
18 | lp = ls[i];
19 | rp = rs[i];
20 | if (lp !== rp) {
21 | return lp - rp;
22 | }
23 | }
24 | return ls.length - rs.length;
25 | };
26 |
27 | checkUpdate = function(callback) {
28 | var $, arch, e, packageInfo, platform, re, version;
29 | if (callback != null) {
30 | try {
31 | packageInfo = require('./package.json');
32 | } catch (_error) {
33 | e = _error;
34 | util.log(e);
35 | return;
36 | }
37 | version = packageInfo.version;
38 | arch = process.arch;
39 | platform = platformMap[process.platform];
40 | $ = window.$;
41 | re = /^.*shadowsocks-gui-([\d\.]+)-(\w+)-(\w+)\..*$/;
42 | return $.get('https://sourceforge.net/api/file/index/project-id/1817190/path/dist/mtime/desc/limit/4/rss', function(data) {
43 | var r, results, _i, _len;
44 | results = [];
45 | $(data).find('content').each(function() {
46 | var g, url;
47 | url = $(this).attr('url');
48 | g = re.exec(url);
49 | if (g != null) {
50 | return results.push(g);
51 | }
52 | });
53 | results.sort(function(l, r) {
54 | return -compareVersion(l[1], r[1]);
55 | });
56 | for (_i = 0, _len = results.length; _i < _len; _i++) {
57 | r = results[_i];
58 | if ((r[2] === platform) && (r[3] === arch)) {
59 | if (compareVersion(r[1], version) > 0) {
60 | callback(r[0], r[1]);
61 | return;
62 | }
63 | }
64 | }
65 | }).fail(function() {
66 | return alert("error");
67 | });
68 | }
69 | };
70 |
71 | exports.checkUpdate = checkUpdate;
72 |
73 | }).call(this);
74 |
--------------------------------------------------------------------------------
/utils/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | shadowsocks
9 | CFBundleDocumentTypes
10 |
11 | CFBundleExecutable
12 | node-webkit
13 | CFBundleIconFile
14 | shadowsocks.icns
15 | CFBundleIdentifier
16 | org.shadowsocks
17 | CFBundleInfoDictionaryVersion
18 | 6.0
19 | CFBundleName
20 | shadowsocks
21 | CFBundlePackageType
22 | APPL
23 | CFBundleShortVersionString
24 | 1.0
25 | CFBundleVersion
26 | 1.0
27 | LSFileQuarantineEnabled
28 |
29 | LSMinimumSystemVersion
30 | 10.6.0
31 | NSPrincipalClass
32 | NSApplication
33 | NSSupportsAutomaticGraphicsSwitching
34 |
35 | SCMRevision
36 | 199640
37 | UTExportedTypeDeclarations
38 |
39 |
40 | UTTypeConformsTo
41 |
42 | com.pkware.zip-archive
43 |
44 | UTTypeDescription
45 | node-webkit App
46 | UTTypeIconFile
47 | nw.icns
48 | UTTypeIdentifier
49 | com.intel.nw.app
50 | UTTypeReferenceURL
51 | https://github.com/rogerwang/node-webkit/wiki/How-to-package-and-distribute-your-apps
52 | UTTypeTagSpecification
53 |
54 | com.apple.ostype
55 | node-webkit
56 | public.filename-extension
57 |
58 | nw
59 |
60 | public.mime-type
61 | application/x-node-webkit-app
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/utils/build.sh:
--------------------------------------------------------------------------------
1 | NW_VERSION=v0.6.2
2 | if [ $# == 0 ]; then
3 | echo 'usage: build.sh version'
4 | exit 1
5 | fi
6 | pushd `dirname $0`
7 | cd ..
8 | mkdir -p dist
9 | cd dist
10 | rm -rf app
11 | mkdir app
12 | pushd app && \
13 | cp ../../*.js . && \
14 | cp -r ../../css . && \
15 | cp -r ../../img . && \
16 | cp ../../*.json . && \
17 | cp ../../*.htm* . && \
18 | cp ../../*.png . && \
19 | cp -r ../../node_modules . || \
20 | exit 1
21 | rm ../app.nw
22 | zip -r ../app.nw * && \
23 | popd && \
24 | rm -rf app || \
25 | exit 1
26 | for platform in osx-ia32 win-ia32
27 | do
28 | if [ -f shadowsocks-gui-$1-$platform.7z ]; then
29 | continue
30 | fi
31 | if [ ! -f node-webkit-$NW_VERSION-$platform.zip ] ; then
32 | if [ ! -f node-webkit-$NW_VERSION-$platform.tar.gz ] ; then
33 | axel https://s3.amazonaws.com/node-webkit/$NW_VERSION/node-webkit-$NW_VERSION-$platform.zip || \
34 | curl https://s3.amazonaws.com/node-webkit/$NW_VERSION/node-webkit-$NW_VERSION-$platform.zip > node-webkit-$NW_VERSION-$platform.zip || \
35 | axel https://s3.amazonaws.com/node-webkit/$NW_VERSION/node-webkit-$NW_VERSION-$platform.tar.gz || \
36 | curl https://s3.amazonaws.com/node-webkit/$NW_VERSION/node-webkit-$NW_VERSION-$platform.tar.gz > node-webkit-$NW_VERSION-$platform.tar.gz || \
37 | exit 1
38 | fi
39 | fi
40 | mkdir shadowsocks-gui-$1-$platform && \
41 | pushd shadowsocks-gui-$1-$platform && \
42 | unzip ../node-webkit-$NW_VERSION-$platform.zip || \
43 | tar xf ../node-webkit-$NW_VERSION-$platform.tar.gz || \
44 | exit 1
45 | if [ -d node-webkit-$NW_VERSION-$platform ]; then
46 | mv node-webkit-$NW_VERSION-$platform/* ./ && \
47 | rm -r node-webkit-$NW_VERSION-$platform || \
48 | exit 1
49 | fi
50 | if [ $platform == win-ia32 ]; then
51 | cat nw.exe ../app.nw > shadowsocks.exe && \
52 | rm nwsnapshot.exe && \
53 | rm ffmpegsumo.dll && \
54 | rm libEGL.dll && \
55 | rm libGLESv2.dll && \
56 | rm nw.exe || \
57 | exit 1
58 | fi
59 | if [ $platform == osx-ia32 ]; then
60 | rm nwsnapshot && \
61 | cp ../app.nw node-webkit.app/Contents/Resources/ && \
62 | cp ../../utils/Info.plist node-webkit.app/Contents/ && \
63 | cp ../../utils/*.icns node-webkit.app/Contents/Resources/ && \
64 | /usr/libexec/PlistBuddy -c "Set CFBundleVersion $1" node-webkit.app/Contents/Info.plist && \
65 | /usr/libexec/PlistBuddy -c "Set CFBundleShortVersionString $1" node-webkit.app/Contents/Info.plist && \
66 | mv node-webkit.app shadowsocks.app || \
67 | exit 1
68 | fi
69 | popd && \
70 | 7z a -t7z shadowsocks-gui-$1-$platform.7z shadowsocks-gui-$1-$platform && \
71 | rm -r shadowsocks-gui-$1-$platform && \
72 | rsync --progress -e ssh shadowsocks-gui-$1-$platform.7z frs.sourceforge.net:/home/frs/project/shadowsocksgui/dist/shadowsocks-gui-$1-$platform.7z || \
73 | exit 1
74 | done
75 | popd
76 |
--------------------------------------------------------------------------------
/utils/make_icon.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | LAST=`pwd`
4 | pushd /tmp
5 | mkdir icon.iconset
6 | sips -z 16 16 $LAST/icon.png --out icon.iconset/icon_16x16.png
7 | sips -z 32 32 $LAST/icon.png --out icon.iconset/icon_16x16@2x.png
8 | sips -z 32 32 $LAST/icon.png --out icon.iconset/icon_32x32.png
9 | sips -z 64 64 $LAST/icon.png --out icon.iconset/icon_32x32@2x.png
10 | sips -z 128 128 $LAST/icon.png --out icon.iconset/icon_128x128.png
11 | sips -z 256 256 $LAST/icon.png --out icon.iconset/icon_128x128@2x.png
12 | sips -z 256 256 $LAST/icon.png --out icon.iconset/icon_256x256.png
13 | sips -z 512 512 $LAST/icon.png --out icon.iconset/icon_256x256@2x.png
14 | sips -z 512 512 $LAST/icon.png --out icon.iconset/icon_512x512.png
15 | cp $LAST/icon.png icon.iconset/icon_512x512@2x.png
16 | iconutil -c icns icon.iconset
17 | rm -R icon.iconset
18 | popd
19 | mv /tmp/icon.icns utils/shadowsocks.icns
--------------------------------------------------------------------------------
/utils/shadowsocks.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lupino/shadowsocks-gui/8bc29e0d009d22194e438e4a14edc63c723447f1/utils/shadowsocks.icns
--------------------------------------------------------------------------------