This commit is contained in:
nsnail 2022-12-17 11:29:42 +08:00
parent a2eb6caf35
commit fe181ca914
15 changed files with 289 additions and 17 deletions

View File

@ -18,7 +18,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "meta", "meta", "{AD79881E-7
CodeQuality.props = CodeQuality.props
Directory.Build.props = Directory.Build.props
dot.sln.DotSettings = dot.sln.DotSettings
dot.sln.DotSettings.user = dot.sln.DotSettings.user
dotnet-tools.json = dotnet-tools.json
GenerateResx.targets = GenerateResx.targets
git-clean.cmd = git-clean.cmd

View File

@ -1,4 +1,8 @@
<wpf:ResourceDictionary xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xml:space="preserve">
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/ReSpeller/ReSpellerEnabled/@EntryValue">False</s:Boolean>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_CODE/@EntryValue">1</s:Int64>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue">1</s:Int64>

View File

@ -1,5 +1,4 @@
using System.Collections.Concurrent;
using System.Globalization;
using System.Text.RegularExpressions;
// ReSharper disable once RedundantUsingDirective

View File

@ -2,7 +2,6 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using NSExt.Extensions;

View File

@ -1,4 +1,3 @@
global using System.ComponentModel;
global using System.Globalization;
global using Dot.Lang;
global using Spectre.Console;
global using Spectre.Console.Cli;

View File

@ -1,6 +1,5 @@
// ReSharper disable ClassNeverInstantiated.Global
using System.Globalization;
#if NET7_0_WINDOWS
using TextCopy;
#endif

View File

@ -264,4 +264,7 @@
<data name="BufferSize" xml:space="preserve">
<value>缓冲区大小(千字节)</value>
</data>
<data name="Translating" xml:space="preserve">
<value>翻译中...</value>
</data>
</root>

View File

@ -1,5 +1,8 @@
// ReSharper disable UnusedAutoPropertyAccessor.Global
global using Spectre.Console;
global using Spectre.Console.Cli;
namespace Dot;
internal abstract class OptionBase : CommandSettings, IOption

View File

