using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; using CefSharp; using CefSharp.WinForms; using TweetDck.Configuration; using TweetDck.Core.Handling; using TweetDck.Resources; using TweetDck.Core.Utils; using TweetDck.Plugins; namespace TweetDck.Core{ sealed partial class FormNotification : Form{ private const string NotificationScriptFile = "notification.js"; private static readonly string NotificationScriptIdentifier = ScriptLoader.GetRootIdentifier(NotificationScriptFile); private static readonly string PluginScriptIdentifier = ScriptLoader.GetRootIdentifier(PluginManager.PluginNotificationScriptFile); public Func<bool> CanMoveWindow = () => true; private readonly Form owner; private readonly PluginManager plugins; private readonly ChromiumWebBrowser browser; private readonly Queue<TweetNotification> tweetQueue = new Queue<TweetNotification>(4); private readonly bool autoHide; private int timeLeft, totalTime; private readonly NativeMethods.HookProc mouseHookDelegate; private IntPtr mouseHook; private bool? prevDisplayTimer; private int? prevFontSize; private bool RequiresResize{ get{ return !prevDisplayTimer.HasValue || !prevFontSize.HasValue || prevDisplayTimer != Program.UserConfig.DisplayNotificationTimer || prevFontSize != TweetNotification.FontSizeLevel; } set{ if (value){ prevDisplayTimer = null; prevFontSize = null; } else{ prevDisplayTimer = Program.UserConfig.DisplayNotificationTimer; prevFontSize = TweetNotification.FontSizeLevel; } } } private readonly string notificationJS; private readonly string pluginJS; protected override bool ShowWithoutActivation{ get{ return true; } } public bool FreezeTimer { get; set; } public bool ContextMenuOpen { get; set; } public string CurrentUrl { get; private set; } public EventHandler Initialized; private bool isInitialized; private static int BaseClientWidth{ get{ int level = TweetNotification.FontSizeLevel; return level == 0 ? 284 : (int)Math.Round(284.0*(1.0+0.05*level)); } } private static int BaseClientHeight{ get{ int level = TweetNotification.FontSizeLevel; return level == 0 ? 118 : (int)Math.Round(118.0*(1.0+0.075*level)); } } public FormNotification(FormBrowser owner, PluginManager pluginManager, bool autoHide){ InitializeComponent(); Text = Program.BrandName; this.owner = owner; this.plugins = pluginManager; this.autoHide = autoHide; owner.FormClosed += (sender, args) => Close(); notificationJS = ScriptLoader.LoadResource(NotificationScriptFile); pluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile); browser = new ChromiumWebBrowser("about:blank"){ MenuHandler = new ContextMenuNotification(this, autoHide), LifeSpanHandler = new LifeSpanHandler() }; browser.IsBrowserInitializedChanged += Browser_IsBrowserInitializedChanged; browser.FrameLoadEnd += Browser_FrameLoadEnd; browser.RegisterJsObject("$TD", new TweetDeckBridge(owner, this)); browser.RegisterAsyncJsObject("$TDP", plugins.Bridge); panelBrowser.Controls.Add(browser); if (autoHide){ Program.UserConfig.MuteToggled += Config_MuteToggled; Disposed += (sender, args) => Program.UserConfig.MuteToggled -= Config_MuteToggled; } mouseHookDelegate = MouseHookProc; Disposed += FormNotification_Disposed; } protected override void WndProc(ref Message m){ if (m.Msg == 0x0112 && (m.WParam.ToInt32() & 0xFFF0) == 0xF010 && !CanMoveWindow()){ // WM_SYSCOMMAND, SC_MOVE return; } base.WndProc(ref m); } // mouse wheel hook private void StartMouseHook(){ if (mouseHook == IntPtr.Zero){ mouseHook = NativeMethods.SetWindowsHookEx(NativeMethods.WH_MOUSE_LL, mouseHookDelegate, IntPtr.Zero, 0); } } private void StopMouseHook(){ if (mouseHook != IntPtr.Zero){ NativeMethods.UnhookWindowsHookEx(mouseHook); mouseHook = IntPtr.Zero; } } private IntPtr MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam){ if (!ContainsFocus && wParam.ToInt32() == NativeMethods.WH_MOUSEWHEEL && browser.Bounds.Contains(PointToClient(Cursor.Position))){ // fuck it, Activate() doesn't work with this Point prevPos = Cursor.Position; Cursor.Position = PointToScreen(new Point(-1, -1)); NativeMethods.SimulateMouseClick(NativeMethods.MouseButton.Left); Cursor.Position = prevPos; } return NativeMethods.CallNextHookEx(mouseHook, nCode, wParam, lParam); } // event handlers private void timerHideProgress_Tick(object sender, EventArgs e){ if (Bounds.Contains(Cursor.Position) || FreezeTimer || ContextMenuOpen)return; timeLeft -= timerProgress.Interval; int value = (int)Math.Round(1025.0*(totalTime-timeLeft)/totalTime); progressBarTimer.SetValueInstant(Math.Min(1000, Math.Max(0, Program.UserConfig.NotificationTimerCountDown ? 1000-value : value))); if (timeLeft <= 0){ FinishCurrentTweet(); } } private void Config_MuteToggled(object sender, EventArgs e){ if (Program.UserConfig.MuteNotifications){ HideNotification(true); } else if (tweetQueue.Count > 0){ LoadNextNotification(); } } private void Browser_IsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs e){ if (e.IsBrowserInitialized && Initialized != null){ Initialized(this, new EventArgs()); } } private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){ if (!e.Frame.IsMain)return; if (!isInitialized && !Program.UserConfig.NotificationLegacyLoad){ isInitialized = true; if (Initialized != null){ Initialized(this, new EventArgs()); } } else if (notificationJS != null && browser.Address != "about:blank"){ ScriptLoader.ExecuteScript(e.Frame, notificationJS, NotificationScriptIdentifier); if (plugins.HasAnyPlugin(PluginEnvironment.Notification)){ ScriptLoader.ExecuteScript(e.Frame, pluginJS, PluginScriptIdentifier); ScriptLoader.ExecuteFile(e.Frame, PluginManager.PluginGlobalScriptFile); plugins.ExecutePlugins(e.Frame, PluginEnvironment.Notification, false); } } } private void FormNotification_FormClosing(object sender, FormClosingEventArgs e){ if (e.CloseReason == CloseReason.UserClosing){ HideNotification(false); tweetQueue.Clear(); e.Cancel = true; } } private void FormNotification_Disposed(object sender, EventArgs e){ browser.Dispose(); StopMouseHook(); } // notification methods public void ShowNotification(TweetNotification notification){ if (Program.UserConfig.MuteNotifications){ tweetQueue.Enqueue(notification); } else{ tweetQueue.Enqueue(notification); UpdateTitle(); if (!timerProgress.Enabled){ LoadNextNotification(); } } } public void ShowNotificationForSettings(bool reset){ if (reset){ LoadTweet(TweetNotification.ExampleTweet); } else{ MoveToVisibleLocation(); } } public void HideNotification(bool loadBlank){ if (loadBlank || Program.UserConfig.NotificationLegacyLoad){ browser.LoadHtml("", "about:blank"); } Location = new Point(-32000, -32000); progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0; timerProgress.Stop(); StopMouseHook(); } public void OnNotificationReady(){ UpdateTitle(); MoveToVisibleLocation(); timerProgress.Start(); } public void FinishCurrentTweet(){ if (tweetQueue.Count > 0){ LoadNextNotification(); } else if (autoHide){ HideNotification(true); } else{ timerProgress.Stop(); } } private void LoadNextNotification(){ LoadTweet(tweetQueue.Dequeue()); } private void LoadTweet(TweetNotification tweet){ CurrentUrl = tweet.Url; timerProgress.Stop(); totalTime = timeLeft = tweet.GetDisplayDuration(Program.UserConfig.NotificationDurationValue); progressBarTimer.Value = Program.UserConfig.NotificationTimerCountDown ? 1000 : 0; browser.LoadHtml(tweet.GenerateHtml(), "http://tweetdeck.twitter.com/?"+DateTime.Now.Ticks); if (Program.UserConfig.NotificationLegacyLoad){ OnNotificationReady(); } } private void MoveToVisibleLocation(){ UserConfig config = Program.UserConfig; if (RequiresResize){ RequiresResize = false; if (config.DisplayNotificationTimer){ ClientSize = new Size(BaseClientWidth, BaseClientHeight+4); progressBarTimer.Visible = true; } else{ ClientSize = new Size(BaseClientWidth, BaseClientHeight); progressBarTimer.Visible = false; } panelBrowser.Height = BaseClientHeight; } Screen screen = Screen.FromControl(owner); if (config.NotificationDisplay > 0 && config.NotificationDisplay <= Screen.AllScreens.Length){ screen = Screen.AllScreens[config.NotificationDisplay-1]; } bool needsReactivating = Location.X == -32000; int edgeDist = config.NotificationEdgeDistance; switch(config.NotificationPosition){ case TweetNotification.Position.TopLeft: Location = new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+edgeDist); break; case TweetNotification.Position.TopRight: Location = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist); break; case TweetNotification.Position.BottomLeft: Location = new Point(screen.WorkingArea.X+edgeDist, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height); break; case TweetNotification.Position.BottomRight: Location = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+screen.WorkingArea.Height-edgeDist-Height); break; case TweetNotification.Position.Custom: if (!config.IsCustomNotificationPositionSet){ config.CustomNotificationPosition = new Point(screen.WorkingArea.X+screen.WorkingArea.Width-edgeDist-Width, screen.WorkingArea.Y+edgeDist); config.Save(); } Location = config.CustomNotificationPosition; break; } if (needsReactivating){ NativeMethods.SetFormPos(this, NativeMethods.HWND_TOPMOST, NativeMethods.SWP_NOACTIVATE); } StartMouseHook(); } private void UpdateTitle(){ Text = tweetQueue.Count > 0 ? Program.BrandName+" ("+tweetQueue.Count+" more left)" : Program.BrandName; } public void DisplayTooltip(string text){ if (string.IsNullOrEmpty(text)){ toolTip.Hide(this); } else{ Point position = PointToClient(Cursor.Position); position.Offset(20, 5); toolTip.Show(text, this, position); } } } }