diff --git a/shadowsocks-csharp/Controller/Service/NamedPipeServer.cs b/shadowsocks-csharp/Controller/Service/NamedPipeServer.cs new file mode 100644 index 00000000..23689231 --- /dev/null +++ b/shadowsocks-csharp/Controller/Service/NamedPipeServer.cs @@ -0,0 +1,47 @@ +using System; +using System.IO; +using System.IO.Pipes; +using System.Net; +using System.Text; + +namespace Shadowsocks.Controller +{ + class RequestAddUrlEventArgs : EventArgs + { + public readonly string Url; + + public RequestAddUrlEventArgs(string url) + { + this.Url = url; + } + } + + internal class NamedPipeServer + { + public event EventHandler AddUrlRequested; + public async void Run(string path) + { + byte[] buf = new byte[4096]; + while (true) + { + using (NamedPipeServerStream stream = new NamedPipeServerStream(path)) + { + stream.WaitForConnection(); + await stream.ReadAsync(buf, 0, 4); + int opcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(buf, 0)); + if (opcode == 1) + { + await stream.ReadAsync(buf, 0, 4); + int strlen = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(buf, 0)); + + await stream.ReadAsync(buf, 0, strlen); + string url = Encoding.UTF8.GetString(buf, 0, strlen); + + AddUrlRequested?.Invoke(this, new RequestAddUrlEventArgs(url)); + } + stream.Close(); + } + } + } + } +} diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index f4ea1294..e1df6186 100644 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -218,6 +218,25 @@ namespace Shadowsocks.Controller StatisticsStrategyConfiguration.Save(configuration); } + public bool AskAddServerBySSURL(string ssURL) + { + var dr = MessageBox.Show(I18N.GetString("Import from URL: {0} ?", ssURL), I18N.GetString("Shadowsocks"), MessageBoxButtons.YesNo); + if (dr == DialogResult.Yes) + { + if (AddServerBySSURL(ssURL)) + { + MessageBox.Show(I18N.GetString("Successfully imported from {0}", ssURL)); + return true; + } + else + { + MessageBox.Show(I18N.GetString("Failed to import. Please check if the link is valid.")); + } + } + return false; + } + + public bool AddServerBySSURL(string ssURL) { try diff --git a/shadowsocks-csharp/Controller/System/ProtocolHandler.cs b/shadowsocks-csharp/Controller/System/ProtocolHandler.cs new file mode 100644 index 00000000..2a0112cb --- /dev/null +++ b/shadowsocks-csharp/Controller/System/ProtocolHandler.cs @@ -0,0 +1,107 @@ +using Microsoft.Win32; +using NLog; +using Shadowsocks.Util; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Shadowsocks.Controller +{ + static class ProtocolHandler + { + const string ssURLRegKey = @"SOFTWARE\Classes\ss"; + + private static Logger logger = LogManager.GetCurrentClassLogger(); + + // Don't use Application.ExecutablePath + // see https://stackoverflow.com/questions/12945805/odd-c-sharp-path-issue + private static readonly string ExecutablePath = Assembly.GetEntryAssembly().Location; + + // TODO: Elevate when necessary + public static bool Set(bool enabled) + { + RegistryKey ssURLAssociation = null; + + try + { + ssURLAssociation = Registry.CurrentUser.CreateSubKey(ssURLRegKey, RegistryKeyPermissionCheck.ReadWriteSubTree); + if (ssURLAssociation == null) + { + logger.Error(@"Failed to create HKCU\SOFTWARE\Classes\ss to register ss:// association."); + return false; + } + if (enabled) + { + ssURLAssociation.SetValue("", "URL:Shadowsocks"); + ssURLAssociation.SetValue("URL Protocol", ""); + var shellOpen = ssURLAssociation.CreateSubKey("shell").CreateSubKey("open").CreateSubKey("command"); + shellOpen.SetValue("", $"{ExecutablePath} --open-url %1"); + logger.Info(@"Successfully added ss:// association."); + } + else + { + Registry.CurrentUser.DeleteSubKeyTree(ssURLRegKey); + logger.Info(@"Successfully removed ss:// association."); + } + return true; + } + catch (Exception e) + { + logger.LogUsefulException(e); + return false; + } + finally + { + if (ssURLAssociation != null) + { + try + { + ssURLAssociation.Close(); + ssURLAssociation.Dispose(); + } + catch (Exception e) + { logger.LogUsefulException(e); } + } + } + } + + public static bool Check() + { + RegistryKey ssURLAssociation = null; + try + { + ssURLAssociation = Registry.CurrentUser.OpenSubKey(ssURLRegKey, true); + if (ssURLAssociation == null) + { + //logger.Info(@"ss:// links not associated."); + return false; + } + + var shellOpen = ssURLAssociation.OpenSubKey("shell").OpenSubKey("open").OpenSubKey("command"); + return (string)shellOpen.GetValue("") == $"{ExecutablePath} --open-url %1"; + } + catch (Exception e) + { + logger.LogUsefulException(e); + return false; + } + finally + { + if (ssURLAssociation != null) + { + try + { + ssURLAssociation.Close(); + ssURLAssociation.Dispose(); + } + catch (Exception e) + { logger.LogUsefulException(e); } + } + } + } + + } +} diff --git a/shadowsocks-csharp/Data/i18n.csv b/shadowsocks-csharp/Data/i18n.csv index 595ac2bd..e220d9ba 100644 --- a/shadowsocks-csharp/Data/i18n.csv +++ b/shadowsocks-csharp/Data/i18n.csv @@ -1,4 +1,4 @@ -en,ru-RU,zh-CN,zh-TW,ja,ko,fr +en,ru-RU,zh-CN,zh-TW,ja,ko,fr #Restart program to apply translation,,,,,, #This is comment line,,,,,, #Always keep language name at head of file,,,,,, @@ -18,6 +18,7 @@ Servers,Серверы,服务器,伺服器,サーバー,서버,Serveurs Edit Servers...,Редактировать серверы…,编辑服务器...,編輯伺服器...,サーバーの編集...,서버 수정…,Éditer serveurs… Statistics Config...,Настройки статистики…,统计配置...,統計設定檔...,統計情報の設定...,통계 설정,Configuration des statistiques… Start on Boot,Автозагрузка,开机启动,開機啟動,システムと同時に起動,시스템 시작 시에 시작하기,Démarrage automatique +Associate ss:// Links,Ассоциированный ss:// Ссылки,关联 ss:// 链接,關聯 ss:// 鏈接,,, Forward Proxy...,Прямой прокси…,正向代理设置...,正向 Proxy 設定...,フォワードプロキシの設定...,포워드 프록시,Forward-proxy… Allow other Devices to connect,Общий доступ к подключению,允许其他设备连入,允許其他裝置連入,他のデバイスからの接続を許可する,다른 기기에서 연결 허용,Autoriser d'autres appareils à se connecter Local PAC,Локальный PAC,使用本地 PAC,使用本機 PAC,ローカル PAC,로컬 PAC,PAC local @@ -184,6 +185,9 @@ Find Shadowsocks icon in your notify tray.,Значок Shadowsocks можно "If you want to start multiple Shadowsocks, make a copy in another directory.","Если вы хотите запустить несколько копий Shadowsocks одновременно, создайте отдельную папку на каждую копию.",如果想同时启动多个,可以另外复制一份到别的目录。,如果想同時啟動多個,可以另外複製一份至別的目錄。,複数起動したい場合は、プログラムファイルを別のフォルダーにコピーしてから、もう一度実行して下さい。,"만약 여러 개의 Shadowsocks를 사용하고 싶으시다면, 파일을 다른 곳에 복사해주세요.","Si vous souhaitez démarrer plusieurs Shadowsocks, faites une copie dans un autre répertoire." Failed to decode QRCode,Не удалось распознать QRCode,无法解析二维码,QR 碼解碼失敗,QR コードの読み取りに失敗しました。,QR코드를 해석하는데에 실패했습니다.,Impossible de décoder le QRCode Failed to update registry,Не удалось обновить запись в реестре,无法修改注册表,無法修改登錄檔,レジストリの更新に失敗しました。,레지스트리를 업데이트하는데에 실패했습니다.,Impossible de mettre à jour de la base de registre +Import from URL: {0} ?,импортировать из адреса: {0} ?,从URL导入: {0} ?,從URL匯入: {0} ?,,, +Successfully imported from {0},Успешно импортировано из {0},导入成功:{0},導入成功:{0},,, +Failed to import. Please check if the link is valid.,,导入失败,请检查链接是否有效。,導入失敗,請檢查鏈接是否有效。,,, System Proxy On: ,Системный прокси:,系统代理已启用:,系統 Proxy 已啟用:,システム プロキシが有効:,시스템 프록시 활성화됨: ,Proxy système activé: Running: Port {0},Запущен на порту {0},正在运行:端口 {0},正在執行:連接埠號碼 {0},実行中:ポート {0},실행 중: 포트 {0}번,En cours d'exécution: port {0} "Unexpected error, shadowsocks will exit. Please report to","Непредвиденная ошибка, пожалуйста сообщите на",非预期错误,Shadowsocks将退出。请提交此错误到,非預期錯誤,Shadowsocks 將結束。請報告此錯誤至,予想外のエラーが発生したため、Shadowsocks を終了します。詳しくは下記までお問い合わせ下さい:,알 수 없는 오류로 Shadowsocks가 종료될 것입니다. 오류를 제보해주세요:,Shadowsocks va quitter en présence d/érreur inattendue. Veuillez signaler à diff --git a/shadowsocks-csharp/Program.cs b/shadowsocks-csharp/Program.cs index 5b723acd..3f181105 100755 --- a/shadowsocks-csharp/Program.cs +++ b/shadowsocks-csharp/Program.cs @@ -1,21 +1,26 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Windows.Forms; +using Microsoft.Win32; using NLog; -using Microsoft.Win32; - using Shadowsocks.Controller; using Shadowsocks.Controller.Hotkeys; using Shadowsocks.Util; using Shadowsocks.View; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Pipes; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; namespace Shadowsocks { - static class Program + internal static class Program { - private static Logger logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); public static ShadowsocksController MainController { get; private set; } public static MenuViewController MenuController { get; private set; } public static string[] Args { get; private set; } @@ -24,15 +29,15 @@ namespace Shadowsocks /// /// [STAThread] - static void Main(string[] args) + private static void Main(string[] args) { Directory.SetCurrentDirectory(Application.StartupPath); // todo: initialize the NLog configuartion Model.NLogConfig.TouchAndApplyNLogConfig(); // .NET Framework 4.7.2 on Win7 compatibility - System.Net.ServicePointManager.SecurityProtocol |= - System.Net.SecurityProtocolType.Tls | System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls12; + ServicePointManager.SecurityProtocol |= + SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; // store args for further use Args = args; @@ -50,27 +55,58 @@ namespace Shadowsocks if (DialogResult.OK == MessageBox.Show(I18N.GetString("Unsupported .NET Framework, please update to {0} or later.", "4.7.2"), "Shadowsocks Error", MessageBoxButtons.OKCancel, MessageBoxIcon.Error)) { - //Process.Start("https://www.microsoft.com/download/details.aspx?id=53344"); // 4.6.2 Process.Start("https://dotnet.microsoft.com/download/dotnet-framework/net472"); } return; } + string pipename = $"Shadowsocks\\{Application.StartupPath.GetHashCode()}"; - Utils.ReleaseMemory(true); - using (Mutex mutex = new Mutex(false, $"Global\\Shadowsocks_{Application.StartupPath.GetHashCode()}")) + string addedUrl = null; + + using (NamedPipeClientStream pipe = new NamedPipeClientStream(pipename)) { - Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); - // handle UI exceptions - Application.ThreadException += Application_ThreadException; - // handle non-UI exceptions - AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; - Application.ApplicationExit += Application_ApplicationExit; - SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - AutoStartup.RegisterForRestart(true); - - if (!mutex.WaitOne(0, false)) + bool pipeExist = false; + try + { + pipe.Connect(10); + pipeExist = true; + } + catch (TimeoutException) + { + pipeExist = false; + } + + // TODO: switch to better argv parser when it's getting complicate + List alist = Args.ToList(); + // check --open-url param + int urlidx = alist.IndexOf("--open-url") + 1; + if (urlidx > 0) + { + if (Args.Length <= urlidx) + { + return; + } + + // --open-url exist, and no other instance, add it later + if (!pipeExist) + { + addedUrl = Args[urlidx]; + } + // has other instance, send url via pipe then exit + else + { + byte[] b = Encoding.UTF8.GetBytes(Args[urlidx]); + byte[] opAddUrl = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(1)); + byte[] blen = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(b.Length)); + pipe.Write(opAddUrl, 0, 4); // opcode addurl + pipe.Write(blen, 0, 4); + pipe.Write(b, 0, b.Length); + pipe.Close(); + return; + } + } + // has another instance, and no need to communicate with it return + else if (pipeExist) { Process[] oldProcesses = Process.GetProcessesByName("Shadowsocks"); if (oldProcesses.Length > 0) @@ -83,21 +119,44 @@ namespace Shadowsocks I18N.GetString("Shadowsocks is already running.")); return; } - Directory.SetCurrentDirectory(Application.StartupPath); - + } + + Utils.ReleaseMemory(true); + + Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); + // handle UI exceptions + Application.ThreadException += Application_ThreadException; + // handle non-UI exceptions + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + Application.ApplicationExit += Application_ApplicationExit; + SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + AutoStartup.RegisterForRestart(true); + + Directory.SetCurrentDirectory(Application.StartupPath); + #if DEBUG - // truncate privoxy log file while debugging - string privoxyLogFilename = Utils.GetTempPath("privoxy.log"); - if (File.Exists(privoxyLogFilename)) - using (new FileStream(privoxyLogFilename, FileMode.Truncate)) { } + // truncate privoxy log file while debugging + string privoxyLogFilename = Utils.GetTempPath("privoxy.log"); + if (File.Exists(privoxyLogFilename)) + using (new FileStream(privoxyLogFilename, FileMode.Truncate)) { } #endif - MainController = new ShadowsocksController(); - MenuController = new MenuViewController(MainController); + MainController = new ShadowsocksController(); + MenuController = new MenuViewController(MainController); - HotKeys.Init(MainController); - MainController.Start(); - Application.Run(); - } + HotKeys.Init(MainController); + MainController.Start(); + + NamedPipeServer namedPipeServer = new NamedPipeServer(); + Task.Run(() => namedPipeServer.Run(pipename)); + namedPipeServer.AddUrlRequested += (_1, e) => MainController.AskAddServerBySSURL(e.Url); + if (!addedUrl.IsNullOrEmpty()) + { + MainController.AskAddServerBySSURL(addedUrl); + } + + Application.Run(); } private static int exited = 0; @@ -135,7 +194,7 @@ namespace Shadowsocks logger.Info("os wake up"); if (MainController != null) { - System.Threading.Tasks.Task.Factory.StartNew(() => + Task.Factory.StartNew(() => { Thread.Sleep(10 * 1000); try diff --git a/shadowsocks-csharp/View/MenuViewController.cs b/shadowsocks-csharp/View/MenuViewController.cs index ddcf21f4..253c54c2 100644 --- a/shadowsocks-csharp/View/MenuViewController.cs +++ b/shadowsocks-csharp/View/MenuViewController.cs @@ -36,6 +36,7 @@ namespace Shadowsocks.View private ContextMenu contextMenu1; private MenuItem disableItem; private MenuItem AutoStartupItem; + private MenuItem ProtocolHandlerItem; private MenuItem ShareOverLANItem; private MenuItem SeperatorItem; private MenuItem ConfigItem; @@ -315,6 +316,7 @@ namespace Shadowsocks.View this.proxyItem = CreateMenuItem("Forward Proxy...", new EventHandler(this.proxyItem_Click)), new MenuItem("-"), this.AutoStartupItem = CreateMenuItem("Start on Boot", new EventHandler(this.AutoStartupItem_Click)), + this.ProtocolHandlerItem = CreateMenuItem("Associate ss:// Links", new EventHandler(this.ProtocolHandlerItem_Click)), this.ShareOverLANItem = CreateMenuItem("Allow other Devices to connect", new EventHandler(this.ShareOverLANItem_Click)), new MenuItem("-"), this.hotKeyItem = CreateMenuItem("Edit Hotkeys...", new EventHandler(this.hotKeyItem_Click)), @@ -442,6 +444,7 @@ namespace Shadowsocks.View VerboseLoggingToggleItem.Checked = config.isVerboseLogging; ShowPluginOutputToggleItem.Checked = config.showPluginOutput; AutoStartupItem.Checked = AutoStartup.Check(); + ProtocolHandlerItem.Checked = ProtocolHandler.Check(); onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac; localPACItem.Checked = !onlinePACItem.Checked; secureLocalPacUrlToggleItem.Checked = config.secureLocalPac; @@ -821,8 +824,7 @@ namespace Shadowsocks.View private void ImportURLItem_Click(object sender, EventArgs e) { - var success = controller.AddServerBySSURL(Clipboard.GetText(TextDataFormat.Text)); - if (success) + if (controller.AskAddServerBySSURL(Clipboard.GetText(TextDataFormat.Text))) { ShowConfigForm(); } @@ -845,6 +847,16 @@ namespace Shadowsocks.View { MessageBox.Show(I18N.GetString("Failed to update registry")); } + LoadCurrentConfiguration(); + } + private void ProtocolHandlerItem_Click(object sender, EventArgs e) + { + ProtocolHandlerItem.Checked = !ProtocolHandlerItem.Checked; + if (!ProtocolHandler.Set(ProtocolHandlerItem.Checked)) + { + MessageBox.Show(I18N.GetString("Failed to update registry")); + } + LoadCurrentConfiguration(); } private void LocalPACItem_Click(object sender, EventArgs e) diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index bf562337..978e58d3 100644 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -113,6 +113,8 @@ + +