FEATURE-731: Backport of the title detection code

This commit is contained in:
Robin 2016-09-03 23:32:24 +02:00
parent 4a0ec2448f
commit 1d0bdf23c1
17 changed files with 1080 additions and 447 deletions

View file

@ -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;
}
}
/// <summary>
/// Set the enable state of the expert settings

View file

@ -65,7 +65,7 @@ namespace GreenshotJiraPlugin.Forms {
{
if (!_jiraConnector.IsLoggedIn)
{
await _jiraConnector.Login();
await _jiraConnector.LoginAsync();
}
}
catch (Exception e)

View file

@ -78,12 +78,19 @@
<Compile Include="Forms\SettingsForm.Designer.cs">
<DependentUpon>SettingsForm.cs</DependentUpon>
</Compile>
<Compile Include="Hooking\TitleChangeEventArgs.cs" />
<Compile Include="Hooking\TitleChangeEventDelegate.cs" />
<Compile Include="Hooking\WindowsEventHook.cs" />
<Compile Include="Hooking\WindowsTitleMonitor.cs" />
<Compile Include="IssueTypeBitmapCache.cs" />
<Compile Include="JiraConnector.cs" />
<Compile Include="JiraConfiguration.cs" />
<Compile Include="JiraDestination.cs" />
<Compile Include="JiraDetails.cs" />
<Compile Include="JiraEventArgs.cs" />
<Compile Include="JiraEventTypes.cs" />
<Compile Include="JiraMonitor.cs" />
<Compile Include="JiraPlugin.cs" />
<Compile Include="JiraUtils.cs" />
<Compile Include="LanguageKeys.cs" />
<Compile Include="Log4NetLogger.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
using System;
namespace GreenshotJiraPlugin.Hooking
{
/// <summary>
/// Event arguments for the TitleChangeEvent
/// </summary>
public class TitleChangeEventArgs : EventArgs
{
/// <summary>
/// HWnd of the window which has a changed title
/// </summary>
public IntPtr HWnd
{
get;
set;
}
/// <summary>
/// Title which is changed
/// </summary>
public string Title
{
get;
set;
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
namespace GreenshotJiraPlugin.Hooking
{
/// <summary>
/// Delegate for the title change event
/// </summary>
/// <param name="eventArgs"></param>
public delegate void TitleChangeEventDelegate(TitleChangeEventArgs eventArgs);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using GreenshotPlugin.UnmanagedHelpers;
namespace GreenshotJiraPlugin.Hooking
{
/// <summary>
/// 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.
/// </summary>
public class WindowsEventHook : IDisposable
{
private readonly WinEventDelegate _winEventHandler;
private GCHandle _gcHandle;
/// <summary>
/// Used with Register hook
/// </summary>
/// <param name="eventType"></param>
/// <param name="hwnd"></param>
/// <param name="idObject"></param>
/// <param name="idChild"></param>
/// <param name="dwEventThread"></param>
/// <param name="dwmsEventTime"></param>
public delegate void WinEventHandler(WinEvent eventType, IntPtr hwnd, EventObjects idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
/// <summary>
/// Create a WindowsEventHook object
/// </summary>
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);
/// <summary>
/// Used with SetWinEventHook
/// </summary>
/// <param name="hWinEventHook"></param>
/// <param name="eventType"></param>
/// <param name="hwnd"></param>
/// <param name="idObject"></param>
/// <param name="idChild"></param>
/// <param name="dwEventThread"></param>
/// <param name="dwmsEventTime"></param>
private delegate void WinEventDelegate(IntPtr hWinEventHook, WinEvent eventType, IntPtr hwnd, EventObjects idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
#endregion
private readonly IDictionary<IntPtr, WinEventHandler> _winEventHandlers = new Dictionary<IntPtr, WinEventHandler>();
/// <summary>
/// Are hooks active?
/// </summary>
public bool IsHooked => _winEventHandlers.Count > 0;
/// <summary>
/// Hook a WinEvent
/// </summary>
/// <param name="winEvent"></param>
/// <param name="winEventHandler"></param>
/// <returns>true if success</returns>
public void Hook(WinEvent winEvent, WinEventHandler winEventHandler)
{
Hook(winEvent, winEvent, winEventHandler);
}
/// <summary>
/// Hook a WinEvent
/// </summary>
/// <param name="winEventStart"></param>
/// <param name="winEventEnd"></param>
/// <param name="winEventHandler"></param>
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);
}
/// <summary>
/// Remove all hooks
/// </summary>
private void Unhook()
{
foreach (var hookPtr in _winEventHandlers.Keys)
{
if (hookPtr != IntPtr.Zero)
{
UnhookWinEvent(hookPtr);
}
}
_winEventHandlers.Clear();
_gcHandle.Free();
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Unhook();
}
/// <summary>
/// Call the WinEventHandler for this event
/// </summary>
/// <param name="hWinEventHook"></param>
/// <param name="eventType"></param>
/// <param name="hWnd"></param>
/// <param name="idObject"></param>
/// <param name="idChild"></param>
/// <param name="dwEventThread"></param>
/// <param name="dwmsEventTime"></param>
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);
}
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
using System;
using GreenshotPlugin.Core;
using GreenshotPlugin.UnmanagedHelpers;
namespace GreenshotJiraPlugin.Hooking
{
/// <summary>
/// Monitor all title changes
/// </summary>
public sealed class WindowsTitleMonitor : IDisposable
{
private WindowsEventHook _hook;
private readonly object _lockObject = new object();
// ReSharper disable once InconsistentNaming
private event TitleChangeEventDelegate _titleChangeEvent;
/// <summary>
/// Add / remove event handler to the title monitor
/// </summary>
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;
}
}
}
}
}
/// <summary>
/// WinEventDelegate for the creation & destruction
/// </summary>
/// <param name="eventType"></param>
/// <param name="hWnd"></param>
/// <param name="idObject"></param>
/// <param name="idChild"></param>
/// <param name="dwEventThread"></param>
/// <param name="dwmsEventTime"></param>
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
/// <summary>
/// Dispose the underlying hook
/// </summary>
public void Dispose(bool disposing)
{
if (_disposedValue)
{
return;
}
lock (_lockObject)
{
_hook?.Dispose();
}
_disposedValue = true;
}
/// <summary>
/// Make sure the finalizer disposes the underlying hook
/// </summary>
~WindowsTitleMonitor()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(false);
}
/// <summary>
/// Dispose the underlying hook
/// </summary>
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}

