fux translation
This commit is contained in:
@@ -1,13 +1,13 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using LibreTranslate.Net.Enhanced.Constants;
|
using System.Net;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using LibreTranslate.Net.Enhanced.Models;
|
using LibreTranslate.Net.Enhanced.Models;
|
||||||
using Overlay.NET.Common;
|
using Overlay.NET.Common;
|
||||||
using Overlay.NET.Directx;
|
using Overlay.NET.Directx;
|
||||||
using Process.NET.Windows;
|
using Process.NET.Windows;
|
||||||
using TesseractOCR;
|
using TesseractOCR;
|
||||||
using TesseractOCR.Enums;
|
|
||||||
using Image = TesseractOCR.Pix.Image;
|
using Image = TesseractOCR.Pix.Image;
|
||||||
using ImageFormat = System.Drawing.Imaging.ImageFormat;
|
using ImageFormat = System.Drawing.Imaging.ImageFormat;
|
||||||
using Point = System.Drawing.Point;
|
using Point = System.Drawing.Point;
|
||||||
@@ -17,9 +17,22 @@ namespace OnscreenTranslator;
|
|||||||
|
|
||||||
public class DirectXOverlay : DirectXOverlayPlugin
|
public class DirectXOverlay : DirectXOverlayPlugin
|
||||||
{
|
{
|
||||||
|
private const string TessdataPath = "C:/Users/luis-/Downloads/tessdata-4.1.0/tessdata-4.1.0";
|
||||||
|
|
||||||
|
private static readonly Regex LineRegex =
|
||||||
|
new(@"class=['""]ocr_(?:line|textline|caption|header)['""][^>]*?title=['""]bbox (\d+) (\d+) (\d+) (\d+)");
|
||||||
|
|
||||||
|
private static readonly Regex WordRegex = new(@"class=['""]ocrx_word['""][^>]*>(.*?)</span>", RegexOptions.Singleline);
|
||||||
|
private static readonly Regex TagRegex = new("<[^>]+>");
|
||||||
|
|
||||||
private readonly TickEngine _tickEngine = new();
|
private readonly TickEngine _tickEngine = new();
|
||||||
public readonly ISettings<OverlaySettings> Settings = new SerializableSettings<OverlaySettings>();
|
public readonly ISettings<OverlaySettings> Settings = new SerializableSettings<OverlaySettings>();
|
||||||
|
private readonly LibreTranslate.Net.Enhanced.LibreTranslate _libreTranslate = new("http://127.0.0.1:5000", "");
|
||||||
|
private int _backgroundBrush;
|
||||||
private int _displayFps;
|
private int _displayFps;
|
||||||
|
private Engine? _engine;
|
||||||
|
private TranslationLanguage? _engineLanguage;
|
||||||
|
private bool _excludedFromCapture;
|
||||||
private int _font;
|
private int _font;
|
||||||
private int _hugeFont;
|
private int _hugeFont;
|
||||||
private int _i;
|
private int _i;
|
||||||
@@ -27,9 +40,21 @@ public class DirectXOverlay : DirectXOverlayPlugin
|
|||||||
private int _redBrush;
|
private int _redBrush;
|
||||||
private int _redOpacityBrush;
|
private int _redOpacityBrush;
|
||||||
private float _rotation;
|
private float _rotation;
|
||||||
|
private int _textBrush;
|
||||||
|
private bool _translating;
|
||||||
private Stopwatch _watch;
|
private Stopwatch _watch;
|
||||||
private readonly List<Element> TranslatedElements = new();
|
|
||||||
private List<Element> UntranslatedElements = new();
|
// Replaced as a whole when a translation finishes and never mutated, so rendering can't see a half-built list
|
||||||
|
private IReadOnlyList<Element> _translatedElements = [];
|
||||||
|
|
||||||
|
public TranslationLanguage SourceLanguage { get; set; } = TranslationLanguage.Japanese;
|
||||||
|
public TranslationLanguage TargetLanguage { get; set; } = TranslationLanguage.German;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skip LibreTranslate and show Tesseract's text as-is, for checking OCR results.
|
||||||
|
/// Applies to the next TriggerTranslation.
|
||||||
|
/// </summary>
|
||||||
|
public bool ShowOcrText { get; set; }
|
||||||
|
|
||||||
public override void Initialize(IWindow targetWindow)
|
public override void Initialize(IWindow targetWindow)
|
||||||
{
|
{
|
||||||
@@ -55,11 +80,15 @@ public class DirectXOverlay : DirectXOverlayPlugin
|
|||||||
//Console.Title = @"OverlayExample";
|
//Console.Title = @"OverlayExample";
|
||||||
|
|
||||||
OverlayWindow = new DirectXOverlayWindow(targetWindow.Handle, false);
|
OverlayWindow = new DirectXOverlayWindow(targetWindow.Handle, false);
|
||||||
|
// Keep the overlay out of the screenshots fed to Tesseract
|
||||||
|
_excludedFromCapture = OverlayWindow.ExcludeFromCapture();
|
||||||
_watch = Stopwatch.StartNew();
|
_watch = Stopwatch.StartNew();
|
||||||
|
|
||||||
_redBrush = OverlayWindow.Graphics.CreateBrush(0x7FFF0000);
|
_redBrush = OverlayWindow.Graphics.CreateBrush(0x7FFF0000);
|
||||||
_redOpacityBrush = OverlayWindow.Graphics.CreateBrush(Color.FromArgb(80, 255, 0, 0));
|
_redOpacityBrush = OverlayWindow.Graphics.CreateBrush(Color.FromArgb(80, 255, 0, 0));
|
||||||
_interiorBrush = OverlayWindow.Graphics.CreateBrush(0x7FFFFF00);
|
_interiorBrush = OverlayWindow.Graphics.CreateBrush(0x7FFFFF00);
|
||||||
|
_backgroundBrush = OverlayWindow.Graphics.CreateBrush(Color.FromArgb(200, 0, 0, 0));
|
||||||
|
_textBrush = OverlayWindow.Graphics.CreateBrush(Color.FromArgb(255, 255, 255, 255));
|
||||||
|
|
||||||
_font = OverlayWindow.Graphics.CreateFont("Arial", 20);
|
_font = OverlayWindow.Graphics.CreateFont("Arial", 20);
|
||||||
_hugeFont = OverlayWindow.Graphics.CreateFont("Arial", 50, true);
|
_hugeFont = OverlayWindow.Graphics.CreateFont("Arial", 50, true);
|
||||||
@@ -121,7 +150,7 @@ public class DirectXOverlay : DirectXOverlayPlugin
|
|||||||
OverlayWindow.Graphics.BeginScene();
|
OverlayWindow.Graphics.BeginScene();
|
||||||
OverlayWindow.Graphics.ClearScene();
|
OverlayWindow.Graphics.ClearScene();
|
||||||
|
|
||||||
RenderElements(TranslatedElements);
|
RenderElements(_translatedElements);
|
||||||
|
|
||||||
if (_watch.ElapsedMilliseconds > 1000)
|
if (_watch.ElapsedMilliseconds > 1000)
|
||||||
{
|
{
|
||||||
@@ -138,110 +167,151 @@ public class DirectXOverlay : DirectXOverlayPlugin
|
|||||||
OverlayWindow.Graphics.EndScene();
|
OverlayWindow.Graphics.EndScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TriggerTranslation()
|
/// <summary>
|
||||||
|
/// Captures the target window, reads its text and replaces the overlay with the translation.
|
||||||
|
/// Must be called from the UI thread; calls made while a translation is running are ignored.
|
||||||
|
/// </summary>
|
||||||
|
public async Task TriggerTranslation()
|
||||||
{
|
{
|
||||||
var HTML = GetScreenHTML();
|
if (_translating || OverlayWindow == null) return;
|
||||||
|
_translating = true;
|
||||||
|
|
||||||
UntranslatedElements = GetScreenElements(HTML);
|
try
|
||||||
TranslateElements();
|
{
|
||||||
|
using var screenshot = await CaptureTargetWindow();
|
||||||
|
var source = SourceLanguage;
|
||||||
|
var target = TargetLanguage;
|
||||||
|
|
||||||
|
// OCR takes a while, keep it off the UI thread so rendering continues
|
||||||
|
var html = await Task.Run(() => GetScreenHTML(screenshot, source));
|
||||||
|
var untranslatedElements = GetScreenElements(html, source);
|
||||||
|
var translatedElements = ShowOcrText
|
||||||
|
? ShowUntranslated(untranslatedElements)
|
||||||
|
: await TranslateElements(untranslatedElements, source, target);
|
||||||
|
|
||||||
|
if (OverlayWindow == null) return; // disposed while translating
|
||||||
|
|
||||||
|
foreach (var element in translatedElements)
|
||||||
|
{
|
||||||
|
// Cover the original line, growing the box if the translation is longer
|
||||||
|
var size = OverlayWindow.Graphics.MeasureText(element.Output, _font);
|
||||||
|
element.BoxWidth = Math.Max(element.Width, (int)Math.Ceiling(size.Width));
|
||||||
|
element.BoxHeight = Math.Max(element.Height, (int)Math.Ceiling(size.Height));
|
||||||
|
}
|
||||||
|
|
||||||
|
_translatedElements = translatedElements;
|
||||||
|
// DrawText caches a layout per distinct string; drop the ones from the previous translation
|
||||||
|
OverlayWindow.Graphics.DeleteLayoutContainer();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_translating = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetScreenHTML()
|
private string GetScreenHTML(MemoryStream screenshot, TranslationLanguage language)
|
||||||
{
|
{
|
||||||
var mem = GetScreenDataFromTargetWindow();
|
// Loading the models is slow, so the engine is kept until the source language changes
|
||||||
var languages = new List<Language>();
|
if (_engine == null || _engineLanguage != language)
|
||||||
languages.Add(Language.Japanese);
|
{
|
||||||
languages.Add(Language.JapaneseVertical);
|
_engine?.Dispose();
|
||||||
languages.Add(Language.English);
|
_engine = new Engine(TessdataPath, language.OcrLanguages.ToList());
|
||||||
var engine = new Engine("C:/Users/luis-/Downloads/tessdata-4.1.0/tessdata-4.1.0", languages);
|
_engineLanguage = language;
|
||||||
var img = Image.LoadFromMemory(mem);
|
}
|
||||||
var page = engine.Process(img);
|
|
||||||
|
using var img = Image.LoadFromMemory(screenshot);
|
||||||
|
using var page = _engine.Process(img);
|
||||||
return page.HOcrText();
|
return page.HOcrText();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Element> GetScreenElements(string HTML)
|
/// <summary>
|
||||||
|
/// Turns hOCR into one element per text line, positioned at the line's bounding box.
|
||||||
|
/// </summary>
|
||||||
|
private static List<Element> GetScreenElements(string html, TranslationLanguage language)
|
||||||
{
|
{
|
||||||
var elements = new List<Element>();
|
var elements = new List<Element>();
|
||||||
|
var lines = LineRegex.Matches(html);
|
||||||
|
|
||||||
var Index = 0;
|
for (var i = 0; i < lines.Count; i++)
|
||||||
while (Index != -1)
|
|
||||||
{
|
{
|
||||||
Index = HTML.IndexOf("class='ocrx_word'", Index);
|
var line = lines[i];
|
||||||
if (Index == -1) break;
|
var end = i + 1 < lines.Count ? lines[i + 1].Index : html.Length;
|
||||||
var BbboxIndex = HTML.IndexOf("bbox", Index);
|
var lineHtml = html.Substring(line.Index, end - line.Index);
|
||||||
if (BbboxIndex == -1) break;
|
|
||||||
var XIndex = BbboxIndex + 4;
|
|
||||||
var YIndex = HTML.IndexOf(" ", XIndex + 1);
|
|
||||||
if (YIndex == -1) break;
|
|
||||||
++YIndex;
|
|
||||||
var YEnd = HTML.IndexOf(" ", YIndex + 1);
|
|
||||||
if (YEnd == -1) break;
|
|
||||||
|
|
||||||
var WordStart = HTML.IndexOf(">", YIndex);
|
// Word text may be wrapped in <strong>/<em> and contain entities like &
|
||||||
if (WordStart == -1) break;
|
var words = WordRegex.Matches(lineHtml)
|
||||||
++WordStart;
|
.Select(word => WebUtility.HtmlDecode(TagRegex.Replace(word.Groups[1].Value, "")).Trim())
|
||||||
var WordEnd = HTML.IndexOf("<", WordStart);
|
.Where(word => word.Length > 0);
|
||||||
if (WordEnd == -1) break;
|
var text = string.Join(language.JoinWordsWithSpace ? " " : "", words);
|
||||||
|
if (text.Length == 0) continue;
|
||||||
|
|
||||||
var NewElement = new Element();
|
var x0 = int.Parse(line.Groups[1].Value);
|
||||||
var XString = HTML.Substring(XIndex, YIndex - XIndex);
|
var y0 = int.Parse(line.Groups[2].Value);
|
||||||
var YString = HTML.Substring(YIndex, YEnd - YIndex);
|
elements.Add(new Element
|
||||||
NewElement.X = int.Parse(XString);
|
{
|
||||||
NewElement.Y = int.Parse(YString);
|
X = x0,
|
||||||
NewElement.Input = HTML.Substring(WordStart, WordEnd - WordStart);
|
Y = y0,
|
||||||
elements.Add(NewElement);
|
Width = int.Parse(line.Groups[3].Value) - x0,
|
||||||
|
Height = int.Parse(line.Groups[4].Value) - y0,
|
||||||
Index = WordEnd;
|
Input = text
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return elements;
|
return elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void TranslateElements()
|
private async Task<List<Element>> TranslateElements(List<Element> elements, TranslationLanguage source,
|
||||||
|
TranslationLanguage target)
|
||||||
{
|
{
|
||||||
var libreTranslate = new LibreTranslate.Net.Enhanced.LibreTranslate("http://127.0.0.1:5000", "");
|
var translated = new List<Element>();
|
||||||
|
|
||||||
TranslatedElements.Clear();
|
foreach (var element in elements)
|
||||||
|
|
||||||
foreach (var element in UntranslatedElements)
|
|
||||||
{
|
{
|
||||||
if (element.Input == "")
|
var targetText = await _libreTranslate.TranslateAsync(new Translate
|
||||||
continue;
|
|
||||||
|
|
||||||
var Detection = await libreTranslate.DetectAsync(new Detect
|
|
||||||
{
|
{
|
||||||
|
Source = source.Code,
|
||||||
|
Target = target.Code,
|
||||||
Text = element.Input
|
Text = element.Input
|
||||||
});
|
});
|
||||||
|
if (string.IsNullOrWhiteSpace(targetText)) continue;
|
||||||
|
|
||||||
if (Detection != null)
|
element.Output = targetText;
|
||||||
{
|
translated.Add(element);
|
||||||
var First = Detection.FirstOrDefault();
|
|
||||||
if (First != null)
|
|
||||||
if (First.Confidence == 0)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var TargetText = await libreTranslate.TranslateAsync(new Translate
|
|
||||||
{
|
|
||||||
Source = LanguageCode.English,
|
|
||||||
Target = LanguageCode.German,
|
|
||||||
Text = element.Input
|
|
||||||
});
|
|
||||||
var New = element;
|
|
||||||
New.Output = TargetText;
|
|
||||||
TranslatedElements.Add(New);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return translated;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderElements(List<Element> elements)
|
private static List<Element> ShowUntranslated(List<Element> elements)
|
||||||
{
|
{
|
||||||
|
foreach (var element in elements) element.Output = element.Input;
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderElements(IReadOnlyList<Element> elements)
|
||||||
|
{
|
||||||
|
const int padding = 4;
|
||||||
|
|
||||||
foreach (var element in elements)
|
foreach (var element in elements)
|
||||||
OverlayWindow.Graphics.DrawText(element.Input, _font, _redBrush, element.X, element.Y);
|
{
|
||||||
|
OverlayWindow.Graphics.FillRectangle(element.X - padding, element.Y - padding,
|
||||||
|
element.BoxWidth + padding * 2, element.BoxHeight + padding * 2, _backgroundBrush);
|
||||||
|
OverlayWindow.Graphics.DrawText(element.Output, _font, _textBrush, element.X, element.Y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
if (OverlayWindow != null) OverlayWindow.Dispose();
|
_tickEngine.IsTicking = false;
|
||||||
|
_tickEngine.PreTick -= OnPreTick;
|
||||||
|
_tickEngine.Tick -= OnTick;
|
||||||
|
|
||||||
|
OverlayWindow?.Dispose();
|
||||||
|
OverlayWindow = null;
|
||||||
|
|
||||||
|
_engine?.Dispose();
|
||||||
|
_engine = null;
|
||||||
|
|
||||||
base.Dispose();
|
base.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,31 +322,53 @@ public class DirectXOverlay : DirectXOverlayPlugin
|
|||||||
OverlayWindow.Graphics.EndScene();
|
OverlayWindow.Graphics.EndScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
private MemoryStream GetScreenDataFromTargetWindow()
|
private async Task<MemoryStream> CaptureTargetWindow()
|
||||||
{
|
{
|
||||||
var bounds = new Rectangle(TargetWindow.X,
|
// Without capture exclusion (Windows before 10 2004) the overlay would end up in its own screenshot
|
||||||
TargetWindow.Y,
|
var hideOverlay = !_excludedFromCapture && OverlayWindow.IsVisible;
|
||||||
TargetWindow.Width,
|
if (hideOverlay)
|
||||||
TargetWindow.Height);
|
|
||||||
|
|
||||||
using var bitmap = new Bitmap(bounds.Width, bounds.Height);
|
|
||||||
using (var g = Graphics.FromImage(bitmap))
|
|
||||||
{
|
{
|
||||||
g.CopyFromScreen(new Point(bounds.Left, bounds.Top), Point.Empty, bounds.Size);
|
OverlayWindow.Hide();
|
||||||
|
await Task.Delay(50); // let DWM compose a frame without the overlay
|
||||||
}
|
}
|
||||||
|
|
||||||
// bitmap.Save("C://test.jpg", ImageFormat.Jpeg);
|
try
|
||||||
System.Drawing.Image im = System.Drawing.Image.FromHbitmap(bitmap.GetHbitmap());
|
{
|
||||||
var mem = new MemoryStream();
|
// Use the overlay's bounds (GetWindowRect of the target), not TargetWindow.X/Y/Width/Height:
|
||||||
im.Save(mem, ImageFormat.Bmp);
|
// Process.NET reports the restored placement rect, which is wrong for snapped or maximized windows
|
||||||
return mem;
|
OverlayWindow.Update();
|
||||||
|
var bounds = new Rectangle(OverlayWindow.X,
|
||||||
|
OverlayWindow.Y,
|
||||||
|
OverlayWindow.Width,
|
||||||
|
OverlayWindow.Height);
|
||||||
|
|
||||||
|
using var bitmap = new Bitmap(bounds.Width, bounds.Height);
|
||||||
|
using (var g = Graphics.FromImage(bitmap))
|
||||||
|
{
|
||||||
|
g.CopyFromScreen(new Point(bounds.Left, bounds.Top), Point.Empty, bounds.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// bitmap.Save("C://test.jpg", ImageFormat.Jpeg);
|
||||||
|
var mem = new MemoryStream();
|
||||||
|
bitmap.Save(mem, ImageFormat.Bmp);
|
||||||
|
mem.Position = 0;
|
||||||
|
return mem;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (hideOverlay) OverlayWindow.Show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct Element
|
private class Element
|
||||||
{
|
{
|
||||||
public int X;
|
public int X;
|
||||||
public int Y;
|
public int Y;
|
||||||
public string Input;
|
public int Width;
|
||||||
public string Output;
|
public int Height;
|
||||||
|
public int BoxWidth;
|
||||||
|
public int BoxHeight;
|
||||||
|
public string Input = "";
|
||||||
|
public string Output = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
<Grid>
|
<Grid>
|
||||||
<ComboBox x:Name="ComboBox" HorizontalAlignment="Left" Margin="319,208,0,0" VerticalAlignment="Top" Width="120" Loaded="ComboBox_Loaded" SelectionChanged="ComboBox_SelectionChanged"/>
|
<ComboBox x:Name="ComboBox" HorizontalAlignment="Left" Margin="319,208,0,0" VerticalAlignment="Top" Width="120" Loaded="ComboBox_Loaded" SelectionChanged="ComboBox_SelectionChanged"/>
|
||||||
<Button x:Name="Translate" Content="Button" HorizontalAlignment="Left" Margin="444,209,0,0" VerticalAlignment="Top" Click="Translate_Click"/>
|
<Button x:Name="Translate" Content="Button" HorizontalAlignment="Left" Margin="444,209,0,0" VerticalAlignment="Top" Click="Translate_Click"/>
|
||||||
|
<ComboBox x:Name="SourceLanguageBox" HorizontalAlignment="Left" Margin="319,240,0,0" VerticalAlignment="Top" Width="120" SelectionChanged="SourceLanguageBox_SelectionChanged"/>
|
||||||
|
<ComboBox x:Name="TargetLanguageBox" HorizontalAlignment="Left" Margin="444,240,0,0" VerticalAlignment="Top" Width="120" SelectionChanged="TargetLanguageBox_SelectionChanged"/>
|
||||||
|
<CheckBox x:Name="ShowOcrTextBox" Content="Show OCR text only (no translation)" HorizontalAlignment="Left" Margin="319,270,0,0" VerticalAlignment="Top" Checked="ShowOcrTextBox_Changed" Unchecked="ShowOcrTextBox_Changed"/>
|
||||||
<TextBlock x:Name="TextBlock" HorizontalAlignment="Center" Margin="0,297,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top"/>
|
<TextBlock x:Name="TextBlock" HorizontalAlignment="Center" Margin="0,297,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top"/>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Windows;
|
|||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
|
using System.Windows.Threading;
|
||||||
using Process.NET;
|
using Process.NET;
|
||||||
using Process.NET.Memory;
|
using Process.NET.Memory;
|
||||||
using Process.NET.Windows;
|
using Process.NET.Windows;
|
||||||
@@ -15,8 +16,8 @@ namespace OnscreenTranslator;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
private bool IsRendering;
|
private DirectXOverlay? Overlay;
|
||||||
private DirectXOverlay Overlay = new();
|
private readonly DispatcherTimer RenderTimer = new(DispatcherPriority.Render);
|
||||||
private IWindow? TargetWindow;
|
private IWindow? TargetWindow;
|
||||||
private System.Diagnostics.Process? TrackedProcess;
|
private System.Diagnostics.Process? TrackedProcess;
|
||||||
|
|
||||||
@@ -43,6 +44,55 @@ public partial class MainWindow : Window
|
|||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
SourceLanguageBox.ItemsSource = TranslationLanguage.All;
|
||||||
|
SourceLanguageBox.SelectedItem = TranslationLanguage.Japanese;
|
||||||
|
TargetLanguageBox.ItemsSource = TranslationLanguage.All;
|
||||||
|
TargetLanguageBox.SelectedItem = TranslationLanguage.German;
|
||||||
|
|
||||||
|
// Drives the overlay's tick engine on the UI thread, which owns the overlay window
|
||||||
|
RenderTimer.Interval = TimeSpan.FromMilliseconds(1000.0 / 60);
|
||||||
|
RenderTimer.Tick += (_, _) => Overlay?.Update();
|
||||||
|
RenderTimer.Start();
|
||||||
|
|
||||||
|
Closed += (_, _) =>
|
||||||
|
{
|
||||||
|
RenderTimer.Stop();
|
||||||
|
Overlay?.Dispose();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AttachOverlay(IWindow targetWindow)
|
||||||
|
{
|
||||||
|
TargetWindow = targetWindow;
|
||||||
|
|
||||||
|
// A fresh instance per target: Initialize subscribes tick handlers, so reusing one would stack them
|
||||||
|
Overlay?.Dispose();
|
||||||
|
Overlay = new DirectXOverlay
|
||||||
|
{
|
||||||
|
SourceLanguage = (TranslationLanguage)SourceLanguageBox.SelectedItem,
|
||||||
|
TargetLanguage = (TranslationLanguage)TargetLanguageBox.SelectedItem,
|
||||||
|
ShowOcrText = ShowOcrTextBox.IsChecked == true
|
||||||
|
};
|
||||||
|
|
||||||
|
var fps = 60;
|
||||||
|
Overlay.Settings.Current.UpdateRate = 1000 / fps;
|
||||||
|
Overlay.Initialize(targetWindow);
|
||||||
|
Overlay.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RunTranslation()
|
||||||
|
{
|
||||||
|
if (Overlay == null) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Overlay.TriggerTranslation();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
TextBlock.Text = $"Translation failed: {ex.Message}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
@@ -75,7 +125,7 @@ public partial class MainWindow : Window
|
|||||||
int vkey = (((int)lParam >> 16) & 0xFFFF);
|
int vkey = (((int)lParam >> 16) & 0xFFFF);
|
||||||
if (vkey == VK_F9)
|
if (vkey == VK_F9)
|
||||||
{
|
{
|
||||||
GetWindow();
|
_ = GetWindow();
|
||||||
}
|
}
|
||||||
handled = true;
|
handled = true;
|
||||||
break;
|
break;
|
||||||
@@ -85,7 +135,7 @@ public partial class MainWindow : Window
|
|||||||
return IntPtr.Zero;
|
return IntPtr.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GetWindow()
|
public async Task GetWindow()
|
||||||
{
|
{
|
||||||
// Get own hWnd
|
// Get own hWnd
|
||||||
WindowInteropHelper windowHwnd = new(this);
|
WindowInteropHelper windowHwnd = new(this);
|
||||||
@@ -115,19 +165,9 @@ public partial class MainWindow : Window
|
|||||||
if (process.MainWindowTitle == GetWindowTitle(targetHWnd))
|
if (process.MainWindowTitle == GetWindowTitle(targetHWnd))
|
||||||
{
|
{
|
||||||
var processSharp = new ProcessSharp(process, MemoryType.Remote);
|
var processSharp = new ProcessSharp(process, MemoryType.Remote);
|
||||||
TargetWindow = processSharp.WindowFactory.MainWindow;
|
AttachOverlay(processSharp.WindowFactory.MainWindow);
|
||||||
|
|
||||||
Overlay.Dispose();
|
await RunTranslation();
|
||||||
|
|
||||||
var fps = 60;
|
|
||||||
Overlay.Settings.Current.UpdateRate = 1000 / fps;
|
|
||||||
Overlay.Initialize(TargetWindow);
|
|
||||||
|
|
||||||
Overlay.ClearScreen();
|
|
||||||
|
|
||||||
Overlay.TriggerTranslation();
|
|
||||||
|
|
||||||
Overlay.Update();
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -168,9 +208,24 @@ public partial class MainWindow : Window
|
|||||||
RegisterHotKey(handle, HOTKEY_ID, MOD_CONTROL, VK_F9); //CTRL + CAPS_LOCK
|
RegisterHotKey(handle, HOTKEY_ID, MOD_CONTROL, VK_F9); //CTRL + CAPS_LOCK
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Translate_Click(object sender, RoutedEventArgs e)
|
private async void Translate_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (Overlay != null) Overlay.TriggerTranslation();
|
await RunTranslation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SourceLanguageBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (Overlay != null) Overlay.SourceLanguage = (TranslationLanguage)SourceLanguageBox.SelectedItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TargetLanguageBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (Overlay != null) Overlay.TargetLanguage = (TranslationLanguage)TargetLanguageBox.SelectedItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowOcrTextBox_Changed(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (Overlay != null) Overlay.ShowOcrText = ShowOcrTextBox.IsChecked == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
@@ -181,43 +236,11 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
TextBlock.Text = "FOund something";
|
TextBlock.Text = "FOund something";
|
||||||
var processSharp = new ProcessSharp(TrackedProcess, MemoryType.Remote);
|
var processSharp = new ProcessSharp(TrackedProcess, MemoryType.Remote);
|
||||||
TargetWindow = processSharp.WindowFactory.MainWindow;
|
AttachOverlay(processSharp.WindowFactory.MainWindow);
|
||||||
|
|
||||||
Overlay.Dispose();
|
|
||||||
|
|
||||||
var fps = 60;
|
|
||||||
Overlay.Settings.Current.UpdateRate = 1000 / fps;
|
|
||||||
Overlay.Initialize(TargetWindow);
|
|
||||||
|
|
||||||
Overlay.ClearScreen();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TextBlock.Text = "FOund NOTHING";
|
TextBlock.Text = "FOund NOTHING";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartRendering()
|
|
||||||
{
|
|
||||||
if (IsRendering)
|
|
||||||
return;
|
|
||||||
|
|
||||||
IsRendering = true;
|
|
||||||
|
|
||||||
if (TargetWindow == null) return;
|
|
||||||
|
|
||||||
Overlay = new DirectXOverlay();
|
|
||||||
|
|
||||||
var fps = 60;
|
|
||||||
Overlay.Settings.Current.UpdateRate = 1000 / fps;
|
|
||||||
Overlay.Initialize(TargetWindow);
|
|
||||||
Overlay.Enable();
|
|
||||||
|
|
||||||
var info = Overlay.Settings.Current;
|
|
||||||
|
|
||||||
|
|
||||||
while (true) Overlay.Update();
|
|
||||||
|
|
||||||
// Overlay.Update();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
33
src/OnscreenTranslator/TranslationLanguage.cs
Normal file
33
src/OnscreenTranslator/TranslationLanguage.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using LibreTranslate.Net.Enhanced.Constants;
|
||||||
|
using TesseractOCR.Enums;
|
||||||
|
|
||||||
|
namespace OnscreenTranslator;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A language as both LibreTranslate and Tesseract know it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Name">Display name</param>
|
||||||
|
/// <param name="Code">LibreTranslate language code</param>
|
||||||
|
/// <param name="OcrLanguages">Tesseract models needed to read this language (tessdata must contain them)</param>
|
||||||
|
/// <param name="JoinWordsWithSpace">False for scripts that don't separate words with spaces</param>
|
||||||
|
public record TranslationLanguage(string Name, LanguageCode Code, Language[] OcrLanguages, bool JoinWordsWithSpace = true)
|
||||||
|
{
|
||||||
|
public static readonly TranslationLanguage Japanese =
|
||||||
|
new("Japanese", LanguageCode.Japanese, [Language.Japanese, Language.JapaneseVertical], false);
|
||||||
|
|
||||||
|
public static readonly TranslationLanguage English = new("English", LanguageCode.English, [Language.English]);
|
||||||
|
public static readonly TranslationLanguage German = new("German", LanguageCode.German, [Language.German]);
|
||||||
|
public static readonly TranslationLanguage French = new("French", LanguageCode.French, [Language.French]);
|
||||||
|
|
||||||
|
public static readonly TranslationLanguage Spanish =
|
||||||
|
new("Spanish", LanguageCode.Spanish, [Language.SpanishCastilian]);
|
||||||
|
|
||||||
|
public static readonly TranslationLanguage Korean = new("Korean", LanguageCode.Korean, [Language.Korean]);
|
||||||
|
|
||||||
|
public static readonly TranslationLanguage Chinese =
|
||||||
|
new("Chinese", LanguageCode.Chinese, [Language.ChineseSimplified], false);
|
||||||
|
|
||||||
|
public static readonly TranslationLanguage[] All = [Japanese, English, German, French, Spanish, Korean, Chinese];
|
||||||
|
|
||||||
|
public override string ToString() => Name;
|
||||||
|
}
|
||||||
@@ -47,6 +47,20 @@ namespace Overlay.NET.Common {
|
|||||||
[DllImport("dwmapi.dll")]
|
[DllImport("dwmapi.dll")]
|
||||||
public static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd, ref RawMargin pMargins);
|
public static extern void DwmExtendFrameIntoClientArea(IntPtr hWnd, ref RawMargin pMargins);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern bool SetWindowDisplayAffinity(IntPtr hWnd, uint dwAffinity);
|
||||||
|
|
||||||
|
public delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||||
|
public static extern ushort RegisterClassEx(ref WndClassEx lpwcx);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||||
|
public static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||||
|
public static extern IntPtr GetModuleHandle(string lpModuleName);
|
||||||
|
|
||||||
public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
|
public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
|
||||||
=> IntPtr.Size == 8 ? SetWindowLongPtr64(hWnd, nIndex, dwNewLong) : new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32()));
|
=> IntPtr.Size == 8 ? SetWindowLongPtr64(hWnd, nIndex, dwNewLong) : new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32()));
|
||||||
|
|
||||||
@@ -80,6 +94,22 @@ namespace Overlay.NET.Common {
|
|||||||
public int Y;
|
public int Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||||
|
public struct WndClassEx {
|
||||||
|
public uint cbSize;
|
||||||
|
public uint style;
|
||||||
|
public IntPtr lpfnWndProc;
|
||||||
|
public int cbClsExtra;
|
||||||
|
public int cbWndExtra;
|
||||||
|
public IntPtr hInstance;
|
||||||
|
public IntPtr hIcon;
|
||||||
|
public IntPtr hCursor;
|
||||||
|
public IntPtr hbrBackground;
|
||||||
|
public string lpszMenuName;
|
||||||
|
public string lpszClassName;
|
||||||
|
public IntPtr hIconSm;
|
||||||
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct RawMargin {
|
public struct RawMargin {
|
||||||
public int cxLeftWidth;
|
public int cxLeftWidth;
|
||||||
|
|||||||
@@ -13,15 +13,15 @@ namespace Overlay.NET.Common {
|
|||||||
public const int SmCyScreen = 1;
|
public const int SmCyScreen = 1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The desktop class
|
/// The overlay window class. Registered without a background brush so GDI never paints
|
||||||
|
/// an opaque background over the target window (the "Static" system class does).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string DesktopClass = "Static"; // System Class for a static control
|
public const string OverlayClass = "OverlayNET_DirectXOverlay";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The window style dx
|
/// The window style dx. Created hidden; shown after the first transparent frame is presented.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const uint WindowStyleDx = 0x8000000 //WS_DISABLED
|
public const uint WindowStyleDx = 0x8000000 //WS_DISABLED
|
||||||
| 0x10000000 //WS_VISIBLE
|
|
||||||
| 0x80000000; //WS_POPUP
|
| 0x80000000; //WS_POPUP
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -38,6 +38,11 @@ namespace Overlay.NET.Common {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const int LwaAlpha = 0x00000002;
|
public const int LwaAlpha = 0x00000002;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Excludes the window from screen captures (WDA_EXCLUDEFROMCAPTURE, Windows 10 2004+)
|
||||||
|
/// </summary>
|
||||||
|
public const uint WdaExcludeFromCapture = 0x11;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The HWND notopmost
|
/// The HWND notopmost
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -73,6 +78,11 @@ namespace Overlay.NET.Common {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const uint SwShow = 5;
|
public const uint SwShow = 5;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sw show without activating (keeps focus on the target window)
|
||||||
|
/// </summary>
|
||||||
|
public const uint SwShowNa = 8;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The sw hide
|
/// The sw hide
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -609,6 +609,15 @@ namespace Overlay.NET.Directx {
|
|||||||
/// <param name="x">The x.</param>
|
/// <param name="x">The x.</param>
|
||||||
/// <param name="y">The y.</param>
|
/// <param name="y">The y.</param>
|
||||||
/// <param name="bufferText">if set to <c>true</c> [buffer text].</param>
|
/// <param name="bufferText">if set to <c>true</c> [buffer text].</param>
|
||||||
|
/// <summary>
|
||||||
|
/// Measures the size the text takes when drawn with the given font.
|
||||||
|
/// </summary>
|
||||||
|
public System.Drawing.SizeF MeasureText(string text, int font) {
|
||||||
|
using (var layout = new TextLayout(_fontFactory, text, _fontContainer[font], float.MaxValue, float.MaxValue)) {
|
||||||
|
return new System.Drawing.SizeF(layout.Metrics.WidthIncludingTrailingWhitespace, layout.Metrics.Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void DrawText(string text, int font, int brush, int x, int y, bool bufferText = true) {
|
public void DrawText(string text, int font, int brush, int x, int y, bool bufferText = true) {
|
||||||
if (bufferText) {
|
if (bufferText) {
|
||||||
var bufferPos = -1;
|
var bufferPos = -1;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Overlay.NET.Common;
|
using Overlay.NET.Common;
|
||||||
@@ -102,7 +103,7 @@ namespace Overlay.NET.Directx {
|
|||||||
/// <exception cref="Exception">Could not create OverlayWindow</exception>
|
/// <exception cref="Exception">Could not create OverlayWindow</exception>
|
||||||
public DirectXOverlayWindow(bool limitFps = true) {
|
public DirectXOverlayWindow(bool limitFps = true) {
|
||||||
IsDisposing = false;
|
IsDisposing = false;
|
||||||
IsVisible = true;
|
IsVisible = false;
|
||||||
IsTopMost = true;
|
IsTopMost = true;
|
||||||
|
|
||||||
ParentWindowExists = false;
|
ParentWindowExists = false;
|
||||||
@@ -121,6 +122,8 @@ namespace Overlay.NET.Directx {
|
|||||||
Graphics = new Direct2DRenderer(Handle, limitFps);
|
Graphics = new Direct2DRenderer(Handle, limitFps);
|
||||||
|
|
||||||
SetBounds(X, Y, Width, Height);
|
SetBounds(X, Y, Width, Height);
|
||||||
|
|
||||||
|
PresentEmptyFrameAndShow();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -142,7 +145,7 @@ namespace Overlay.NET.Directx {
|
|||||||
Native.GetWindowRect(parent, out bounds);
|
Native.GetWindowRect(parent, out bounds);
|
||||||
|
|
||||||
IsDisposing = false;
|
IsDisposing = false;
|
||||||
IsVisible = true;
|
IsVisible = false;
|
||||||
IsTopMost = true;
|
IsTopMost = true;
|
||||||
|
|
||||||
ParentWindowExists = true;
|
ParentWindowExists = true;
|
||||||
@@ -162,6 +165,8 @@ namespace Overlay.NET.Directx {
|
|||||||
Graphics = new Direct2DRenderer(Handle, limitFps);
|
Graphics = new Direct2DRenderer(Handle, limitFps);
|
||||||
|
|
||||||
SetBounds(X, Y, Width, Height);
|
SetBounds(X, Y, Width, Height);
|
||||||
|
|
||||||
|
PresentEmptyFrameAndShow();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -175,11 +180,69 @@ namespace Overlay.NET.Directx {
|
|||||||
/// Clean up used ressources and destroy window
|
/// Clean up used ressources and destroy window
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
|
if (IsDisposing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
IsDisposing = true;
|
IsDisposing = true;
|
||||||
|
// Already cleaned up; the finalizer would otherwise destroy the window a second time
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
Graphics.Dispose();
|
Graphics.Dispose();
|
||||||
Native.DestroyWindow(Handle);
|
Native.DestroyWindow(Handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Keeps the window out of screen captures (BitBlt/CopyFromScreen, Graphics Capture), so the
|
||||||
|
/// overlay never ends up in its own screenshots. Requires Windows 10 2004+.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// true on success
|
||||||
|
/// </returns>
|
||||||
|
public bool ExcludeFromCapture() => Native.SetWindowDisplayAffinity(Handle, WindowConstants.WdaExcludeFromCapture);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Presents a fully transparent frame before the window becomes visible, so it never shows
|
||||||
|
/// undefined content on screen.
|
||||||
|
/// </summary>
|
||||||
|
private void PresentEmptyFrameAndShow() {
|
||||||
|
Graphics.BeginScene();
|
||||||
|
Graphics.ClearScene();
|
||||||
|
Graphics.EndScene();
|
||||||
|
|
||||||
|
Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Must stay referenced for the lifetime of the process, the window class points at it.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Native.WndProc WndProcDelegate = Native.DefWindowProc;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the overlay window class was registered
|
||||||
|
/// </summary>
|
||||||
|
private static bool _classRegistered;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers the overlay window class without a background brush.
|
||||||
|
/// </summary>
|
||||||
|
private static void EnsureClassRegistered() {
|
||||||
|
if (_classRegistered) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var windowClass = new Native.WndClassEx {
|
||||||
|
cbSize = (uint) Marshal.SizeOf(typeof(Native.WndClassEx)),
|
||||||
|
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(WndProcDelegate),
|
||||||
|
hInstance = Native.GetModuleHandle(null),
|
||||||
|
hbrBackground = IntPtr.Zero,
|
||||||
|
lpszClassName = WindowConstants.OverlayClass
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns 0 if the class already exists, which is fine; CreateWindowEx reports real failures
|
||||||
|
Native.RegisterClassEx(ref windowClass);
|
||||||
|
_classRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a window with the information's stored in this class
|
/// Creates a window with the information's stored in this class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -187,9 +250,11 @@ namespace Overlay.NET.Directx {
|
|||||||
/// true on success
|
/// true on success
|
||||||
/// </returns>
|
/// </returns>
|
||||||
private bool CreateWindow() {
|
private bool CreateWindow() {
|
||||||
|
EnsureClassRegistered();
|
||||||
|
|
||||||
Handle = Native.CreateWindowEx(
|
Handle = Native.CreateWindowEx(
|
||||||
WindowConstants.WindowExStyleDx,
|
WindowConstants.WindowExStyleDx,
|
||||||
WindowConstants.DesktopClass,
|
WindowConstants.OverlayClass,
|
||||||
"",
|
"",
|
||||||
WindowConstants.WindowStyleDx,
|
WindowConstants.WindowStyleDx,
|
||||||
X,
|
X,
|
||||||
@@ -198,7 +263,7 @@ namespace Overlay.NET.Directx {
|
|||||||
Height,
|
Height,
|
||||||
IntPtr.Zero,
|
IntPtr.Zero,
|
||||||
IntPtr.Zero,
|
IntPtr.Zero,
|
||||||
IntPtr.Zero,
|
Native.GetModuleHandle(null),
|
||||||
IntPtr.Zero);
|
IntPtr.Zero);
|
||||||
|
|
||||||
if (Handle == IntPtr.Zero) {
|
if (Handle == IntPtr.Zero) {
|
||||||
@@ -297,7 +362,7 @@ namespace Overlay.NET.Directx {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Native.ShowWindow(Handle, WindowConstants.SwShow);
|
Native.ShowWindow(Handle, WindowConstants.SwShowNa);
|
||||||
IsVisible = true;
|
IsVisible = true;
|
||||||
|
|
||||||
ExtendFrameIntoClient();
|
ExtendFrameIntoClient();
|
||||||
|
|||||||
Reference in New Issue
Block a user