@ -1,4 +1,3 @@
using System.Globalization;
using System.Text;
using Dot.Git;
#if NET7_0_WINDOWS
@ -7,7 +6,7 @@ using System.Runtime.InteropServices;
namespace Dot;
internal sealed class Program
internal static class Program
{
public static int Main(string[] args)
{
@ -21,6 +20,7 @@ internal sealed class Program
#if NET7_0_WINDOWS
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
config.AddCommand<Color.Main>(nameof(Color).ToLower(CultureInfo.InvariantCulture));
config.AddCommand<Tran.Main>(nameof(Tran).ToLower(CultureInfo.InvariantCulture));
}
#endif
config.AddCommand<Guid.Main>(nameof(Guid).ToLower(CultureInfo.InvariantCulture));

View File

@ -1,6 +1,5 @@
// ReSharper disable ClassNeverInstantiated.Global
using System.Globalization;
using System.Net.Sockets;
namespace Dot.Time;

233
src/Tran/FrmMain.cs Normal file
View File

@ -0,0 +1,233 @@
#if NET7_0_WINDOWS
using NSExt.Extensions;
using Colors = System.Drawing.Color;
using Padding = System.Windows.Forms.Padding;
using Size = System.Drawing.Size;
namespace Dot.Tran;
internal sealed class FrmMain : Form
{
private const int _HIDING_MIL_SECS = 1000; // 鼠标离开超过此时间后隐藏窗体
private const string _TOKEN_URL = "https://fanyi.qq.com/api/reauth12f";
private const string _URL = "https://fanyi.qq.com/api/translate";
private static ManualResetEvent _mre = new(false); // 隐藏窗体侦测线程信号
private readonly Colors _bgColor // 背景颜色
= Colors.FromArgb(0xff, 0x1e, 0x1e, 0x1e);
private readonly Colors _foreColor = Colors.White; // 字体颜色
private readonly HttpClient _httpClient = new();
private readonly RichTextBox _richTextSource = new(); // 显示剪贴板内容的富文本框
private readonly TextBox _textDest = new(); // 显示翻译内容的文本框
private readonly Padding _windowPadding = new(10, 10, 10, 10); // 窗体边距
private readonly Size _windowSize = new(640, 360); // 窗体大小
private DateTime? _mouseLeaveTime; // 鼠标离开窗体时间
private nint _nextHwnd; // 下一个剪贴板监视链对象句柄
public FrmMain()
{
InitForm();
InitTextSource();
InitTextDest();
_nextHwnd = Win32.SetClipboardViewer(Handle);
Task.Run(HideWindow);
}
~FrmMain()
{
_mre?.Dispose();
_richTextSource?.Dispose();
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// 结束定时隐藏窗体线程
_mre = null;
// 从剪贴板监视链移除本窗体
Win32.ChangeClipboardChain(Handle, _nextHwnd);
}
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
_mouseLeaveTime = null;
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
if (ClientRectangle.Contains(PointToClient(MousePosition))) {
return;
}
_mouseLeaveTime = DateTime.Now;
}
protected override void WndProc(ref Message m)
{
void SendToNext(Message message)
{
_ = Win32.SendMessageA(_nextHwnd, (uint)message.Msg, message.WParam, message.LParam);
}
switch (m.Msg) {
case Win32.WM_DRAWCLIPBOARD:
DisplayClipboardData();
SendToNext(m);
break;
case Win32.WM_CHANGECBCHAIN:
if (m.WParam == _nextHwnd) {
_nextHwnd = m.LParam;
}
else {
SendToNext(m);
}
break;
default:
base.WndProc(ref m);
break;
}
}
/// <summary>
/// 显示剪贴板内容.
/// </summary>
private void DisplayClipboardData()
{
var clipData = Clipboard.GetDataObject();
if (clipData is null) {
return;
}
if (clipData.GetDataPresent(DataFormats.Rtf)) {
_richTextSource.Rtf = clipData.GetData(DataFormats.Rtf) as string;
}
else if (clipData.GetDataPresent(DataFormats.Text)) {
_richTextSource.Text = clipData.GetData(DataFormats.Text) as string;
}
else {
return;
}
var mousePos = Cursor.Position;
mousePos.Offset(-_windowPadding.Left, -_windowPadding.Top);
Location = mousePos;
Visible = true;
_mouseLeaveTime = null;
_mre.Set();
}
private TokenStruct GetTranslateToken()
{
using var rsp = _httpClient.PostAsync(_TOKEN_URL, new StringContent(string.Empty)).Result;
var rspStr = rsp.Content.ReadAsStringAsync().Result;
return rspStr.Object<TokenStruct>();
}
private int HalfHeight()
{
return (_windowSize.Height - _windowPadding.Top - _windowPadding.Bottom) / 2 - _windowPadding.Top / 2;
}
private async Task HideWindow()
{
while (_mre is not null) {
while (Visible) {
await Task.Delay(100);
if (_mouseLeaveTime is null ||
!((DateTime.Now - _mouseLeaveTime.Value).TotalMilliseconds > _HIDING_MIL_SECS)) {
continue;
}
Invoke(() => {
Visible = false;
_richTextSource.Clear();
_textDest.Clear();
});
}
_mre.WaitOne();
}
}
private void InitForm()
{
BackColor = _bgColor;
FormBorderStyle = FormBorderStyle.None;
Padding = _windowPadding;
Size = _windowSize;
TopMost = true;
Visible = false;
}
private void InitTextDest()
{
_textDest.BackColor = _bgColor;
_textDest.BorderStyle = BorderStyle.None;
_textDest.Dock = DockStyle.Bottom;
_textDest.ForeColor = _foreColor;
_textDest.Height = HalfHeight();
_textDest.Margin = new Padding(0, _windowPadding.Top / 2, 0, 0);
_textDest.MouseEnter += (_, e) => OnMouseEnter(e);
_textDest.Multiline = true;
_textDest.ScrollBars = ScrollBars.None;
Controls.Add(_textDest);
}
private void InitTextSource()
{
_richTextSource.TextChanged += (sender, e) => {
if (_richTextSource.Text.NullOrWhiteSpace()) {
return;
}
_textDest.Text = Str.Translating;
_textDest.Text = TranslateText(_richTextSource.Text);
};
_richTextSource.BackColor = _bgColor;
_richTextSource.BorderStyle = BorderStyle.None;
_richTextSource.Dock = DockStyle.Top;
_richTextSource.ForeColor = _foreColor;
_richTextSource.Height = HalfHeight();
_richTextSource.Margin = new Padding(0, 0, 0, _windowPadding.Top / 2);
_richTextSource.MouseEnter += (_, e) => OnMouseEnter(e);
_richTextSource.ScrollBars = RichTextBoxScrollBars.None;
Controls.Add(_richTextSource);
}
private string TranslateText(string sourceText)
{
var token = GetTranslateToken();
var content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>> {
new("source", "auto")
, new("target", "zh")
, new("qtv", token.Qtv.Url())
, new("qtk", token.Qtk.Url())
, new("sourceText", sourceText.Url())
});
var rsp = _httpClient.PostAsync(_URL, content).Result;
var ret = rsp.Content.ReadAsStringAsync().Result;
return ret;
}
private readonly struct TokenStruct
{
// ReSharper disable UnusedAutoPropertyAccessor.Local
public string Qtk { get; init; }
public string Qtv { get; init; }
// ReSharper restore UnusedAutoPropertyAccessor.Local
}
}
#endif