View file

@ -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();
/// <summary>
/// Initialize some basic stuff, in the case the SVG to bitmap converter
/// </summary>
static JiraConnector()
{
if (HttpExtensionsGlobals.HttpContentConverters.All(x => x.GetType() != typeof(SvgBitmapHttpContentConverter)))
@ -72,46 +73,48 @@ namespace GreenshotJiraPlugin {
}
/// <summary>
/// Dispose, logout the users
/// </summary>
public void Dispose() {
if (_jiraApi != null)
{
Task.Run(async () => await Logout()).Wait();
Task.Run(async () => await LogoutAsync()).Wait();
}
}
/// <summary>
/// Constructor
/// </summary>
public JiraConnector()
{
_url = JiraConfig.Url.Replace(DefaultPostfix, "");
JiraConfig.Url = JiraConfig.Url.Replace(DefaultPostfix, "");
_timeout = JiraConfig.Timeout;
}
/// <summary>
/// Access the jira monitor
/// </summary>
public JiraMonitor Monitor { get; private set; }
/// <summary>
/// Internal login which catches the exceptions
/// </summary>
/// <returns>true if login was done sucessfully</returns>
private async Task<bool> DoLogin(string user, string password)
private async Task<bool> 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();
/// <summary>
/// Use the credentials dialog, this will show if there are not correct credentials.
/// If there are credentials, call the real login.
/// </summary>
/// <returns>Task</returns>
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 {
/// <summary>
/// End the session, if there was one
/// </summary>
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.
/// </summary>
/// <returns></returns>
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 {
/// <returns>List with filters</returns>
public async Task<IList<Filter>> GetFavoriteFiltersAsync()
{
await CheckCredentials();
await CheckCredentialsAsync();
return await _jiraApi.GetFavoriteFiltersAsync().ConfigureAwait(false);
}
@ -200,7 +208,7 @@ namespace GreenshotJiraPlugin {
/// <returns>Issue</returns>
public async Task<Issue> GetIssueAsync(string issueKey)
{
await CheckCredentials();
await CheckCredentialsAsync();
try
{
return await _jiraApi.GetIssueAsync(issueKey).ConfigureAwait(false);
@ -220,7 +228,7 @@ namespace GreenshotJiraPlugin {
/// <returns></returns>
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 {
/// <param name="cancellationToken">CancellationToken</param>
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 {
/// <returns></returns>
public async Task<IList<Issue>> 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);
}
/// <summary>
/// Get the base uri
/// </summary>
public Uri JiraBaseUri => _jiraApi.JiraBaseUri;
/// <summary>
/// Is the user "logged in?
/// </summary>
public bool IsLoggedIn => _loggedIn;
}
}

View file

@ -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<IDestination> DynamicDestinations() {
if (JiraPlugin.Instance.CurrentJiraConnector == null || !JiraPlugin.Instance.CurrentJiraConnector.IsLoggedIn) {
public override IEnumerable<IDestination> 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);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
using System;
using Dapplo.Jira.Entities;
namespace GreenshotJiraPlugin
{
public class JiraDetails : IComparable<JiraDetails>
{
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);
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
using System;
namespace GreenshotJiraPlugin
{
public class JiraEventArgs : EventArgs
{
public JiraEventTypes EventType
{
get;
set;
}
public JiraDetails Details
{
get;
set;
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
namespace GreenshotJiraPlugin
{
public enum JiraEventTypes
{
OrderChanged,
DetectedNewJiraIssue
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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
{
/// <summary>
/// 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!
/// </summary>
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<JiraApi> _jiraInstances = new List<JiraApi>();
private readonly IDictionary<string, JiraApi> _projectJiraApiMap = new Dictionary<string, JiraApi>();
private readonly int _maxEntries;
private IDictionary<string, JiraDetails> _recentJiras = new Dictionary<string, JiraDetails>();
/// <summary>
/// Register to this event to get events when new jira issues are detected
/// </summary>
public event EventHandler<JiraEventArgs> JiraEvent;
public JiraMonitor(int maxEntries = 40)
{
_maxEntries = maxEntries;
_monitor = new WindowsTitleMonitor();
_monitor.TitleChangeEvent += MonitorTitleChangeEvent;
}
#region Dispose
/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Dispose all managed resources
/// </summary>
/// <param name="disposing">when true is passed all managed resources are disposed.</param>
protected void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
// free managed resources
_monitor.TitleChangeEvent -= MonitorTitleChangeEvent;
_monitor.Dispose();
// free native resources if there are any.
}
#endregion
/// <summary>
/// Retrieve the API belonging to a JiraDetails
/// </summary>
/// <param name="jiraDetails"></param>
/// <returns>JiraAPI</returns>
public JiraApi GetJiraApiForKey(JiraDetails jiraDetails)
{
return _projectJiraApiMap[jiraDetails.ProjectKey];
}
/// <summary>
/// Get the "list" of recently seen Jiras
/// </summary>
public IEnumerable<JiraDetails> RecentJiras =>
(from jiraDetails in _recentJiras.Values
orderby jiraDetails.SeenAt descending
select jiraDetails);
/// <summary>
/// Check if this monitor has active instances
/// </summary>
public bool HasJiraInstances => _jiraInstances.Count > 0;
/// <summary>
/// Add an instance of a JIRA system
/// </summary>
/// <param name="jiraInstance"></param>
/// <param name="token"></param>
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);
}
}
}
}
/// <summary>
/// This method will update details, like the title, and send an event to registed listeners of the JiraEvent
/// </summary>
/// <param name="jiraDetails">Contains the jira key to retrieve the title (XYZ-1234)</param>
/// <returns>Task</returns>
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);
}
}
/// <summary>
/// Handle title changes, check for JIRA
/// </summary>
/// <param name="eventArgs"></param>
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<JiraDetails> clonedList = new List<JiraDetails>(_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);
}
}
}
}

