diff --git a/Configuration/SystemConfig.cs b/Configuration/SystemConfig.cs index 3a9aaed3..881a69a4 100644 --- a/Configuration/SystemConfig.cs +++ b/Configuration/SystemConfig.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using TweetDuck.Core; using TweetDuck.Data.Serialization; namespace TweetDuck.Configuration{ @@ -16,6 +17,8 @@ sealed class SystemConfig{ public bool ClearCacheAutomatically { get; set; } = true; public int ClearCacheThreshold { get; set; } = 250; + public FormBrowser.ThrottleBehavior ThrottleBehavior { get; set; } = FormBrowser.ThrottleBehavior.Covered; + // SPECIAL PROPERTIES public bool HardwareAcceleration{ diff --git a/Core/FormBrowser.Designer.cs b/Core/FormBrowser.Designer.cs index 80ac482e..23b09b1d 100644 --- a/Core/FormBrowser.Designer.cs +++ b/Core/FormBrowser.Designer.cs @@ -27,6 +27,7 @@ private void InitializeComponent() { this.trayIcon = new TweetDuck.Core.Other.TrayIcon(this.components); this.toolTip = new System.Windows.Forms.ToolTip(this.components); this.timerResize = new System.Windows.Forms.Timer(this.components); + this.timerThrottle = new System.Windows.Forms.Timer(this.components); this.SuspendLayout(); // // timerResize @@ -34,6 +35,10 @@ private void InitializeComponent() { this.timerResize.Interval = 500; this.timerResize.Tick += new System.EventHandler(this.timerResize_Tick); // + // timerThrottle + // + this.timerThrottle.Tick += new System.EventHandler(this.timerThrottle_Tick); + // // FormBrowser // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -46,6 +51,7 @@ private void InitializeComponent() { this.Name = "FormBrowser"; this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; this.Activated += new System.EventHandler(this.FormBrowser_Activated); + this.Deactivate += new System.EventHandler(this.FormBrowser_Deactivate); this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FormBrowser_FormClosing); this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.FormBrowser_FormClosed); this.ResizeEnd += new System.EventHandler(this.FormBrowser_ResizeEnd); @@ -60,6 +66,7 @@ private void InitializeComponent() { private TweetDuck.Core.Other.TrayIcon trayIcon; private System.Windows.Forms.ToolTip toolTip; private System.Windows.Forms.Timer timerResize; + private System.Windows.Forms.Timer timerThrottle; } } diff --git a/Core/FormBrowser.cs b/Core/FormBrowser.cs index c014f961..a84d540d 100644 --- a/Core/FormBrowser.cs +++ b/Core/FormBrowser.cs @@ -19,6 +19,10 @@ namespace TweetDuck.Core{ sealed partial class FormBrowser : Form, AnalyticsFile.IProvider{ + public enum ThrottleBehavior{ // keep order + Minimized, Covered, Unfocused + } + private static UserConfig Config => Program.UserConfig; public bool IsWaiting{ @@ -96,7 +100,7 @@ public FormBrowser(){ Config.TrayBehaviorChanged += Config_TrayBehaviorChanged; UpdateTray(); - + if (Config.MuteNotifications){ UpdateFormIcon(); } @@ -145,6 +149,34 @@ private void timerResize_Tick(object sender, EventArgs e){ FormBrowser_ResizeEnd(this, e); // also stops timer } + private void timerThrottle_Tick(object sender, EventArgs e){ + if (!browser.Ready){ + timerThrottle.Interval = 5000; + return; + } + + bool shouldThrottle; + + switch(Program.SystemConfig.ThrottleBehavior){ + case ThrottleBehavior.Covered: + shouldThrottle = WindowState != FormWindowState.Minimized && NativeMethods.IsFormCoveredByLargerWindow(this); + break; + + case ThrottleBehavior.Unfocused: + shouldThrottle = !FormManager.HasAnyDialogs; + break; + + default: + timerThrottle.Stop(); + return; + } + + if (shouldThrottle){ + browser.SetThrottle(true); + timerThrottle.Stop(); + } + } + private void FormBrowser_Activated(object sender, EventArgs e){ if (!isLoaded)return; @@ -153,6 +185,15 @@ private void FormBrowser_Activated(object sender, EventArgs e){ if (!browser.Enabled){ // when taking a screenshot, the window is unfocused and browser.Enabled = true; // the browser is disabled; if the user clicks back into } // the window, enable the browser again + + timerThrottle.Stop(); + browser.SetThrottle(false); + } + + private void FormBrowser_Deactivate(object sender, EventArgs e){ + timerThrottle.Stop(); + timerThrottle.Interval = Program.SystemConfig.ThrottleBehavior == ThrottleBehavior.Covered ? 30000 : 1000; + timerThrottle.Start(); } private void FormBrowser_LocationChanged(object sender, EventArgs e){ diff --git a/Core/Other/Settings/TabSettingsAdvanced.Designer.cs b/Core/Other/Settings/TabSettingsAdvanced.Designer.cs index 1134a599..1aca5e07 100644 --- a/Core/Other/Settings/TabSettingsAdvanced.Designer.cs +++ b/Core/Other/Settings/TabSettingsAdvanced.Designer.cs @@ -43,6 +43,8 @@ private void InitializeComponent() { this.panelConfiguration = new System.Windows.Forms.Panel(); this.labelConfiguration = new System.Windows.Forms.Label(); this.flowPanel = new System.Windows.Forms.FlowLayoutPanel(); + this.labelThrottle = new System.Windows.Forms.Label(); + this.comboBoxThrottle = new System.Windows.Forms.ComboBox(); ((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).BeginInit(); this.panelAppButtons.SuspendLayout(); this.panelClearCacheAuto.SuspendLayout(); @@ -209,7 +211,7 @@ private void InitializeComponent() { this.labelPerformance.Location = new System.Drawing.Point(0, 102); this.labelPerformance.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0); this.labelPerformance.Name = "labelPerformance"; - this.labelPerformance.Size = new System.Drawing.Size(93, 20); + this.labelPerformance.Size = new System.Drawing.Size(92, 20); this.labelPerformance.TabIndex = 2; this.labelPerformance.Text = "Performance"; // @@ -219,7 +221,7 @@ private void InitializeComponent() { this.panelClearCacheAuto.Controls.Add(this.checkClearCacheAuto); this.panelClearCacheAuto.Controls.Add(this.numClearCacheThreshold); this.panelClearCacheAuto.Location = new System.Drawing.Point(0, 207); - this.panelClearCacheAuto.Margin = new System.Windows.Forms.Padding(0); + this.panelClearCacheAuto.Margin = new System.Windows.Forms.Padding(0, 0, 0, 2); this.panelClearCacheAuto.Name = "panelClearCacheAuto"; this.panelClearCacheAuto.Size = new System.Drawing.Size(322, 28); this.panelClearCacheAuto.TabIndex = 6; @@ -240,7 +242,7 @@ private void InitializeComponent() { this.panelConfiguration.Anchor = System.Windows.Forms.AnchorStyles.Top; this.panelConfiguration.Controls.Add(this.btnEditCSS); this.panelConfiguration.Controls.Add(this.btnEditCefArgs); - this.panelConfiguration.Location = new System.Drawing.Point(0, 275); + this.panelConfiguration.Location = new System.Drawing.Point(0, 333); this.panelConfiguration.Margin = new System.Windows.Forms.Padding(0); this.panelConfiguration.Name = "panelConfiguration"; this.panelConfiguration.Size = new System.Drawing.Size(322, 31); @@ -250,7 +252,7 @@ private void InitializeComponent() { // this.labelConfiguration.AutoSize = true; this.labelConfiguration.Font = new System.Drawing.Font("Segoe UI", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); - this.labelConfiguration.Location = new System.Drawing.Point(0, 255); + this.labelConfiguration.Location = new System.Drawing.Point(0, 313); this.labelConfiguration.Margin = new System.Windows.Forms.Padding(0, 20, 0, 0); this.labelConfiguration.Name = "labelConfiguration"; this.labelConfiguration.Size = new System.Drawing.Size(100, 20); @@ -269,22 +271,46 @@ private void InitializeComponent() { this.flowPanel.Controls.Add(this.labelCache); this.flowPanel.Controls.Add(this.btnClearCache); this.flowPanel.Controls.Add(this.panelClearCacheAuto); + this.flowPanel.Controls.Add(this.labelThrottle); + this.flowPanel.Controls.Add(this.comboBoxThrottle); this.flowPanel.Controls.Add(this.labelConfiguration); this.flowPanel.Controls.Add(this.panelConfiguration); this.flowPanel.FlowDirection = System.Windows.Forms.FlowDirection.TopDown; this.flowPanel.Location = new System.Drawing.Point(9, 9); this.flowPanel.Name = "flowPanel"; - this.flowPanel.Size = new System.Drawing.Size(322, 307); + this.flowPanel.Size = new System.Drawing.Size(322, 364); this.flowPanel.TabIndex = 0; this.flowPanel.WrapContents = false; // + // labelThrottle + // + this.labelThrottle.AutoSize = true; + this.labelThrottle.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.labelThrottle.Location = new System.Drawing.Point(3, 249); + this.labelThrottle.Margin = new System.Windows.Forms.Padding(3, 12, 3, 0); + this.labelThrottle.Name = "labelThrottle"; + this.labelThrottle.Size = new System.Drawing.Size(92, 15); + this.labelThrottle.TabIndex = 9; + this.labelThrottle.Text = "Throttle When..."; + // + // comboBoxThrottle + // + this.comboBoxThrottle.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxThrottle.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); + this.comboBoxThrottle.FormattingEnabled = true; + this.comboBoxThrottle.Location = new System.Drawing.Point(5, 267); + this.comboBoxThrottle.Margin = new System.Windows.Forms.Padding(5, 3, 3, 3); + this.comboBoxThrottle.Name = "comboBoxThrottle"; + this.comboBoxThrottle.Size = new System.Drawing.Size(173, 23); + this.comboBoxThrottle.TabIndex = 10; + // // TabSettingsAdvanced // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.Controls.Add(this.flowPanel); this.Name = "TabSettingsAdvanced"; - this.Size = new System.Drawing.Size(340, 325); + this.Size = new System.Drawing.Size(340, 382); ((System.ComponentModel.ISupportInitialize)(this.numClearCacheThreshold)).EndInit(); this.panelAppButtons.ResumeLayout(false); this.panelClearCacheAuto.ResumeLayout(false); @@ -317,5 +343,7 @@ private void InitializeComponent() { private Controls.NumericUpDownEx numClearCacheThreshold; private System.Windows.Forms.CheckBox checkClearCacheAuto; private System.Windows.Forms.FlowLayoutPanel flowPanel; + private System.Windows.Forms.Label labelThrottle; + private System.Windows.Forms.ComboBox comboBoxThrottle; } } diff --git a/Core/Other/Settings/TabSettingsAdvanced.cs b/Core/Other/Settings/TabSettingsAdvanced.cs index 0fbe6801..60c39304 100644 --- a/Core/Other/Settings/TabSettingsAdvanced.cs +++ b/Core/Other/Settings/TabSettingsAdvanced.cs @@ -29,6 +29,8 @@ public TabSettingsAdvanced(Action<string> reinjectBrowserCSS){ toolTip.SetToolTip(btnClearCache, "Clearing cache will free up space taken by downloaded images and other resources."); toolTip.SetToolTip(checkClearCacheAuto, "Automatically clears cache when its size exceeds the set threshold. Note that cache can only be cleared when closing TweetDuck."); + toolTip.SetToolTip(comboBoxThrottle, "Decides when to stop rendering and throttle the browser to improve performance and battery life."); + toolTip.SetToolTip(btnEditCefArgs, "Set custom command line arguments for Chromium Embedded Framework."); toolTip.SetToolTip(btnEditCSS, "Set custom CSS for browser and notification windows."); @@ -48,6 +50,11 @@ public TabSettingsAdvanced(Action<string> reinjectBrowserCSS){ string text = task.Status == TaskStatus.RanToCompletion ? (int)Math.Ceiling(task.Result/(1024.0*1024.0))+" MB" : "unknown"; this.InvokeSafe(() => btnClearCache.Text = $"Clear Cache ({text})"); }); + + comboBoxThrottle.Items.Add("Minimized (Naive)"); + comboBoxThrottle.Items.Add("Covered (Smart)"); + comboBoxThrottle.Items.Add("Unfocused (Aggressive)"); + comboBoxThrottle.SelectedIndex = Math.Min(Math.Max((int)SysConfig.ThrottleBehavior, 0), comboBoxThrottle.Items.Count-1); } public override void OnReady(){ @@ -60,6 +67,8 @@ public override void OnReady(){ btnClearCache.Click += btnClearCache_Click; checkClearCacheAuto.CheckedChanged += checkClearCacheAuto_CheckedChanged; + + comboBoxThrottle.SelectedIndexChanged += comboBoxThrottle_SelectedIndexChanged; btnEditCefArgs.Click += btnEditCefArgs_Click; btnEditCSS.Click += btnEditCSS_Click; @@ -86,6 +95,10 @@ private void checkHardwareAcceleration_CheckedChanged(object sender, EventArgs e PromptRestart(); // calls OnClosing } + private void comboBoxThrottle_SelectedIndexChanged(object sender, EventArgs e){ + SysConfig.ThrottleBehavior = (FormBrowser.ThrottleBehavior)comboBoxThrottle.SelectedIndex; + } + private void btnEditCefArgs_Click(object sender, EventArgs e){ DialogSettingsCefArgs form = new DialogSettingsCefArgs(); diff --git a/Core/TweetDeckBrowser.cs b/Core/TweetDeckBrowser.cs index 50f56ef4..b15434a8 100644 --- a/Core/TweetDeckBrowser.cs +++ b/Core/TweetDeckBrowser.cs @@ -87,6 +87,16 @@ public void Focus(){ browser.Focus(); } + public void SetThrottle(bool throttle){ + if (throttle && browser.Dock == DockStyle.Fill){ + browser.Dock = DockStyle.None; + browser.Size = Size.Empty; + } + else if (!throttle && browser.Dock == DockStyle.None){ + browser.Dock = DockStyle.Fill; + } + } + public void Dispose(){ Program.UserConfig.MuteToggled -= UserConfig_MuteToggled; Program.UserConfig.ZoomLevelChanged -= UserConfig_ZoomLevelChanged; diff --git a/Core/Utils/NativeMethods.cs b/Core/Utils/NativeMethods.cs index 0b3b3500..6ef8afc9 100644 --- a/Core/Utils/NativeMethods.cs +++ b/Core/Utils/NativeMethods.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Runtime.InteropServices; @@ -14,6 +15,7 @@ static class NativeMethods{ public const int HWND_TOPMOST = -1; public const uint SWP_NOACTIVATE = 0x0010; public const int WS_DISABLED = 0x08000000; + public const int GW_HWNDPREV = 3; public const int GWL_STYLE = -16; public const int SB_HORZ = 0; @@ -23,6 +25,7 @@ static class NativeMethods{ public const int WM_XBUTTONDOWN = 0x020B; public const int WM_XBUTTONUP = 0x020C; public const int WM_PARENTNOTIFY = 0x0210; + [StructLayout(LayoutKind.Sequential)] private struct LASTINPUTINFO{ @@ -38,6 +41,14 @@ private struct POINT{ public int Y; } + [StructLayout(LayoutKind.Sequential)] + private struct RECT{ + public int left; + public int top; + public int right; + public int bottom; + } + [StructLayout(LayoutKind.Sequential)] private struct MSLLHOOKSTRUCT{ public POINT pt; @@ -93,6 +104,17 @@ private struct MSLLHOOKSTRUCT{ [DllImport("user32.dll")] private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); + [DllImport("user32.dll")] + private static extern IntPtr GetWindow(IntPtr hWnd, int uCmd); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool GetWindowRect(IntPtr hWnd, [Out] out RECT lpRect); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool IsWindowVisible(IntPtr hWnd); + public static void SetFormPos(Form form, int hWndOrder, uint flags){ SetWindowPos(form.Handle.ToInt32(), hWndOrder, form.Left, form.Top, form.Width, form.Height, flags); } @@ -106,6 +128,30 @@ public static void SetFormDisabled(Form form, bool disabled){ } } + public static bool IsFormCoveredByLargerWindow(Form form){ + IntPtr handle = form.Handle; + + if (!IsWindowVisible(handle)){ + return false; + } + + HashSet<IntPtr> visited = new HashSet<IntPtr>{ handle }; + GetWindowRect(handle, out RECT thisRect); + + while((handle = GetWindow(handle, GW_HWNDPREV)) != IntPtr.Zero && visited.Add(handle)){ + if (IsWindowVisible(handle) && + GetWindowRect(handle, out RECT otherRect) && + otherRect.left <= thisRect.left && + otherRect.top <= thisRect.top && + otherRect.right >= thisRect.right && + otherRect.bottom >= thisRect.bottom){ + return true; + } + } + + return false; + } + public static void BroadcastMessage(uint msg, uint wParam, int lParam){ PostMessage(HWND_BROADCAST, msg, new UIntPtr(wParam), new IntPtr(lParam)); }