20
src/Tran/Main.cs Normal file
View File

@ -0,0 +1,20 @@
#if NET7_0_WINDOWS
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Dot.Tran;
internal sealed class Main : ToolBase<Option>
{
[SupportedOSPlatform(nameof(OSPlatform.Windows))]
protected override Task Core()
{
var th = new Thread(() => { Application.Run(new FrmMain()); });
th.SetApartmentState(ApartmentState.STA);
th.Start();
th.Join();
return Task.CompletedTask;
}
}
#endif

3
src/Tran/Option.cs Normal file
View File

@ -0,0 +1,3 @@
namespace Dot.Tran;
internal sealed class Option : OptionBase { }

View File

@ -8,6 +8,8 @@ namespace Dot;
internal static partial class Win32
{
public const int SW_HIDE = 0;
public const int WM_CHANGECBCHAIN = 0x030D;
public const int WM_DRAWCLIPBOARD = 0x308;
private const string _GDI32_DLL = "gdi32.dll";
private const string _KERNEL32_DLL = "kernel32.dll";
@ -16,6 +18,10 @@ internal static partial class Win32
[LibraryImport(_USER32_DLL)]
internal static partial nint CallNextHookEx(nint hhk, int nCode, nint wParam, nint lParam);
[LibraryImport(_USER32_DLL)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool ChangeClipboardChain(nint hWndRemove, nint hWndNewNext);
[LibraryImport(_KERNEL32_DLL)]
internal static partial nint GetConsoleWindow();
@ -34,6 +40,12 @@ internal static partial class Win32
[LibraryImport(_USER32_DLL)]
internal static partial int ReleaseDC(nint hWnd, nint dc);
[LibraryImport(_USER32_DLL)]
internal static partial int SendMessageA(nint hwnd, uint wMsg, nint wParam, nint lParam);
[LibraryImport(_USER32_DLL)]
internal static partial int SetClipboardViewer(nint hWnd);
[LibraryImport(_KERNEL32_DLL)]
internal static partial void SetLocalTime(Systemtime st);

View File

@ -13,10 +13,10 @@
<DefineConstants>$(DefineConstants);NET7_0_WINDOWS</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NSExt" Version="1.0.9-alpha.0.1"/>
<PackageReference Include="Spectre.Console.Cli.NS" Version="0.45.1-preview.0.48"/>
<PackageReference Include="Spectre.Console.NS" Version="0.45.1-preview.0.48"/>
<PackageReference Condition="'$(TargetFramework)' == 'net7.0-windows'" Include="TextCopy" Version="6.2.0"/>
<PackageReference Include="NSExt" Version="1.0.9-alpha.0.1" />
<PackageReference Include="Spectre.Console.Cli.NS" Version="0.45.1-preview.0.48" />
<PackageReference Include="Spectre.Console.NS" Version="0.45.1-preview.0.48" />
<PackageReference Condition="'$(TargetFramework)' == 'net7.0-windows'" Include="TextCopy" Version="6.2.0" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Lang\Str.resx">
@ -24,6 +24,6 @@
<LastGenOutput>Str.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<Import Project="../CodeQuality.props"/>
<Import Project="../GenerateResx.targets"/>
<Import Project="../CodeQuality.props" />
<Import Project="../GenerateResx.targets" />
</Project>