New option for ss:// URL associationtags/4.2.0.0
@@ -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<RequestAddUrlEventArgs> 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(); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -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 | |||
@@ -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); } | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -18,6 +18,7 @@ Servers,Серверы,服务器,伺服器,サーバー,서버 | |||
Edit Servers...,Редактировать серверы…,编辑服务器...,編輯伺服器...,サーバーの編集...,서버 수정… | |||
Statistics Config...,Настройки статистики…,统计配置...,統計設定檔...,統計情報の設定...,통계 설정 | |||
Start on Boot,Автозагрузка,开机启动,開機啟動,システムと同時に起動,시스템 시작 시에 시작하기 | |||
Associate ss:// Links,Ассоциированный ss:// Ссылки,关联 ss:// 链接,關聯 ss:// 鏈接,, | |||
Forward Proxy...,Прямой прокси…,正向代理设置...,正向 Proxy 設定...,フォワードプロキシの設定...,포워드 프록시 | |||
Allow other Devices to connect,Общий доступ к подключению,允许其他设备连入,允許其他裝置連入,他のデバイスからの接続を許可する,다른 기기에서 연결 허용 | |||
Local PAC,Локальный PAC,使用本地 PAC,使用本機 PAC,ローカル PAC,로컬 PAC | |||
@@ -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를 사용하고 싶으시다면, 파일을 다른 곳에 복사해주세요." | |||
Failed to decode QRCode,Не удалось распознать QRCode,无法解析二维码,QR 碼解碼失敗,QR コードの読み取りに失敗しました。,QR코드를 해석하는데에 실패했습니다. | |||
Failed to update registry,Не удалось обновить запись в реестре,无法修改注册表,無法修改登錄檔,レジストリの更新に失敗しました。,레지스트리를 업데이트하는데에 실패했습니다. | |||
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 已啟用:,システム プロキシが有効:,시스템 프록시 활성화됨: | |||
Running: Port {0},Запущен на порту {0},正在运行:端口 {0},正在執行:連接埠號碼 {0},実行中:ポート {0},실행 중: 포트 {0}번 | |||
"Unexpected error, shadowsocks will exit. Please report to","Непредвиденная ошибка, пожалуйста сообщите на",非预期错误,Shadowsocks将退出。请提交此错误到,非預期錯誤,Shadowsocks 將結束。請報告此錯誤至,予想外のエラーが発生したため、Shadowsocks を終了します。詳しくは下記までお問い合わせ下さい:,알 수 없는 오류로 Shadowsocks가 종료될 것입니다. 오류를 제보해주세요: | |||
@@ -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 | |||
/// </summary> | |||
/// </summary> | |||
[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<string> 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 | |||
@@ -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) | |||
@@ -113,6 +113,8 @@ | |||
<Compile Include="Controller\HotkeyReg.cs" /> | |||
<Compile Include="Controller\LoggerExtension.cs" /> | |||
<Compile Include="Controller\Service\PACDaemon.cs" /> | |||
<Compile Include="Controller\Service\NamedPipeServer.cs" /> | |||
<Compile Include="Controller\System\ProtocolHandler.cs" /> | |||
<Compile Include="Controller\System\Hotkeys\HotkeyCallbacks.cs" /> | |||
<Compile Include="Encryption\AEAD\AEADEncryptor.cs" /> | |||
<Compile Include="Encryption\AEAD\AEADMbedTLSEncryptor.cs" /> | |||