From 51aae53725960db30cb41208e54e7a05d6522d81 Mon Sep 17 00:00:00 2001 From: kriastans Date: Fri, 2 Aug 2024 20:37:49 +0800 Subject: [PATCH] =?UTF-8?q?[update]=20=E5=8F=88=E4=B8=80=E6=AC=A1=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=AA=97=E5=8F=A3=E6=88=AA=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- InkCanvasForClass/InkCanvasForClass.csproj | 17 + InkCanvasForClass/MainWindow.xaml.cs | 2 +- .../MainWindow_cs/MW_FloatingBarIcons.cs | 2 +- .../MainWindow_cs/MW_Screenshot.cs | 200 ++++++++-- InkCanvasForClass/MainWindow_cs/MW_Storage.cs | 7 +- .../Popups/ScreenshotWindow.xaml | 363 ++++++++++++----- .../Popups/ScreenshotWindow.xaml.cs | 373 ++++++++++++------ .../classic-icons/desktop-small-icon.png | Bin 0 -> 1279 bytes .../classic-icons/photo-small-icon.png | Bin 0 -> 1345 bytes .../Icons-png/classic-icons/program-icon.png | Bin 0 -> 1070 bytes 10 files changed, 720 insertions(+), 244 deletions(-) create mode 100644 InkCanvasForClass/Resources/Icons-png/classic-icons/desktop-small-icon.png create mode 100644 InkCanvasForClass/Resources/Icons-png/classic-icons/photo-small-icon.png create mode 100644 InkCanvasForClass/Resources/Icons-png/classic-icons/program-icon.png diff --git a/InkCanvasForClass/InkCanvasForClass.csproj b/InkCanvasForClass/InkCanvasForClass.csproj index b46a5c3..ab33dfe 100644 --- a/InkCanvasForClass/InkCanvasForClass.csproj +++ b/InkCanvasForClass/InkCanvasForClass.csproj @@ -123,6 +123,8 @@ + + @@ -190,6 +192,15 @@ False True + + tlbimp + 0 + 1 + 50a7e9b0-70ef-11d1-b75a-00a0c90564fe + 0 + false + true + @@ -209,9 +220,12 @@ + + + @@ -509,9 +523,12 @@ + + + diff --git a/InkCanvasForClass/MainWindow.xaml.cs b/InkCanvasForClass/MainWindow.xaml.cs index de8404f..cda1ef6 100644 --- a/InkCanvasForClass/MainWindow.xaml.cs +++ b/InkCanvasForClass/MainWindow.xaml.cs @@ -186,7 +186,7 @@ namespace Ink_Canvas { public static Settings Settings = new Settings(); public static string settingsFileName = "Settings.json"; - private bool isLoaded = false; + public bool isLoaded = false; [DllImport("user32.dll")] static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert); diff --git a/InkCanvasForClass/MainWindow_cs/MW_FloatingBarIcons.cs b/InkCanvasForClass/MainWindow_cs/MW_FloatingBarIcons.cs index b854e8f..b8d3fde 100644 --- a/InkCanvasForClass/MainWindow_cs/MW_FloatingBarIcons.cs +++ b/InkCanvasForClass/MainWindow_cs/MW_FloatingBarIcons.cs @@ -1030,7 +1030,7 @@ namespace Ink_Canvas { HideSubPanelsImmediately(); await Task.Delay(50); //SaveScreenShotToDesktop(); - var scrwin = new ScreenshotWindow(this); + var scrwin = new ScreenshotWindow(this,Settings); scrwin.Show(); } diff --git a/InkCanvasForClass/MainWindow_cs/MW_Screenshot.cs b/InkCanvasForClass/MainWindow_cs/MW_Screenshot.cs index a4c01c1..bf6987a 100644 --- a/InkCanvasForClass/MainWindow_cs/MW_Screenshot.cs +++ b/InkCanvasForClass/MainWindow_cs/MW_Screenshot.cs @@ -17,10 +17,13 @@ using Vanara.PInvoke; using Encoder = System.Drawing.Imaging.Encoder; using OperatingSystem = OSVersionExtension.OperatingSystem; using PixelFormat = System.Drawing.Imaging.PixelFormat; +using System.Management; +using System.Windows.Shapes; +using Path = System.IO.Path; +using Rectangle = System.Drawing.Rectangle; namespace Ink_Canvas { public partial class MainWindow : Window { - #region MagnificationAPI 获取屏幕截图并过滤ICC窗口 #region Dubi906w 的轮子 @@ -155,7 +158,8 @@ namespace Ink_Canvas { IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); // 設定窗體樣式和排布 int style = GetWindowLong(windowHostHandle, GWL_STYLE); - style &= ~WS_CAPTION; // 隐藏标题栏style &= ~WS_THICKFRAME; // 禁止窗口拉伸 + style &= ~WS_CAPTION; // 隐藏标题栏 + style &= ~WS_THICKFRAME; // 禁止窗口拉伸 SetWindowLong(windowHostHandle, GWL_STYLE, style); SetWindowPos(windowHostHandle, IntPtr.Zero, 0, 0, 0, 0, SWP_NOSIZE | SWP_FRAMECHANGED); // 設定額外樣式 @@ -166,11 +170,42 @@ namespace Ink_Canvas { // 設定放大鏡工廠 Magnification.MAGTRANSFORM matrix = new Magnification.MAGTRANSFORM(); matrix[0, 0] = 1.0f; + matrix[0, 1] = 0.0f; + matrix[0, 2] = 0.0f; + matrix[1, 0] = 0.0f; matrix[1, 1] = 1.0f; - matrix[2, 2] = 1.0f; + matrix[1, 2] = 0.0f; + matrix[2, 0] = 1.0f; + matrix[2, 1] = 0.0f; + matrix[2, 2] = 0.0f; if (!Magnification.MagSetWindowTransform(hwndMag, matrix)) return; // 設定放大鏡轉化矩乘陣列 Magnification.MAGCOLOREFFECT magEffect = new Magnification.MAGCOLOREFFECT(); + magEffect[0, 0] = 1.0f; + magEffect[0, 1] = 0.0f; + magEffect[0, 2] = 0.0f; + magEffect[0, 3] = 0.0f; + magEffect[0, 4] = 0.0f; + magEffect[1, 0] = 0.0f; + magEffect[1, 1] = 1.0f; + magEffect[1, 2] = 0.0f; + magEffect[1, 3] = 0.0f; + magEffect[1, 4] = 0.0f; + magEffect[2, 0] = 0.0f; + magEffect[2, 1] = 0.0f; + magEffect[2, 2] = 1.0f; + magEffect[2, 3] = 0.0f; + magEffect[2, 4] = 0.0f; + magEffect[3, 0] = 0.0f; + magEffect[3, 1] = 0.0f; + magEffect[3, 2] = 0.0f; + magEffect[3, 3] = 1.0f; + magEffect[3, 4] = 0.0f; + magEffect[4, 0] = 0.0f; + magEffect[4, 1] = 0.0f; + magEffect[4, 2] = 0.0f; + magEffect[4, 3] = 0.0f; + magEffect[4, 4] = 1.0f; if (!Magnification.MagSetColorEffect(hwndMag, magEffect)) return; // 顯示窗體 ShowWindow(windowHostHandle, SW_SHOW); @@ -202,6 +237,7 @@ namespace Ink_Canvas { memoryGraphics.ReleaseHdc(); callbackAction(bmp); } + // 反注册宿主窗口 UnregisterClass("ICCMagnifierWindowHost", IntPtr.Zero); // 销毁宿主窗口 @@ -209,10 +245,11 @@ namespace Ink_Canvas { DestroyWindow(windowHostHandle); } - public Task SaveScreenshotToDesktopByMagnificationAPIAsync(HWND[] hwndsList, bool isUsingCallback = false) { + public Task SaveScreenshotToDesktopByMagnificationAPIAsync(HWND[] hwndsList, + bool isUsingCallback = false) { return Task.Run(() => { var t = new TaskCompletionSource(); - SaveScreenshotToDesktopByMagnificationAPI(hwndsList, bitmap => t.TrySetResult(bitmap),isUsingCallback); + SaveScreenshotToDesktopByMagnificationAPI(hwndsList, bitmap => t.TrySetResult(bitmap), isUsingCallback); return t.Task; }); } @@ -229,9 +266,9 @@ namespace Ink_Canvas { static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam); public static IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex) { - if (IntPtr.Size > 4) + if (IntPtr.Size > 4) return GetClassLongPtr64(hWnd, nIndex); - else + else return new IntPtr(GetClassLongPtr32(hWnd, nIndex)); } @@ -276,6 +313,45 @@ namespace Ink_Canvas { [DllImport("dwmapi.dll")] static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out bool pvAttribute, int cbAttribute); + [DllImport("dwmapi.dll")] + static extern int DwmGetWindowAttribute(IntPtr hwnd, DwmWindowAttribute dwAttribute, out RECT pvAttribute, int cbAttribute); + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + static extern bool GetLayeredWindowAttributes(IntPtr hwnd, out uint crKey, out byte bAlpha, out uint dwFlags); + public delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam); + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam); + [DllImport("user32.dll", SetLastError=true)] + static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId); + + enum DwmWindowAttribute : uint { + NCRenderingEnabled = 1, + NCRenderingPolicy, + TransitionsForceDisabled, + AllowNCPaint, + CaptionButtonBounds, + NonClientRtlLayout, + ForceIconicRepresentation, + Flip3DPolicy, + ExtendedFrameBounds, + HasIconicBitmap, + DisallowPeek, + ExcludedFromPeek, + Cloak, + Cloaked, + FreezeRepresentation, + PassiveUpdateMode, + UseHostBackdropBrush, + UseImmersiveDarkMode = 20, + WindowCornerPreference = 33, + BorderColor, + CaptionColor, + TextColor, + VisibleFrameBorderThickness, + SystemBackdropType, + Last + } public Icon GetAppIcon(IntPtr hwnd) { IntPtr iconHandle = SendMessage(hwnd, 0x7F, 2, 0); @@ -303,6 +379,9 @@ namespace Ink_Canvas { public RECT Rect { get; set; } public WINDOWPLACEMENT Placement { get; set; } public HWND hwnd { get; set; } + public RECT RealRect { get; set; } + public Rectangle ContentRect { get; set; } + public IntPtr Handle { get; set; } } public struct WINDOWPLACEMENT { @@ -334,7 +413,7 @@ namespace Ink_Canvas { new HWND(hShellWnd), new HWND(hDefView), new HWND(folderView), new HWND(taskBar) }; var excludedWindowTitle = new string[] { - "NVIDIA GeForce Overlay" + "NVIDIA GeForce Overlay", "Ink Canvas 画板", "Ink Canvas Annotation", "Ink Canvas Artistry", "InkCanvasForClass" }; excluded.AddRange(excludedHwnds); if (!EnumDesktopWindows(IntPtr.Zero, new EnumDesktopWindowsDelegate((hwnd, param) => { @@ -342,16 +421,19 @@ namespace Ink_Canvas { var isvisible = IsWindowVisible(hwnd); if (!isvisible) return true; var windowLong = (int)GetWindowLongPtr(hwnd, -20); + GetLayeredWindowAttributes(hwnd, out uint crKey, out byte bAlpha, out uint dwFlags); if ((windowLong & 0x00000080L) != 0) return true; - DwmGetWindowAttribute(hwnd, 14, out bool isCloacked, Marshal.SizeOf(typeof(bool))); + if ((windowLong & 0x00080000) != 0 && (dwFlags & 0x00000002) != 0 && bAlpha == 0) return true; //分层窗口且全透明 + DwmGetWindowAttribute(hwnd, (int)DwmWindowAttribute.Cloaked, out bool isCloacked, Marshal.SizeOf(typeof(bool))); + DwmGetWindowAttribute(hwnd, DwmWindowAttribute.ExtendedFrameBounds, out RECT realRect, Marshal.SizeOf(typeof(RECT))); if (isCloacked) return true; var icon = GetAppIcon(hwnd); var length = GetWindowTextLength(hwnd) + 1; var title = new StringBuilder(length); GetWindowText(hwnd, title, length); - if (title.ToString().Length == 0) return true; + // if (title.ToString().Length == 0) return true; // 這裡懶得做 Alt Tab窗口的校驗了,直接窗體標題黑名單 - if (excludedWindowTitle.Contains(title.ToString())) return true; + if (excludedWindowTitle.Equals(title.ToString())) return true; RECT rect; WINDOWPLACEMENT placement = new WINDOWPLACEMENT(); GetWindowPlacement(hwnd, ref placement); @@ -359,6 +441,7 @@ namespace Ink_Canvas { GetWindowRect(hwnd, out rect); var w = rect.Width; var h = rect.Height; + Trace.WriteLine($"x: {realRect.X - rect.X} y: {realRect.Y - rect.Y} w: {realRect.Width} h: {realRect.Height}"); if (w == 0 || h == 0) return true; Bitmap bmp = new Bitmap(rect.Width, rect.Height); Graphics memoryGraphics = Graphics.FromImage(bmp); @@ -373,6 +456,9 @@ namespace Ink_Canvas { Height = h, Rect = rect, Placement = placement, + RealRect = realRect, + Handle = hwnd, + ContentRect = new Rectangle(realRect.X - rect.X, realRect.Y - rect.Y, realRect.Width, realRect.Height), }); memoryGraphics.ReleaseHdc(hdc); System.GC.Collect(); @@ -383,9 +469,57 @@ namespace Ink_Canvas { return windows.ToArray(); } + public static string GetProcessPathByPid(int processId) { + string query = $"SELECT Name, ExecutablePath FROM Win32_Process WHERE ProcessId = {processId}"; + ManagementObjectSearcher searcher = new ManagementObjectSearcher(query); + foreach (ManagementObject obj in searcher.Get()) { + string executablePath = obj["ExecutablePath"]?.ToString(); + if (!string.IsNullOrEmpty(executablePath)) return executablePath; + } + return ""; + } + + public async Task GetProcessPathByPidAsync(int processId) { + var result = await Task.Run(() => GetProcessPathByPid(processId)); + return result; + } + + private static string GetAppFriendlyName(string filePath) + { + var versionInfo = FileVersionInfo.GetVersionInfo(filePath); + return versionInfo.FileDescription; + } + public async Task GetAllWindowsAsync(HWND[] excludedHwnds) { var windows = await Task.Run(() => GetAllWindows(excludedHwnds)); - return windows; + var _wins = new List(){}; + foreach (var w in windows) { + _wins.Add(w); + } + foreach (var windowInformation in windows) { + if (windowInformation.Title.Length == 0) { + GetWindowThreadProcessId(windowInformation.Handle, out uint Pid); + if (Pid != 0) { + var _path = Path.GetFullPath(await GetProcessPathByPidAsync((int)Pid)); + var processPath = Path.GetFullPath(Process.GetCurrentProcess().MainModule.FileName); + if (string.Equals(_path, processPath, StringComparison.OrdinalIgnoreCase) || _path == "") { + _wins.Remove(windowInformation); + } else { + var _des = GetAppFriendlyName(_path); + Trace.WriteLine(_des); + if (_des == null) { + _wins.Remove(windowInformation); + } else { + var index = _wins.IndexOf(windowInformation); + _wins[index].Title = _des; + } + } + } else { + _wins.Remove(windowInformation); + } + } + } + return _wins.ToArray(); } #endregion @@ -398,6 +532,7 @@ namespace Ink_Canvas { using (Graphics memoryGrahics = Graphics.FromImage(bitmap)) { memoryGrahics.CopyFromScreen(rc.X, rc.Y, 0, 0, rc.Size, CopyPixelOperation.SourceCopy); } + return bitmap; } @@ -405,10 +540,8 @@ namespace Ink_Canvas { #region 通用截圖API - private BitmapImage BitmapToImageSource(Bitmap bitmap) - { - using (MemoryStream memory = new MemoryStream()) - { + private BitmapImage BitmapToImageSource(Bitmap bitmap) { + using (MemoryStream memory = new MemoryStream()) { bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp); memory.Position = 0; BitmapImage bitmapimage = new BitmapImage(); @@ -444,40 +577,48 @@ namespace Ink_Canvas { public HWND[] ExcludedHwnds { get; set; } = new HWND[] { }; } - private static ImageCodecInfo GetEncoderInfo(string mimeType) - { + private static ImageCodecInfo GetEncoderInfo(string mimeType) { foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders()) if (codec.MimeType == mimeType) return codec; return null; } - + public async Task FullscreenSnapshot(SnapshotConfig config) { - Bitmap bitmap = new Bitmap(1,1); + Bitmap bitmap = new Bitmap(1, 1); var ex = new List() { new HWND(new WindowInteropHelper(this).Handle) }; ex.AddRange(config.ExcludedHwnds); if (config.SnapshotMethod == SnapshotMethod.Auto) { if (OSVersion.GetOperatingSystem() >= OperatingSystem.Windows81) { - bitmap = await SaveScreenshotToDesktopByMagnificationAPIAsync(ex.ToArray(),false); + bitmap = await SaveScreenshotToDesktopByMagnificationAPIAsync(ex.ToArray(), false); } else { - if (ex.Count != 0) foreach (var hwnd in ex) ShowWindow(hwnd.DangerousGetHandle(), 0); + if (ex.Count != 0) + foreach (var hwnd in ex) + ShowWindow(hwnd.DangerousGetHandle(), 0); bitmap = GetScreenshotBitmap(); foreach (var hwnd in ex) ShowWindow(hwnd.DangerousGetHandle(), 5); } - } else if (config.SnapshotMethod == SnapshotMethod.MagnificationAPIWithPrintWindow || config.SnapshotMethod == SnapshotMethod.MagnificationAPIWithCallback) { + } else if (config.SnapshotMethod == SnapshotMethod.MagnificationAPIWithPrintWindow || + config.SnapshotMethod == SnapshotMethod.MagnificationAPIWithCallback) { if (!(OSVersion.GetOperatingSystem() >= OperatingSystem.Windows81)) throw new Exception("您的系統版本不支持 MagnificationAPI 截圖!"); - bitmap = await SaveScreenshotToDesktopByMagnificationAPIAsync(ex.ToArray(),config.SnapshotMethod == SnapshotMethod.MagnificationAPIWithCallback); + bitmap = await SaveScreenshotToDesktopByMagnificationAPIAsync(ex.ToArray(), + config.SnapshotMethod == SnapshotMethod.MagnificationAPIWithCallback); } else if (config.SnapshotMethod == SnapshotMethod.GraphicsAPICopyFromScreen) { - if (ex.Count != 0) foreach (var hwnd in ex) ShowWindow(hwnd.DangerousGetHandle(), 0); + if (ex.Count != 0) + foreach (var hwnd in ex) + ShowWindow(hwnd.DangerousGetHandle(), 0); bitmap = GetScreenshotBitmap(); foreach (var hwnd in ex) ShowWindow(hwnd.DangerousGetHandle(), 5); } + if (bitmap.Width == 1 && bitmap.Height == 1) throw new Exception("截圖失敗"); try { if (config.IsCopyToClipboard) Clipboard.SetImage(BitmapToImageSource(bitmap)); - } catch (NotSupportedException e) {} + } + catch (NotSupportedException e) { } + if (config.IsSaveToLocal) { var fullPath = config.BitmapSavePath.FullName; if (!config.BitmapSavePath.Exists) config.BitmapSavePath.Create(); @@ -486,10 +627,13 @@ namespace Ink_Canvas { .Replace("[HH]", DateTime.Now.Hour.ToString()).Replace("[mm]", DateTime.Now.Minute.ToString()) .Replace("[ss]", DateTime.Now.Second.ToString()).Replace("[width]", bitmap.Width.ToString()) .Replace("[height]", bitmap.Height.ToString()); - var finalPath = (fullPath.EndsWith("\\") ? fullPath.Substring(0, fullPath.Length - 1) : fullPath) + $"\\{fileName}"; - bitmap.Save(finalPath, config.OutputMIMEType == OutputImageMIMEFormat.Png ? ImageFormat.Png : + var finalPath = (fullPath.EndsWith("\\") ? fullPath.Substring(0, fullPath.Length - 1) : fullPath) + + $"\\{fileName}"; + bitmap.Save(finalPath, config.OutputMIMEType == OutputImageMIMEFormat.Png ? ImageFormat.Png : config.OutputMIMEType == OutputImageMIMEFormat.Bmp ? ImageFormat.Bmp : ImageFormat.Jpeg); } + bitmap.Dispose(); + return bitmap; } diff --git a/InkCanvasForClass/MainWindow_cs/MW_Storage.cs b/InkCanvasForClass/MainWindow_cs/MW_Storage.cs index 77b72fa..52e4d3a 100644 --- a/InkCanvasForClass/MainWindow_cs/MW_Storage.cs +++ b/InkCanvasForClass/MainWindow_cs/MW_Storage.cs @@ -270,8 +270,11 @@ namespace Ink_Canvas { } - - //private DirectoryInfo GetDirectoryBySettings() + private DirectoryInfo GetDirectoryBySettings() { + var si = Settings.Storage.StorageLocation; + throw new NotImplementedException(); + return new DirectoryInfo(""); + } private DirectoryInfo GetDirectoryInfoByIndex(int index) { var autoSavedInkPath = new DirectoryInfo(storageLocationItems[ComboBoxStoragePath.SelectedIndex].Path+"\\AutoSavedInk"); diff --git a/InkCanvasForClass/Popups/ScreenshotWindow.xaml b/InkCanvasForClass/Popups/ScreenshotWindow.xaml index d89193d..4578541 100644 --- a/InkCanvasForClass/Popups/ScreenshotWindow.xaml +++ b/InkCanvasForClass/Popups/ScreenshotWindow.xaml @@ -5,114 +5,277 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Ink_Canvas.Popups" xmlns:modern="http://schemas.inkore.net/lib/ui/wpf/modern" - mc:Ignorable="d" AllowsTransparency="True" Background="Transparent" - WindowStyle="None" ResizeMode="NoResize" ShowInTaskbar="False" - Title="ScreenshotWindow" Height="220" Width="360" Topmost="True"> + mc:Ignorable="d" AllowsTransparency="True" Background="Transparent" + WindowStyle="None" ResizeMode="NoResize" ShowInTaskbar="False" WindowState="Maximized" + KeyDown="ScreenshotWindow_OnKeyDown" + Title="ScreenshotWindow" Width="1920" Height="1080" Topmost="True"> + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + - + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 无法截取 + ICC的权限不足 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/InkCanvasForClass/Popups/ScreenshotWindow.xaml.cs b/InkCanvasForClass/Popups/ScreenshotWindow.xaml.cs index 31c39f9..2913021 100644 --- a/InkCanvasForClass/Popups/ScreenshotWindow.xaml.cs +++ b/InkCanvasForClass/Popups/ScreenshotWindow.xaml.cs @@ -1,10 +1,13 @@ -using System; +using Ink_Canvas.Helpers; +using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; +using System.Security.Principal; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -16,11 +19,15 @@ using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Media.Animation; +using System.Windows.Media.Effects; using System.Windows.Media.Imaging; using System.Windows.Shapes; +using System.Windows.Shell; using System.Xml.Linq; using Vanara.PInvoke; using Color = System.Windows.Media.Color; +using Shell32; +using static Ink_Canvas.MainWindow; namespace Ink_Canvas.Popups { @@ -30,45 +37,186 @@ namespace Ink_Canvas.Popups public partial class ScreenshotWindow : Window { private MainWindow mainWindow; + private Settings settings; - public ScreenshotWindow(MainWindow mainWin) { + public ScreenshotWindow(MainWindow mainWin, Settings s) { InitializeComponent(); mainWindow = mainWin; + settings = s; iconList = new Border[] { FullScreenIcon, WindowIcon, - SelectionIcon - }; - iconGeometryList = new GeometryDrawing[] { - FullScreenIconGeometry, - WindowIconsGeometry, - SelectionIconGeometry, - }; - iconTextList = new TextBlock[] { - FullScreenIconText, - WindowIconText, - SelectionIconText, + SelectionIcon, + DesktopIcon }; foreach (var b in iconList) { b.MouseLeave += IconMouseLeave; b.MouseUp += IconMouseUp; b.MouseDown += IconMouseDown; + b.Background = new SolidColorBrush(Colors.Transparent); } - CaptureButton.MouseUp += CaptureButton_MouseUp; - CaptureButton.MouseDown += CaptureButton_MouseDown; - CaptureButton.MouseLeave += CaptureButton_MouseLeave; - - UpdateModeIconSelection(); ReArrangeWindowPosition(); mainWin.Hide(); + + if (DwmCompositionHelper.DwmIsCompositionEnabled()) { + AllowsTransparency = false; + Background = new SolidColorBrush(Colors.Transparent); + WindowChrome.SetWindowChrome(this, new WindowChrome() { + GlassFrameThickness = new Thickness(-1), + CaptionHeight = 0, + CornerRadius = new CornerRadius(0), + ResizeBorderThickness = new Thickness(0), + }); + } else { + AllowsTransparency = true; + Background = new SolidColorBrush(Color.FromArgb(1,0,0,0)); + } + + ToggleSwitchCopyToClipBoard.IsChecked = settings.Snapshot.CopyScreenshotToClipboard; + ToggleSwitchAttachInk.IsChecked = settings.Snapshot.AttachInkWhenScreenshot; + ToggleSwitchCopyToClipBoard.Checked += ToggleSwitchCopyToClipBoard_CheckChanged; + ToggleSwitchCopyToClipBoard.Unchecked += ToggleSwitchCopyToClipBoard_CheckChanged; + ToggleSwitchAttachInk.Checked += ToggleSwitchAttachInk_CheckChanged; + ToggleSwitchAttachInk.Unchecked += ToggleSwitchAttachInk_CheckChanged; + + WindowsItemsControl.ItemsSource = _winInfos; + WindowScreenshotOverlay.Visibility = Visibility.Collapsed; + WindowsSnapshotLoadingOverlay.Visibility = Visibility.Collapsed; + + EscBorder.MouseDown += EscBorder_MouseDown; + EscBorder.MouseUp += EscBorder_MouseUp; + EscBorder.MouseLeave += EscBorder_MouseLeave; + + var identity = WindowsIdentity.GetCurrent(); + var principal = new WindowsPrincipal(identity); + NeedAdminTextPanel.Visibility = principal.IsInRole(WindowsBuiltInRole.Administrator) ? Visibility.Collapsed : Visibility.Visible; + if (principal.IsInRole(WindowsBuiltInRole.Administrator)) { + WindowsItemsScrollViewer.Margin = new Thickness(36, 148, 36, 0); + } else { + WindowsItemsScrollViewer.Margin = new Thickness(36, 178, 36, 0); + } + } + + private bool isEscBorderDown = false; + + private void EscBorder_MouseLeave(object sender, MouseEventArgs e) { + if (!isEscBorderDown) return; + isEscBorderDown = false; + EscBorder.Background = new SolidColorBrush(Colors.Transparent); + } + + private void EscBorder_MouseUp(object sender, MouseButtonEventArgs e) { + if (!isEscBorderDown) return; + if (isWindowsSnapshotLoaded == false) return; + EscBorder_MouseLeave(null, null); + ScreenshotPanel.Visibility = Visibility.Visible; + WindowScreenshotOverlay.Visibility = Visibility.Collapsed; + _winInfos.Clear(); + isWindowsSnapshotLoaded = null; + } + + private void EscBorder_MouseDown(object sender, MouseButtonEventArgs e) { + if (isEscBorderDown) return; + isEscBorderDown = true; + EscBorder.Background = new SolidColorBrush(Color.FromRgb(39, 39, 42)); + } + + private ObservableCollection _winInfos = new ObservableCollection(); + + private bool AllOneColor(Bitmap bmp) + { + // Lock the bitmap's bits. + System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height); + BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat); + + // Get the address of the first line. + IntPtr ptr = bmpData.Scan0; + + // Declare an array to hold the bytes of the bitmap. + int bytes = bmpData.Stride * bmp.Height; + byte[] rgbValues = new byte[bytes]; + + // Copy the RGB values into the array. + System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes); + + bool AllOneColor = true; + for (int index = 0; index < rgbValues.Length; index++) { + //compare the current A or R or G or B with the A or R or G or B at position 0,0. + if (rgbValues[index] != rgbValues[index % 4]) { + AllOneColor= false; + break; + } + } + // Unlock the bits. + bmp.UnlockBits(bmpData); + return AllOneColor; + } + + private async Task AllOneColorAsync(Bitmap bmp) { + var result = await Task.Run(() => AllOneColor(bmp)); + return result; + } + + private BitmapImage BitmapToImageSource(Bitmap bitmap) + { + using (MemoryStream memory = new MemoryStream()) + { + bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp); + memory.Position = 0; + BitmapImage bitmapimage = new BitmapImage(); + bitmapimage.BeginInit(); + bitmapimage.StreamSource = memory; + bitmapimage.CacheOption = BitmapCacheOption.OnLoad; + bitmapimage.EndInit(); + + return bitmapimage; + } + } + + private static ImageSource IconToImageSource(Icon icon) + { + ImageSource imageSource = Imaging.CreateBitmapSourceFromHIcon( + icon.Handle, + Int32Rect.Empty, + BitmapSizeOptions.FromEmptyOptions()); + + return imageSource; + } + + private class WinInfo { + public string Title { get; set; } + public BitmapImage Snapshot { get; set; } + public ImageSource Icon { get; set; } + public HWND Handle { get; set; } + public Bitmap OriginBitmap { get; set; } + public double Width { get; set; } + public double TextBlockWidth { get; set; } + public bool IsAllOneColor { get; set; } + public bool IsDisplayFailedBorder { get; set; } + public Visibility ShouldDisplayFailedBorder { + get => IsDisplayFailedBorder ? Visibility.Visible : Visibility.Collapsed; + } + public bool IsHidden { get; set; } + public Visibility Visibility { + get => IsHidden ? Visibility.Collapsed : Visibility.Visible; + } } private Border lastDownIcon; - private int selectedMode = 0; + + private void ToggleSwitchCopyToClipBoard_CheckChanged(object sender, RoutedEventArgs e) { + if (!mainWindow.isLoaded) return; + mainWindow.ToggleSwitchCopyScreenshotToClipboard.IsOn = ToggleSwitchCopyToClipBoard.IsChecked ?? true; + } + + private void ToggleSwitchAttachInk_CheckChanged(object sender, RoutedEventArgs e) { + if (!mainWindow.isLoaded) return; + mainWindow.ToggleSwitchAttachInkWhenScreenshot.IsOn = ToggleSwitchAttachInk.IsChecked ?? true; + } private void ReArrangeWindowPosition() { var workAreaWidth = SystemParameters.WorkArea.Width; @@ -79,57 +227,15 @@ namespace Ink_Canvas.Popups Top = workAreaHeight - Height - toolbarHeight - 64; } - private void UpdateModeIconSelection() { - foreach (var b in iconList) b.Background = new SolidColorBrush(Colors.Transparent); - foreach (var g in iconGeometryList) g.Brush = new SolidColorBrush(Color.FromRgb(24, 24, 27)); - foreach (var t in iconTextList) t.Foreground = new SolidColorBrush(Color.FromRgb(24, 24, 27)); - iconList[selectedMode].Background = new SolidColorBrush(Color.FromRgb(37, 99, 235)); - iconGeometryList[selectedMode].Brush = new SolidColorBrush(Colors.White); - iconTextList[selectedMode].Foreground = new SolidColorBrush(Colors.White); - } - private bool isCaptureButtonDown = false; - private void CaptureButton_MouseDown(object sender, MouseButtonEventArgs e) { - if (isCaptureButtonDown) return; - - isCaptureButtonDown = true; - var sb = new Storyboard(); - var animation = new DoubleAnimation { - From = 1, - To = 0.9, - Duration = TimeSpan.FromMilliseconds(200) - }; - var animation2 = new DoubleAnimation { - From = 1, - To = 0.9, - Duration = TimeSpan.FromMilliseconds(200) - }; - var animation3 = new ThicknessAnimation() { - From = new Thickness(5), - To = new Thickness(7), - Duration = TimeSpan.FromMilliseconds(200) - }; - Storyboard.SetTargetProperty(animation, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleX)")); - Storyboard.SetTargetProperty(animation2, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleY)")); - Storyboard.SetTargetProperty(animation3, new PropertyPath(Border.BorderThicknessProperty)); - animation.EasingFunction = new CubicEase(); - animation2.EasingFunction = new CubicEase(); - animation3.EasingFunction = new CubicEase(); - sb.Children.Add(animation); - sb.Children.Add(animation2); - sb.Children.Add(animation3); - sb.Begin(CaptureButton); - } - - private void CaptureButton_MouseUp(object sender, MouseButtonEventArgs e) { - if (isCaptureButtonDown != true) return; - CaptureButton_MouseLeave(sender, null); - - if (selectedMode == 0) CaptureFullScreen(); - } - private async void CaptureFullScreen() { + LoadingOverlay.Visibility = Visibility.Visible; + MainFuncPanel.Effect = new BlurEffect() { + KernelType = KernelType.Gaussian, + Radius = 24, + RenderingBias = RenderingBias.Performance, + }; try { var bm = await mainWindow.FullscreenSnapshot(new MainWindow.SnapshotConfig() { BitmapSavePath = @@ -137,81 +243,49 @@ namespace Ink_Canvas.Popups ExcludedHwnds = new HWND[] { new HWND(new WindowInteropHelper(this).Handle) }, - IsCopyToClipboard = true, + IsCopyToClipboard = settings.Snapshot.CopyScreenshotToClipboard, IsSaveToLocal = true, OutputMIMEType = MainWindow.OutputImageMIMEFormat.Png, }); bm.Dispose(); + LoadingOverlay.Visibility = Visibility.Collapsed; + MainFuncPanel.Effect = null; mainWindow.ShowNewToast("已保存截图到桌面!", MW_Toast.ToastType.Success, 3000); await Task.Delay(1); Close(); } catch (Exception e) { + LoadingOverlay.Visibility = Visibility.Collapsed; + MainFuncPanel.Effect = null; mainWindow.ShowNewToast($"截图失败!{e.Message}", MW_Toast.ToastType.Error, 3000); await Task.Delay(1); Close(); } } - private void CaptureButton_MouseLeave(object sender, MouseEventArgs e) { - if (isCaptureButtonDown != true) return; - - var sb = new Storyboard(); - var animation = new DoubleAnimation { - From = 0.9, - To = 1, - Duration = TimeSpan.FromMilliseconds(200) - }; - var animation2 = new DoubleAnimation { - From = 0.9, - To = 1, - Duration = TimeSpan.FromMilliseconds(200) - }; - var animation3 = new ThicknessAnimation() { - From = new Thickness(7), - To = new Thickness(5), - Duration = TimeSpan.FromMilliseconds(200) - }; - Storyboard.SetTargetProperty(animation, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleX)")); - Storyboard.SetTargetProperty(animation2, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleY)")); - Storyboard.SetTargetProperty(animation3, new PropertyPath(Border.BorderThicknessProperty)); - animation.EasingFunction = new CubicEase(); - animation2.EasingFunction = new CubicEase(); - animation3.EasingFunction = new CubicEase(); - sb.Children.Add(animation); - sb.Children.Add(animation2); - sb.Children.Add(animation3); - sb.Begin(CaptureButton); - - isCaptureButtonDown = false; - } - private void IconMouseLeave(object sender, MouseEventArgs e) { if (lastDownIcon == null) return; lastDownIcon = null; var b = (Border)sender; - if (Array.IndexOf(iconList,b)!=selectedMode) - b.Background = new SolidColorBrush(Colors.Transparent); + b.Background = new SolidColorBrush(Colors.Transparent); } private void IconMouseDown(object sender, MouseButtonEventArgs e) { if (lastDownIcon != null) return; lastDownIcon = (Border)sender; var b = (Border)sender; - if (Array.IndexOf(iconList,b)!=selectedMode) - b.Background = new SolidColorBrush(Color.FromArgb(22, 24, 24, 27)); + b.Background = new SolidColorBrush(Color.FromArgb(22, 24, 24, 27)); } - private WindowScreenshotGridWindow _screenshotGridWindow = null; + private bool? isWindowsSnapshotLoaded = false; + private async void IconMouseUp(object sender, MouseButtonEventArgs e) { if (lastDownIcon == null) return; IconMouseLeave(sender, null); - var index = Array.IndexOf(iconList, (Border)sender); - selectedMode = index; - UpdateModeIconSelection(); - if (selectedMode == 1) { + + /*if (selectedMode == 1) { try { MainWindow.WindowInformation[] windows = await mainWindow.GetAllWindowsAsync(new HWND[] { new HWND(new WindowInteropHelper(this).Handle), new HWND(new WindowInteropHelper(mainWindow).Handle) @@ -226,12 +300,57 @@ namespace Ink_Canvas.Popups _screenshotGridWindow.Close(); } catch (Exception ex) { } + }*/ + + if (Array.IndexOf(iconList, (Border)sender) == 0) { + CaptureFullScreen(); + } else if (Array.IndexOf(iconList, (Border)sender) == 3) { + Shell shellObject = new Shell(); + shellObject.ToggleDesktop(); + } else if (Array.IndexOf(iconList, ((Border)sender)) == 1) { + isWindowsSnapshotLoaded = false; + await Dispatcher.InvokeAsync(() => { + _winInfos.Clear(); + ScreenshotPanel.Visibility = Visibility.Collapsed; + WindowScreenshotOverlay.Visibility = Visibility.Visible; + WindowsSnapshotLoadingOverlay.Visibility = Visibility.Visible; + WindowScreenshotWindowsGrid.Effect = new BlurEffect() { + KernelType = KernelType.Gaussian, + RenderingBias = RenderingBias.Performance, + Radius = 32, + }; + }); + var wins = await mainWindow.GetAllWindowsAsync(new HWND[] { + new HWND(new WindowInteropHelper(this).Handle) + }); + foreach (var windowInformation in wins) { + var bitmapHeight = windowInformation.WindowBitmap.Height; + var w = windowInformation.WindowBitmap.Width * (226D / bitmapHeight); + var allonecolor = await AllOneColorAsync(windowInformation.WindowBitmap); + _winInfos.Add(new WinInfo() { + Title = windowInformation.Title, + Snapshot = BitmapToImageSource(windowInformation.WindowBitmap.Clone(windowInformation.ContentRect,windowInformation.WindowBitmap.PixelFormat)), + Handle = windowInformation.hwnd, + OriginBitmap = windowInformation.WindowBitmap, + Icon = windowInformation.AppIcon == null ? new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/classic-icons/program-icon.png")) : IconToImageSource(windowInformation.AppIcon), + Width = w, + TextBlockWidth = w - 48 - 8, + IsAllOneColor = allonecolor, + IsDisplayFailedBorder = allonecolor, + IsHidden = settings.Snapshot.OnlySnapshotMaximizeWindow, + }); + if (Array.IndexOf(wins, windowInformation)>= wins.Length - 1) Dispatcher.InvokeAsync(() => { + WindowScreenshotWindowsGrid.Effect = null; + WindowsSnapshotLoadingOverlay.Visibility = Visibility.Collapsed;}); + } + Dispatcher.InvokeAsync(() => { + WindowScreenshotWindowsGrid.Effect = null; + WindowsSnapshotLoadingOverlay.Visibility = Visibility.Collapsed;}); + isWindowsSnapshotLoaded = true; } } private Border[] iconList = new Border[] { }; - private GeometryDrawing[] iconGeometryList = new GeometryDrawing[] { }; - private TextBlock[] iconTextList = new TextBlock[] { }; private void CloseButton_CloseWindow(object sender, MouseButtonEventArgs e) { Close(); @@ -241,5 +360,35 @@ namespace Ink_Canvas.Popups mainWindow.Show(); base.OnClosed(e); } + + private void ScreenshotWindow_OnKeyDown(object sender, KeyEventArgs e) { + if (e.Key == Key.Escape) { + if (isWindowsSnapshotLoaded==false) return; + ScreenshotPanel.Visibility = Visibility.Visible; + WindowScreenshotOverlay.Visibility = Visibility.Collapsed; + _winInfos.Clear(); + isWindowsSnapshotLoaded = null; + } + } + + public void WindowsItemsScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e) { + var scrollViewer = (ScrollViewer)sender; + var sb = new Storyboard(); + var ofs = scrollViewer.VerticalOffset; + var animation = new DoubleAnimation + { + From = ofs, + To = ofs - e.Delta * 2.5, + Duration = TimeSpan.FromMilliseconds(155) + }; + animation.EasingFunction = new CubicEase() { + EasingMode = EasingMode.EaseOut, + }; + Storyboard.SetTargetProperty(animation, new PropertyPath(ColorPalette.ScrollViewerBehavior.VerticalOffsetProperty)); + Storyboard.SetTargetName(animation,"WindowsItemsScrollViewer"); + sb.Children.Add(animation); + scrollViewer.ScrollToVerticalOffset(ofs); + sb.Begin(scrollViewer); + } } } diff --git a/InkCanvasForClass/Resources/Icons-png/classic-icons/desktop-small-icon.png b/InkCanvasForClass/Resources/Icons-png/classic-icons/desktop-small-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4f6235ef88109470d096e0953ffa0e5aa680ff74 GIT binary patch literal 1279 zcmV?m6$?_ii*1AqYXRNWdZi1<@*1 zY|@F1P0D~`V<{G5=RaU=5qsM}6uUs01Y`6)Nf8wz0pI;N`+Lu6vDQ9!pY!gG_P!09 zx#nW;HRc>+tbNvjr`P}cCGUUm$A1O!Y{boEL=XWUfd_K6(I?8R27j-O+N(9ZsfWhsG5PcbH)flKm<$`GtjA66*LK^iW>W7RE3{> z_XR|RT!4ToY)_V)ZC7kJYZl8DniN%q2nG+j!KntN9)vCow;&Kya|07YYC^J{t7WEg zQ504)v#OK=B2W>wnT#qaa0}c7Q_5mOO8GWbED0%< zdCR$$XRQ>ddj=v<6;8KH)+bBM7Kkpv5)sNk&G=tE&;3B91nzhzc;zYCEq?p{^)i|DKy)fuSH<83TtR5QIPVDvn} z03PLo(*;!*v^@--0f9Ry)>|35ZCoUrzN^u6Qc?RmVA{?~1HhxQyAx8Re;(McwB z{Xo+N%Ovz641;`*@s2w`^28I`Pdi|e0AgZb7#&lk>7AFq_6+N-$6MZg)ZPwy zV8Ekd^pG_~4!X}095G;Ky!PT1f2u3q+da?4^=G^w4|(%j54fsYgiJjGNrorPW~O=N zn0z5K&1W6}2toDR6MkkOnR0(?e0Xu6w|?^te(|%f@Y~=0l2^Zc&8L6=2jDl0GTVCG zwgY4jw$Is{zA*S52%mW%DHoSlT-9q{`td)x`s(+1>%CJRef($MdHeU}WHi_F2$W;m z@94hB@zWpv0N+dCj+yZAqrW2kCF7T#M?d&A=i7(;<=qc?d~tD5zzx_Ppfk-m80UAH zV3Gw1VhnjKWF~zb6vn^+*YfiTtR;vX7T|ZFO4cEJh>s`&Rs;QcWzVFK} pnCZXoF$&mm<0LIDYM}^G0jdD0AR!Rk za?d~D79@TPzlD1Q6eJ{sicmvUsnki&4R!2Ydk-^Qj8jtDx+xd!O-CBddUmw)?tA!l z3@_S?_MDpn&ON8Se*`c9-uoXOx5|6}(Dh-)Mc189=l%U>nne(wcJspmIoKWm@X0qj zk3n)ku>Z_y4N(Hi9oX!_=@l5}Fq(SKtig#cbOlBYynCek5Qql=Sb=1D^RZ@$3|I}b z8X56TZ5L;;K zu!$Bpek5#uZ33+bPQ;KXxHyBGIb0vZ(GIMnkCx#90KF8t9cZUe)q&=`45KK}24W43 z48Xj3`|L#XrK;Z+0 zPC*jS^DofTkN`6cQ@{(bw zc5BGlH{WLG9!$!DUh6pRcpVVR(&By1krupnI%P8r9uZ*oNep=JxqD?yx@LeFU)9VC zPch!*?|w}hZX0Z8=0q;GgGa%|_7^@Ji5CLs8+G?x~V-{;7qwq@5R$(KJ zQi|9LBAV1H)-p{$(a=CI(`>dayUsp21+_5{S)2he3abSjYcLuSMHFcea7r**g>Y95 z&V%zBYdmVM}n`W_=-TgR+Ljm8fm>@ESvfbKH_a1R&)BY=MZULC!9iL?a900000NkvXXu0mjf Db0<^Q literal 0 HcmV?d00001 diff --git a/InkCanvasForClass/Resources/Icons-png/classic-icons/program-icon.png b/InkCanvasForClass/Resources/Icons-png/classic-icons/program-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1c61bf41c4c906b87b095b1bd57b26cceed69225 GIT binary patch literal 1070 zcmV+}1kwA6P)e}}JTzu>Gk?Zfi{pJhcYv3Mm7*2w!7HH8m@EEYyM__mJ?}?7j@s%g- zGz65LB=zFCa}!PAk3Tp1?j3oAj1iO)wAL77a-R)?0AsY*_a8X{5rZ)X5fG6PWVQGA z4JErbI}tm?$?^`k;Dbsp!RX~cq`n|x=$aml{TBBR_R-QyM<$c~guoaMxZqV-B0|-8 z4|H{xt!9&EYkz3&UEt&p@N&Qb9|WZ~aX_QB&-%_TnqJb|hXEgS9mfF|d=Qpsqxbgq zxV^K3D1!=2p~ys6{ZJfm!KL z-jRvGI~^-SU~_Xb*MSo!j;BCBn3vfDB_fj|(MQ0ryu6$OmIWGZ;Njh>lmkAI$qrk# zvuthEbJd)mpYH=)%7MG}b>4pBwXu&?wzu!VnT15{#l?jo5y^I?(ZF*FtgXFDeS}(r z53vN1=oAisGYc6d)7`~4E?yC`XE{a79vy0}<8GppuA+5s=;ri^%aI-}Dy5S1&HuAf z1b+GT_o@4fDFkRX8s~5ReB-BS0Q}L1A3P7VfiA@VkpUH8E*_N$=gA7J^%m!07*qoM6N<$f}17!+W-In literal 0 HcmV?d00001