From 1d0bdf23c1cf081c4bd2e5eb46ad473fa55142a0 Mon Sep 17 00:00:00 2001 From: Robin Date: Sat, 3 Sep 2016 23:32:24 +0200 Subject: [PATCH] FEATURE-731: Backport of the title detection code --- Greenshot/Forms/SettingsForm.cs | 14 +- GreenshotJiraPlugin/Forms/JiraForm.cs | 2 +- .../GreenshotJiraPlugin.csproj | 9 +- .../Hooking/TitleChangeEventArgs.cs | 50 ++ .../Hooking/TitleChangeEventDelegate.cs | 29 ++ .../Hooking/WindowsEventHook.cs | 152 ++++++ .../Hooking/WindowsTitleMonitor.cs | 139 ++++++ GreenshotJiraPlugin/JiraConnector.cs | 84 ++-- GreenshotJiraPlugin/JiraDestination.cs | 16 +- GreenshotJiraPlugin/JiraDetails.cs | 71 +++ GreenshotJiraPlugin/JiraEventArgs.cs | 40 ++ GreenshotJiraPlugin/JiraEventTypes.cs | 29 ++ GreenshotJiraPlugin/JiraMonitor.cs | 225 +++++++++ GreenshotJiraPlugin/JiraPlugin.cs | 37 +- GreenshotJiraPlugin/JiraUtils.cs | 83 ---- GreenshotPlugin/Core/CredentialsHelper.cs | 113 ++--- .../UnmanagedHelpers/Enumerations.cs | 434 +++++++++--------- 17 files changed, 1080 insertions(+), 447 deletions(-) create mode 100644 GreenshotJiraPlugin/Hooking/TitleChangeEventArgs.cs create mode 100644 GreenshotJiraPlugin/Hooking/TitleChangeEventDelegate.cs create mode 100644 GreenshotJiraPlugin/Hooking/WindowsEventHook.cs create mode 100644 GreenshotJiraPlugin/Hooking/WindowsTitleMonitor.cs create mode 100644 GreenshotJiraPlugin/JiraDetails.cs create mode 100644 GreenshotJiraPlugin/JiraEventArgs.cs create mode 100644 GreenshotJiraPlugin/JiraEventTypes.cs create mode 100644 GreenshotJiraPlugin/JiraMonitor.cs delete mode 100644 GreenshotJiraPlugin/JiraUtils.cs diff --git a/Greenshot/Forms/SettingsForm.cs b/Greenshot/Forms/SettingsForm.cs index 9e7fd5ad4..00588bdee 100644 --- a/Greenshot/Forms/SettingsForm.cs +++ b/Greenshot/Forms/SettingsForm.cs @@ -295,7 +295,7 @@ namespace Greenshot { private void UpdateClipboardFormatDescriptions() { foreach(ListViewItem item in listview_clipboardformats.Items) { ClipboardFormat cf = (ClipboardFormat) item.Tag; - item.Text = Language.Translate(cf); + item.Text = Language.Translate(cf); } } @@ -592,12 +592,12 @@ namespace Greenshot { CheckDestinationSettings(); } - protected override void OnFieldsFilled() { - // the color radio button is not actually bound to a setting, but checked when monochrome/grayscale are not checked - if(!radioBtnGrayScale.Checked && !radioBtnMonochrome.Checked) { - radioBtnColorPrint.Checked = true; - } - } + protected override void OnFieldsFilled() { + // the color radio button is not actually bound to a setting, but checked when monochrome/grayscale are not checked + if(!radioBtnGrayScale.Checked && !radioBtnMonochrome.Checked) { + radioBtnColorPrint.Checked = true; + } + } /// /// Set the enable state of the expert settings diff --git a/GreenshotJiraPlugin/Forms/JiraForm.cs b/GreenshotJiraPlugin/Forms/JiraForm.cs index e6fbf6717..efee62973 100644 --- a/GreenshotJiraPlugin/Forms/JiraForm.cs +++ b/GreenshotJiraPlugin/Forms/JiraForm.cs @@ -65,7 +65,7 @@ namespace GreenshotJiraPlugin.Forms { { if (!_jiraConnector.IsLoggedIn) { - await _jiraConnector.Login(); + await _jiraConnector.LoginAsync(); } } catch (Exception e) diff --git a/GreenshotJiraPlugin/GreenshotJiraPlugin.csproj b/GreenshotJiraPlugin/GreenshotJiraPlugin.csproj index 2c02694f4..850aafdbf 100644 --- a/GreenshotJiraPlugin/GreenshotJiraPlugin.csproj +++ b/GreenshotJiraPlugin/GreenshotJiraPlugin.csproj @@ -78,12 +78,19 @@ SettingsForm.cs + + + + + + + + - diff --git a/GreenshotJiraPlugin/Hooking/TitleChangeEventArgs.cs b/GreenshotJiraPlugin/Hooking/TitleChangeEventArgs.cs new file mode 100644 index 000000000..6ec8878fd --- /dev/null +++ b/GreenshotJiraPlugin/Hooking/TitleChangeEventArgs.cs @@ -0,0 +1,50 @@ +/* + * dapplo - building blocks for desktop applications + * Copyright (C) Dapplo 2015-2016 + * + * For more information see: http://dapplo.net/ + * dapplo repositories are hosted on GitHub: https://github.com/dapplo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; + +namespace GreenshotJiraPlugin.Hooking +{ + /// + /// Event arguments for the TitleChangeEvent + /// + public class TitleChangeEventArgs : EventArgs + { + /// + /// HWnd of the window which has a changed title + /// + public IntPtr HWnd + { + get; + set; + } + + /// + /// Title which is changed + /// + public string Title + { + get; + set; + } + } + +} diff --git a/GreenshotJiraPlugin/Hooking/TitleChangeEventDelegate.cs b/GreenshotJiraPlugin/Hooking/TitleChangeEventDelegate.cs new file mode 100644 index 000000000..29847a1e5 --- /dev/null +++ b/GreenshotJiraPlugin/Hooking/TitleChangeEventDelegate.cs @@ -0,0 +1,29 @@ +/* + * dapplo - building blocks for desktop applications + * Copyright (C) Dapplo 2015-2016 + * + * For more information see: http://dapplo.net/ + * dapplo repositories are hosted on GitHub: https://github.com/dapplo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace GreenshotJiraPlugin.Hooking +{ + /// + /// Delegate for the title change event + /// + /// + public delegate void TitleChangeEventDelegate(TitleChangeEventArgs eventArgs); +} \ No newline at end of file diff --git a/GreenshotJiraPlugin/Hooking/WindowsEventHook.cs b/GreenshotJiraPlugin/Hooking/WindowsEventHook.cs new file mode 100644 index 000000000..e2be9d0b0 --- /dev/null +++ b/GreenshotJiraPlugin/Hooking/WindowsEventHook.cs @@ -0,0 +1,152 @@ +/* + * dapplo - building blocks for desktop applications + * Copyright (C) Dapplo 2015-2016 + * + * For more information see: http://dapplo.net/ + * dapplo repositories are hosted on GitHub: https://github.com/dapplo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using GreenshotPlugin.UnmanagedHelpers; + +namespace GreenshotJiraPlugin.Hooking +{ + /// + /// The WinEventHook can register handlers to become important windows events + /// This makes it possible to know a.o. when a window is created, moved, updated and closed. + /// + public class WindowsEventHook : IDisposable + { + private readonly WinEventDelegate _winEventHandler; + private GCHandle _gcHandle; + + /// + /// Used with Register hook + /// + /// + /// + /// + /// + /// + /// + public delegate void WinEventHandler(WinEvent eventType, IntPtr hwnd, EventObjects idObject, int idChild, uint dwEventThread, uint dwmsEventTime); + + /// + /// Create a WindowsEventHook object + /// + public WindowsEventHook() + { + _winEventHandler = WinEventDelegateHandler; + _gcHandle = GCHandle.Alloc(_winEventHandler); + } + + #region native code + [DllImport("user32", SetLastError = true)] + private static extern bool UnhookWinEvent(IntPtr hWinEventHook); + [DllImport("user32", SetLastError = true)] + private static extern IntPtr SetWinEventHook(WinEvent eventMin, WinEvent eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, int idProcess, int idThread, WinEventHookFlags dwFlags); + + /// + /// Used with SetWinEventHook + /// + /// + /// + /// + /// + /// + /// + /// + private delegate void WinEventDelegate(IntPtr hWinEventHook, WinEvent eventType, IntPtr hwnd, EventObjects idObject, int idChild, uint dwEventThread, uint dwmsEventTime); + #endregion + + private readonly IDictionary _winEventHandlers = new Dictionary(); + + /// + /// Are hooks active? + /// + public bool IsHooked => _winEventHandlers.Count > 0; + + /// + /// Hook a WinEvent + /// + /// + /// + /// true if success + public void Hook(WinEvent winEvent, WinEventHandler winEventHandler) + { + Hook(winEvent, winEvent, winEventHandler); + } + + /// + /// Hook a WinEvent + /// + /// + /// + /// + public void Hook(WinEvent winEventStart, WinEvent winEventEnd, WinEventHandler winEventHandler) + { + var hookPtr = SetWinEventHook(winEventStart, winEventEnd, IntPtr.Zero, _winEventHandler, 0, 0, WinEventHookFlags.WINEVENT_SKIPOWNPROCESS | WinEventHookFlags.WINEVENT_OUTOFCONTEXT); + _winEventHandlers.Add(hookPtr, winEventHandler); + } + + /// + /// Remove all hooks + /// + private void Unhook() + { + foreach (var hookPtr in _winEventHandlers.Keys) + { + if (hookPtr != IntPtr.Zero) + { + UnhookWinEvent(hookPtr); + } + } + _winEventHandlers.Clear(); + _gcHandle.Free(); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Unhook(); + } + + /// + /// Call the WinEventHandler for this event + /// + /// + /// + /// + /// + /// + /// + /// + private void WinEventDelegateHandler(IntPtr hWinEventHook, WinEvent eventType, IntPtr hWnd, EventObjects idObject, int idChild, uint dwEventThread, uint dwmsEventTime) + { + WinEventHandler handler; + if (_winEventHandlers.TryGetValue(hWinEventHook, out handler)) + { + handler(eventType, hWnd, idObject, idChild, dwEventThread, dwmsEventTime); + } + } + + } + +} diff --git a/GreenshotJiraPlugin/Hooking/WindowsTitleMonitor.cs b/GreenshotJiraPlugin/Hooking/WindowsTitleMonitor.cs new file mode 100644 index 000000000..5e0a05cf7 --- /dev/null +++ b/GreenshotJiraPlugin/Hooking/WindowsTitleMonitor.cs @@ -0,0 +1,139 @@ +/* + * dapplo - building blocks for desktop applications + * Copyright (C) Dapplo 2015-2016 + * + * For more information see: http://dapplo.net/ + * dapplo repositories are hosted on GitHub: https://github.com/dapplo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using GreenshotPlugin.Core; +using GreenshotPlugin.UnmanagedHelpers; + +namespace GreenshotJiraPlugin.Hooking +{ + /// + /// Monitor all title changes + /// + public sealed class WindowsTitleMonitor : IDisposable + { + private WindowsEventHook _hook; + private readonly object _lockObject = new object(); + // ReSharper disable once InconsistentNaming + private event TitleChangeEventDelegate _titleChangeEvent; + + /// + /// Add / remove event handler to the title monitor + /// + public event TitleChangeEventDelegate TitleChangeEvent + { + add + { + lock (_lockObject) + { + if (_hook == null) + { + _hook = new WindowsEventHook(); + _hook.Hook(WinEvent.EVENT_OBJECT_NAMECHANGE, WinEventHandler); + } + _titleChangeEvent += value; + } + } + remove + { + lock (_lockObject) + { + _titleChangeEvent -= value; + if (_titleChangeEvent == null || _titleChangeEvent.GetInvocationList().Length == 0) + { + if (_hook != null) + { + _hook.Dispose(); + _hook = null; + } + } + } + } + } + + /// + /// WinEventDelegate for the creation & destruction + /// + /// + /// + /// + /// + /// + /// + private void WinEventHandler(WinEvent eventType, IntPtr hWnd, EventObjects idObject, int idChild, uint dwEventThread, uint dwmsEventTime) + { + if (hWnd == IntPtr.Zero || idObject != EventObjects.OBJID_WINDOW) + { + return; + } + if (eventType == WinEvent.EVENT_OBJECT_NAMECHANGE) + { + if (_titleChangeEvent != null) + { + string newTitle = new WindowDetails(hWnd).Text; + _titleChangeEvent(new TitleChangeEventArgs { HWnd = hWnd, Title = newTitle }); + } + } + } + + #region IDisposable Support + + private bool _disposedValue; // To detect redundant calls + + /// + /// Dispose the underlying hook + /// + public void Dispose(bool disposing) + { + if (_disposedValue) + { + return; + } + lock (_lockObject) + { + _hook?.Dispose(); + } + _disposedValue = true; + } + + /// + /// Make sure the finalizer disposes the underlying hook + /// + ~WindowsTitleMonitor() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(false); + } + + /// + /// Dispose the underlying hook + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + } + +} diff --git a/GreenshotJiraPlugin/JiraConnector.cs b/GreenshotJiraPlugin/JiraConnector.cs index 65f2baa66..d3ded2449 100644 --- a/GreenshotJiraPlugin/JiraConnector.cs +++ b/GreenshotJiraPlugin/JiraConnector.cs @@ -47,12 +47,13 @@ namespace GreenshotJiraPlugin { private DateTimeOffset _loggedInTime = DateTimeOffset.MinValue; private bool _loggedIn; private readonly int _timeout; - private readonly object _lock = new object(); - private string _url; private JiraApi _jiraApi; private IssueTypeBitmapCache _issueTypeBitmapCache; private static readonly SvgBitmapHttpContentConverter SvgBitmapHttpContentConverterInstance = new SvgBitmapHttpContentConverter(); + /// + /// Initialize some basic stuff, in the case the SVG to bitmap converter + /// static JiraConnector() { if (HttpExtensionsGlobals.HttpContentConverters.All(x => x.GetType() != typeof(SvgBitmapHttpContentConverter))) @@ -72,46 +73,48 @@ namespace GreenshotJiraPlugin { } + /// + /// Dispose, logout the users + /// public void Dispose() { if (_jiraApi != null) { - Task.Run(async () => await Logout()).Wait(); + Task.Run(async () => await LogoutAsync()).Wait(); } } + /// + /// Constructor + /// public JiraConnector() { - _url = JiraConfig.Url.Replace(DefaultPostfix, ""); + JiraConfig.Url = JiraConfig.Url.Replace(DefaultPostfix, ""); _timeout = JiraConfig.Timeout; } + /// + /// Access the jira monitor + /// + public JiraMonitor Monitor { get; private set; } + /// /// Internal login which catches the exceptions /// /// true if login was done sucessfully - private async Task DoLogin(string user, string password) + private async Task DoLoginAsync(string user, string password) { - lock (_lock) + if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(password)) { - if (_url.EndsWith("wsdl")) - { - _url = _url.Replace(DefaultPostfix, ""); - } - if (_jiraApi == null) - { - // recreate the service with the new url - _jiraApi = new JiraApi(new Uri(_url)); - _issueTypeBitmapCache = new IssueTypeBitmapCache(_jiraApi); - } + return false; } - + _jiraApi = new JiraApi(new Uri(JiraConfig.Url)); + _issueTypeBitmapCache = new IssueTypeBitmapCache(_jiraApi); + Monitor = new JiraMonitor(); + await Monitor.AddJiraInstanceAsync(_jiraApi); LoginInfo loginInfo; try { loginInfo = await _jiraApi.StartSessionAsync(user, password); - // Worked, store the url in the configuration - JiraConfig.Url = _url; - IniConfig.Save(); } catch (Exception) { @@ -120,17 +123,21 @@ namespace GreenshotJiraPlugin { return loginInfo != null; } - public async Task Login() { - await Logout(); + /// + /// Use the credentials dialog, this will show if there are not correct credentials. + /// If there are credentials, call the real login. + /// + /// Task + public async Task LoginAsync() { + await LogoutAsync(); try { // Get the system name, so the user knows where to login to - string systemName = _url.Replace(DefaultPostfix,""); - var credentialsDialog = new CredentialsDialog(systemName) + var credentialsDialog = new CredentialsDialog(JiraConfig.Url) { Name = null }; while (credentialsDialog.Show(credentialsDialog.Name) == DialogResult.OK) { - if (await DoLogin(credentialsDialog.Name, credentialsDialog.Password)) { + if (await DoLoginAsync(credentialsDialog.Name, credentialsDialog.Password)) { if (credentialsDialog.SaveChecked) { credentialsDialog.Confirm(true); } @@ -159,9 +166,10 @@ namespace GreenshotJiraPlugin { /// /// End the session, if there was one /// - public async Task Logout() { + public async Task LogoutAsync() { if (_jiraApi != null && _loggedIn) { + Monitor.Dispose(); await _jiraApi.EndSessionAsync(); _loggedIn = false; } @@ -172,14 +180,14 @@ namespace GreenshotJiraPlugin { /// Do not use ConfigureAwait to call this, as it will move await from the UI thread. /// /// - private async Task CheckCredentials() { + private async Task CheckCredentialsAsync() { if (_loggedIn) { if (_loggedInTime.AddMinutes(_timeout-1).CompareTo(DateTime.Now) < 0) { - await Logout(); - await Login(); + await LogoutAsync(); + await LoginAsync(); } } else { - await Login(); + await LoginAsync(); } } @@ -189,7 +197,7 @@ namespace GreenshotJiraPlugin { /// List with filters public async Task> GetFavoriteFiltersAsync() { - await CheckCredentials(); + await CheckCredentialsAsync(); return await _jiraApi.GetFavoriteFiltersAsync().ConfigureAwait(false); } @@ -200,7 +208,7 @@ namespace GreenshotJiraPlugin { /// Issue public async Task GetIssueAsync(string issueKey) { - await CheckCredentials(); + await CheckCredentialsAsync(); try { return await _jiraApi.GetIssueAsync(issueKey).ConfigureAwait(false); @@ -220,7 +228,7 @@ namespace GreenshotJiraPlugin { /// public async Task AttachAsync(string issueKey, IBinaryContainer content, CancellationToken cancellationToken = default(CancellationToken)) { - await CheckCredentials(); + await CheckCredentialsAsync(); using (var memoryStream = new MemoryStream()) { content.WriteToStream(memoryStream); @@ -238,7 +246,7 @@ namespace GreenshotJiraPlugin { /// CancellationToken public async Task AddCommentAsync(string issueKey, string body, string visibility = null, CancellationToken cancellationToken = default(CancellationToken)) { - await CheckCredentials(); + await CheckCredentialsAsync(); await _jiraApi.AddCommentAsync(issueKey, body, visibility, cancellationToken).ConfigureAwait(false); } @@ -250,7 +258,7 @@ namespace GreenshotJiraPlugin { /// public async Task> SearchAsync(Filter filter, CancellationToken cancellationToken = default(CancellationToken)) { - await CheckCredentials(); + await CheckCredentialsAsync(); var searchResult = await _jiraApi.SearchAsync(filter.Jql, 20, new[] { "summary", "reporter", "assignee", "created", "issuetype" }, cancellationToken).ConfigureAwait(false); return searchResult.Issues; } @@ -266,8 +274,14 @@ namespace GreenshotJiraPlugin { return await _issueTypeBitmapCache.GetOrCreateAsync(issue.Fields.IssueType, cancellationToken).ConfigureAwait(false); } + /// + /// Get the base uri + /// public Uri JiraBaseUri => _jiraApi.JiraBaseUri; + /// + /// Is the user "logged in? + /// public bool IsLoggedIn => _loggedIn; } } \ No newline at end of file diff --git a/GreenshotJiraPlugin/JiraDestination.cs b/GreenshotJiraPlugin/JiraDestination.cs index 6d472535a..d221ba996 100644 --- a/GreenshotJiraPlugin/JiraDestination.cs +++ b/GreenshotJiraPlugin/JiraDestination.cs @@ -61,7 +61,7 @@ namespace GreenshotJiraPlugin { return Language.GetString("jira", LangKey.upload_menu_item); } // Format the title of this destination - return Designation + " - " + _jiraIssue.Key + ": " + _jiraIssue.Fields.Summary.Substring(0, Math.Min(20, _jiraIssue.Fields.Summary.Length)); + return _jiraIssue.Key + ": " + _jiraIssue.Fields.Summary.Substring(0, Math.Min(20, _jiraIssue.Fields.Summary.Length)); } } @@ -86,15 +86,15 @@ namespace GreenshotJiraPlugin { } } - public override IEnumerable DynamicDestinations() { - if (JiraPlugin.Instance.CurrentJiraConnector == null || !JiraPlugin.Instance.CurrentJiraConnector.IsLoggedIn) { + public override IEnumerable DynamicDestinations() + { + var jiraConnector = JiraPlugin.Instance.CurrentJiraConnector; + if (jiraConnector == null || !jiraConnector.IsLoggedIn) { yield break; } - var issues = JiraUtils.GetCurrentJirasAsync().Result; - if (issues != null) { - foreach(var jiraIssue in issues) { - yield return new JiraDestination(_jiraPlugin, jiraIssue); - } + foreach(var jiraDetails in jiraConnector.Monitor.RecentJiras) + { + yield return new JiraDestination(_jiraPlugin,jiraDetails.JiraIssue); } } diff --git a/GreenshotJiraPlugin/JiraDetails.cs b/GreenshotJiraPlugin/JiraDetails.cs new file mode 100644 index 000000000..e03172873 --- /dev/null +++ b/GreenshotJiraPlugin/JiraDetails.cs @@ -0,0 +1,71 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2016 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on GitHub: https://github.com/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using Dapplo.Jira.Entities; + +namespace GreenshotJiraPlugin +{ + public class JiraDetails : IComparable + { + public JiraDetails() + { + FirstSeenAt = SeenAt = DateTimeOffset.Now; + } + + public string ProjectKey + { + get; + set; + } + + public string Id + { + get; + set; + } + + public string JiraKey => ProjectKey + "-" + Id; + + public Issue JiraIssue + { + get; + set; + } + + public DateTimeOffset FirstSeenAt + { + get; + private set; + } + + public DateTimeOffset SeenAt + { + get; + set; + } + + public int CompareTo(JiraDetails other) + { + return SeenAt.CompareTo(other.SeenAt); + } + } +} \ No newline at end of file diff --git a/GreenshotJiraPlugin/JiraEventArgs.cs b/GreenshotJiraPlugin/JiraEventArgs.cs new file mode 100644 index 000000000..cbe7caa0d --- /dev/null +++ b/GreenshotJiraPlugin/JiraEventArgs.cs @@ -0,0 +1,40 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2016 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on GitHub: https://github.com/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; + +namespace GreenshotJiraPlugin +{ + public class JiraEventArgs : EventArgs + { + public JiraEventTypes EventType + { + get; + set; + } + + public JiraDetails Details + { + get; + set; + } + } +} diff --git a/GreenshotJiraPlugin/JiraEventTypes.cs b/GreenshotJiraPlugin/JiraEventTypes.cs new file mode 100644 index 000000000..06b95db22 --- /dev/null +++ b/GreenshotJiraPlugin/JiraEventTypes.cs @@ -0,0 +1,29 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2016 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on GitHub: https://github.com/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace GreenshotJiraPlugin +{ + public enum JiraEventTypes + { + OrderChanged, + DetectedNewJiraIssue + } +} \ No newline at end of file diff --git a/GreenshotJiraPlugin/JiraMonitor.cs b/GreenshotJiraPlugin/JiraMonitor.cs new file mode 100644 index 000000000..1d3784fd0 --- /dev/null +++ b/GreenshotJiraPlugin/JiraMonitor.cs @@ -0,0 +1,225 @@ +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2016 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on GitHub: https://github.com/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Dapplo.Jira; +using Dapplo.Log.Facade; +using GreenshotJiraPlugin.Hooking; + +namespace GreenshotJiraPlugin +{ + + /// + /// This class will monitor all _jira activity by registering for title changes + /// It keeps a list of the last "accessed" jiras, and makes it easy to upload to one. + /// Make sure this is instanciated on the UI thread! + /// + public class JiraMonitor : IDisposable + { + private static readonly LogSource Log = new LogSource(); + private readonly Regex _jiraKeyPattern = new Regex(@"[A-Z][A-Z0-9]+\-[0-9]+"); + private readonly WindowsTitleMonitor _monitor; + private readonly IList _jiraInstances = new List(); + private readonly IDictionary _projectJiraApiMap = new Dictionary(); + private readonly int _maxEntries; + private IDictionary _recentJiras = new Dictionary(); + + /// + /// Register to this event to get events when new jira issues are detected + /// + public event EventHandler JiraEvent; + + public JiraMonitor(int maxEntries = 40) + { + _maxEntries = maxEntries; + _monitor = new WindowsTitleMonitor(); + _monitor.TitleChangeEvent += MonitorTitleChangeEvent; + } + + #region Dispose + + /// + /// Dispose + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose all managed resources + /// + /// when true is passed all managed resources are disposed. + protected void Dispose(bool disposing) + { + if (!disposing) + { + return; + } + // free managed resources + _monitor.TitleChangeEvent -= MonitorTitleChangeEvent; + _monitor.Dispose(); + // free native resources if there are any. + } + + #endregion + + /// + /// Retrieve the API belonging to a JiraDetails + /// + /// + /// JiraAPI + public JiraApi GetJiraApiForKey(JiraDetails jiraDetails) + { + return _projectJiraApiMap[jiraDetails.ProjectKey]; + } + + /// + /// Get the "list" of recently seen Jiras + /// + public IEnumerable RecentJiras => + (from jiraDetails in _recentJiras.Values + orderby jiraDetails.SeenAt descending + select jiraDetails); + + /// + /// Check if this monitor has active instances + /// + public bool HasJiraInstances => _jiraInstances.Count > 0; + + /// + /// Add an instance of a JIRA system + /// + /// + /// + public async Task AddJiraInstanceAsync(JiraApi jiraInstance, CancellationToken token = default(CancellationToken)) + { + _jiraInstances.Add(jiraInstance); + var projects = await jiraInstance.GetProjectsAsync(token).ConfigureAwait(false); + if (projects != null) + { + foreach (var project in projects) + { + if (!_projectJiraApiMap.ContainsKey(project.Key)) + { + _projectJiraApiMap.Add(project.Key, jiraInstance); + } + } + } + } + + /// + /// This method will update details, like the title, and send an event to registed listeners of the JiraEvent + /// + /// Contains the jira key to retrieve the title (XYZ-1234) + /// Task + private async Task DetectedNewJiraIssueAsync(JiraDetails jiraDetails) + { + try + { + JiraApi jiraApi; + if (_projectJiraApiMap.TryGetValue(jiraDetails.ProjectKey, out jiraApi)) + { + var issue = await jiraApi.GetIssueAsync(jiraDetails.JiraKey).ConfigureAwait(false); + jiraDetails.JiraIssue = issue; + } + // Send event + JiraEvent?.Invoke(this, new JiraEventArgs { Details = jiraDetails, EventType = JiraEventTypes.DetectedNewJiraIssue }); + } + catch (Exception ex) + { + Log.Warn().WriteLine("Couldn't retrieve JIRA title: {0}", ex.Message); + } + } + + /// + /// Handle title changes, check for JIRA + /// + /// + private void MonitorTitleChangeEvent(TitleChangeEventArgs eventArgs) + { + string windowTitle = eventArgs.Title; + if (string.IsNullOrEmpty(windowTitle)) + { + return; + } + var jiraKeyMatch = _jiraKeyPattern.Match(windowTitle); + if (!jiraKeyMatch.Success) + { + return; + } + // Found a possible JIRA title + var jiraKey = jiraKeyMatch.Value; + var jiraKeyParts = jiraKey.Split('-'); + var projectKey = jiraKeyParts[0]; + var jiraId = jiraKeyParts[1]; + + JiraApi jiraApi; + // Check if we have a JIRA instance with a project for this key + if (_projectJiraApiMap.TryGetValue(projectKey, out jiraApi)) + { + // We have found a project for this _jira key, so it must be a valid & known JIRA + JiraDetails currentJiraDetails; + if (_recentJiras.TryGetValue(jiraKey, out currentJiraDetails)) + { + // update + currentJiraDetails.SeenAt = DateTimeOffset.Now; + + // Notify the order change + JiraEvent?.Invoke(this, new JiraEventArgs { Details = currentJiraDetails, EventType = JiraEventTypes.OrderChanged }); + // Nothing else to do + + return; + } + // We detected an unknown JIRA, so add it to our list + currentJiraDetails = new JiraDetails() + { + Id = jiraId, + ProjectKey = projectKey + }; + _recentJiras.Add(currentJiraDetails.JiraKey, currentJiraDetails); + + // Make sure we don't collect _jira's until the memory is full + if (_recentJiras.Count > _maxEntries) + { + // Add it to the list of recent Jiras + IList clonedList = new List(_recentJiras.Values); + _recentJiras = (from jiraDetails in clonedList + orderby jiraDetails.SeenAt descending + select jiraDetails).Take(_maxEntries).ToDictionary(jd => jd.JiraKey, jd => jd); + } + // Now we can get the title from JIRA itself + // ReSharper disable once UnusedVariable + var updateTitleTask = DetectedNewJiraIssueAsync(currentJiraDetails); + } + else + { + Log.Info().WriteLine("Couldn't match possible JIRA key {0} to projects in a configured JIRA instance, ignoring", projectKey); + } + } + } +} \ No newline at end of file diff --git a/GreenshotJiraPlugin/JiraPlugin.cs b/GreenshotJiraPlugin/JiraPlugin.cs index 6f9e499e9..009ac9ce3 100644 --- a/GreenshotJiraPlugin/JiraPlugin.cs +++ b/GreenshotJiraPlugin/JiraPlugin.cs @@ -33,9 +33,9 @@ namespace GreenshotJiraPlugin { /// public class JiraPlugin : IGreenshotPlugin { private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(JiraPlugin)); - private JiraConnector _jiraConnector; private JiraConfiguration _config; private static JiraPlugin _instance; + private JiraConnector _jiraConnector; public void Dispose() { Dispose(true); @@ -44,9 +44,9 @@ namespace GreenshotJiraPlugin { protected void Dispose(bool disposing) { if (disposing) { - if (_jiraConnector != null) { - _jiraConnector.Dispose(); - _jiraConnector = null; + if (JiraConnector != null) { + JiraConnector.Dispose(); + JiraConnector = null; } } } @@ -66,9 +66,23 @@ namespace GreenshotJiraPlugin { } //Needed for a fail-fast - public JiraConnector CurrentJiraConnector => _jiraConnector; + public JiraConnector CurrentJiraConnector => JiraConnector; - public JiraConnector JiraConnector => _jiraConnector ?? (_jiraConnector = new JiraConnector()); + public JiraConnector JiraConnector + { + get + { + lock (_instance) + { + if (_jiraConnector == null) + { + JiraConnector = new JiraConnector(); + } + } + return _jiraConnector; + } + private set { _jiraConnector = value; } + } /// /// Implementation of the IGreenshotPlugin.Initialize @@ -80,14 +94,15 @@ namespace GreenshotJiraPlugin { // Register configuration (don't need the configuration itself) _config = IniConfig.GetIniSection(); LogSettings.RegisterDefaultLogger(); + return true; } public void Shutdown() { Log.Debug("Jira Plugin shutdown."); - if (_jiraConnector != null) + if (JiraConnector != null) { - Task.Run(async () => await _jiraConnector.Logout()); + Task.Run(async () => await JiraConnector.LogoutAsync()); } } @@ -98,12 +113,12 @@ namespace GreenshotJiraPlugin { string url = _config.Url; if (ShowConfigDialog()) { // check for re-login - if (_jiraConnector != null && _jiraConnector.IsLoggedIn && !string.IsNullOrEmpty(url)) { + if (JiraConnector != null && JiraConnector.IsLoggedIn && !string.IsNullOrEmpty(url)) { if (!url.Equals(_config.Url)) { Task.Run(async () => { - await _jiraConnector.Logout(); - await _jiraConnector.Login(); + await JiraConnector.LogoutAsync(); + await JiraConnector.LoginAsync(); }); } } diff --git a/GreenshotJiraPlugin/JiraUtils.cs b/GreenshotJiraPlugin/JiraUtils.cs deleted file mode 100644 index 94984f555..000000000 --- a/GreenshotJiraPlugin/JiraUtils.cs +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Greenshot - a free and open source screenshot tool - * Copyright (C) 2007-2016 Thomas Braun, Jens Klingen, Robin Krom - * - * For more information see: http://getgreenshot.org/ - * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 1 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Dapplo.Jira.Entities; -using Greenshot.IniFile; -using GreenshotPlugin.Core; - -namespace GreenshotJiraPlugin { - /// - /// Description of JiraUtils. - /// - public static class JiraUtils { - private static readonly Regex JiraKeyRegex = new Regex(@"/browse/([A-Z0-9]+\-[0-9]+)"); - private static readonly JiraConfiguration Config = IniConfig.GetIniSection(); - private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(JiraUtils)); - - public static async Task> GetCurrentJirasAsync() { - // Make sure we suppress the login - var jirakeys = new List(); - foreach(string url in IEHelper.GetIEUrls()) { - if (url == null) { - continue; - } - var jiraKeyMatch = JiraKeyRegex.Matches(url); - if (jiraKeyMatch.Count > 0) { - string jiraKey = jiraKeyMatch[0].Groups[1].Value; - jirakeys.Add(jiraKey); - } - } - if (!string.IsNullOrEmpty(Config.LastUsedJira) && !jirakeys.Contains(Config.LastUsedJira)) { - jirakeys.Add(Config.LastUsedJira); - } - if (jirakeys.Count > 0) { - var jiraIssues = new List(); - foreach(string jiraKey in jirakeys) { - try - { - var issue = await JiraPlugin.Instance.JiraConnector.GetIssueAsync(jiraKey).ConfigureAwait(false); - if (issue != null) - { - jiraIssues.Add(issue); - } - } - catch (Exception ex) - { - Log.Error(ex); - // Remove issue from the last used jira config, as it caused an issue (probably not there) - if (Config.LastUsedJira == jiraKey) - { - Config.LastUsedJira = null; - } - } - } - if (jiraIssues.Count > 0) { - return jiraIssues; - } - } - return null; - } - } -} diff --git a/GreenshotPlugin/Core/CredentialsHelper.cs b/GreenshotPlugin/Core/CredentialsHelper.cs index 5f9eaa8ab..6aa8d2bff 100644 --- a/GreenshotPlugin/Core/CredentialsHelper.cs +++ b/GreenshotPlugin/Core/CredentialsHelper.cs @@ -59,10 +59,10 @@ namespace GreenshotPlugin.Core { /// Encapsulates dialog functionality from the Credential Management API. public sealed class CredentialsDialog { [DllImport("gdi32.dll", SetLastError=true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool DeleteObject(IntPtr hObject); + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool DeleteObject(IntPtr hObject); - /// The only valid bitmap height (in pixels) of a user-defined banner. + /// The only valid bitmap height (in pixels) of a user-defined banner. private const int ValidBannerHeight = 60; /// The only valid bitmap width (in pixels) of a user-defined banner. @@ -102,65 +102,25 @@ namespace GreenshotPlugin.Core { Banner = banner; } - private bool _alwaysDisplay; /// /// Gets or sets if the dialog will be shown even if the credentials /// can be returned from an existing credential in the credential manager. /// - public bool AlwaysDisplay { - get { - return _alwaysDisplay; - } - set { - _alwaysDisplay = value; - } - } + public bool AlwaysDisplay { get; set; } - private bool _excludeCertificates = true; /// Gets or sets if the dialog is populated with name/password only. - public bool ExcludeCertificates { - get { - return _excludeCertificates; - } - set { - _excludeCertificates = value; - } - } + public bool ExcludeCertificates { get; set; } = true; - private bool _persist = true; /// Gets or sets if the credentials are to be persisted in the credential manager. - public bool Persist { - get { - return _persist; - } - set { - _persist = value; - } - } + public bool Persist { get; set; } = true; - private bool _incorrectPassword; /// Gets or sets if the incorrect password balloontip needs to be shown. Introduced AFTER Windows XPGets> - public bool IncorrectPassword { - get { - return _incorrectPassword; - } - set { - _incorrectPassword = value; - } - } + public bool IncorrectPassword { get; set; } - private bool _keepName; /// Gets or sets if the name is read-only. - public bool KeepName { - get { - return _keepName; - } - set { - _keepName = value; - } - } + public bool KeepName { get; set; } - private string _name = String.Empty; + private string _name = string.Empty; /// Gets or sets the name for the credentials. public string Name { get { @@ -169,18 +129,18 @@ namespace GreenshotPlugin.Core { set { if (value != null) { if (value.Length > CREDUI.MAX_USERNAME_LENGTH) { - string message = String.Format( + string message = string.Format( Thread.CurrentThread.CurrentUICulture, "The name has a maximum length of {0} characters.", CREDUI.MAX_USERNAME_LENGTH); - throw new ArgumentException(message, "Name"); + throw new ArgumentException(message, nameof(Name)); } } _name = value; } } - private string _password = String.Empty; + private string _password = string.Empty; /// Gets or sets the password for the credentials. public string Password { get { @@ -189,41 +149,25 @@ namespace GreenshotPlugin.Core { set { if (value != null) { if (value.Length > CREDUI.MAX_PASSWORD_LENGTH) { - string message = String.Format( + string message = string.Format( Thread.CurrentThread.CurrentUICulture, "The password has a maximum length of {0} characters.", CREDUI.MAX_PASSWORD_LENGTH); - throw new ArgumentException(message, "Password"); + throw new ArgumentException(message, nameof(Password)); } } _password = value; } } - private bool _saveChecked; /// Gets or sets if the save checkbox status. - public bool SaveChecked { - get { - return _saveChecked; - } - set { - _saveChecked = value; - } - } + public bool SaveChecked { get; set; } - private bool _saveDisplayed = true; /// Gets or sets if the save checkbox is displayed. /// This value only has effect if Persist is true. - public bool SaveDisplayed { - get { - return _saveDisplayed; - } - set { - _saveDisplayed = value; - } - } + public bool SaveDisplayed { get; set; } = true; - private string _target = String.Empty; + private string _target = string.Empty; /// Gets or sets the name of the target for the credentials, typically a server name. public string Target { get { @@ -232,18 +176,19 @@ namespace GreenshotPlugin.Core { set { if (value == null) { throw new ArgumentException("The target cannot be a null value.", "Target"); - } else if (value.Length > CREDUI.MAX_GENERIC_TARGET_LENGTH) { - string message = String.Format( + } + if (value.Length > CREDUI.MAX_GENERIC_TARGET_LENGTH) { + string message = string.Format( Thread.CurrentThread.CurrentUICulture, "The target has a maximum length of {0} characters.", CREDUI.MAX_GENERIC_TARGET_LENGTH); - throw new ArgumentException(message, "Target"); + throw new ArgumentException(message, nameof(Target)); } _target = value; } } - private string _caption = String.Empty; + private string _caption = string.Empty; /// Gets or sets the caption of the dialog. /// A null value will cause a system default caption to be used. public string Caption { @@ -253,18 +198,18 @@ namespace GreenshotPlugin.Core { set { if (value != null) { if (value.Length > CREDUI.MAX_CAPTION_LENGTH) { - string message = String.Format( + string message = string.Format( Thread.CurrentThread.CurrentUICulture, "The caption has a maximum length of {0} characters.", CREDUI.MAX_CAPTION_LENGTH); - throw new ArgumentException(message, "Caption"); + throw new ArgumentException(message, nameof(Caption)); } } _caption = value; } } - private string _message = String.Empty; + private string _message = string.Empty; /// Gets or sets the message of the dialog. /// A null value will cause a system default message to be used. public string Message { @@ -274,11 +219,11 @@ namespace GreenshotPlugin.Core { set { if (value != null) { if (value.Length > CREDUI.MAX_MESSAGE_LENGTH) { - string message = String.Format( + string message = string.Format( Thread.CurrentThread.CurrentUICulture, "The message has a maximum length of {0} characters.", CREDUI.MAX_MESSAGE_LENGTH); - throw new ArgumentException(message, "Message"); + throw new ArgumentException(message, nameof(Message)); } } _message = value; @@ -295,10 +240,10 @@ namespace GreenshotPlugin.Core { set { if (value != null) { if (value.Width != ValidBannerWidth) { - throw new ArgumentException("The banner image width must be 320 pixels.", "Banner"); + throw new ArgumentException("The banner image width must be 320 pixels.", nameof(Banner)); } if (value.Height != ValidBannerHeight) { - throw new ArgumentException("The banner image height must be 60 pixels.", "Banner"); + throw new ArgumentException("The banner image height must be 60 pixels.", nameof(Banner)); } } _banner = value; diff --git a/GreenshotPlugin/UnmanagedHelpers/Enumerations.cs b/GreenshotPlugin/UnmanagedHelpers/Enumerations.cs index c5bcb7daa..0d73ebeef 100644 --- a/GreenshotPlugin/UnmanagedHelpers/Enumerations.cs +++ b/GreenshotPlugin/UnmanagedHelpers/Enumerations.cs @@ -112,7 +112,7 @@ namespace GreenshotPlugin.UnmanagedHelpers { WS_EX_LAYOUTRTL = 0x00400000, // Right to left mirroring WS_EX_COMPOSITED = 0x02000000, WS_EX_NOACTIVATE = 0x08000000 - } + } [Flags] [SuppressMessage("ReSharper", "InconsistentNaming")] @@ -202,38 +202,38 @@ namespace GreenshotPlugin.UnmanagedHelpers { [SuppressMessage("ReSharper", "InconsistentNaming")] public enum SYSCOLOR { - SCROLLBAR = 0, - BACKGROUND = 1, - DESKTOP = 1, - ACTIVECAPTION = 2, - INACTIVECAPTION = 3, - MENU = 4, - WINDOW = 5, - WINDOWFRAME = 6, - MENUTEXT = 7, - WINDOWTEXT = 8, - CAPTIONTEXT = 9, - ACTIVEBORDER = 10, - INACTIVEBORDER = 11, - APPWORKSPACE = 12, - HIGHLIGHT = 13, - HIGHLIGHTTEXT = 14, - BTNFACE = 15, - THREEDFACE = 15, - BTNSHADOW = 16, - THREEDSHADOW = 16, - GRAYTEXT = 17, - BTNTEXT = 18, - INACTIVECAPTIONTEXT = 19, - BTNHIGHLIGHT = 20, - TREEDHIGHLIGHT = 20, - THREEDHILIGHT = 20, - BTNHILIGHT = 20, - THREEDDKSHADOW = 21, - THREEDLIGHT = 22, - INFOTEXT = 23, - INFOBK = 24 - } + SCROLLBAR = 0, + BACKGROUND = 1, + DESKTOP = 1, + ACTIVECAPTION = 2, + INACTIVECAPTION = 3, + MENU = 4, + WINDOW = 5, + WINDOWFRAME = 6, + MENUTEXT = 7, + WINDOWTEXT = 8, + CAPTIONTEXT = 9, + ACTIVEBORDER = 10, + INACTIVEBORDER = 11, + APPWORKSPACE = 12, + HIGHLIGHT = 13, + HIGHLIGHTTEXT = 14, + BTNFACE = 15, + THREEDFACE = 15, + BTNSHADOW = 16, + THREEDSHADOW = 16, + GRAYTEXT = 17, + BTNTEXT = 18, + INACTIVECAPTIONTEXT = 19, + BTNHIGHLIGHT = 20, + TREEDHIGHLIGHT = 20, + THREEDHILIGHT = 20, + BTNHILIGHT = 20, + THREEDDKSHADOW = 21, + THREEDLIGHT = 22, + INFOTEXT = 23, + INFOBK = 24 + } /// /// Flags used with the Windows API (User32.dll):GetSystemMetrics(SystemMetric smIndex) /// @@ -642,12 +642,12 @@ namespace GreenshotPlugin.UnmanagedHelpers { DWMWA_FORCE_ICONIC_REPRESENTATION, DWMWA_FLIP3D_POLICY, DWMWA_EXTENDED_FRAME_BOUNDS, // This is the one we need for retrieving the Window size since Windows Vista - DWMWA_HAS_ICONIC_BITMAP, // Since Windows 7 - DWMWA_DISALLOW_PEEK, // Since Windows 7 - DWMWA_EXCLUDED_FROM_PEEK, // Since Windows 7 - DWMWA_CLOAK, // Since Windows 8 - DWMWA_CLOAKED, // Since Windows 8 - DWMWA_FREEZE_REPRESENTATION, // Since Windows 8 + DWMWA_HAS_ICONIC_BITMAP, // Since Windows 7 + DWMWA_DISALLOW_PEEK, // Since Windows 7 + DWMWA_EXCLUDED_FROM_PEEK, // Since Windows 7 + DWMWA_CLOAK, // Since Windows 8 + DWMWA_CLOAKED, // Since Windows 8 + DWMWA_FREEZE_REPRESENTATION, // Since Windows 8 DWMWA_LAST } @@ -987,199 +987,199 @@ namespace GreenshotPlugin.UnmanagedHelpers { Synchronize = 0x00100000 } - /// - /// See: http://msdn.microsoft.com/en-us/library/aa909766.aspx - /// - [Flags] - [SuppressMessage("ReSharper", "InconsistentNaming")] - public enum SoundFlags - { - SND_SYNC = 0x0000, // play synchronously (default) - SND_ASYNC = 0x0001, // play asynchronously - SND_NODEFAULT = 0x0002, // silence (!default) if sound not found - SND_MEMORY = 0x0004, // pszSound points to a memory file - SND_LOOP = 0x0008, // loop the sound until next sndPlaySound - SND_NOSTOP = 0x0010, // don't stop any currently playing sound - SND_NOWAIT = 0x00002000, // don't wait if the driver is busy - SND_ALIAS = 0x00010000, // name is a registry alias - SND_ALIAS_ID = 0x00110000, // alias is a predefined id - SND_FILENAME = 0x00020000, // name is file name - } - + /// + /// See: http://msdn.microsoft.com/en-us/library/aa909766.aspx + /// + [Flags] + [SuppressMessage("ReSharper", "InconsistentNaming")] + public enum SoundFlags + { + SND_SYNC = 0x0000, // play synchronously (default) + SND_ASYNC = 0x0001, // play asynchronously + SND_NODEFAULT = 0x0002, // silence (!default) if sound not found + SND_MEMORY = 0x0004, // pszSound points to a memory file + SND_LOOP = 0x0008, // loop the sound until next sndPlaySound + SND_NOSTOP = 0x0010, // don't stop any currently playing sound + SND_NOWAIT = 0x00002000, // don't wait if the driver is busy + SND_ALIAS = 0x00010000, // name is a registry alias + SND_ALIAS_ID = 0x00110000, // alias is a predefined id + SND_FILENAME = 0x00020000, // name is file name + } + /// /// Used by GDI32.GetDeviceCaps /// See: http://msdn.microsoft.com/en-us/library/windows/desktop/dd144877%28v=vs.85%29.aspx /// [SuppressMessage("ReSharper", "InconsistentNaming")] public enum DeviceCaps { - /// - /// Device driver version - /// - DRIVERVERSION = 0, - /// - /// Device classification - /// - TECHNOLOGY = 2, - /// - /// Horizontal size in millimeters - /// - HORZSIZE = 4, - /// - /// Vertical size in millimeters - /// - VERTSIZE = 6, - /// - /// Horizontal width in pixels - /// - HORZRES = 8, - /// - /// Vertical height in pixels - /// - VERTRES = 10, - /// - /// Number of bits per pixel - /// - BITSPIXEL = 12, - /// - /// Number of planes - /// - PLANES = 14, - /// - /// Number of brushes the device has - /// - NUMBRUSHES = 16, - /// - /// Number of pens the device has - /// - NUMPENS = 18, - /// - /// Number of markers the device has - /// - NUMMARKERS = 20, - /// - /// Number of fonts the device has - /// - NUMFONTS = 22, - /// - /// Number of colors the device supports - /// - NUMCOLORS = 24, - /// - /// Size required for device descriptor - /// - PDEVICESIZE = 26, - /// - /// Curve capabilities - /// - CURVECAPS = 28, - /// - /// Line capabilities - /// - LINECAPS = 30, - /// - /// Polygonal capabilities - /// - POLYGONALCAPS = 32, - /// - /// Text capabilities - /// - TEXTCAPS = 34, - /// - /// Clipping capabilities - /// - CLIPCAPS = 36, - /// - /// Bitblt capabilities - /// - RASTERCAPS = 38, - /// - /// Length of the X leg - /// - ASPECTX = 40, - /// - /// Length of the Y leg - /// - ASPECTY = 42, - /// - /// Length of the hypotenuse - /// - ASPECTXY = 44, - /// - /// Shading and Blending caps - /// - SHADEBLENDCAPS = 45, + /// + /// Device driver version + /// + DRIVERVERSION = 0, + /// + /// Device classification + /// + TECHNOLOGY = 2, + /// + /// Horizontal size in millimeters + /// + HORZSIZE = 4, + /// + /// Vertical size in millimeters + /// + VERTSIZE = 6, + /// + /// Horizontal width in pixels + /// + HORZRES = 8, + /// + /// Vertical height in pixels + /// + VERTRES = 10, + /// + /// Number of bits per pixel + /// + BITSPIXEL = 12, + /// + /// Number of planes + /// + PLANES = 14, + /// + /// Number of brushes the device has + /// + NUMBRUSHES = 16, + /// + /// Number of pens the device has + /// + NUMPENS = 18, + /// + /// Number of markers the device has + /// + NUMMARKERS = 20, + /// + /// Number of fonts the device has + /// + NUMFONTS = 22, + /// + /// Number of colors the device supports + /// + NUMCOLORS = 24, + /// + /// Size required for device descriptor + /// + PDEVICESIZE = 26, + /// + /// Curve capabilities + /// + CURVECAPS = 28, + /// + /// Line capabilities + /// + LINECAPS = 30, + /// + /// Polygonal capabilities + /// + POLYGONALCAPS = 32, + /// + /// Text capabilities + /// + TEXTCAPS = 34, + /// + /// Clipping capabilities + /// + CLIPCAPS = 36, + /// + /// Bitblt capabilities + /// + RASTERCAPS = 38, + /// + /// Length of the X leg + /// + ASPECTX = 40, + /// + /// Length of the Y leg + /// + ASPECTY = 42, + /// + /// Length of the hypotenuse + /// + ASPECTXY = 44, + /// + /// Shading and Blending caps + /// + SHADEBLENDCAPS = 45, - /// - /// Logical pixels inch in X - /// - LOGPIXELSX = 88, - /// - /// Logical pixels inch in Y - /// - LOGPIXELSY = 90, + /// + /// Logical pixels inch in X + /// + LOGPIXELSX = 88, + /// + /// Logical pixels inch in Y + /// + LOGPIXELSY = 90, - /// - /// Number of entries in physical palette - /// - SIZEPALETTE = 104, - /// - /// Number of reserved entries in palette - /// - NUMRESERVED = 106, - /// - /// Actual color resolution - /// - COLORRES = 108, + /// + /// Number of entries in physical palette + /// + SIZEPALETTE = 104, + /// + /// Number of reserved entries in palette + /// + NUMRESERVED = 106, + /// + /// Actual color resolution + /// + COLORRES = 108, - // Printing related DeviceCaps. These replace the appropriate Escapes - /// - /// Physical Width in device units - /// - PHYSICALWIDTH = 110, - /// - /// Physical Height in device units - /// - PHYSICALHEIGHT = 111, - /// - /// Physical Printable Area x margin - /// - PHYSICALOFFSETX = 112, - /// - /// Physical Printable Area y margin - /// - PHYSICALOFFSETY = 113, - /// - /// Scaling factor x - /// - SCALINGFACTORX = 114, - /// - /// Scaling factor y - /// - SCALINGFACTORY = 115, + // Printing related DeviceCaps. These replace the appropriate Escapes + /// + /// Physical Width in device units + /// + PHYSICALWIDTH = 110, + /// + /// Physical Height in device units + /// + PHYSICALHEIGHT = 111, + /// + /// Physical Printable Area x margin + /// + PHYSICALOFFSETX = 112, + /// + /// Physical Printable Area y margin + /// + PHYSICALOFFSETY = 113, + /// + /// Scaling factor x + /// + SCALINGFACTORX = 114, + /// + /// Scaling factor y + /// + SCALINGFACTORY = 115, - /// - /// Current vertical refresh rate of the display device (for displays only) in Hz - /// - VREFRESH = 116, - /// - /// Horizontal width of entire desktop in pixels - /// - DESKTOPVERTRES = 117, - /// - /// Vertical height of entire desktop in pixels - /// - DESKTOPHORZRES = 118, - /// - /// Preferred blt alignment - /// - BLTALIGNMENT = 119 - } + /// + /// Current vertical refresh rate of the display device (for displays only) in Hz + /// + VREFRESH = 116, + /// + /// Horizontal width of entire desktop in pixels + /// + DESKTOPVERTRES = 117, + /// + /// Vertical height of entire desktop in pixels + /// + DESKTOPHORZRES = 118, + /// + /// Preferred blt alignment + /// + BLTALIGNMENT = 119 + } /// /// Used for User32.SetWinEventHook /// See: http://msdn.microsoft.com/en-us/library/windows/desktop/dd373640%28v=vs.85%29.aspx /// - [SuppressMessage("ReSharper", "InconsistentNaming")] + [SuppressMessage("ReSharper", "InconsistentNaming"), Flags] public enum WinEventHookFlags { WINEVENT_SKIPOWNTHREAD = 1,