├── .gitignore ├── .gitmodules ├── CHANGES ├── CONTRIBUTING.md ├── Cakefile ├── LICENSE ├── README.md ├── args.js ├── bootstrap.min.js ├── bootstrap3-typeahead.min.js ├── css └── bootstrap.min.css ├── fonts ├── glyphicons-halflings-regular.eot ├── glyphicons-halflings-regular.svg ├── glyphicons-halflings-regular.ttf └── glyphicons-halflings-regular.woff ├── icon.png ├── img ├── glyphicons-halflings-white.png └── glyphicons-halflings.png ├── index.html ├── jquery-1.9.0.min.js ├── main.js ├── menu_icon.png ├── menu_icon@2x.png ├── package.json ├── qrcode.min.js ├── screenshot.png ├── src ├── args.coffee ├── main.coffee └── update.coffee ├── update.js └── utils ├── Info.plist ├── build.sh ├── linux └── start.sh ├── make_icon.sh └── shadowsocks.icns /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node-webkit.app/ 3 | node_modules/ 4 | nwsnapshot 5 | gui-config.json 6 | *.exe 7 | *.dll 8 | *.pak 9 | .idea 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Long-live-shadowsocks/shadowsocks-gui/069dd20c00d9f188bd1994b4c4b4f6aa177b8492/.gitmodules -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 0.6.4 2014-11-01 2 | - Support share over LAN 3 | 4 | 0.6.2 2014-10-12 5 | - Fix IPv6 address 6 | 7 | 0.6.1 2014-10-09 8 | - Support QRCode 9 | 10 | 0.6.0 2014-09-23 11 | - Fix Linux on some latest distributions 12 | 13 | 0.5.0 2014-09-09 14 | - Add RC4-MD5 encryption 15 | 16 | 0.4.1 2014-03-28 17 | - Close button should hide the window instead of quit on OS X 18 | - Listen to 127.0.0.1 instead of 0.0.0.0 19 | 20 | 0.4 2013-07-31 21 | - Update shadowsocks-nodejs to 1.4.1 22 | 23 | 0.3.0 2013-07-21 24 | - Update shadowsocks-nodejs to 1.4.0 25 | 26 | 0.2.2 2013-07-15 27 | - Fix keyboard on OSX; disabled hiding Dock icon 28 | 29 | 0.2.1 2013-07-14 30 | - Save config to gui-config.json file on Windows 31 | - Fix incorrect window size on Windows 32 | - Hide Dock icon on OSX 33 | - Check for updates 34 | - Reduce binary size 35 | - Run gc() every 30s to reduce memory usage 36 | 37 | 0.2 2013-07-12 38 | - Support multiple profiles 39 | - Fix problems with restarting shadowsocks 40 | 41 | 0.1.5 2013-06-23 42 | - Package binary into a single exe/app 43 | 44 | 0.1.4 2013-06-22 45 | - Minimize on startup and after config is saved 46 | 47 | 0.1.3 2013-06-19 48 | - Update shadowsocks-nodejs 49 | 50 | 0.1.2 2013-06-18 51 | - Update shadowsocks-nodejs 52 | 53 | 0.1.1 2013-05-30 54 | - Auto-Complete Server IP 55 | 56 | 0.1 2013-05-29 57 | - Initial version 58 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | How to contribute 2 | ================= 3 | 4 | Before you submit issues, please take a few minutes to read this guide. 5 | 6 | 在你提交问题前,请花两分钟阅读一下这份指南。 7 | 8 | Issues 9 | ------ 10 | 11 | Please include the following information in your submission: 12 | 13 | 1. How did you set up your environment? (OS, version of Shadowsocks) 14 | 2. Where did you see this error, was it on local or on server? 15 | 3. What happened in your browser? Just no response, or any error message? 16 | 4. 10 lines of log on the local side of shadowsocks when the error happened. 17 | 5. 10 lines of log on the server side of shadowsocks when the error happened. 18 | 6. Any other useful information. 19 | 20 | Skip any of them if you don't know its meaning. 21 | 22 | 问题反馈 23 | ------- 24 | 25 | 请提交下面的信息: 26 | 27 | 1. 你是如何搭建环境的?(操作系统,Shadowsocks 版本) 28 | 2. 错误是发生在哪里,本地还是服务器? 29 | 3. 浏览器里的现象是什么?一直转菊花,还是有提示错误? 30 | 4. 发生错误时,本地端的十行完整的日志。 31 | 5. 发生错误时,服务器端的十行完整的日志。 32 | 6. 其它你认为可能和问题有关的信息。 33 | 34 | 如果你不清楚其中某条的含义, 可以直接跳过那一条。 35 | -------------------------------------------------------------------------------- /Cakefile: -------------------------------------------------------------------------------- 1 | {print} = require 'util' 2 | {spawn} = require 'child_process' 3 | 4 | build = () -> 5 | os = require 'os' 6 | if os.platform() == 'win32' 7 | coffeeCmd = 'coffee.cmd' 8 | else 9 | coffeeCmd = 'coffee' 10 | coffee = spawn coffeeCmd, ['-c', '-o', '.', 'src'] 11 | coffee.stderr.on 'data', (data) -> 12 | process.stderr.write data.toString() 13 | coffee.stdout.on 'data', (data) -> 14 | print data.toString() 15 | coffee.on 'exit', (code) -> 16 | if code != 0 17 | process.exit code 18 | 19 | task 'build', 'Build ./ from src/', -> 20 | build() 21 | 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | shadowsocks-gui 2 | 3 | Copyright (c) 2014 clowwindy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | shadowsocks-gui 2 | =============== 3 | 4 | #### Windows 5 | 6 | Please upgrade to [Shadowsocks for Windows](https://github.com/clowwindy/shadowsocks-csharp) 7 | 8 | #### OS X 9 | 10 | Please upgrade to [ShadowsocksX](https://github.com/shadowsocks/shadowsocks-iOS/wiki/Shadowsocks-for-OSX-Help) 11 | 12 | #### Linux users 13 | 14 | Please upgrade to [shadowsocks-qt5](https://github.com/librehat/shadowsocks-qt5) 15 | -------------------------------------------------------------------------------- /args.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | (function() { 3 | var allConfigs, defaultConfig, deleteConfig, fs, guiconfigFilename, loadConfig, loadConfigs, loadFromJSON, loadIndex, localStorage, publicConfig, saveConfig, saveConfigs, saveIndex, saveToJSON, util; 4 | 5 | localStorage = window.localStorage; 6 | 7 | util = require('util'); 8 | 9 | fs = require('fs'); 10 | 11 | guiconfigFilename = fs.realpathSync(process.execPath + '/..') + '/gui-config.json'; 12 | 13 | loadFromJSON = function() { 14 | var data, e, temp; 15 | if (process.platform === 'win32') { 16 | try { 17 | data = fs.readFileSync(guiconfigFilename); 18 | temp = JSON.parse(data.toString('utf-8')); 19 | if (temp.configs) { 20 | temp.configs = JSON.stringify(temp.configs); 21 | } 22 | localStorage = temp; 23 | return util.log('reading config file'); 24 | } catch (_error) { 25 | e = _error; 26 | return console.log(e); 27 | } 28 | } 29 | }; 30 | 31 | loadFromJSON(); 32 | 33 | saveToJSON = function() { 34 | var data, e, temp; 35 | if (process.platform === 'win32') { 36 | util.log('saving config file'); 37 | temp = JSON.parse(JSON.stringify(localStorage)); 38 | if (temp.configs) { 39 | temp.configs = JSON.parse(temp.configs); 40 | } 41 | data = JSON.stringify(temp, null, 2); 42 | try { 43 | return fs.writeFileSync(guiconfigFilename, data, { 44 | 'encoding': 'utf-8' 45 | }); 46 | } catch (_error) { 47 | e = _error; 48 | return util.log(e); 49 | } 50 | } 51 | }; 52 | 53 | publicConfig = { 54 | server: '209.141.36.62', 55 | server_port: 8348, 56 | local_port: 1080, 57 | password: '$#HAL9000!', 58 | method: 'aes-256-cfb', 59 | timeout: 600 60 | }; 61 | 62 | defaultConfig = { 63 | server_port: 8388, 64 | local_port: 1080, 65 | method: 'aes-256-cfb', 66 | timeout: 600 67 | }; 68 | 69 | loadConfigs = function() { 70 | var e; 71 | try { 72 | return JSON.parse(localStorage['configs'] || '[]'); 73 | } catch (_error) { 74 | e = _error; 75 | util.log(e); 76 | return []; 77 | } 78 | }; 79 | 80 | allConfigs = function() { 81 | var c, configs, e, i, result; 82 | if (localStorage['configs']) { 83 | result = []; 84 | try { 85 | configs = loadConfigs(); 86 | for (i in configs) { 87 | c = configs[i]; 88 | result.push("" + c.server + ":" + c.server_port); 89 | } 90 | return result; 91 | } catch (_error) { 92 | e = _error; 93 | } 94 | } 95 | return []; 96 | }; 97 | 98 | saveIndex = function(index) { 99 | localStorage['index'] = index; 100 | return saveToJSON(); 101 | }; 102 | 103 | loadIndex = function() { 104 | return +localStorage['index']; 105 | }; 106 | 107 | saveConfigs = function(configs) { 108 | localStorage['configs'] = JSON.stringify(configs); 109 | return saveToJSON(); 110 | }; 111 | 112 | saveConfig = function(index, config) { 113 | var configs; 114 | if (index === -1) { 115 | index = NaN; 116 | } 117 | configs = loadConfigs(); 118 | if (isNaN(index)) { 119 | configs.push(config); 120 | index = configs.length - 1; 121 | } else { 122 | configs[index] = config; 123 | } 124 | saveConfigs(configs); 125 | return index; 126 | }; 127 | 128 | loadConfig = function(index) { 129 | var configs; 130 | if (isNaN(index)) { 131 | return defaultConfig; 132 | } 133 | if (index === -1) { 134 | return publicConfig; 135 | } 136 | configs = loadConfigs(); 137 | return configs[index] || defaultConfig; 138 | }; 139 | 140 | deleteConfig = function(index) { 141 | var configs; 142 | if ((!isNaN(index)) && !(index === -1)) { 143 | configs = loadConfigs(); 144 | configs.splice(index, 1); 145 | return saveConfigs(configs); 146 | } 147 | }; 148 | 149 | exports.allConfigs = allConfigs; 150 | 151 | exports.saveConfig = saveConfig; 152 | 153 | exports.loadConfig = loadConfig; 154 | 155 | exports.deleteConfig = deleteConfig; 156 | 157 | exports.loadIndex = loadIndex; 158 | 159 | exports.saveIndex = saveIndex; 160 | 161 | exports.publicConfig = publicConfig; 162 | 163 | }).call(this); 164 | -------------------------------------------------------------------------------- /bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.2.0 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.2.0",d.prototype.close=function(b){function c(){f.detach().trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",c).emulateTransitionEnd(150):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.2.0",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),d[e](null==f[b]?this.options[b]:f[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b).on("keydown.bs.carousel",a.proxy(this.keydown,this)),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.2.0",c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},c.prototype.keydown=function(a){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.to=function(b){var c=this,d=this.getItemIndex(this.$active=this.$element.find(".item.active"));return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=e[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:g});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,f&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(e)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:g});return a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one("bsTransitionEnd",function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger(m)),f&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(b=!b),e||d.data("bs.collapse",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};c.VERSION="3.2.0",c.DEFAULTS={toggle:!0},c.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},c.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var c=a.Event("show.bs.collapse");if(this.$element.trigger(c),!c.isDefaultPrevented()){var d=this.$parent&&this.$parent.find("> .panel > .in");if(d&&d.length){var e=d.data("bs.collapse");if(e&&e.transitioning)return;b.call(d,"hide"),e||d.data("bs.collapse",null)}var f=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[f](0),this.transitioning=1;var g=function(){this.$element.removeClass("collapsing").addClass("collapse in")[f](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return g.call(this);var h=a.camelCase(["scroll",f].join("-"));this.$element.one("bsTransitionEnd",a.proxy(g,this)).emulateTransitionEnd(350)[f](this.$element[0][h])}}},c.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},c.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var d=a.fn.collapse;a.fn.collapse=b,a.fn.collapse.Constructor=c,a.fn.collapse.noConflict=function(){return a.fn.collapse=d,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(c){var d,e=a(this),f=e.attr("data-target")||c.preventDefault()||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),g=a(f),h=g.data("bs.collapse"),i=h?"toggle":e.data(),j=e.attr("data-parent"),k=j&&a(j);h&&h.transitioning||(k&&k.find('[data-toggle="collapse"][data-parent="'+j+'"]').not(e).addClass("collapsed"),e[g.hasClass("in")?"addClass":"removeClass"]("collapsed")),b.call(g,i)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.2.0",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('");var query=this.query;var i=item.indexOf(query);var len,leftPart,middlePart,rightPart,strong;len=query.length;if(len==0){return html.text(item).html()}while(i>-1){leftPart=item.substr(0,i);middlePart=item.substr(i,len);rightPart=item.substr(i+len);strong=$("").text(middlePart);html.append(document.createTextNode(leftPart)).append(strong);item=rightPart;i=item.indexOf(query)}return html.append(document.createTextNode(item)).html()},render:function(items){var that=this;items=$(items).map(function(i,item){i=$(that.options.item).data("value",item);i.find("a").html(that.highlighter(item));return i[0]});if(this.autoSelect){items.first().addClass("active")}this.$menu.html(items);return this},next:function(event){var active=this.$menu.find(".active").removeClass("active"),next=active.next();if(!next.length){next=$(this.$menu.find("li")[0])}next.addClass("active")},prev:function(event){var active=this.$menu.find(".active").removeClass("active"),prev=active.prev();if(!prev.length){prev=this.$menu.find("li").last()}prev.addClass("active")},listen:function(){this.$element.on("focus",$.proxy(this.focus,this)).on("blur",$.proxy(this.blur,this)).on("keypress",$.proxy(this.keypress,this)).on("keyup",$.proxy(this.keyup,this));if(this.eventSupported("keydown")){this.$element.on("keydown",$.proxy(this.keydown,this))}this.$menu.on("click",$.proxy(this.click,this)).on("mouseenter","li",$.proxy(this.mouseenter,this)).on("mouseleave","li",$.proxy(this.mouseleave,this))},destroy:function(){this.$element.data("typeahead",null);this.$element.off("focus").off("blur").off("keypress").off("keyup");if(this.eventSupported("keydown")){this.$element.off("keydown")}this.$menu.remove()},eventSupported:function(eventName){var isSupported=eventName in this.$element;if(!isSupported){this.$element.setAttribute(eventName,"return;");isSupported=typeof this.$element[eventName]==="function"}return isSupported},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault();this.prev();break;case 40:e.preventDefault();this.next();break}e.stopPropagation()},keydown:function(e){this.suppressKeyPressRepeat=~$.inArray(e.keyCode,[40,38,9,13,27]);if(!this.shown&&e.keyCode==40){this.lookup("")}else{this.move(e)}},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation();e.preventDefault()},focus:function(e){if(!this.focused){this.focused=true;if(this.options.minLength===0&&!this.$element.val()||this.options.showHintOnFocus){this.lookup()}}},blur:function(e){this.focused=false;if(!this.mousedover&&this.shown)this.hide()},click:function(e){e.stopPropagation();e.preventDefault();this.select();this.$element.focus()},mouseenter:function(e){this.mousedover=true;this.$menu.find(".active").removeClass("active");$(e.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=false;if(!this.focused&&this.shown)this.hide()}};var old=$.fn.typeahead;$.fn.typeahead=function(option){var arg=arguments;return this.each(function(){var $this=$(this),data=$this.data("typeahead"),options=typeof option=="object"&&option;if(!data)$this.data("typeahead",data=new Typeahead(this,options));if(typeof option=="string"){if(arg.length>1){data[option].apply(data,Array.prototype.slice.call(arg,1))}else{data[option]()}}})};$.fn.typeahead.defaults={source:[],items:8,menu:'',item:'
  • ',minLength:1,scrollHeight:0,autoSelect:true};$.fn.typeahead.Constructor=Typeahead;$.fn.typeahead.noConflict=function(){$.fn.typeahead=old;return this};$(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(e){var $this=$(this);if($this.data("typeahead"))return;$this.typeahead($this.data())})}); -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Long-live-shadowsocks/shadowsocks-gui/069dd20c00d9f188bd1994b4c4b4f6aa177b8492/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Long-live-shadowsocks/shadowsocks-gui/069dd20c00d9f188bd1994b4c4b4f6aa177b8492/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Long-live-shadowsocks/shadowsocks-gui/069dd20c00d9f188bd1994b4c4b4f6aa177b8492/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Long-live-shadowsocks/shadowsocks-gui/069dd20c00d9f188bd1994b4c4b4f6aa177b8492/icon.png -------------------------------------------------------------------------------- /img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Long-live-shadowsocks/shadowsocks-gui/069dd20c00d9f188bd1994b4c4b4f6aa177b8492/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Long-live-shadowsocks/shadowsocks-gui/069dd20c00d9f188bd1994b4c4b4f6aa177b8492/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Shadowsocks GUI 5 | 6 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
    43 | Please fill in the blanks and click Save. 44 |
    45 |
    46 |
    47 |
    48 | 49 | 50 |
    51 |
    52 |
    53 | 54 |
    55 |
    56 |
    57 | 58 | 59 | 62 | 69 | 70 |
    71 |
    72 |
    73 |
    74 |
    75 | 76 |
    77 | 78 |
    79 |
    80 |
    81 | 82 |
    83 | 84 |
    85 |
    86 |
    87 | 88 |
    89 | 90 |
    91 |
    92 |
    93 | 94 |
    95 | 112 |
    113 |
    114 |
    115 | 116 |
    117 |
    118 | 121 |
    122 |
    123 |
    124 |
    125 | 126 |
    127 |
    128 | 129 | 130 | 131 | 132 |
    133 |
    134 |
    135 |
    136 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 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, hide, isRestarting, load, local, menu, os, publicConfig, qrcode, 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 | if (this.type === 'checkbox') { 103 | val = this.checked; 104 | } else { 105 | val = $(this).val(); 106 | } 107 | return config[key] = val; 108 | }); 109 | index = args.saveConfig(args.loadIndex(), config); 110 | args.saveIndex(index); 111 | reloadServerList(); 112 | util.log('config saved'); 113 | restartServer(config); 114 | return false; 115 | }; 116 | load = function(restart) { 117 | var config; 118 | config = args.loadConfig(args.loadIndex()); 119 | $('input,select').each(function() { 120 | var key, val; 121 | key = $(this).attr('data-key'); 122 | val = config[key] || ''; 123 | if (this.type === 'checkbox') { 124 | this.checked = val; 125 | return config[key] = this.checked; 126 | } else { 127 | $(this).val(val); 128 | return config[key] = this.value; 129 | } 130 | }); 131 | if (restart) { 132 | return restartServer(config); 133 | } 134 | }; 135 | isRestarting = false; 136 | restartServer = function(config) { 137 | var e, start; 138 | if (config.server && +config.server_port && config.password && +config.local_port && config.method) { 139 | if (isRestarting) { 140 | util.log("Already restarting"); 141 | return; 142 | } 143 | isRestarting = true; 144 | start = function() { 145 | var e; 146 | try { 147 | isRestarting = false; 148 | util.log('Starting shadowsocks...'); 149 | console.log(config); 150 | window.local = local.createServer(config.server, config.server_port, config.local_port, config.password, config.method, 1000 * 600, config.share ? '0.0.0.0' : '127.0.0.1'); 151 | addServer(config.server); 152 | $('#divError').fadeOut(); 153 | return gui.Window.get().hide(); 154 | } catch (_error) { 155 | e = _error; 156 | return util.log(e); 157 | } 158 | }; 159 | if (window.local != null) { 160 | try { 161 | util.log('Restarting shadowsocks'); 162 | if (window.local.address()) { 163 | window.local.close(); 164 | } 165 | return setTimeout(start, 1000); 166 | } catch (_error) { 167 | e = _error; 168 | isRestarting = false; 169 | return util.log(e); 170 | } 171 | } else { 172 | return start(); 173 | } 174 | } else { 175 | return $('#divError').fadeIn(); 176 | } 177 | }; 178 | qrcode = function(method, password, hostname, port) { 179 | return "ss://" + (btoa("" + method + ":" + password + "@" + hostname + ":" + port)); 180 | }; 181 | $('#buttonSave').on('click', save); 182 | $('#buttonNewProfile').on('click', addConfig); 183 | $('#buttonDeleteProfile').on('click', deleteConfig); 184 | $('#buttonPublicServer').on('click', publicConfig); 185 | $('#buttonConsole').on('click', function() { 186 | return gui.Window.get().showDevTools(); 187 | }); 188 | $('#buttonAbout').on('click', function() { 189 | return gui.Shell.openExternal('https://github.com/shadowsocks/shadowsocks-gui'); 190 | }); 191 | $("#buttonQr").on('click', function() { 192 | return window.qrCode.makeCode(qrcode($('#selectMethod').val(), $('#inputPassword').val(), $('#inputServerIP').val(), $('#inputServerPort').val())); 193 | }); 194 | tray = new gui.Tray({ 195 | icon: 'menu_icon@2x.png' 196 | }); 197 | menu = new gui.Menu(); 198 | tray.on('click', function() { 199 | return gui.Window.get().show(); 200 | }); 201 | show = new gui.MenuItem({ 202 | type: 'normal', 203 | label: 'Show', 204 | click: function() { 205 | return gui.Window.get().show(); 206 | } 207 | }); 208 | quit = new gui.MenuItem({ 209 | type: 'normal', 210 | label: 'Quit', 211 | click: function() { 212 | return gui.Window.get().close(true); 213 | } 214 | }); 215 | hide = new gui.MenuItem({ 216 | type: 'normal', 217 | label: 'Hide', 218 | click: function() { 219 | return gui.Window.get().hide(); 220 | } 221 | }); 222 | menu.append(show); 223 | menu.append(hide); 224 | menu.append(quit); 225 | tray.menu = menu; 226 | window.tray = tray; 227 | win = gui.Window.get(); 228 | win.on('minimize', function() { 229 | return this.hide(); 230 | }); 231 | win.on('close', function(quit) { 232 | if (os.platform() === 'darwin' && !quit) { 233 | return this.hide(); 234 | } else { 235 | return this.close(true); 236 | } 237 | }); 238 | reloadServerList(); 239 | return load(true); 240 | }); 241 | 242 | }).call(this); 243 | -------------------------------------------------------------------------------- /menu_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Long-live-shadowsocks/shadowsocks-gui/069dd20c00d9f188bd1994b4c4b4f6aa177b8492/menu_icon.png -------------------------------------------------------------------------------- /menu_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Long-live-shadowsocks/shadowsocks-gui/069dd20c00d9f188bd1994b4c4b4f6aa177b8492/menu_icon@2x.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shadowsocks-gui", 3 | "main": "index.html", 4 | "description": "shadowsocks client with GUI", 5 | "version": "0.6.4", 6 | "keywords": [ "shadowsocks", "node-webkit" ], 7 | "single-instance": false, 8 | "dependencies": { 9 | "shadowsocks": "1.5.2" 10 | }, 11 | "window": { 12 | "icon": "icon.png", 13 | "toolbar": false, 14 | "width": 360, 15 | "height": 420, 16 | "position": "mouse", 17 | "min_width": 360, 18 | "min_height": 420, 19 | "max_width": 360, 20 | "max_height": 420 21 | }, 22 | "js-flags": "--expose-gc", 23 | "licenses": [ 24 | { 25 | "type": "MIT", 26 | "url": "http://opensource.org/licenses/MIT" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /qrcode.min.js: -------------------------------------------------------------------------------- 1 | var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j=0?p.get(q):0}}for(var r=0,m=0;mm;m++)for(var j=0;jm;m++)for(var j=0;j=0;)b^=f.G15<=0;)b^=f.G18<>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;cf;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=[''],h=0;d>h;h++){g.push("");for(var i=0;d>i;i++)g.push('');g.push("")}g.push("
    "),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}(); -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Long-live-shadowsocks/shadowsocks-gui/069dd20c00d9f188bd1994b4c4b4f6aa177b8492/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 | if this.type == 'checkbox' 106 | val = this.checked 107 | else 108 | val = $(this).val() 109 | config[key] = val 110 | index = args.saveConfig(args.loadIndex(), config) 111 | args.saveIndex(index) 112 | reloadServerList() 113 | util.log 'config saved' 114 | restartServer config 115 | false 116 | 117 | load = (restart)-> 118 | config = args.loadConfig(args.loadIndex()) 119 | $('input,select').each -> 120 | key = $(this).attr 'data-key' 121 | val = config[key] or '' 122 | if this.type == 'checkbox' 123 | this.checked = val 124 | config[key] = this.checked 125 | else 126 | $(this).val(val) 127 | config[key] = this.value 128 | if restart 129 | restartServer config 130 | 131 | isRestarting = false 132 | 133 | restartServer = (config) -> 134 | if config.server and +config.server_port and config.password and +config.local_port and config.method 135 | if isRestarting 136 | util.log "Already restarting" 137 | return 138 | isRestarting = true 139 | start = -> 140 | try 141 | isRestarting = false 142 | util.log 'Starting shadowsocks...' 143 | console.log config 144 | window.local = local.createServer config.server, config.server_port, config.local_port, config.password, config.method, 1000 * 600, if config.share then '0.0.0.0' else '127.0.0.1' 145 | addServer config.server 146 | $('#divError').fadeOut() 147 | gui.Window.get().hide() 148 | catch e 149 | util.log e 150 | if window.local? 151 | try 152 | util.log 'Restarting shadowsocks' 153 | if window.local.address() 154 | window.local.close() 155 | setTimeout start, 1000 156 | catch e 157 | isRestarting = false 158 | util.log e 159 | else 160 | start() 161 | else 162 | $('#divError').fadeIn() 163 | 164 | qrcode = (method, password, hostname, port) -> 165 | "ss://#{btoa("#{method}:#{password}@#{hostname}:#{port}")}" 166 | 167 | $('#buttonSave').on 'click', save 168 | $('#buttonNewProfile').on 'click', addConfig 169 | $('#buttonDeleteProfile').on 'click', deleteConfig 170 | $('#buttonPublicServer').on 'click', publicConfig 171 | $('#buttonConsole').on 'click', -> 172 | gui.Window.get().showDevTools() 173 | $('#buttonAbout').on 'click', -> 174 | gui.Shell.openExternal 'https://github.com/shadowsocks/shadowsocks-gui' 175 | $("#buttonQr").on 'click', -> 176 | window.qrCode.makeCode( 177 | qrcode($('#selectMethod').val(), 178 | $('#inputPassword').val(), 179 | $('#inputServerIP').val(), 180 | $('#inputServerPort').val()), 181 | ) 182 | 183 | tray = new gui.Tray icon: 'menu_icon@2x.png' 184 | menu = new gui.Menu() 185 | 186 | tray.on 'click', -> 187 | gui.Window.get().show() 188 | 189 | show = new gui.MenuItem 190 | type: 'normal' 191 | label: 'Show' 192 | click: -> 193 | gui.Window.get().show() 194 | 195 | quit = new gui.MenuItem 196 | type: 'normal' 197 | label: 'Quit' 198 | click: -> 199 | gui.Window.get().close(true) 200 | 201 | hide = new gui.MenuItem 202 | type: 'normal' 203 | label: 'Hide' 204 | click: -> 205 | gui.Window.get().hide() 206 | 207 | menu.append show 208 | menu.append hide 209 | menu.append quit 210 | tray.menu = menu 211 | window.tray = tray 212 | 213 | win = gui.Window.get() 214 | 215 | win.on 'minimize', -> 216 | this.hide() 217 | 218 | win.on 'close', (quit) -> 219 | if os.platform() == 'darwin' and not quit 220 | this.hide() 221 | else 222 | this.close true 223 | 224 | reloadServerList() 225 | load true 226 | -------------------------------------------------------------------------------- /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.8.0 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.8.6 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 ../../fonts . && \ 20 | cp -r ../../node_modules . || \ 21 | exit 1 22 | rm ../app.nw 23 | zip -r ../app.nw * && \ 24 | popd && \ 25 | rm -rf app || \ 26 | exit 1 27 | for platform in linux-x64 win-ia32 28 | do 29 | if [ -f shadowsocks-gui-$1-$platform.tar.xz ]; then 30 | continue 31 | fi 32 | if [ ! -f node-webkit-$NW_VERSION-$platform.zip ] ; then 33 | if [ ! -f node-webkit-$NW_VERSION-$platform.tar.gz ] ; then 34 | axel http://dl.node-webkit.org/$NW_VERSION/node-webkit-$NW_VERSION-$platform.zip || \ 35 | axel http://dl.node-webkit.org/$NW_VERSION/node-webkit-$NW_VERSION-$platform.tar.gz || \ 36 | exit 1 37 | fi 38 | fi 39 | mkdir shadowsocks-gui-$1-$platform && \ 40 | pushd shadowsocks-gui-$1-$platform && \ 41 | unzip ../node-webkit-$NW_VERSION-$platform.zip || \ 42 | tar xf ../node-webkit-$NW_VERSION-$platform.tar.gz || \ 43 | exit 1 44 | if [ -d node-webkit-$NW_VERSION-$platform ]; then 45 | mv node-webkit-$NW_VERSION-$platform/* ./ && \ 46 | rm -r node-webkit-$NW_VERSION-$platform || \ 47 | exit 1 48 | fi 49 | if [ $platform == win-ia32 ]; then 50 | cat nw.exe ../app.nw > shadowsocks.exe && \ 51 | rm nwsnapshot.exe && \ 52 | rm ffmpegsumo.dll && \ 53 | rm libEGL.dll && \ 54 | rm libGLESv2.dll && \ 55 | rm nw.exe || \ 56 | exit 1 57 | fi 58 | if [ $platform == osx-ia32 ]; then 59 | rm nwsnapshot && \ 60 | cp ../app.nw node-webkit.app/Contents/Resources/ && \ 61 | cp ../../utils/Info.plist node-webkit.app/Contents/ && \ 62 | cp ../../utils/*.icns node-webkit.app/Contents/Resources/ && \ 63 | /usr/libexec/PlistBuddy -c "Set CFBundleVersion $1" node-webkit.app/Contents/Info.plist && \ 64 | /usr/libexec/PlistBuddy -c "Set CFBundleShortVersionString $1" node-webkit.app/Contents/Info.plist && \ 65 | mv node-webkit.app shadowsocks.app || \ 66 | exit 1 67 | fi 68 | if [ $platform == linux-x64 ]; then 69 | rm nwsnapshot && \ 70 | cp ../app.nw . && \ 71 | cp ../../utils/linux/start.sh . && \ 72 | rm libffmpegsumo.so || \ 73 | exit 1 74 | fi 75 | popd && \ 76 | tar Jcf shadowsocks-gui-$1-$platform.tar.xz shadowsocks-gui-$1-$platform && \ 77 | rm -r shadowsocks-gui-$1-$platform && \ 78 | rsync --progress -e ssh shadowsocks-gui-$1-$platform.tar.xz frs.sourceforge.net:/home/frs/project/shadowsocksgui/dist/shadowsocks-gui-$1-$platform.tar.xz || \ 79 | exit 1 80 | done 81 | popd 82 | -------------------------------------------------------------------------------- /utils/linux/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "`dirname $0`" 4 | 5 | paths=( 6 | "/lib/x86_64-linux-gnu/libudev.so.1" # Ubuntu, Xubuntu, Mint 7 | "/usr/lib64/libudev.so.1" # SUSE, Fedora 8 | "/usr/lib/libudev.so.1" # Arch, Fedora 32bit 9 | "/lib/i386-linux-gnu/libudev.so.1" # Ubuntu 32bit 10 | ) 11 | mkdir -p /tmp/shadowsocks-gui 12 | for i in "${paths[@]}" 13 | do 14 | if [ -f $i ] 15 | then 16 | ln -sf "$i" /tmp/shadowsocks-gui/libudev.so.0 17 | break 18 | fi 19 | done 20 | LD_LIBRARY_PATH=/tmp/shadowsocks-gui/ $PWD/nw app.nw 21 | -------------------------------------------------------------------------------- /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/Long-live-shadowsocks/shadowsocks-gui/069dd20c00d9f188bd1994b4c4b4f6aa177b8492/utils/shadowsocks.icns --------------------------------------------------------------------------------