using System.Drawing; using System.Runtime.InteropServices; using System.Reflection; using System.Threading.Tasks; namespace Photino.NET; public partial class PhotinoWindow { //PRIVATE FIELDS /// /// Parameters sent to Photino.Native to start a new instance of a Photino.Native window. /// /// Indicates whether the window is resizable. /// Specifies whether the context menu is enabled. /// Specifies whether the user zoom is enabled. /// An array of strings representing custom scheme names. /// Specifies whether developer tools are enabled. /// Indicates whether browser permissions are granted. /// Defines the path for temporary files. /// Sets the title of the window. /// Specifies whether the window should use the OS default location. /// Indicates whether the window should use the OS default size. /// Sets the zoom level for the window. private PhotinoNativeParameters _startupParameters = new() { Resizable = true, //These values can't be initialized within the struct itself. Set required defaults. ContextMenuEnabled = true, ZoomEnabled = true, CustomSchemeNames = new string[16], DevToolsEnabled = true, GrantBrowserPermissions = true, UserAgent = "Photino WebView", MediaAutoplayEnabled = true, FileSystemAccessEnabled = true, WebSecurityEnabled = true, JavascriptClipboardAccessEnabled = true, MediaStreamEnabled = true, SmoothScrollingEnabled = true, IgnoreCertificateErrorsEnabled = false, NotificationsEnabled = true, TemporaryFilesPath = IsWindowsPlatform ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Photino") : null, Title = "Photino", UseOsDefaultLocation = true, UseOsDefaultSize = true, Zoom = 100, MaxHeight = int.MaxValue, MaxWidth = int.MaxValue, }; //Pointers to the type and instance. private static IntPtr _nativeType = IntPtr.Zero; private IntPtr _nativeInstance; private readonly int _managedThreadId; //There can only be 1 message loop for all windows. private static bool _messageLoopIsStarted = false; //READ ONLY PROPERTIES /// /// Indicates whether the current platform is Windows. /// /// /// true if the current platform is Windows; otherwise, false. /// public static bool IsWindowsPlatform => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); /// /// Indicates whether the current platform is MacOS. /// /// /// true if the current platform is MacOS; otherwise, false. /// public static bool IsMacOsPlatform => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); /// /// Indicates the version of MacOS /// public static Version MacOsVersion => IsMacOsPlatform ? Version.Parse(RuntimeInformation.OSDescription.Split(' ')[1]) : null; /// /// Indicates whether the current platform is Linux. /// /// /// true if the current platform is Linux; otherwise, false. /// public static bool IsLinuxPlatform => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); /// /// Represents a property that gets the handle of the native window on a Windows platform. /// /// /// Only available on the Windows platform. /// If this property is accessed from a non-Windows platform, a PlatformNotSupportedException will be thrown. /// If this property is accessed before the window is initialized, an ApplicationException will be thrown. /// /// /// The handle of the native window. The value is of type . /// /// Thrown when the window is not initialized yet. /// Thrown when accessed from a non-Windows platform. public IntPtr WindowHandle { get { if (IsWindowsPlatform) { if (_nativeInstance == IntPtr.Zero) throw new ApplicationException("The Photino window is not initialized yet"); var handle = IntPtr.Zero; Invoke(() => handle = Photino_getHwnd_win32(_nativeInstance)); return handle; } else throw new PlatformNotSupportedException($"{nameof(WindowHandle)} is only supported on Windows."); } } /// /// Gets list of information for each monitor from the native window. /// This property represents a list of Monitor objects associated to each display monitor. /// /// /// If called when the native instance of the window is not initialized, it will throw an ApplicationException. /// /// Thrown when the native instance of the window is not initialized. /// /// A read-only list of Monitor objects representing information about each display monitor. /// public IReadOnlyList Monitors { get { if (_nativeInstance == IntPtr.Zero) throw new ApplicationException("The Photino window hasn't been initialized yet."); List monitors = new(); int callback(in NativeMonitor monitor) { monitors.Add(new Monitor(monitor)); return 1; } Invoke(() => Photino_GetAllMonitors(_nativeInstance, callback)); return monitors; } } /// /// Retrieves the primary monitor information from the native window instance. /// /// Thrown when the window hasn't been initialized yet. /// /// Returns a Monitor object representing the main monitor. The main monitor is the first monitor in the list of available monitors. /// public Monitor MainMonitor { get { if (_nativeInstance == IntPtr.Zero) throw new ApplicationException("The Photino window hasn't been initialized yet."); return Monitors[0]; } } /// /// Gets the dots per inch (DPI) for the primary display from the native window. /// /// /// An ApplicationException is thrown if the window hasn't been initialized yet. /// public uint ScreenDpi { get { if (_nativeInstance == IntPtr.Zero) throw new ApplicationException("The Photino window hasn't been initialized yet."); uint dpi = 0; Invoke(() => dpi = Photino_GetScreenDpi(_nativeInstance)); return dpi; } } /// /// Gets a unique GUID to identify the native window. /// /// /// This property is not currently utilized by the Photino framework. /// public Guid Id { get; } = Guid.NewGuid(); //READ-WRITE PROPERTIES /// /// When true, the native window will appear centered on the screen. By default, this is set to false. /// /// /// Thrown if trying to set value after native window is initalized. /// public bool Centered { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.CenterOnInitialize; return false; } set { if (_nativeInstance == IntPtr.Zero) { if (_startupParameters.CenterOnInitialize != value) _startupParameters.CenterOnInitialize = value; } else Invoke(() => Photino_Center(_nativeInstance)); } } /// /// Gets or sets a value indicating whether the native window should be chromeless. /// When true, the native window will appear without a title bar or border. /// By default, this is set to false. /// /// /// Thrown if trying to set value after native window is initalized. /// /// /// The user has to supply titlebar, border, dragging and resizing manually. /// public bool Chromeless { get { return _startupParameters.Chromeless; } set { if (_nativeInstance == IntPtr.Zero) { if (_startupParameters.Chromeless != value) _startupParameters.Chromeless = value; } else throw new ApplicationException("Chromeless can only be set before the native window is instantiated."); } } /// /// When true, the native window and browser control can be displayed with transparent background. /// Html document's body background must have alpha-based value. /// WebView2 on Windows can only be fully transparent or fully opaque. /// By default, this is set to false. /// /// /// On Windows, thrown if trying to set value after native window is initalized. /// public bool Transparent { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.Transparent; var enabled = false; Invoke(() => Photino_GetTransparentEnabled(_nativeInstance, out enabled)); return enabled; } set { if (Transparent != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.Transparent = value; else { if (IsWindowsPlatform) throw new ApplicationException("Transparent can only be set on Windows before the native window is instantiated."); else { Log($"Invoking Photino_SetTransparentEnabled({value})"); Invoke(() => Photino_SetTransparentEnabled(_nativeInstance, value)); } } } } } /// /// When true, the user can access the browser control's context menu. /// By default, this is set to true. /// public bool ContextMenuEnabled { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.ContextMenuEnabled; var enabled = false; Invoke(() => Photino_GetContextMenuEnabled(_nativeInstance, out enabled)); return enabled; } set { if (ContextMenuEnabled != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.ContextMenuEnabled = value; else Invoke(() => Photino_SetContextMenuEnabled(_nativeInstance, value)); } } } /// /// When true, the user can zoom. /// By default, this is set to true. /// public bool ZoomEnabled { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.ZoomEnabled; var enabled = false; Invoke(() => Photino_GetZoomEnabled(_nativeInstance, out enabled)); return enabled; } set { if (ZoomEnabled != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.ZoomEnabled = value; else Invoke(() => Photino_SetZoomEnabled(_nativeInstance, value)); } } } /// /// When true, the user can access the browser control's developer tools. /// By default, this is set to true. /// public bool DevToolsEnabled { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.DevToolsEnabled; var enabled = false; Invoke(() => Photino_GetDevToolsEnabled(_nativeInstance, out enabled)); return enabled; } set { if (DevToolsEnabled != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.DevToolsEnabled = value; else Invoke(() => Photino_SetDevToolsEnabled(_nativeInstance, value)); } } } public bool MediaAutoplayEnabled { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.MediaAutoplayEnabled; var enabled = false; Invoke(() => Photino_GetMediaAutoplayEnabled(_nativeInstance, out enabled)); return enabled; } set { if (MediaAutoplayEnabled != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.MediaAutoplayEnabled = value; else throw new ApplicationException("MediaAutoplayEnabled can only be set before the native window is instantiated."); } } } public string UserAgent { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.UserAgent; var userAgent = string.Empty; Invoke(() => { var ptr = Photino_GetUserAgent(_nativeInstance); userAgent = Marshal.PtrToStringAuto(ptr); }); return userAgent; } set { if (UserAgent != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.UserAgent = value; else throw new ApplicationException("UserAgent can only be set before the native window is instantiated."); } } } public bool FileSystemAccessEnabled { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.FileSystemAccessEnabled; var enabled = false; Invoke(() => Photino_GetFileSystemAccessEnabled(_nativeInstance, out enabled)); return enabled; } set { if (FileSystemAccessEnabled != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.FileSystemAccessEnabled = value; else throw new ApplicationException("FileSystemAccessEnabled can only be set before the native window is instantiated."); } } } public bool WebSecurityEnabled { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.WebSecurityEnabled; var enabled = true; Invoke(() => Photino_GetWebSecurityEnabled(_nativeInstance, out enabled)); return enabled; } set { if (WebSecurityEnabled != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.WebSecurityEnabled = value; else throw new ApplicationException("WebSecurityEnabled can only be set before the native window is instantiated."); } } } public bool JavascriptClipboardAccessEnabled { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.JavascriptClipboardAccessEnabled; var enabled = true; Invoke(() => Photino_GetJavascriptClipboardAccessEnabled(_nativeInstance, out enabled)); return enabled; } set { if (JavascriptClipboardAccessEnabled != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.JavascriptClipboardAccessEnabled = value; else throw new ApplicationException("JavascriptClipboardAccessEnabled can only be set before the native window is instantiated."); } } } public bool MediaStreamEnabled { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.MediaStreamEnabled; var enabled = true; Invoke(() => Photino_GetMediaStreamEnabled(_nativeInstance, out enabled)); return enabled; } set { if (MediaStreamEnabled != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.MediaStreamEnabled = value; else throw new ApplicationException("MediaStreamEnabled can only be set before the native window is instantiated."); } } } public bool SmoothScrollingEnabled { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.SmoothScrollingEnabled; var enabled = false; Invoke(() => Photino_GetSmoothScrollingEnabled(_nativeInstance, out enabled)); return enabled; } set { if (SmoothScrollingEnabled != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.SmoothScrollingEnabled = value; else throw new ApplicationException("SmoothScrollingEnabled can only be set before the native window is instantiated."); } } } public bool IgnoreCertificateErrorsEnabled { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.IgnoreCertificateErrorsEnabled; var enabled = false; Invoke(() => Photino_GetIgnoreCertificateErrorsEnabled(_nativeInstance, out enabled)); return enabled; } set { if (IgnoreCertificateErrorsEnabled != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.IgnoreCertificateErrorsEnabled = value; else throw new ApplicationException("IgnoreCertificateErrorsEnabled can only be set before the native window is instantiated."); } } } public bool NotificationsEnabled { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.NotificationsEnabled; var enabled = false; Invoke(() => Photino_GetNotificationsEnabled(_nativeInstance, out enabled)); return enabled; } set { if (NotificationsEnabled != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.NotificationsEnabled = value; else throw new ApplicationException("NotificationsEnabled can only be set before the native window is instantiated."); } } } /// /// This property returns or sets the fullscreen status of the window. /// When set to true, the native window will cover the entire screen, similar to kiosk mode. /// By default, this is set to false. /// public bool FullScreen { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.FullScreen; var fullScreen = false; Invoke(() => Photino_GetFullScreen(_nativeInstance, out fullScreen)); return fullScreen; } set { if (FullScreen != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.FullScreen = value; else Invoke(() => Photino_SetFullScreen(_nativeInstance, value)); } } } /// /// Gets or Sets whether the native browser control grants all requests for access to local resources /// such as the users camera and microphone. By default, this is set to true. /// /// /// This only works on Windows. /// public bool GrantBrowserPermissions { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.GrantBrowserPermissions; var grant = false; Invoke(() => Photino_GetGrantBrowserPermissions(_nativeInstance, out grant)); return grant; } set { if (GrantBrowserPermissions != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.GrantBrowserPermissions = value; else throw new ApplicationException("GrantBrowserPermissions can only be set before the native window is instantiated."); } } } /// /// /// Gets or Sets the Height property of the native window in pixels. /// Default value is 0. /// /// public int Height { get => Size.Height; set { var currentSize = Size; if (currentSize.Height != value) Size = new Size(currentSize.Width, value); } } private string _iconFile; /// /// Gets or sets the icon file for the native window title bar. /// The file must be located on the local machine and cannot be a URL. The default is none. /// /// /// This only works on Windows and Linux. /// /// /// The file path to the icon. /// /// Icon file: {value} does not exist. public string IconFile { get => _iconFile; set { if (_iconFile != value) { if (!File.Exists(value)) { var absolutePath = $"{System.AppContext.BaseDirectory}{value}"; if (!File.Exists(absolutePath)) throw new ArgumentException($"Icon file: {value} does not exist."); } _iconFile = value; if (_nativeInstance == IntPtr.Zero) _startupParameters.WindowIconFile = _iconFile; else Invoke(() => Photino_SetIconFile(_nativeInstance, _iconFile)); } } } /// /// Gets or sets the native window Left (X) and Top coordinates (Y) in pixels. /// Default is 0,0 which means the window will be aligned to the top left edge of the screen. /// /// public Point Location { get { if (_nativeInstance == IntPtr.Zero) return new Point(_startupParameters.Left, _startupParameters.Top); var left = 0; var top = 0; Invoke(() => Photino_GetPosition(_nativeInstance, out left, out top)); return new Point(left, top); } set { if (Location.X != value.X || Location.Y != value.Y) { if (_nativeInstance == IntPtr.Zero) { _startupParameters.Left = value.X; _startupParameters.Top = value.Y; } else Invoke(() => Photino_SetPosition(_nativeInstance, value.X, value.Y)); } } } /// /// Gets or sets the native window Left (X) coordinate in pixels. /// This represents the horizontal position of the window relative to the screen. /// Default value is 0 which means the window will be aligned to the left edge of the screen. /// /// public int Left { get => Location.X; set { if (Location.X != value) Location = new Point(value, Location.Y); } } /// /// Gets or sets whether the native window is maximized. /// Default is false. /// public bool Maximized { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.Maximized; bool maximized = false; Invoke(() => Photino_GetMaximized(_nativeInstance, out maximized)); return maximized; } set { if (Maximized != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.Maximized = value; else Invoke(() => Photino_SetMaximized(_nativeInstance, value)); } } } ///Gets or set the maximum size of the native window in pixels. public Point MaxSize { get => new Point(MaxWidth, MaxHeight); set { if (MaxWidth != value.X || MaxHeight != value.Y) { if (_nativeInstance == IntPtr.Zero) { _startupParameters.MaxWidth = value.X; _startupParameters.MaxHeight = value.Y; } else Invoke(() => Photino_SetMaxSize(_nativeInstance, value.X, value.Y)); } } } ///Gets or sets the native window maximum height in pixels. private int _maxHeight = int.MaxValue; public int MaxHeight { get => _maxHeight; set { if (_maxHeight != value) { MaxSize = new Point(MaxSize.X, value); _maxHeight = value; } } } ///Gets or sets the native window maximum height in pixels. private int _maxWidth = int.MaxValue; public int MaxWidth { get => _maxWidth; set { if (_maxWidth != value) { MaxSize = new Point(value, MaxSize.Y); _maxWidth = value; } } } /// /// Gets or sets whether the native window is minimized (hidden). /// Default is false. /// public bool Minimized { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.Minimized; bool minimized = false; Invoke(() => Photino_GetMinimized(_nativeInstance, out minimized)); return minimized; } set { if (Minimized != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.Minimized = value; else Invoke(() => Photino_SetMinimized(_nativeInstance, value)); } } } ///Gets or set the minimum size of the native window in pixels. public Point MinSize { get => new Point(MinWidth, MinHeight); set { if (MinWidth != value.X || MinHeight != value.Y) { if (_nativeInstance == IntPtr.Zero) { _startupParameters.MinWidth = value.X; _startupParameters.MinHeight = value.Y; } else Invoke(() => Photino_SetMinSize(_nativeInstance, value.X, value.Y)); } } } ///Gets or sets the native window minimum height in pixels. private int _minHeight = 0; public int MinHeight { get => _minHeight; set { if (_minHeight != value) { MinSize = new Point(MinSize.X, value); _minHeight = value; } } } ///Gets or sets the native window minimum height in pixels. private int _minWidth = 0; public int MinWidth { get => _minWidth; set { if (_minWidth != value) { MinSize = new Point(value, MinSize.Y); _minWidth = value; } } } private PhotinoWindow _dotNetParent; /// /// Gets the reference to parent PhotinoWindow instance. /// This property can only be set in the constructor and it is optional. /// public PhotinoWindow Parent { get { return _dotNetParent; } } /// /// Gets or sets whether the native window can be resized by the user. /// Default is true. /// public bool Resizable { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.Resizable; var resizable = false; Invoke(() => Photino_GetResizable(_nativeInstance, out resizable)); return resizable; } set { if (Resizable != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.Resizable = value; else Invoke(() => Photino_SetResizable(_nativeInstance, value)); } } } /// /// Gets or sets the native window Size. This represents the width and the height of the window in pixels. /// The default Size is 0,0. /// /// public Size Size { get { if (_nativeInstance == IntPtr.Zero) return new Size(_startupParameters.Width, _startupParameters.Height); var width = 0; var height = 0; Invoke(() => Photino_GetSize(_nativeInstance, out width, out height)); return new Size(width, height); } set { if (Size.Width != value.Width || Size.Height != value.Height) { if (_nativeInstance == IntPtr.Zero) { _startupParameters.Height = value.Height; _startupParameters.Width = value.Width; } else Invoke(() => Photino_SetSize(_nativeInstance, value.Width, value.Height)); } } } /// /// Gets or sets platform specific initialization parameters for the native browser control on startup. /// Default is none. ///WINDOWS: WebView2 specific string. Space separated. ///https://peter.sh/experiments/chromium-command-line-switches/ ///https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2environmentoptions.additionalbrowserarguments?view=webview2-dotnet-1.0.1938.49&viewFallbackFrom=webview2-dotnet-1.0.1901.177view%3Dwebview2-1.0.1901.177 ///https://www.chromium.org/developers/how-tos/run-chromium-with-flags/ ///LINUX: Webkit2Gtk specific string. Enter parameter names and values as JSON string. ///e.g. { "set_enable_encrypted_media": true } ///https://webkitgtk.org/reference/webkit2gtk/2.5.1/WebKitSettings.html ///https://lazka.github.io/pgi-docs/WebKit2-4.0/classes/Settings.html ///MAC: Webkit specific string. Enter parameter names and values as JSON string. ///e.g. { "minimumFontSize": 8 } ///https://developer.apple.com/documentation/webkit/wkwebviewconfiguration?language=objc ///https://developer.apple.com/documentation/webkit/wkpreferences?language=objc /// public string BrowserControlInitParameters { get { return _startupParameters.BrowserControlInitParameters; } set { var ss = _startupParameters.BrowserControlInitParameters; if (string.Compare(ss, value, true) != 0) { if (_nativeInstance == IntPtr.Zero) _startupParameters.BrowserControlInitParameters = value; else throw new ApplicationException($"{nameof(ss)} cannot be changed after Photino Window is initialized"); } } } /// /// Gets or sets an HTML string that the browser control will render when initialized. /// Default is none. /// /// /// Either StartString or StartUrl must be specified. /// /// /// /// Thrown if trying to set value after native window is initalized. /// public string StartString { get { return _startupParameters.StartString; } set { var ss = _startupParameters.StartString; if (string.Compare(ss, value, true) != 0) { if (_nativeInstance != IntPtr.Zero) throw new ApplicationException($"{nameof(ss)} cannot be changed after Photino Window is initialized"); LoadRawString(value); } } } /// /// Gets or sets an URL that the browser control will navigate to when initialized. /// Default is none. /// /// /// Either StartString or StartUrl must be specified. /// /// /// /// Thrown if trying to set value after native window is initalized. /// public string StartUrl { get { return _startupParameters.StartUrl; } set { var su = _startupParameters.StartUrl; if (string.Compare(su, value, true) != 0) { if (_nativeInstance != IntPtr.Zero) throw new ApplicationException($"{nameof(su)} cannot be changed after Photino Window is initialized"); Load(value); } } } /// /// Gets or sets the local path to store temp files for browser control. /// Default is the user's AppDataLocal folder. /// /// /// Only available on Windows. /// /// /// Thrown if platform is not Windows. /// public string TemporaryFilesPath { get { return _startupParameters.TemporaryFilesPath; } set { var tfp = _startupParameters.TemporaryFilesPath; if (tfp != value) { if (_nativeInstance != IntPtr.Zero) throw new ApplicationException($"{nameof(tfp)} cannot be changed after Photino Window is initialized"); _startupParameters.TemporaryFilesPath = value; } } } /// /// Gets or sets the registration Id for doing toast notifications. /// Default is to use the window title. /// /// /// Only available on Windows. /// /// /// Thrown if platform is not Windows. /// public string NotificationRegistrationId { get { return _startupParameters.NotificationRegistrationId; } set { var nri = _startupParameters.NotificationRegistrationId; if (nri != value) { if (_nativeInstance != IntPtr.Zero) throw new ApplicationException($"{nameof(nri)} cannot be changed after Photino Window is initialized"); _startupParameters.NotificationRegistrationId = value; } } } /// /// Gets or sets the native window title. /// Default is "Photino". /// public string Title { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.Title; var title = string.Empty; Invoke(() => { var ptr = Photino_GetTitle(_nativeInstance); title = Marshal.PtrToStringAuto(ptr); }); return title; } set { if (Title != value) { // Due to Linux/Gtk platform limitations, the window title has to be no more than 31 chars if (value.Length > 31 && IsLinuxPlatform) value = value[..31]; if (_nativeInstance == IntPtr.Zero) _startupParameters.Title = value; else Invoke(() => Photino_SetTitle(_nativeInstance, value)); } } } /// /// Gets or sets the native window Top (Y) coordinate in pixels. /// Default is 0. /// /// public int Top { get => Location.Y; set { if (Location.Y != value) Location = new Point(Location.X, value); } } /// /// Gets or sets whether the native window is always at the top of the z-order. /// Default is false. /// public bool Topmost { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.Topmost; var topmost = false; Invoke(() => Photino_GetTopmost(_nativeInstance, out topmost)); return topmost; } set { if (Topmost != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.Topmost = value; else Invoke(() => Photino_SetTopmost(_nativeInstance, value)); } } } /// /// When true the native window starts up at the OS Default location. /// Default is true. /// /// /// Overrides Left (X) and Top (Y) properties. /// /// /// Thrown if trying to set value after native window is initalized. /// public bool UseOsDefaultLocation { get { return _startupParameters.UseOsDefaultLocation; } set { if (_nativeInstance == IntPtr.Zero) { if (UseOsDefaultLocation != value) _startupParameters.UseOsDefaultLocation = value; } else throw new ApplicationException("UseOsDefaultLocation can only be set before the native window is instantiated."); } } /// /// When true the native window starts at the OS Default size. /// Default is true. /// /// /// Overrides Height and Width properties. /// /// /// Thrown if trying to set value after native window is initalized. /// public bool UseOsDefaultSize { get { return _startupParameters.UseOsDefaultSize; } set { if (_nativeInstance == IntPtr.Zero) { if (UseOsDefaultSize != value) _startupParameters.UseOsDefaultSize = value; } else throw new ApplicationException("UseOsDefaultSize can only be set before the native window is instantiated."); } } /// /// Gets or sets handlers for WebMessageReceived event. /// Set assigns a new handler to the event. /// /// public EventHandler WebMessageReceivedHandler { get { return WebMessageReceived; } set { WebMessageReceived += value; } } /// /// Gets or Sets the native window width in pixels. /// Default is 0. /// /// public int Width { get => Size.Width; set { var currentSize = Size; if (currentSize.Width != value) Size = new Size(value, currentSize.Height); } } /// /// Gets or sets the handlers for WindowClosing event. /// Set assigns a new handler to the event. /// /// public NetClosingDelegate WindowClosingHandler { get { return WindowClosing; } set { WindowClosing += value; } } /// /// Gets or sets handlers for WindowCreating event. /// Set assigns a new handler to the event. /// /// public EventHandler WindowCreatingHandler { get { return WindowCreating; } set { WindowCreating += value; } } /// /// Gets or sets handlers for WindowCreated event. /// Set assigns a new handler to the event. /// /// public EventHandler WindowCreatedHandler { get { return WindowCreated; } set { WindowCreated += value; } } /// /// Gets or sets handlers for WindowLocationChanged event. /// Set assigns a new handler to the event. /// /// public EventHandler WindowLocationChangedHandler { get { return WindowLocationChanged; } set { WindowLocationChanged += value; } } /// /// Gets or sets handlers for WindowSizeChanged event. /// Set assigns a new handler to the event. /// /// public EventHandler WindowSizeChangedHandler { get { return WindowSizeChanged; } set { WindowSizeChanged += value; } } /// /// Gets or sets handlers for WindowFocusIn event. /// Set assigns a new handler to the event. /// /// public EventHandler WindowFocusInHandler { get { return WindowFocusIn; } set { WindowFocusIn += value; } } /// /// Gets or sets handlers for WindowFocusOut event. /// Set assigns a new handler to the event. /// /// public EventHandler WindowFocusOutHandler { get { return WindowFocusOut; } set { WindowFocusOut += value; } } /// /// Gets or sets handlers for WindowMaximized event. /// Set assigns a new handler to the event. /// /// public EventHandler WindowMaximizedHandler { get { return WindowMaximized; } set { WindowMaximized += value; } } /// /// Gets or sets handlers for WindowRestored event. /// Set assigns a new handler to the event. /// /// public EventHandler WindowRestoredHandler { get { return WindowRestored; } set { WindowRestored += value; } } /// /// Gets or sets handlers for WindowMinimized event. /// Set assigns a new handler to the event. /// /// public EventHandler WindowMinimizedHandler { get { return WindowMinimized; } set { WindowMinimized += value; } } /// /// Gets or sets the native browser control . /// Default is 100. /// /// 100 = 100%, 50 = 50% public int Zoom { get { if (_nativeInstance == IntPtr.Zero) return _startupParameters.Zoom; var zoom = 0; Invoke(() => Photino_GetZoom(_nativeInstance, out zoom)); return zoom; } set { if (Zoom != value) { if (_nativeInstance == IntPtr.Zero) _startupParameters.Zoom = value; else Invoke(() => Photino_SetZoom(_nativeInstance, value)); } } } /// /// Gets or sets the logging verbosity to standard output (Console/Terminal). /// 0 = Critical Only /// 1 = Critical and Warning /// 2 = Verbose /// >2 = All Details /// Default is 2. /// public int LogVerbosity { get; set; } = 2; //CONSTRUCTOR /// /// Initializes a new instance of the PhotinoWindow class. /// /// /// This class represents a native window with a native browser control taking up the entire client area. /// If a parent window is specified, this window will be created as a child of the specified parent window. /// /// The parent PhotinoWindow. This is optional and defaults to null. public PhotinoWindow(PhotinoWindow parent = null) { _dotNetParent = parent; _managedThreadId = Environment.CurrentManagedThreadId; //This only has to be done once if (_nativeType == IntPtr.Zero) _nativeType = NativeLibrary.GetMainProgramHandle(); //Wire up handlers from C++ to C# _startupParameters.ClosingHandler = OnWindowClosing; _startupParameters.ResizedHandler = OnSizeChanged; _startupParameters.MaximizedHandler = OnMaximized; _startupParameters.RestoredHandler = OnRestored; _startupParameters.MinimizedHandler = OnMinimized; _startupParameters.MovedHandler = OnLocationChanged; _startupParameters.FocusInHandler = OnFocusIn; _startupParameters.FocusOutHandler = OnFocusOut; _startupParameters.WebMessageReceivedHandler = OnWebMessageReceived; _startupParameters.CustomSchemeHandler = OnCustomScheme; } //FLUENT METHODS FOR INITIALIZING STARTUP PARAMETERS FOR NEW WINDOWS //CAN ALSO BE CALLED AFTER INITIALIZATION TO SET VALUES //ONE OF THESE 3 METHODS *MUST* BE CALLED PRIOR TO CALLING WAITFORCLOSE() OR CREATECHILDWINDOW() /// /// Dispatches an Action to the UI thread if called from another thread. /// /// /// Returns the current instance. /// /// The delegate encapsulating a method / action to be executed in the UI thread. public PhotinoWindow Invoke(Action workItem) { // If we're already on the UI thread, no need to dispatch if (Environment.CurrentManagedThreadId == _managedThreadId) workItem(); else Photino_Invoke(_nativeInstance, workItem.Invoke); return this; } /// /// Loads a specified into the browser control. /// /// /// Returns the current instance. /// /// /// Load() or LoadString() must be called before native window is initialized. /// /// A Uri pointing to the file or the URL to load. public PhotinoWindow Load(Uri uri) { Log($".Load({uri})"); if (_nativeInstance == IntPtr.Zero) _startupParameters.StartUrl = uri.ToString(); else Invoke(() => Photino_NavigateToUrl(_nativeInstance, uri.ToString())); return this; } /// /// Loads a specified path into the browser control. /// /// /// Returns the current instance. /// /// /// Load() or LoadString() must be called before native window is initialized. /// /// A path pointing to the ressource to load. public PhotinoWindow Load(string path) { Log($".Load({path})"); // –––––––––––––––––––––– // SECURITY RISK! // This needs validation! // –––––––––––––––––––––– // Open a web URL string path if (path.Contains("http://") || path.Contains("https://")) return Load(new Uri(path)); // Open a file resource string path string absolutePath = Path.GetFullPath(path); // For bundled app it can be necessary to consider // the app context base directory. Check there too. if (File.Exists(absolutePath) == false) { absolutePath = $"{System.AppContext.BaseDirectory}/{path}"; if (File.Exists(absolutePath) == false) { Log($" ** File \"{path}\" could not be found."); return this; } } return Load(new Uri(absolutePath, UriKind.Absolute)); } /// /// Loads a raw string into the browser control. /// /// /// Returns the current instance. /// /// /// Used to load HTML into the browser control directly. /// Load() or LoadString() must be called before native window is initialized. /// /// Raw content (such as HTML) public PhotinoWindow LoadRawString(string content) { var shortContent = content.Length > 50 ? string.Concat(content.AsSpan(0, 50), "...") : content; Log($".LoadRawString({shortContent})"); if (_nativeInstance == IntPtr.Zero) _startupParameters.StartString = content; else Invoke(() => Photino_NavigateToString(_nativeInstance, content)); return this; } /// /// Centers the native window on the primary display. /// /// /// If called prior to window initialization, overrides Left (X) and Top (Y) properties. /// /// /// Returns the current instance. /// /// public PhotinoWindow Center() { Log(".Center()"); Centered = true; return this; } /// /// Moves the native window to the specified location on the screen in pixels using a Point. /// /// /// Returns the current instance. /// /// Position as /// Whether the window can go off-screen (work area) public PhotinoWindow MoveTo(Point location, bool allowOutsideWorkArea = false) { Log($".MoveTo({location}, {allowOutsideWorkArea})"); if (LogVerbosity > 2) { Log($" Current location: {Location}"); Log($" New location: {location}"); } // If the window is outside of the work area, // recalculate the position and continue. //When window isn't initialized yet, cannot determine screen size. if (allowOutsideWorkArea == false && _nativeInstance != IntPtr.Zero) { int horizontalWindowEdge = location.X + Width; int verticalWindowEdge = location.Y + Height; int horizontalWorkAreaEdge = MainMonitor.WorkArea.Width; int verticalWorkAreaEdge = MainMonitor.WorkArea.Height; bool isOutsideHorizontalWorkArea = horizontalWindowEdge > horizontalWorkAreaEdge; bool isOutsideVerticalWorkArea = verticalWindowEdge > verticalWorkAreaEdge; var locationInsideWorkArea = new Point( isOutsideHorizontalWorkArea ? horizontalWorkAreaEdge - Width : location.X, isOutsideVerticalWorkArea ? verticalWorkAreaEdge - Height : location.Y ); location = locationInsideWorkArea; } // Bug: // For some reason the vertical position is not handled correctly. // Whenever a positive value is set, the window appears at the // very bottom of the screen and the only visible thing is the // application window title bar. As a workaround we make a // negative value out of the vertical position to "pull" the window up. // Note: // This behavior seems to be a macOS thing. In the Photino.Native // project files it is commented to be expected behavior for macOS. // There is some code trying to mitigate this problem but it might // not work as expected. Further investigation is necessary. // Update: // This behavior seems to have changed with macOS Sonoma. // Therefore we determine the version of macOS and only apply the // workaround for older versions. if (IsMacOsPlatform && MacOsVersion.Major < 23) { var workArea = MainMonitor.WorkArea.Size; location.Y = location.Y >= 0 ? location.Y - workArea.Height : location.Y; } Location = location; return this; } /// /// Moves the native window to the specified location on the screen in pixels /// using (X) and (Y) properties. /// /// /// Returns the current instance. /// /// Position from left in pixels /// Position from top in pixels /// Whether the window can go off-screen (work area) public PhotinoWindow MoveTo(int left, int top, bool allowOutsideWorkArea = false) { Log($".MoveTo({left}, {top}, {allowOutsideWorkArea})"); return MoveTo(new Point(left, top), allowOutsideWorkArea); } /// /// Moves the native window relative to its current location on the screen /// using a . /// /// /// Returns the current instance. /// /// Relative offset public PhotinoWindow Offset(Point offset) { Log($".Offset({offset})"); var location = Location; int left = location.X + offset.X; int top = location.Y + offset.Y; return MoveTo(left, top); } /// /// Moves the native window relative to its current location on the screen in pixels /// using (X) and (Y) properties. /// /// /// Returns the current instance. /// /// Relative offset from left in pixels /// Relative offset from top in pixels public PhotinoWindow Offset(int left, int top) { Log($".Offset({left}, {top})"); return Offset(new Point(left, top)); } /// /// When true, the native window will appear without a title bar or border. /// By default, this is set to false. /// /// /// The user has to supply titlebar, border, dragging and resizing manually. /// /// /// Returns the current instance. /// /// Whether the window should be chromeless public PhotinoWindow SetChromeless(bool chromeless) { Log($".SetChromeless({chromeless})"); if (_nativeInstance != IntPtr.Zero) throw new ApplicationException("Chromeless can only be set before the native window is instantiated."); _startupParameters.Chromeless = chromeless; return this; } /// /// Set the parent window /// /// /// Returns the current instance. /// /// The window that should be used as this window's parent public PhotinoWindow SetParent(PhotinoWindow parent) { Log($".SetParent({parent.Id})"); if (_nativeInstance != IntPtr.Zero) throw new ApplicationException("Parent window can only be set before the native window is instantiated."); _dotNetParent = parent; return this; } /// /// When true, the native window can be displayed with transparent background. /// Chromeless must be set to true. Html document's body background must have alpha-based value. /// By default, this is set to false. /// public PhotinoWindow SetTransparent(bool enabled) { Log($".SetTransparent({enabled})"); Transparent = enabled; return this; } /// /// When true, the user can access the browser control's context menu. /// By default, this is set to true. /// /// /// Returns the current instance. /// /// Whether the context menu should be available public PhotinoWindow SetContextMenuEnabled(bool enabled) { Log($".SetContextMenuEnabled({enabled})"); ContextMenuEnabled = enabled; return this; } /// /// When true, the user can zoom. /// By default, this is set to true. /// /// /// Returns the current instance. /// /// Whether the zoom should be available public PhotinoWindow SetZoomEnabled(bool enabled) { Log($".SetZoomEnabled({enabled})"); ZoomEnabled = enabled; return this; } /// /// When true, the user can access the browser control's developer tools. /// By default, this is set to true. /// /// /// Returns the current instance. /// /// Whether developer tools should be available public PhotinoWindow SetDevToolsEnabled(bool enabled) { Log($".SetDevTools({enabled})"); DevToolsEnabled = enabled; return this; } /// /// When set to true, the native window will cover the entire screen, similar to kiosk mode. /// By default, this is set to false. /// /// /// Returns the current instance. /// /// Whether the window should be fullscreen public PhotinoWindow SetFullScreen(bool fullScreen) { Log($".SetFullScreen({fullScreen})"); FullScreen = fullScreen; return this; } /// /// When set to true, the native browser control grants all requests for access to local resources /// such as the users camera and microphone. By default, this is set to true. /// /// /// This only works on Windows. /// /// /// Returns the current instance. /// /// Whether permissions should be automatically granted. public PhotinoWindow SetGrantBrowserPermissions(bool grant) { Log($".SetGrantBrowserPermission({grant})"); GrantBrowserPermissions = grant; return this; } /// /// Sets . Sets the user agent on the browser control at initialization. /// /// /// Returns the current instance. public PhotinoWindow SetUserAgent(string userAgent) { Log($".SetUserAgent({userAgent})"); UserAgent = userAgent; return this; } /// /// Sets platform specific initialization parameters for the native browser control on startup. /// Default is none. /// /// WINDOWS: WebView2 specific string. Space separated. /// https://peter.sh/experiments/chromium-command-line-switches/ /// https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2environmentoptions.additionalbrowserarguments?view=webview2-dotnet-1.0.1938.49&viewFallbackFrom=webview2-dotnet-1.0.1901.177view%3Dwebview2-1.0.1901.177 /// https://www.chromium.org/developers/how-tos/run-chromium-with-flags/ /// LINUX: Webkit2Gtk specific string. Enter parameter names and values as JSON string. /// e.g. { "set_enable_encrypted_media": true } /// https://webkitgtk.org/reference/webkit2gtk/2.5.1/WebKitSettings.html /// https://lazka.github.io/pgi-docs/WebKit2-4.0/classes/Settings.html /// MAC: Webkit specific string. Enter parameter names and values as JSON string. /// e.g. { "minimumFontSize": 8 } /// https://developer.apple.com/documentation/webkit/wkwebviewconfiguration?language=objc /// https://developer.apple.com/documentation/webkit/wkpreferences?language=objc /// /// /// Returns the current instance. /// public PhotinoWindow SetBrowserControlInitParameters(string parameters) { Log($".SetBrowserControlInitParameters({parameters})"); BrowserControlInitParameters = parameters; return this; } /// /// Sets the registration id for toast notifications. /// /// /// Only available on Windows. /// Defaults to window title if not specified. /// /// /// Thrown if platform is not Windows. /// /// /// Returns the current instance. public PhotinoWindow SetNotificationRegistrationId(string notificationRegistrationId) { Log($".SetNotificationRegistrationId({notificationRegistrationId})"); NotificationRegistrationId = notificationRegistrationId; return this; } /// /// Sets on the browser control at initialization. /// /// /// Returns the current instance. public PhotinoWindow SetMediaAutoplayEnabled(bool enable) { Log($".SetMediaAutoplayEnabled({enable})"); MediaAutoplayEnabled = enable; return this; } /// /// Sets on the browser control at initialization. /// /// /// Returns the current instance. public PhotinoWindow SetFileSystemAccessEnabled(bool enable) { Log($".SetFileSystemAccessEnabled({enable})"); FileSystemAccessEnabled = enable; return this; } /// /// Sets on the browser control at initialization. /// /// /// Returns the current instance. public PhotinoWindow SetWebSecurityEnabled(bool enable) { Log($".SetWebSecurityEnabled({enable})"); WebSecurityEnabled = enable; return this; } /// /// Sets on the browser control at initialization. /// /// /// Returns the current instance. public PhotinoWindow SetJavascriptClipboardAccessEnabled(bool enable) { Log($".SetJavascriptClipboardAccessEnabled({enable})"); JavascriptClipboardAccessEnabled = enable; return this; } /// /// Sets on the browser control at initialization. /// /// /// Returns the current instance. public PhotinoWindow SetMediaStreamEnabled(bool enable) { Log($".SetMediaStreamEnabled({enable})"); MediaStreamEnabled = enable; return this; } /// /// Sets on the browser control at initialization. /// /// /// Returns the current instance. public PhotinoWindow SetSmoothScrollingEnabled(bool enable) { Log($".SetSmoothScrollingEnabled({enable})"); SmoothScrollingEnabled = enable; return this; } /// /// Sets on the browser control at initialization. /// /// /// Returns the current instance. public PhotinoWindow SetIgnoreCertificateErrorsEnabled(bool enable) { Log($".SetIgnoreCertificateErrorsEnabled({enable})"); IgnoreCertificateErrorsEnabled = enable; return this; } /// /// Sets whether ShowNotification() can be called. /// /// /// Only available on Windows. /// /// /// Thrown if platform is not Windows. /// /// /// Returns the current instance. public PhotinoWindow SetNotificationsEnabled(bool enable) { Log($".SetNotificationsEnabled({enable})"); NotificationsEnabled = enable; return this; } /// /// Sets the native window in pixels. /// Default is 0. /// /// /// Returns the current instance. /// /// /// Height in pixels public PhotinoWindow SetHeight(int height) { Log($".SetHeight({height})"); Height = height; return this; } /// /// Sets the icon file for the native window title bar. /// The file must be located on the local machine and cannot be a URL. The default is none. /// /// /// This only works on Windows and Linux. /// /// /// Returns the current instance. /// /// Icon file: {value} does not exist. /// The file path to the icon. public PhotinoWindow SetIconFile(string iconFile) { Log($".SetIconFile({iconFile})"); IconFile = iconFile; return this; } /// /// Sets the icon file for the native window title bar from an embedded resource. /// The resource file is extracted to a temporary file, and its path is then set as the icon. /// /// /// This only works on Windows and Linux. /// The resource file is expected to be embedded in the assembly from the `wwwroot` folder, and the provided namespace is used to locate the resource. /// /// /// Returns the current instance. /// /// The name of the embedded resource file (e.g., "favicon.ico"). /// /// The namespace in which the embedded resource is located (e.g., "MyApp" or "MyCompany.MyApp"). /// This allows for specifying the custom namespace where the resource is embedded. /// public PhotinoWindow SetIconFile(string resourceFileName, string resourceNamespace) { string iconPath = ExtractEmbeddedResourceToTempFile(resourceFileName, resourceNamespace); return iconPath != null ? SetIconFile(iconPath) : this; } /// /// Sets the native window to a new (X) coordinate in pixels. /// Default is 0. /// /// /// /// Returns the current instance. /// /// Position in pixels from the left (X). public PhotinoWindow SetLeft(int left) { Log($".SetLeft({Left})"); Left = left; return this; } /// /// Sets whether the native window can be resized by the user. /// Default is true. /// /// /// Returns the current instance. /// /// Whether the window is resizable public PhotinoWindow SetResizable(bool resizable) { Log($".SetResizable({resizable})"); Resizable = resizable; return this; } /// /// Sets the native window Size. This represents the and the of the window in pixels. /// The default Size is 0,0. /// /// /// /// Returns the current instance. /// /// Width & Height public PhotinoWindow SetSize(Size size) { Log($".SetSize({size})"); Size = size; return this; } /// /// Sets the native window Size. This represents the and the of the window in pixels. /// The default Size is 0,0. /// /// /// /// Returns the current instance. /// /// Width in pixels /// Height in pixels public PhotinoWindow SetSize(int width, int height) { Log($".SetSize({width}, {height})"); Size = new Size(width, height); return this; } /// /// Sets the native window (X) and coordinates (Y) in pixels. /// Default is 0,0 which means the window will be aligned to the top left edge of the screen. /// /// /// /// Returns the current instance. /// /// Location as a public PhotinoWindow SetLocation(Point location) { Log($".SetLocation({location})"); Location = location; return this; } /// /// Sets the logging verbosity to standard output (Console/Terminal). /// 0 = Critical Only /// 1 = Critical and Warning /// 2 = Verbose /// >2 = All Details /// Default is 2. /// /// /// Returns the current instance. /// /// Verbosity as integer public PhotinoWindow SetLogVerbosity(int verbosity) { Log($".SetLogVerbosity({verbosity})"); LogVerbosity = verbosity; return this; } /// /// Sets whether the native window is maximized. /// Default is false. /// /// /// Returns the current instance. /// /// Whether the window should be maximized. public PhotinoWindow SetMaximized(bool maximized) { Log($".SetMaximized({maximized})"); Maximized = maximized; return this; } ///Native window maximum Width and Height in pixels. public PhotinoWindow SetMaxSize(int maxWidth, int maxHeight) { Log($".SetMaxSize({maxWidth}, {maxHeight})"); MaxSize = new Point(maxWidth, maxHeight); return this; } ///Native window maximum Height in pixels. public PhotinoWindow SetMaxHeight(int maxHeight) { Log($".SetMaxHeight({maxHeight})"); MaxHeight = maxHeight; return this; } ///Native window maximum Width in pixels. public PhotinoWindow SetMaxWidth(int maxWidth) { Log($".SetMaxWidth({maxWidth})"); MaxWidth = maxWidth; return this; } /// /// Sets whether the native window is minimized (hidden). /// Default is false. /// /// /// Returns the current instance. /// /// Whether the window should be minimized. public PhotinoWindow SetMinimized(bool minimized) { Log($".SetMinimized({minimized})"); Minimized = minimized; return this; } ///Native window maximum Width and Height in pixels. public PhotinoWindow SetMinSize(int minWidth, int minHeight) { Log($".SetMinSize({minWidth}, {minHeight})"); MinSize = new Point(minWidth, minHeight); return this; } ///Native window maximum Height in pixels. public PhotinoWindow SetMinHeight(int minHeight) { Log($".SetMinHeight({minHeight})"); MinHeight = minHeight; return this; } ///Native window maximum Width in pixels. public PhotinoWindow SetMinWidth(int minWidth) { Log($".SetMinWidth({minWidth})"); MinWidth = minWidth; return this; } /// /// Sets the local path to store temp files for browser control. /// Default is the user's AppDataLocal folder. /// /// /// Only available on Windows. /// /// /// Thrown if platform is not Windows. /// /// /// Returns the current instance. /// /// Path to temp files directory. public PhotinoWindow SetTemporaryFilesPath(string tempFilesPath) { Log($".SetTemporaryFilesPath({tempFilesPath})"); TemporaryFilesPath = tempFilesPath; return this; } /// /// Sets the native window . /// Default is "Photino". /// /// /// Returns the current instance. /// /// Window title public PhotinoWindow SetTitle(string title) { Log($".SetTitle({title})"); Title = title; return this; } /// /// Sets the native window (Y) coordinate in pixels. /// Default is 0. /// /// /// Returns the current instance. /// /// /// Position in pixels from the top (Y). public PhotinoWindow SetTop(int top) { Log($".SetTop({top})"); Top = top; return this; } /// /// Sets whether the native window is always at the top of the z-order. /// Default is false. /// /// /// Returns the current instance. /// /// Whether the window is at the top public PhotinoWindow SetTopMost(bool topMost) { Log($".SetTopMost({topMost})"); Topmost = topMost; return this; } /// /// Sets the native window width in pixels. /// Default is 0. /// /// /// Returns the current instance. /// /// /// Width in pixels public PhotinoWindow SetWidth(int width) { Log($".SetWidth({width})"); Width = width; return this; } /// /// Sets the native browser control . /// Default is 100. /// /// /// Returns the current instance. /// /// Zoomlevel as integer /// 100 = 100%, 50 = 50% public PhotinoWindow SetZoom(int zoom) { Log($".SetZoom({zoom})"); Zoom = zoom; return this; } public PhotinoWindow SetFlash(bool state) { Log($".SetFlash({state})"); Invoke(() => Photino_SetFlash(_nativeInstance, state)); return this; } public PhotinoWindow SetProgress(ulong current, ulong total, PhotinoWindowProgressState state) { Log($".SetProgress({current}, {total}, {state})"); Invoke(() => Photino_SetProgress(_nativeInstance, current, total, state)); return this; } public PhotinoWindow ClearProgress() { Log($".ClearProgress()"); Invoke(() => Photino_ClearProgress(_nativeInstance)); return this; } /// /// When true the native window starts up at the OS Default location. /// Default is true. /// /// /// Overrides (X) and (Y) properties. /// /// /// Returns the current instance. /// /// Whether the OS Default should be used. public PhotinoWindow SetUseOsDefaultLocation(bool useOsDefault) { Log($".SetUseOsDefaultLocation({useOsDefault})"); UseOsDefaultLocation = useOsDefault; return this; } /// /// When true the native window starts at the OS Default size. /// Default is true. /// /// /// Overrides and properties. /// /// /// Returns the current instance. /// /// Whether the OS Default should be used. public PhotinoWindow SetUseOsDefaultSize(bool useOsDefault) { Log($".SetUseOsDefaultSize({useOsDefault})"); UseOsDefaultSize = useOsDefault; return this; } /// /// Set runtime path for WebView2 so that developers can use Photino on Windows using the "Fixed Version" deployment module of the WebView2 runtime. /// /// /// This only works on Windows. /// /// /// Returns the current instance. /// /// /// Runtime path for WebView2 public PhotinoWindow Win32SetWebView2Path(string data) { if (IsWindowsPlatform) Invoke(() => Photino_setWebView2RuntimePath_win32(_nativeType, data)); else Log("Win32SetWebView2Path is only supported on the Windows platform"); return this; } /// /// Clears the auto-fill data in the browser control. /// /// /// This method is only supported on the Windows platform. /// /// /// Returns the current instance. /// public PhotinoWindow ClearBrowserAutoFill() { if (IsWindowsPlatform) Invoke(() => Photino_ClearBrowserAutoFill(_nativeInstance)); else Log("ClearBrowserAutoFill is only supported on the Windows platform"); return this; } //NON-FLUENT METHODS - CAN ONLY BE CALLED AFTER WINDOW IS INITIALIZED //ONE OF THESE 2 METHODS *MUST* BE CALLED TO CREATE THE WINDOW /// /// Responsible for the initialization of the primary native window and remains in operation until the window is closed. /// This method is also applicable for initializing child windows, but in this case, it does not inhibit operation. /// /// /// The operation of the message loop is exclusive to the main native window only. /// public void WaitForClose() { //fill in the fixed size array of custom scheme names var i = 0; foreach (var name in CustomSchemes.Take(16)) { _startupParameters.CustomSchemeNames[i] = name.Key; i++; } _startupParameters.NativeParent = _dotNetParent == null ? IntPtr.Zero : _dotNetParent._nativeInstance; var errors = _startupParameters.GetParamErrors(); if (errors.Count == 0) { OnWindowCreating(); try //All C++ exceptions will bubble up to here. { _nativeType = NativeLibrary.GetMainProgramHandle(); if (IsWindowsPlatform) Invoke(() => Photino_register_win32(_nativeType)); else if (IsMacOsPlatform) Invoke(() => Photino_register_mac()); Invoke(() => _nativeInstance = Photino_ctor(ref _startupParameters)); } catch (Exception ex) { int lastError = 0; if (IsWindowsPlatform) lastError = Marshal.GetLastWin32Error(); Log($"***\n{ex.Message}\n{ex.StackTrace}\nError #{lastError}"); throw new ApplicationException($"Native code exception. Error # {lastError} See inner exception for details.", ex); } OnWindowCreated(); if (!_messageLoopIsStarted) { _messageLoopIsStarted = true; try { Invoke(() => Photino_WaitForExit(_nativeInstance)); //start the message loop. there can only be 1 message loop for all windows. } catch (Exception ex) { int lastError = 0; if (IsWindowsPlatform) lastError = Marshal.GetLastWin32Error(); Log($"***\n{ex.Message}\n{ex.StackTrace}\nError #{lastError}"); throw new ApplicationException($"Native code exception. Error # {lastError} See inner exception for details.", ex); } } } else { var formattedErrors = "\n"; foreach (var error in errors) formattedErrors += error + "\n"; throw new ArgumentException($"Startup Parameters Are Not Valid: {formattedErrors}"); } } /// /// Closes the native window. /// /// /// Thrown when the window is not initialized. /// public void Close() { Log(".Close()"); if (_nativeInstance == IntPtr.Zero) throw new ApplicationException("Close cannot be called until after the Photino window is initialized."); Invoke(() => Photino_Close(_nativeInstance)); } /// /// Send a message to the native window's native browser control's JavaScript context. /// /// /// In JavaScript, messages can be received via window.external.receiveMessage(message) /// /// /// Thrown when the window is not initialized. /// /// Message as string public void SendWebMessage(string message) { Log($".SendWebMessage({message})"); if (_nativeInstance == IntPtr.Zero) throw new ApplicationException("SendWebMessage cannot be called until after the Photino window is initialized."); Invoke(() => Photino_SendWebMessage(_nativeInstance, message)); } public async Task SendWebMessageAsync(string message) { await Task.Run(() => { Log($".SendWebMessage({message})"); if (_nativeInstance == IntPtr.Zero) throw new ApplicationException("SendWebMessage cannot be called until after the Photino window is initialized."); Invoke(() => Photino_SendWebMessage(_nativeInstance, message)); }); } /// /// Start dragging the window as if the title bar was being clicked on /// /// /// Thrown when the window is not initialized. /// public void StartDragging() { Log($".StartDragging()"); if (_nativeInstance == IntPtr.Zero) throw new ApplicationException("StartDragging cannot be called until after the Photino window is initialized."); Invoke(() => Photino_StartDragging(_nativeInstance)); } /// /// Start resizing the window as if an edge/corner was being clicked on /// /// The edge/corner where the resizing should start /// /// Thrown when the window is not initialized. /// public void StartResizing(PhotinoWindowHitTestCode hitTestCode) { Log($".StartResizing({hitTestCode})"); if (_nativeInstance == IntPtr.Zero) throw new ApplicationException("StartResizing cannot be called until after the Photino window is initialized."); Invoke(() => Photino_StartResizing(_nativeInstance, hitTestCode)); } public PhotinoNotification CreateNotification(PhotinoNotificationType type) { Log($".Createnotification({type})"); if (_nativeInstance == IntPtr.Zero) throw new ApplicationException("CreateNotification cannot be called until after the Photino window is initialized."); return new PhotinoNotification(_nativeInstance) .SetType(type); } /// /// Show an open file dialog native to the OS. /// /// /// Filter names are not used on macOS. Use async version for Photino.Blazor as syncronous version crashes. /// /// /// Thrown when the window is not initialized. /// /// Title of the dialog /// Default path. Defaults to /// Whether multiple selections are allowed /// Array of for filtering. /// Array of file paths as strings public string[] ShowOpenFile(string title = "Choose file", string defaultPath = null, bool multiSelect = false, (string Name, string[] Extensions)[] filters = null) => ShowOpenDialog(false, title, defaultPath, multiSelect, filters); /// /// Async version is required for Photino.Blazor /// /// /// Filter names are not used on macOS. Use async version for Photino.Blazor as syncronous version crashes. /// /// /// Thrown when the window is not initialized. /// /// Title of the dialog /// Default path. Defaults to /// Whether multiple selections are allowed /// Array of for filtering. /// Array of file paths as strings public async Task ShowOpenFileAsync(string title = "Choose file", string defaultPath = null, bool multiSelect = false, (string Name, string[] Extensions)[] filters = null) { return await Task.Run(() => ShowOpenFile(title, defaultPath, multiSelect, filters)); } /// /// Show an open folder dialog native to the OS. /// /// /// Thrown when the window is not initialized. /// /// Title of the dialog /// Default path. Defaults to /// Whether multiple selections are allowed /// Array of folder paths as strings public string[] ShowOpenFolder(string title = "Select folder", string defaultPath = null, bool multiSelect = false) => ShowOpenDialog(true, title, defaultPath, multiSelect, null); /// /// Async version is required for Photino.Blazor /// /// /// Thrown when the window is not initialized. /// /// Title of the dialog /// Default path. Defaults to /// Whether multiple selections are allowed /// Array of folder paths as strings public async Task ShowOpenFolderAsync(string title = "Choose file", string defaultPath = null, bool multiSelect = false) { return await Task.Run(() => ShowOpenFolder(title, defaultPath, multiSelect)); } /// /// Show an save folder dialog native to the OS. /// /// /// Filter names are not used on macOS. /// /// /// Thrown when the window is not initialized. /// /// Title of the dialog /// Default path. Defaults to /// Array of for filtering. /// public string ShowSaveFile(string title = "Save file", string defaultPath = null, (string Name, string[] Extensions)[] filters = null, string defaultFileName = null) { defaultPath ??= Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); filters ??= Array.Empty<(string, string[])>(); defaultFileName ??= string.Empty; string result = null; var nativeFilters = GetNativeFilters(filters); Invoke(() => { var ptrResult = Photino_ShowSaveFile(_nativeInstance, title, defaultPath, nativeFilters, filters.Length, defaultFileName); result = Marshal.PtrToStringAuto(ptrResult); }); return result; } /// /// Async version is required for Photino.Blazor /// /// /// Filter names are not used on macOS. /// /// /// Thrown when the window is not initialized. /// /// Title of the dialog /// Default path. Defaults to /// Array of for filtering. /// public async Task ShowSaveFileAsync(string title = "Choose file", string defaultPath = null, (string Name, string[] Extensions)[] filters = null, string defaultFileName = null) { return await Task.Run(() => ShowSaveFile(title, defaultPath, filters, defaultFileName)); } /// /// Show a message dialog native to the OS. /// /// /// Thrown when the window is not initialized. /// /// Title of the dialog /// Text of the dialog /// Available interaction buttons /// Icon of the dialog /// public PhotinoDialogResult ShowMessage(string title, string text, PhotinoDialogButtons buttons = PhotinoDialogButtons.Ok, PhotinoDialogIcon icon = PhotinoDialogIcon.Info) { var result = PhotinoDialogResult.Cancel; Invoke(() => result = Photino_ShowMessage(_nativeInstance, title, text, buttons, icon)); return result; } /// /// Show a native open dialog. /// /// Whether files are hidden /// Title of the dialog /// Default path. Defaults to /// Whether multiple selections are allowed /// Array of for filtering. /// Array of paths private string[] ShowOpenDialog(bool foldersOnly, string title, string defaultPath, bool multiSelect, (string Name, string[] Extensions)[] filters) { defaultPath ??= Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); filters ??= Array.Empty<(string, string[])>(); var results = Array.Empty(); var nativeFilters = GetNativeFilters(filters, foldersOnly); Invoke(() => { var ptrResults = foldersOnly ? Photino_ShowOpenFolder(_nativeInstance, title, defaultPath, multiSelect, out var resultCount) : Photino_ShowOpenFile(_nativeInstance, title, defaultPath, multiSelect, nativeFilters, nativeFilters.Length, out resultCount); if (resultCount == 0) return; var ptrArray = new IntPtr[resultCount]; results = new string[resultCount]; Marshal.Copy(ptrResults, ptrArray, 0, resultCount); for (var i = 0; i < resultCount; i++) { results[i] = Marshal.PtrToStringAuto(ptrArray[i]); } }); return results; } /// /// Logs a message. /// /// Log message private void Log(string message) { if (LogVerbosity < 1) return; Console.WriteLine($"Photino.NET: \"{Title ?? "PhotinoWindow"}\"{message}"); } /// /// Returns an array of strings for native filters /// /// /// /// String array of filters private static string[] GetNativeFilters((string Name, string[] Extensions)[] filters, bool empty = false) { var nativeFilters = Array.Empty(); if (!empty && filters is { Length: > 0 }) { nativeFilters = IsMacOsPlatform ? filters.SelectMany(t => t.Extensions.Select(s => s == "*" ? s : s.TrimStart('*', '.'))).ToArray() : filters.Select(t => $"{t.Name}|{t.Extensions.Select(s => s.StartsWith('.') ? $"*{s}" : !s.StartsWith("*.") ? $"*.{s}" : s).Aggregate((e1, e2) => $"{e1};{e2}")}").ToArray(); } return nativeFilters; } /// /// Extracts an embedded resource from the assembly to a temporary file. /// /// /// The resource is expected to be located within the provided namespace and under the `wwwroot` folder. /// This method will write the resource to a temporary file and return its path. /// /// /// The path to the temporary file containing the extracted resource, or null if the resource was not found. /// /// The name of the embedded resource file (e.g., "favicon.ico"). /// /// The namespace where the embedded resource is located (e.g., "MyApp" or "MyCompany.MyApp"). /// /// The method expects the resource to be in the `wwwroot` folder of the provided namespace. /// private string ExtractEmbeddedResourceToTempFile(string fileName, string resourceNamespace) { string resourceName = $"{resourceNamespace}.wwwroot.{fileName}"; Assembly assembly = Assembly.GetExecutingAssembly(); using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName)) { if (resourceStream == null) { Log($"Resource '{fileName}' couldn't be found in namespace '{resourceNamespace}'"); return null; } string tempFile = Path.Combine(Path.GetTempPath(), fileName); using (FileStream fileStream = new FileStream(tempFile, FileMode.Create, FileAccess.Write)) { resourceStream.CopyTo(fileStream); } return tempFile; } } }