@@ -128,7 +128,7 @@ No QRCode found. Try to zoom in or move it to the center of the screen.,QRCode | |||
Shadowsocks is already running.,Shadowsocks уже запущен.,Shadowsocks 已经在运行。,Shadowsocks 已經在執行。,Shadowsocks 実行中,Shadowsocks가 이미 실행 중입니다.,Shadowsocks est déjà en cours d'exécution. | |||
Find Shadowsocks icon in your notify tray.,Значок Shadowsocks можно найти в области уведомлений.,请在任务栏里寻找 Shadowsocks 图标。,請在工作列裡尋找 Shadowsocks 圖示。,通知領域には Shadowsocks のアイコンがあります。,트레이에서 Shadowsocks를 찾아주세요.,Trouvez l'icône Shadowsocks dans votre barre de notification. | |||
"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 | |||
Invalid QR Code content: {0},,无效二维码内容: {0},無效二維碼內容: {0},,, | |||
Failed to update registry,Не удалось обновить запись в реестре,无法修改注册表,無法修改登錄檔,レジストリの更新に失敗しました。,레지스트리를 업데이트하는데에 실패했습니다.,Impossible de mettre à jour de la base de registre | |||
Import from URL: {0} ?,импортировать из адреса: {0} ?,从URL导入: {0} ?,從URL匯入: {0} ?,{0}:このURLからインポートしますか?,, | |||
Successfully imported from {0},Успешно импортировано из {0},导入成功:{0},導入成功:{0},{0}:インポートしました。,, | |||
@@ -8,6 +8,10 @@ using System.Windows.Forms; | |||
using Microsoft.Win32; | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Model; | |||
using System.Drawing; | |||
using ZXing; | |||
using ZXing.QrCode; | |||
using ZXing.Common; | |||
namespace Shadowsocks.Util | |||
{ | |||
@@ -227,11 +231,47 @@ namespace Shadowsocks.Util | |||
return Environment.OSVersion.Version.Major > 5; | |||
} | |||
[DllImport("kernel32.dll")] | |||
[return: MarshalAs(UnmanagedType.Bool)] | |||
private static extern bool SetProcessWorkingSetSize(IntPtr process, | |||
UIntPtr minimumWorkingSetSize, UIntPtr maximumWorkingSetSize); | |||
public static string ScanQRCodeFromScreen() | |||
{ | |||
foreach (Screen screen in Screen.AllScreens) | |||
{ | |||
using (Bitmap fullImage = new Bitmap(screen.Bounds.Width, | |||
screen.Bounds.Height)) | |||
{ | |||
using (Graphics g = Graphics.FromImage(fullImage)) | |||
{ | |||
g.CopyFromScreen(screen.Bounds.X, | |||
screen.Bounds.Y, | |||
0, 0, | |||
fullImage.Size, | |||
CopyPixelOperation.SourceCopy); | |||
} | |||
int maxTry = 10; | |||
for (int i = 0; i < maxTry; i++) | |||
{ | |||
int marginLeft = (int)((double)fullImage.Width * i / 2.5 / maxTry); | |||
int marginTop = (int)((double)fullImage.Height * i / 2.5 / maxTry); | |||
Rectangle cropRect = new Rectangle(marginLeft, marginTop, fullImage.Width - marginLeft * 2, fullImage.Height - marginTop * 2); | |||
Bitmap target = new Bitmap(screen.Bounds.Width, screen.Bounds.Height); | |||
double imageScale = (double)screen.Bounds.Width / (double)cropRect.Width; | |||
using (Graphics g = Graphics.FromImage(target)) | |||
{ | |||
g.DrawImage(fullImage, new Rectangle(0, 0, target.Width, target.Height), | |||
cropRect, | |||
GraphicsUnit.Pixel); | |||
} | |||
var source = new BitmapLuminanceSource(target); | |||
var bitmap = new BinaryBitmap(new HybridBinarizer(source)); | |||
QRCodeReader reader = new QRCodeReader(); | |||
var result = reader.decode(bitmap); | |||
if (result != null) | |||
return result.Text; | |||
} | |||
} | |||
} | |||
return null; | |||
} | |||
// See: https://msdn.microsoft.com/en-us/library/hh925568(v=vs.110).aspx | |||
public static bool IsSupportedRuntimeVersion() | |||
@@ -714,12 +714,7 @@ namespace Shadowsocks.View | |||
ShowConfigForm(); | |||
} | |||
void splash_FormClosed(object sender, FormClosedEventArgs e) | |||
{ | |||
ShowConfigForm(); | |||
} | |||
void openURLFromQRCode(object sender, FormClosedEventArgs e) | |||
void openURLFromQRCode() | |||
{ | |||
Process.Start(_urlToOpen); | |||
} | |||
@@ -751,86 +746,26 @@ namespace Shadowsocks.View | |||
private void ScanQRCodeItem_Click(object sender, EventArgs e) | |||
{ | |||
foreach (Screen screen in Screen.AllScreens) | |||
var result = Utils.ScanQRCodeFromScreen(); | |||
if (result != null) | |||
{ | |||
using (Bitmap fullImage = new Bitmap(screen.Bounds.Width, | |||
screen.Bounds.Height)) | |||
if (result.ToLowerInvariant().StartsWith("http://") || result.ToLowerInvariant().StartsWith("https://")) | |||
{ | |||
using (Graphics g = Graphics.FromImage(fullImage)) | |||
{ | |||
g.CopyFromScreen(screen.Bounds.X, | |||
screen.Bounds.Y, | |||
0, 0, | |||
fullImage.Size, | |||
CopyPixelOperation.SourceCopy); | |||
} | |||
int maxTry = 10; | |||
for (int i = 0; i < maxTry; i++) | |||
{ | |||
int marginLeft = (int)((double)fullImage.Width * i / 2.5 / maxTry); | |||
int marginTop = (int)((double)fullImage.Height * i / 2.5 / maxTry); | |||
Rectangle cropRect = new Rectangle(marginLeft, marginTop, fullImage.Width - marginLeft * 2, fullImage.Height - marginTop * 2); | |||
Bitmap target = new Bitmap(screen.Bounds.Width, screen.Bounds.Height); | |||
double imageScale = (double)screen.Bounds.Width / (double)cropRect.Width; | |||
using (Graphics g = Graphics.FromImage(target)) | |||
{ | |||
g.DrawImage(fullImage, new Rectangle(0, 0, target.Width, target.Height), | |||
cropRect, | |||
GraphicsUnit.Pixel); | |||
} | |||
var source = new BitmapLuminanceSource(target); | |||
var bitmap = new BinaryBitmap(new HybridBinarizer(source)); | |||
QRCodeReader reader = new QRCodeReader(); | |||
var result = reader.decode(bitmap); | |||
if (result != null) | |||
{ | |||
var success = controller.AddServerBySSURL(result.Text); | |||
QRCodeSplashForm splash = new QRCodeSplashForm(); | |||
if (success) | |||
{ | |||
splash.FormClosed += splash_FormClosed; | |||
} | |||
else if (result.Text.ToLower().StartsWith("http://") || result.Text.ToLower().StartsWith("https://")) | |||
{ | |||
_urlToOpen = result.Text; | |||
splash.FormClosed += openURLFromQRCode; | |||
} | |||
else | |||
{ | |||
MessageBox.Show(I18N.GetString("Failed to decode QRCode")); | |||
return; | |||
} | |||
double minX = Int32.MaxValue, minY = Int32.MaxValue, maxX = 0, maxY = 0; | |||
foreach (ResultPoint point in result.ResultPoints) | |||
{ | |||
minX = Math.Min(minX, point.X); | |||
minY = Math.Min(minY, point.Y); | |||
maxX = Math.Max(maxX, point.X); | |||
maxY = Math.Max(maxY, point.Y); | |||
} | |||
minX /= imageScale; | |||
minY /= imageScale; | |||
maxX /= imageScale; | |||
maxY /= imageScale; | |||
// make it 20% larger | |||
double margin = (maxX - minX) * 0.20f; | |||
minX += -margin + marginLeft; | |||
maxX += margin + marginLeft; | |||
minY += -margin + marginTop; | |||
maxY += margin + marginTop; | |||
splash.Location = new Point(screen.Bounds.X, screen.Bounds.Y); | |||
// we need a panel because a window has a minimal size | |||
// TODO: test on high DPI | |||
splash.TargetRect = new Rectangle((int)minX, (int)minY, (int)maxX - (int)minX, (int)maxY - (int)minY); | |||
splash.Size = new Size(fullImage.Width, fullImage.Height); | |||
splash.Show(); | |||
return; | |||
} | |||
} | |||
_urlToOpen = result; | |||
openURLFromQRCode(); | |||
} | |||
else if (controller.AddServerBySSURL(result)) | |||
{ | |||
ShowConfigForm(); | |||
} | |||
else | |||
{ | |||
MessageBox.Show(I18N.GetString("Invalid QR Code content: {0}", result)); | |||
} | |||
return; | |||
} | |||
MessageBox.Show(I18N.GetString("No QRCode found. Try to zoom in or move it to the center of the screen.")); | |||
else | |||
MessageBox.Show(I18N.GetString("No QRCode found. Try to zoom in or move it to the center of the screen.")); | |||
} | |||
private void ImportURLItem_Click(object sender, EventArgs e) | |||
@@ -1,281 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.ComponentModel; | |||
using System.Data; | |||
using System.Drawing; | |||
using System.Text; | |||
using System.Windows.Forms; | |||
using System.Drawing.Imaging; | |||
using System.Runtime.InteropServices; | |||
using System.Diagnostics; | |||
namespace Shadowsocks.View | |||
{ | |||
public class QRCodeSplashForm : Form | |||
{ | |||
public Rectangle TargetRect; | |||
public QRCodeSplashForm() | |||
{ | |||
FormBorderStyle = FormBorderStyle.None; | |||
this.Load += QRCodeSplashForm_Load; | |||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; | |||
this.BackColor = System.Drawing.Color.White; | |||
this.ClientSize = new System.Drawing.Size(1, 1); | |||
this.ControlBox = false; | |||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; | |||
this.MaximizeBox = false; | |||
this.MinimizeBox = false; | |||
this.Name = "QRCodeSplashForm"; | |||
this.ShowIcon = false; | |||
this.ShowInTaskbar = false; | |||
this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; | |||
this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; | |||
this.TopMost = true; | |||
} | |||
private Timer timer; | |||
private int flashStep; | |||
private static double FPS = 1.0 / 15 * 1000; // System.Windows.Forms.Timer resolution is 15ms | |||
private static double ANIMATION_TIME = 0.5; | |||
private static int ANIMATION_STEPS = (int)(ANIMATION_TIME * FPS); | |||
Stopwatch sw; | |||
int x; | |||
int y; | |||
int w; | |||
int h; | |||
Bitmap bitmap; | |||
Graphics g; | |||
Pen pen; | |||
SolidBrush brush; | |||
private void QRCodeSplashForm_Load(object sender, EventArgs e) | |||
{ | |||
SetStyle(ControlStyles.SupportsTransparentBackColor, true); | |||
this.BackColor = Color.Transparent; | |||
flashStep = 0; | |||
x = 0; | |||
y = 0; | |||
w = Width; | |||
h = Height; | |||
sw = Stopwatch.StartNew(); | |||
timer = new Timer(); | |||
timer.Interval = (int)(ANIMATION_TIME * 1000 / ANIMATION_STEPS); | |||
timer.Tick += timer_Tick; | |||
timer.Start(); | |||
bitmap = new Bitmap(Width, Height, PixelFormat.Format32bppArgb); | |||
g = Graphics.FromImage(bitmap); | |||
pen = new Pen(Color.Red, 3); | |||
brush = new SolidBrush(Color.FromArgb(30, Color.Red)); | |||
} | |||
void timer_Tick(object sender, EventArgs e) | |||
{ | |||
double percent = (double)sw.ElapsedMilliseconds / 1000.0 / (double)ANIMATION_TIME; | |||
if (percent < 1) | |||
{ | |||
// ease out | |||
percent = 1 - Math.Pow((1 - percent), 4); | |||
x = (int)(TargetRect.X * percent); | |||
y = (int)(TargetRect.Y * percent); | |||
w = (int)(TargetRect.Width * percent + this.Size.Width * (1 - percent)); | |||
h = (int)(TargetRect.Height * percent + this.Size.Height * (1 - percent)); | |||
//codeRectView.Location = new Point(x, y); | |||
//codeRectView.Size = new Size(w, h); | |||
pen.Color = Color.FromArgb((int)(255 * percent), Color.Red); | |||
brush.Color = Color.FromArgb((int)(30 * percent), Color.Red); | |||
g.Clear(Color.Transparent); | |||
g.FillRectangle(brush, x, y, w, h); | |||
g.DrawRectangle(pen, x, y, w, h); | |||
SetBitmap(bitmap); | |||
} | |||
else | |||
{ | |||
if (flashStep == 0) | |||
{ | |||
timer.Interval = 100; | |||
g.Clear(Color.Transparent); | |||
SetBitmap(bitmap); | |||
} | |||
else if (flashStep == 1) | |||
{ | |||
timer.Interval = 50; | |||
g.FillRectangle(brush, x, y, w, h); | |||
g.DrawRectangle(pen, x, y, w, h); | |||
SetBitmap(bitmap); | |||
} | |||
else if (flashStep == 2) | |||
{ | |||
g.Clear(Color.Transparent); | |||
SetBitmap(bitmap); | |||
} | |||
else if (flashStep == 3) | |||
{ | |||
g.FillRectangle(brush, x, y, w, h); | |||
g.DrawRectangle(pen, x, y, w, h); | |||
SetBitmap(bitmap); | |||
} | |||
else if (flashStep == 4) | |||
{ | |||
g.Clear(Color.Transparent); | |||
SetBitmap(bitmap); | |||
} | |||
else if (flashStep == 5) | |||
{ | |||
g.FillRectangle(brush, x, y, w, h); | |||
g.DrawRectangle(pen, x, y, w, h); | |||
SetBitmap(bitmap); | |||
} | |||
else | |||
{ | |||
sw.Stop(); | |||
timer.Stop(); | |||
pen.Dispose(); | |||
brush.Dispose(); | |||
bitmap.Dispose(); | |||
this.Close(); | |||
} | |||
flashStep++; | |||
} | |||
} | |||
// PerPixelAlphaForm.cs | |||
// http://www.codeproject.com/Articles/1822/Per-Pixel-Alpha-Blend-in-C | |||
// Rui Lopes | |||
protected override CreateParams CreateParams | |||
{ | |||
get | |||
{ | |||
CreateParams cp = base.CreateParams; | |||
cp.ExStyle |= 0x00080000; // This form has to have the WS_EX_LAYERED extended style | |||
return cp; | |||
} | |||
} | |||
public void SetBitmap(Bitmap bitmap) | |||
{ | |||
SetBitmap(bitmap, 255); | |||
} | |||
/// <para>Changes the current bitmap with a custom opacity level. Here is where all happens!</para> | |||
public void SetBitmap(Bitmap bitmap, byte opacity) | |||
{ | |||
if (bitmap.PixelFormat != PixelFormat.Format32bppArgb) | |||
throw new ApplicationException("The bitmap must be 32ppp with alpha-channel."); | |||
// The idea of this is very simple, | |||
// 1. Create a compatible DC with screen; | |||
// 2. Select the bitmap with 32bpp with alpha-channel in the compatible DC; | |||
// 3. Call the UpdateLayeredWindow. | |||
IntPtr screenDc = Win32.GetDC(IntPtr.Zero); | |||
IntPtr memDc = Win32.CreateCompatibleDC(screenDc); | |||
IntPtr hBitmap = IntPtr.Zero; | |||
IntPtr oldBitmap = IntPtr.Zero; | |||
try | |||
{ | |||
hBitmap = bitmap.GetHbitmap(Color.FromArgb(0)); // grab a GDI handle from this GDI+ bitmap | |||
oldBitmap = Win32.SelectObject(memDc, hBitmap); | |||
Win32.Size size = new Win32.Size(bitmap.Width, bitmap.Height); | |||
Win32.Point pointSource = new Win32.Point(0, 0); | |||
Win32.Point topPos = new Win32.Point(Left, Top); | |||
Win32.BLENDFUNCTION blend = new Win32.BLENDFUNCTION(); | |||
blend.BlendOp = Win32.AC_SRC_OVER; | |||
blend.BlendFlags = 0; | |||
blend.SourceConstantAlpha = opacity; | |||
blend.AlphaFormat = Win32.AC_SRC_ALPHA; | |||
Win32.UpdateLayeredWindow(Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, Win32.ULW_ALPHA); | |||
} | |||
finally | |||
{ | |||
Win32.ReleaseDC(IntPtr.Zero, screenDc); | |||
if (hBitmap != IntPtr.Zero) | |||
{ | |||
Win32.SelectObject(memDc, oldBitmap); | |||
//Windows.DeleteObject(hBitmap); // The documentation says that we have to use the Windows.DeleteObject... but since there is no such method I use the normal DeleteObject from Win32 GDI and it's working fine without any resource leak. | |||
Win32.DeleteObject(hBitmap); | |||
} | |||
Win32.DeleteDC(memDc); | |||
} | |||
} | |||
} | |||
// class that exposes needed win32 gdi functions. | |||
class Win32 | |||
{ | |||
[StructLayout(LayoutKind.Sequential)] | |||
public struct Point | |||
{ | |||
public Int32 x; | |||
public Int32 y; | |||
public Point(Int32 x, Int32 y) { this.x = x; this.y = y; } | |||
} | |||
[StructLayout(LayoutKind.Sequential)] | |||
public struct Size | |||
{ | |||
public Int32 cx; | |||
public Int32 cy; | |||
public Size(Int32 cx, Int32 cy) { this.cx = cx; this.cy = cy; } | |||
} | |||
[StructLayout(LayoutKind.Sequential, Pack = 1)] | |||
struct ARGB | |||
{ | |||
public byte Blue; | |||
public byte Green; | |||
public byte Red; | |||
public byte Alpha; | |||
} | |||
[StructLayout(LayoutKind.Sequential, Pack = 1)] | |||
public struct BLENDFUNCTION | |||
{ | |||
public byte BlendOp; | |||
public byte BlendFlags; | |||
public byte SourceConstantAlpha; | |||
public byte AlphaFormat; | |||
} | |||
public const Int32 ULW_COLORKEY = 0x00000001; | |||
public const Int32 ULW_ALPHA = 0x00000002; | |||
public const Int32 ULW_OPAQUE = 0x00000004; | |||
public const byte AC_SRC_OVER = 0x00; | |||
public const byte AC_SRC_ALPHA = 0x01; | |||
[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)] | |||
public static extern int UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, Int32 crKey, ref BLENDFUNCTION pblend, Int32 dwFlags); | |||
[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)] | |||
public static extern IntPtr GetDC(IntPtr hWnd); | |||
[DllImport("user32.dll", ExactSpelling = true)] | |||
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); | |||
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)] | |||
public static extern IntPtr CreateCompatibleDC(IntPtr hDC); | |||
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)] | |||
public static extern int DeleteDC(IntPtr hdc); | |||
[DllImport("gdi32.dll", ExactSpelling = true)] | |||
public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject); | |||
[DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)] | |||
public static extern int DeleteObject(IntPtr hObject); | |||
} | |||
} |
@@ -332,9 +332,6 @@ | |||
<DependentUpon>LogForm.cs</DependentUpon> | |||
</Compile> | |||
<Compile Include="View\MenuViewController.cs" /> | |||
<Compile Include="View\QRCodeSplashForm.cs"> | |||
<SubType>Form</SubType> | |||
</Compile> | |||
<Compile Include="Views\ServerSharingView.xaml.cs"> | |||
<DependentUpon>ServerSharingView.xaml</DependentUpon> | |||
</Compile> | |||