View file

@ -33,9 +33,9 @@ namespace GreenshotJiraPlugin {
/// </summary>
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; }
}
/// <summary>
/// Implementation of the IGreenshotPlugin.Initialize
@ -80,14 +94,15 @@ namespace GreenshotJiraPlugin {
// Register configuration (don't need the configuration itself)
_config = IniConfig.GetIniSection<JiraConfiguration>();
LogSettings.RegisterDefaultLogger<Log4NetLogger>();
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();
});
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 {
/// <summary>
/// Description of JiraUtils.
/// </summary>
public static class JiraUtils {
private static readonly Regex JiraKeyRegex = new Regex(@"/browse/([A-Z0-9]+\-[0-9]+)");
private static readonly JiraConfiguration Config = IniConfig.GetIniSection<JiraConfiguration>();
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(JiraUtils));
public static async Task<IList<Issue>> GetCurrentJirasAsync() {
// Make sure we suppress the login
var jirakeys = new List<string>();
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<Issue>();
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;
}
}
}

View file

@ -59,10 +59,10 @@ namespace GreenshotPlugin.Core {
/// <summary>Encapsulates dialog functionality from the Credential Management API.</summary>
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);
/// <summary>The only valid bitmap height (in pixels) of a user-defined banner.</summary>
/// <summary>The only valid bitmap height (in pixels) of a user-defined banner.</summary>
private const int ValidBannerHeight = 60;
/// <summary>The only valid bitmap width (in pixels) of a user-defined banner.</summary>
@ -102,65 +102,25 @@ namespace GreenshotPlugin.Core {
Banner = banner;
}
private bool _alwaysDisplay;
/// <summary>
/// Gets or sets if the dialog will be shown even if the credentials
/// can be returned from an existing credential in the credential manager.
/// </summary>
public bool AlwaysDisplay {
get {
return _alwaysDisplay;
}
set {
_alwaysDisplay = value;
}
}
public bool AlwaysDisplay { get; set; }
private bool _excludeCertificates = true;
/// <summary>Gets or sets if the dialog is populated with name/password only.</summary>
public bool ExcludeCertificates {
get {
return _excludeCertificates;
}
set {
_excludeCertificates = value;
}
}
public bool ExcludeCertificates { get; set; } = true;
private bool _persist = true;
/// <summary>Gets or sets if the credentials are to be persisted in the credential manager.</summary>
public bool Persist {
get {
return _persist;
}
set {
_persist = value;
}
}
public bool Persist { get; set; } = true;
private bool _incorrectPassword;
/// <summary>Gets or sets if the incorrect password balloontip needs to be shown. Introduced AFTER Windows XP</summary>Gets></summary>
public bool IncorrectPassword {
get {
return _incorrectPassword;
}
set {
_incorrectPassword = value;
}
}
public bool IncorrectPassword { get; set; }
private bool _keepName;
/// <summary>Gets or sets if the name is read-only.</summary>
public bool KeepName {
get {
return _keepName;
}
set {
_keepName = value;
}
}
public bool KeepName { get; set; }
private string _name = String.Empty;
private string _name = string.Empty;
/// <summary>Gets or sets the name for the credentials.</summary>
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;
/// <summary>Gets or sets the password for the credentials.</summary>
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;
/// <summary>Gets or sets if the save checkbox status.</summary>
public bool SaveChecked {
get {
return _saveChecked;
}
set {
_saveChecked = value;
}
}
public bool SaveChecked { get; set; }
private bool _saveDisplayed = true;
/// <summary>Gets or sets if the save checkbox is displayed.</summary>
/// <remarks>This value only has effect if Persist is true.</remarks>
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;
/// <summary>Gets or sets the name of the target for the credentials, typically a server name.</summary>
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;
/// <summary>Gets or sets the caption of the dialog.</summary>
/// <remarks>A null value will cause a system default caption to be used.</remarks>
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;
/// <summary>Gets or sets the message of the dialog.</summary>
/// <remarks>A null value will cause a system default message to be used.</remarks>
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;

