1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2025-05-05 11:34:07 +02:00

Rewrite screenshot functionality using new DevTools API

This commit is contained in:
chylex 2021-12-24 08:39:28 +01:00
parent 5ebfc67e48
commit a8e7f065cf
Signed by: chylex
GPG Key ID: 4DE42C8F19A80548
4 changed files with 57 additions and 71 deletions

View File

@ -1,9 +1,9 @@
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Forms;
using System.Threading.Tasks;
using CefSharp;
using CefSharp.DevTools.Page;
using TweetDuck.Browser.Adapters;
using TweetDuck.Controls;
using TweetDuck.Dialogs;
@ -54,40 +54,38 @@ private void SetScreenshotHeight(int browserHeight) {
this.height = BrowserUtils.Scale(browserHeight, SizeScale);
}
public bool TakeScreenshot(bool ignoreHeightError = false) {
public Task<Image> TakeScreenshot(bool ignoreHeightError = false) {
if (!ignoreHeightError) {
if (height == 0) {
FormMessage.Error("Screenshot Failed", "Could not detect screenshot size.", FormMessage.OK);
return false;
return null;
}
else if (height > ClientSize.Height) {
FormMessage.Error("Screenshot Failed", $"Screenshot is too large: {height}px > {ClientSize.Height}px", FormMessage.OK);
return false;
return null;
}
}
if (!WindowsUtils.IsAeroEnabled) {
MoveToVisibleLocation(); // TODO make this look nicer I guess
return Task.Run(TakeScreenshotImpl);
}
private async Task<Image> TakeScreenshotImpl() {
if (this.height == 0) {
return null;
}
IntPtr context = NativeMethods.GetDC(this.Handle);
Viewport viewport = new Viewport {
Width = this.ClientSize.Width,
Height = this.height,
Scale = 1
};
if (context == IntPtr.Zero) {
FormMessage.Error("Screenshot Failed", "Could not retrieve a graphics context handle for the notification window to take the screenshot.", FormMessage.OK);
return false;
byte[] data;
using (var devToolsClient = browser.GetDevToolsClient()) {
data = (await devToolsClient.Page.CaptureScreenshotAsync(CaptureScreenshotFormat.Png, clip: viewport)).Data;
}
else {
using Bitmap bmp = new Bitmap(ClientSize.Width, Math.Max(1, height), PixelFormat.Format32bppRgb);
try {
NativeMethods.RenderSourceIntoBitmap(context, bmp);
} finally {
NativeMethods.ReleaseDC(this.Handle, context);
}
Clipboard.SetImage(bmp);
return true;
}
return Image.FromStream(new MemoryStream(data));
}
}
}

View File

@ -7,13 +7,15 @@
#endif
using System;
using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms;
using TweetDuck.Controls;
using TweetLib.Core.Features.Plugins;
#if GEN_SCREENSHOT_FRAMES
using System.Drawing.Imaging;
using System.IO;
using TweetDuck.Core.Utils;
using TweetDuck.Utils;
#endif
namespace TweetDuck.Browser.Notification.Screenshot {
@ -45,7 +47,7 @@ public TweetScreenshotManager(FormBrowser owner, PluginManager pluginManager) {
this.disposer.Tick += disposer_Tick;
#if GEN_SCREENSHOT_FRAMES
this.debugger = new Timer{ Interval = 16 };
this.debugger = new Timer { Interval = 16 };
this.debugger.Tick += debugger_Tick;
#endif
}
@ -85,14 +87,22 @@ private void Callback() {
}
timeout.Stop();
screenshot.TakeScreenshot();
screenshot.TakeScreenshot().ContinueWith(HandleResult, TaskScheduler.FromCurrentSynchronizationContext());
}
#if !NO_HIDE_SCREENSHOTS
OnFinished();
#else
screenshot.MoveToVisibleLocation();
screenshot.FormClosed += (sender, args) => disposer.Start();
#endif
private void HandleResult(Task<Image> task) {
if (task.IsFaulted) {
Program.Reporter.HandleException("Screenshot Failed", "An error occurred while taking a screenshot.", true, task.Exception!.InnerException);
}
else if (task.IsCompleted) {
Clipboard.SetImage(task.Result);
#if !NO_HIDE_SCREENSHOTS
OnFinished();
#else
screenshot.MoveToVisibleLocation();
screenshot.FormClosed += (sender, args) => disposer.Start();
#endif
}
}
private void OnFinished() {
@ -118,30 +128,36 @@ public void Dispose() {
#if GEN_SCREENSHOT_FRAMES
private static readonly string DebugScreenshotPath = Path.Combine(Program.StoragePath, "TD_Screenshots");
private void StartDebugger(){
private void StartDebugger() {
frameCounter = 0;
try{
try {
Directory.Delete(DebugScreenshotPath, true);
WindowsUtils.TrySleepUntil(() => !Directory.Exists(DebugScreenshotPath), 1000, 10);
}catch(DirectoryNotFoundException){}
} catch (DirectoryNotFoundException) {}
Directory.CreateDirectory(DebugScreenshotPath);
debugger.Start();
}
private void debugger_Tick(object sender, EventArgs e){
if (frameCounter < 63 && screenshot.TakeScreenshot(true)){
try{
Clipboard.GetImage()?.Save(Path.Combine(DebugScreenshotPath, "frame_" + (++frameCounter) + ".png"), ImageFormat.Png);
}catch{
System.Diagnostics.Debug.WriteLine("Failed generating frame " + frameCounter);
}
private void debugger_Tick(object sender, EventArgs e) {
if (frameCounter < 63) {
int frame = ++frameCounter;
screenshot.TakeScreenshot(true).ContinueWith(task => SaveDebugFrame(task, frame), TaskScheduler.FromCurrentSynchronizationContext());
}
else{
else {
debugger.Stop();
}
}
private static void SaveDebugFrame(Task<Image> task, int frame) {
if (task.IsFaulted) {
System.Diagnostics.Debug.WriteLine("Failed generating frame " + frame + ": " + task.Exception!.InnerException);
}
else if (task.IsCompleted) {
task.Result?.Save(Path.Combine(DebugScreenshotPath, "frame_" + (++frame) + ".png"), ImageFormat.Png);
}
}
#endif
}
}

View File

@ -1,6 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
@ -68,19 +67,6 @@ private struct MSLLHOOKSTRUCT {
[DllImport("user32.dll")]
private static extern bool GetLastInputInfo(ref LASTINPUTINFO info);
[DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop);
[DllImport("dwmapi.dll")]
public static extern int DwmIsCompositionEnabled(out bool enabled);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ShowScrollBar(IntPtr hWnd, int wBar, bool bShow);
@ -139,16 +125,5 @@ public static int GetIdleSeconds() {
int seconds = (int) Math.Floor(TimeSpan.FromMilliseconds(ticks - info.dwTime).TotalSeconds);
return Math.Max(0, seconds); // ignore rollover after several weeks of uptime
}
public static void RenderSourceIntoBitmap(IntPtr source, Bitmap target) {
using Graphics graphics = Graphics.FromImage(target);
IntPtr graphicsHandle = graphics.GetHdc();
try {
BitBlt(graphicsHandle, 0, 0, target.Width, target.Height, source, 0, 0, 0x00CC0020);
} finally {
graphics.ReleaseHdc(graphicsHandle);
}
}
}
}

View File

@ -6,10 +6,7 @@
namespace TweetDuck.Utils {
static class WindowsUtils {
private static readonly bool IsWindows8OrNewer = OSVersionEquals(major: 6, minor: 2); // windows 8/10
public static bool ShouldAvoidToolWindow { get; } = IsWindows8OrNewer;
public static bool IsAeroEnabled => IsWindows8OrNewer || (NativeMethods.DwmIsCompositionEnabled(out bool isCompositionEnabled) == 0 && isCompositionEnabled);
public static bool ShouldAvoidToolWindow { get; } = OSVersionEquals(major: 6, minor: 2); // windows 8/10
private static bool OSVersionEquals(int major, int minor) {
System.Version ver = Environment.OSVersion.Version;