View file

@ -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
}
/// <summary>
/// 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
}
/// <summary>
/// See: http://msdn.microsoft.com/en-us/library/aa909766.aspx
/// </summary>
[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
}
/// <summary>
/// See: http://msdn.microsoft.com/en-us/library/aa909766.aspx
/// </summary>
[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
}
/// <summary>
/// Used by GDI32.GetDeviceCaps
/// See: http://msdn.microsoft.com/en-us/library/windows/desktop/dd144877%28v=vs.85%29.aspx
/// </summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
public enum DeviceCaps {
/// <summary>
/// Device driver version
/// </summary>
DRIVERVERSION = 0,
/// <summary>
/// Device classification
/// </summary>
TECHNOLOGY = 2,
/// <summary>
/// Horizontal size in millimeters
/// </summary>
HORZSIZE = 4,
/// <summary>
/// Vertical size in millimeters
/// </summary>
VERTSIZE = 6,
/// <summary>
/// Horizontal width in pixels
/// </summary>
HORZRES = 8,
/// <summary>
/// Vertical height in pixels
/// </summary>
VERTRES = 10,
/// <summary>
/// Number of bits per pixel
/// </summary>
BITSPIXEL = 12,
/// <summary>
/// Number of planes
/// </summary>
PLANES = 14,
/// <summary>
/// Number of brushes the device has
/// </summary>
NUMBRUSHES = 16,
/// <summary>
/// Number of pens the device has
/// </summary>
NUMPENS = 18,
/// <summary>
/// Number of markers the device has
/// </summary>
NUMMARKERS = 20,
/// <summary>
/// Number of fonts the device has
/// </summary>
NUMFONTS = 22,
/// <summary>
/// Number of colors the device supports
/// </summary>
NUMCOLORS = 24,
/// <summary>
/// Size required for device descriptor
/// </summary>
PDEVICESIZE = 26,
/// <summary>
/// Curve capabilities
/// </summary>
CURVECAPS = 28,
/// <summary>
/// Line capabilities
/// </summary>
LINECAPS = 30,
/// <summary>
/// Polygonal capabilities
/// </summary>
POLYGONALCAPS = 32,
/// <summary>
/// Text capabilities
/// </summary>
TEXTCAPS = 34,
/// <summary>
/// Clipping capabilities
/// </summary>
CLIPCAPS = 36,
/// <summary>
/// Bitblt capabilities
/// </summary>
RASTERCAPS = 38,
/// <summary>
/// Length of the X leg
/// </summary>
ASPECTX = 40,
/// <summary>
/// Length of the Y leg
/// </summary>
ASPECTY = 42,
/// <summary>
/// Length of the hypotenuse
/// </summary>
ASPECTXY = 44,
/// <summary>
/// Shading and Blending caps
/// </summary>
SHADEBLENDCAPS = 45,
/// <summary>
/// Device driver version
/// </summary>
DRIVERVERSION = 0,
/// <summary>
/// Device classification
/// </summary>
TECHNOLOGY = 2,
/// <summary>
/// Horizontal size in millimeters
/// </summary>
HORZSIZE = 4,
/// <summary>
/// Vertical size in millimeters
/// </summary>
VERTSIZE = 6,
/// <summary>
/// Horizontal width in pixels
/// </summary>
HORZRES = 8,
/// <summary>
/// Vertical height in pixels
/// </summary>
VERTRES = 10,
/// <summary>
/// Number of bits per pixel
/// </summary>
BITSPIXEL = 12,
/// <summary>
/// Number of planes
/// </summary>
PLANES = 14,
/// <summary>
/// Number of brushes the device has
/// </summary>
NUMBRUSHES = 16,
/// <summary>
/// Number of pens the device has
/// </summary>
NUMPENS = 18,
/// <summary>
/// Number of markers the device has
/// </summary>
NUMMARKERS = 20,
/// <summary>
/// Number of fonts the device has
/// </summary>
NUMFONTS = 22,
/// <summary>
/// Number of colors the device supports
/// </summary>
NUMCOLORS = 24,
/// <summary>
/// Size required for device descriptor
/// </summary>
PDEVICESIZE = 26,
/// <summary>
/// Curve capabilities
/// </summary>
CURVECAPS = 28,
/// <summary>
/// Line capabilities
/// </summary>
LINECAPS = 30,
/// <summary>
/// Polygonal capabilities
/// </summary>
POLYGONALCAPS = 32,
/// <summary>
/// Text capabilities
/// </summary>
TEXTCAPS = 34,
/// <summary>
/// Clipping capabilities
/// </summary>
CLIPCAPS = 36,
/// <summary>
/// Bitblt capabilities
/// </summary>
RASTERCAPS = 38,
/// <summary>
/// Length of the X leg
/// </summary>
ASPECTX = 40,
/// <summary>
/// Length of the Y leg
/// </summary>
ASPECTY = 42,
/// <summary>
/// Length of the hypotenuse
/// </summary>
ASPECTXY = 44,
/// <summary>
/// Shading and Blending caps
/// </summary>
SHADEBLENDCAPS = 45,
/// <summary>
/// Logical pixels inch in X
/// </summary>
LOGPIXELSX = 88,
/// <summary>
/// Logical pixels inch in Y
/// </summary>
LOGPIXELSY = 90,
/// <summary>
/// Logical pixels inch in X
/// </summary>
LOGPIXELSX = 88,
/// <summary>
/// Logical pixels inch in Y
/// </summary>
LOGPIXELSY = 90,
/// <summary>
/// Number of entries in physical palette
/// </summary>
SIZEPALETTE = 104,
/// <summary>
/// Number of reserved entries in palette
/// </summary>
NUMRESERVED = 106,
/// <summary>
/// Actual color resolution
/// </summary>
COLORRES = 108,
/// <summary>
/// Number of entries in physical palette
/// </summary>
SIZEPALETTE = 104,
/// <summary>
/// Number of reserved entries in palette
/// </summary>
NUMRESERVED = 106,
/// <summary>
/// Actual color resolution
/// </summary>
COLORRES = 108,
// Printing related DeviceCaps. These replace the appropriate Escapes
/// <summary>
/// Physical Width in device units
/// </summary>
PHYSICALWIDTH = 110,
/// <summary>
/// Physical Height in device units
/// </summary>
PHYSICALHEIGHT = 111,
/// <summary>
/// Physical Printable Area x margin
/// </summary>
PHYSICALOFFSETX = 112,
/// <summary>
/// Physical Printable Area y margin
/// </summary>
PHYSICALOFFSETY = 113,
/// <summary>
/// Scaling factor x
/// </summary>
SCALINGFACTORX = 114,
/// <summary>
/// Scaling factor y
/// </summary>
SCALINGFACTORY = 115,
// Printing related DeviceCaps. These replace the appropriate Escapes
/// <summary>
/// Physical Width in device units
/// </summary>
PHYSICALWIDTH = 110,
/// <summary>
/// Physical Height in device units
/// </summary>
PHYSICALHEIGHT = 111,
/// <summary>
/// Physical Printable Area x margin
/// </summary>
PHYSICALOFFSETX = 112,
/// <summary>
/// Physical Printable Area y margin
/// </summary>
PHYSICALOFFSETY = 113,
/// <summary>
/// Scaling factor x
/// </summary>
SCALINGFACTORX = 114,
/// <summary>
/// Scaling factor y
/// </summary>
SCALINGFACTORY = 115,
/// <summary>
/// Current vertical refresh rate of the display device (for displays only) in Hz
/// </summary>
VREFRESH = 116,
/// <summary>
/// Horizontal width of entire desktop in pixels
/// </summary>
DESKTOPVERTRES = 117,
/// <summary>
/// Vertical height of entire desktop in pixels
/// </summary>
DESKTOPHORZRES = 118,
/// <summary>
/// Preferred blt alignment
/// </summary>
BLTALIGNMENT = 119
}
/// <summary>
/// Current vertical refresh rate of the display device (for displays only) in Hz
/// </summary>
VREFRESH = 116,
/// <summary>
/// Horizontal width of entire desktop in pixels
/// </summary>
DESKTOPVERTRES = 117,
/// <summary>
/// Vertical height of entire desktop in pixels
/// </summary>
DESKTOPHORZRES = 118,
/// <summary>
/// Preferred blt alignment
/// </summary>
BLTALIGNMENT = 119
}
/// <summary>
/// Used for User32.SetWinEventHook
/// See: http://msdn.microsoft.com/en-us/library/windows/desktop/dd373640%28v=vs.85%29.aspx
/// </summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SuppressMessage("ReSharper", "InconsistentNaming"), Flags]
public enum WinEventHookFlags
{
WINEVENT_SKIPOWNTHREAD = 1,