commit dc8b817460f9e07cb37f19c7dbf21c3849795f34 Author: Min Yong Kim Date: Tue Oct 28 21:42:20 2014 -0400 1. Initial code check into CodePlex diff --git a/Ninjacrab.PersistentWindows.Solution/.nuget/packages.config b/Ninjacrab.PersistentWindows.Solution/.nuget/packages.config new file mode 100644 index 0000000..7c2cbd0 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/.nuget/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.Solution.sln b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.Solution.sln new file mode 100644 index 0000000..c778f75 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.Solution.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30723.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ninjacrab.PersistentWindows.WpfShell", "Ninjacrab.PersistentWindows.WpfShell\Ninjacrab.PersistentWindows.WpfShell.csproj", "{4EA12EC8-B50F-4DEA-AA7D-85A5F4C81F83}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{FA5C1FB9-45F3-434F-9176-71A2D8EDA5B2}" + ProjectSection(SolutionItems) = preProject + .nuget\packages.config = .nuget\packages.config + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4EA12EC8-B50F-4DEA-AA7D-85A5F4C81F83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EA12EC8-B50F-4DEA-AA7D-85A5F4C81F83}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EA12EC8-B50F-4DEA-AA7D-85A5F4C81F83}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EA12EC8-B50F-4DEA-AA7D-85A5F4C81F83}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/App.config b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/App.xaml b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/App.xaml new file mode 100644 index 0000000..8ccab7c --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/App.xaml @@ -0,0 +1,7 @@ + + + + + diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/App.xaml.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/App.xaml.cs new file mode 100644 index 0000000..c0f4bc6 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/App.xaml.cs @@ -0,0 +1,18 @@ +using System.Windows; + +namespace Ninjacrab.PersistentWindows.WpfShell +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + protected override void OnStartup(StartupEventArgs e) + { + base.OnStartup(e); + + new MainWindow().Show(); + new PersistentWindowProcessor().Start(); + } + } +} diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Diagnostics/Log.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Diagnostics/Log.cs new file mode 100644 index 0000000..2122757 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Diagnostics/Log.cs @@ -0,0 +1,96 @@ +using System; +using NLog; +using NLog.Config; +using NLog.Targets; + +namespace Ninjacrab.PersistentWindows.WpfShell.Diagnostics +{ + public class Log + { + static Log() + { + var config = new LoggingConfiguration(); + + // Step 2. Create targets and add them to the configuration + var consoleTarget = new ColoredConsoleTarget(); + config.AddTarget("console", consoleTarget); + + var fileTarget = new FileTarget(); + config.AddTarget("file", fileTarget); + + // Step 3. Set target properties + consoleTarget.Layout = @"${date:format=HH\\:MM\\:ss} ${logger} ${message}"; + fileTarget.FileName = "${basedir}/PersistentWindows.Log"; + fileTarget.Layout = "${message}"; + + // Step 4. Define rules + var rule1 = new LoggingRule("*", LogLevel.Trace, consoleTarget); + config.LoggingRules.Add(rule1); + + var rule2 = new LoggingRule("*", LogLevel.Debug, fileTarget); + config.LoggingRules.Add(rule2); + + // Step 5. Activate the configuration + LogManager.Configuration = config; + } + + /// + /// Occurs when something is logged. STATIC EVENT! + /// + public static event Action LogEvent; + + private static Logger _logger; + private static Logger Logger + { + get + { + if(_logger == null) + { + _logger = LogManager.GetLogger("Logger"); + } + return _logger; + } + } + + private static void RaiseLogEvent(LogLevel level, string message) + { + // could, should, would write a new logging target but this is brute force faster + if(LogEvent != null) + { + LogEvent(level, message); + } + } + + public static void Trace(string format, params object[] args) + { + var message = Format(format, args); + Logger.Trace(message); + RaiseLogEvent(LogLevel.Trace, message); + } + + public static void Info(string format, params object[] args) + { + var message = Format(format, args); + Logger.Info(Format(format, args)); + RaiseLogEvent(LogLevel.Info, message); + } + + public static void Error(string format, params object[] args) + { + var message = Format(format, args); + Logger.Error(Format(format, args)); + RaiseLogEvent(LogLevel.Error, message); + } + + /// + /// Since string.Format doesn't like args being null or having no entries. + /// + /// The format. + /// The args. + /// + private static string Format(string format, params object[] args) + { + return args == null || args.Length == 0 ? format : string.Format(format, args); + } + } +} diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/DiagnosticsView.xaml b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/DiagnosticsView.xaml new file mode 100644 index 0000000..5f301f0 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/DiagnosticsView.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/DiagnosticsView.xaml.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/DiagnosticsView.xaml.cs new file mode 100644 index 0000000..a0891b1 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/DiagnosticsView.xaml.cs @@ -0,0 +1,32 @@ +using System.Windows; +using System.Windows.Controls; +using Ninjacrab.PersistentWindows.WpfShell.Diagnostics; + +namespace Ninjacrab.PersistentWindows.WpfShell +{ + /// + /// Interaction logic for DiagnosticsView.xaml + /// + public partial class DiagnosticsView : UserControl + { + private DiagnosticsViewModel viewModel; + + public DiagnosticsView() + { + InitializeComponent(); + viewModel = new DiagnosticsViewModel(); + this.DataContext = viewModel; + Log.LogEvent += (level, message) => + { + Application.Current.Dispatcher.Invoke(() => + { + viewModel.EventLog.Add(string.Format("{0}: {1}", level, message)); + if (viewModel.EventLog.Count > 500) + { + viewModel.EventLog.RemoveAt(0); + } + }); + }; + } + } +} diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/DiagnosticsViewModel.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/DiagnosticsViewModel.cs new file mode 100644 index 0000000..0058e7d --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/DiagnosticsViewModel.cs @@ -0,0 +1,22 @@ +using System.ComponentModel; +using Microsoft.Practices.Prism.Mvvm; + +namespace Ninjacrab.PersistentWindows.WpfShell +{ + public class DiagnosticsViewModel : BindableBase + { + public DiagnosticsViewModel() + { + EventLog = new BindingList(); + } + + public const string AllProcessesPropertyName = "AllProcesses"; + private BindingList allProcesses; + public BindingList EventLog + { + get { return allProcesses; } + set { SetProperty(ref allProcesses, value); } + } + + } +} diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/MainWindow.xaml b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/MainWindow.xaml new file mode 100644 index 0000000..924fe81 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/MainWindow.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/MainWindow.xaml.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/MainWindow.xaml.cs new file mode 100644 index 0000000..a879530 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/MainWindow.xaml.cs @@ -0,0 +1,17 @@ +using System.Windows; + +namespace Ninjacrab.PersistentWindows.WpfShell +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public PersistentWindowProcessor Processor { get; set; } + + public MainWindow() + { + InitializeComponent(); + } + } +} diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Models/ApplicationDisplayMetrics.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Models/ApplicationDisplayMetrics.cs new file mode 100644 index 0000000..f2fadc9 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Models/ApplicationDisplayMetrics.cs @@ -0,0 +1,18 @@ +using System; +using Ninjacrab.PersistentWindows.WpfShell.WinApiBridge; + +namespace Ninjacrab.PersistentWindows.WpfShell.Models +{ + public class ApplicationDisplayMetrics + { + public IntPtr HWnd { get; set; } + public int ProcessId { get; set; } + public string ApplicationName { get; set; } + public WindowPlacement WindowPlacement { get; set; } + + public string Key + { + get { return string.Format("{0}-{1}", HWnd.ToInt64(), ApplicationName); } + } + } +} diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Models/DesktopDisplayMetrics.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Models/DesktopDisplayMetrics.cs new file mode 100644 index 0000000..55e124c --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Models/DesktopDisplayMetrics.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.Linq; +using Ninjacrab.PersistentWindows.WpfShell.WinApiBridge; + +namespace Ninjacrab.PersistentWindows.WpfShell.Models +{ + public class DesktopDisplayMetrics + { + public static DesktopDisplayMetrics AcquireMetrics() + { + DesktopDisplayMetrics metrics = new DesktopDisplayMetrics(); + + var displays = Display.GetDisplays(); + int displayId = 0; + foreach (var display in displays) + { + metrics.SetMonitor(displayId++, display); + } + return metrics; + } + + private Dictionary monitorResolutions = new Dictionary(); + + public int NumberOfDisplays { get { return monitorResolutions.Count; } } + + public void SetMonitor(int id, Display display) + { + if (!monitorResolutions.ContainsKey(id) || + monitorResolutions[id].ScreenWidth != display.ScreenWidth || + monitorResolutions[id].ScreenHeight != display.ScreenHeight) + { + monitorResolutions.Add(id, display); + BuildKey(); + } + } + + private void BuildKey() + { + List keySegments = new List(); + foreach(var entry in monitorResolutions.OrderBy(row => row.Key)) + { + keySegments.Add(string.Format("[{0}-{1}x{2}-{3}x{4}]", entry.Key, entry.Value.Left, entry.Value.Top, entry.Value.ScreenWidth, entry.Value.ScreenHeight)); + } + key = string.Join(",", keySegments); + } + + private string key; + public string Key + { + get + { + return key; + } + } + + public override bool Equals(object obj) + { + var other = obj as DesktopDisplayMetrics; + if (other == null) + { + return false; + } + return this.Key == other.key; + } + + public override int GetHashCode() + { + return key.GetHashCode(); + } + + public int GetHashCode(DesktopDisplayMetrics obj) + { + return obj.key.GetHashCode(); + } + } +} diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Ninjacrab.PersistentWindows.WpfShell.csproj b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Ninjacrab.PersistentWindows.WpfShell.csproj new file mode 100644 index 0000000..264e514 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Ninjacrab.PersistentWindows.WpfShell.csproj @@ -0,0 +1,149 @@ + + + + + Debug + AnyCPU + {4EA12EC8-B50F-4DEA-AA7D-85A5F4C81F83} + WinExe + Properties + Ninjacrab.PersistentWindows.WpfShell + PersistentWindows + v4.5 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\ManagedWinapi.0.3\ManagedWinapi.dll + + + ..\packages\Prism.Composition.5.0.0\lib\NET45\Microsoft.Practices.Prism.Composition.dll + + + ..\packages\Prism.Interactivity.5.0.0\lib\NET45\Microsoft.Practices.Prism.Interactivity.dll + + + ..\packages\Prism.Mvvm.1.0.0\lib\net45\Microsoft.Practices.Prism.Mvvm.dll + + + ..\packages\Prism.Mvvm.1.0.0\lib\net45\Microsoft.Practices.Prism.Mvvm.Desktop.dll + + + ..\packages\Prism.PubSubEvents.1.0.0\lib\portable-sl4+wp7+windows8+net40\Microsoft.Practices.Prism.PubSubEvents.dll + + + ..\packages\Prism.Mvvm.1.0.0\lib\net45\Microsoft.Practices.Prism.SharedInterfaces.dll + + + ..\packages\CommonServiceLocator.1.2\lib\portable-windows8+net40+sl5+windowsphone8\Microsoft.Practices.ServiceLocation.dll + + + ..\packages\NLog.3.1.0.0\lib\net45\NLog.dll + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + + + + + + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + DiagnosticsView.xaml + + + + MainWindow.xaml + Code + + + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + \ No newline at end of file diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/PersistentWindowProcessor.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/PersistentWindowProcessor.cs new file mode 100644 index 0000000..263aff8 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/PersistentWindowProcessor.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using ManagedWinapi.Windows; +using Microsoft.Win32; +using Ninjacrab.PersistentWindows.WpfShell.Diagnostics; +using Ninjacrab.PersistentWindows.WpfShell.Models; +using Ninjacrab.PersistentWindows.WpfShell.WinApiBridge; +using NLog; + +namespace Ninjacrab.PersistentWindows.WpfShell +{ + public class PersistentWindowProcessor + { + private DesktopDisplayMetrics lastMetrics = null; + + public void Start() + { + var thread = new Thread(InternalRun); + thread.IsBackground = true; + thread.Name = "PersistentWindowProcessor.InternalRun()"; + thread.Start(); + + SystemEvents.DisplaySettingsChanged += (s, e) => BeginRestoreApplicationsOnCurrentDisplays(); + SystemEvents.PowerModeChanged += (s, e) => + { + switch (e.Mode) + { + case PowerModes.Suspend: + BeginCaptureApplicationsOnCurrentDisplays(); + break; + + case PowerModes.Resume: + BeginRestoreApplicationsOnCurrentDisplays(); + break; + } + }; + CaptureApplicationsOnCurrentDisplays(); + lastMetrics = DesktopDisplayMetrics.AcquireMetrics(); + } + + private readonly Dictionary> monitorApplications = new Dictionary>(); + private readonly object displayChangeLock = new object(); + + private void InternalRun() + { + while(true) + { + CaptureApplicationsOnCurrentDisplays(); + Thread.Sleep(1000); + } + } + + private void BeginCaptureApplicationsOnCurrentDisplays() + { + var thread = new Thread(() => CaptureApplicationsOnCurrentDisplays()); + thread.IsBackground = true; + thread.Name = "PersistentWindowProcessor.BeginCaptureApplicationsOnCurrentDisplays()"; + thread.Start(); + } + + private void CaptureApplicationsOnCurrentDisplays(string displayKey = null) + { + lock(displayChangeLock) + { + DesktopDisplayMetrics metrics = DesktopDisplayMetrics.AcquireMetrics(); + if (displayKey == null) + { + displayKey = metrics.Key; + } + + if (!metrics.Equals(lastMetrics)) + { + // since the resolution doesn't match, lets wait till it's restored + return; + } + + Log.Info("Capturing applications for {0}", displayKey); + if (!monitorApplications.ContainsKey(displayKey)) + { + monitorApplications.Add(displayKey, new Dictionary()); + } + + var windows = SystemWindow.AllToplevelWindows.Where(row => row.VisibilityFlag == true); + foreach (var window in windows) + { + WindowPlacement windowPlacement = new WindowPlacement(); + User32.GetWindowPlacement(window.HWnd, ref windowPlacement); + + var applicationDisplayMetric = new ApplicationDisplayMetrics + { + HWnd = window.HWnd, + ApplicationName = window.Process.ProcessName, + ProcessId = window.Process.Id, + WindowPlacement = windowPlacement + }; + + if (!monitorApplications[displayKey].ContainsKey(applicationDisplayMetric.Key)) + { + monitorApplications[displayKey].Add(applicationDisplayMetric.Key, applicationDisplayMetric); + } + else + { + monitorApplications[displayKey][applicationDisplayMetric.Key].WindowPlacement = applicationDisplayMetric.WindowPlacement; + } + } + } + } + + private void BeginRestoreApplicationsOnCurrentDisplays() + { + var thread = new Thread(() => RestoreApplicationsOnCurrentDisplays()); + thread.IsBackground = true; + thread.Name = "PersistentWindowProcessor.RestoreApplicationsOnCurrentDisplays()"; + thread.Start(); + } + + private void RestoreApplicationsOnCurrentDisplays(string displayKey = null) + { + lock (displayChangeLock) + { + DesktopDisplayMetrics metrics = DesktopDisplayMetrics.AcquireMetrics(); + if (displayKey == null) + { + displayKey = metrics.Key; + } + + lastMetrics = DesktopDisplayMetrics.AcquireMetrics(); + if (!monitorApplications.ContainsKey(displayKey)) + { + // no old profile, we're done + Log.Info("No old profile found for {0}", displayKey); + return; + } + + Log.Info("Restoring applications for {0}", displayKey); + foreach (var window in SystemWindow.AllToplevelWindows.Where(row => row.VisibilityFlag == true)) + { + string applicationKey = string.Format("{0}-{1}", window.HWnd.ToInt64(), window.Process.ProcessName); + if (monitorApplications[displayKey].ContainsKey(applicationKey)) + { + // looks like the window is still here for us to restore + WindowPlacement windowPlacement = monitorApplications[displayKey][applicationKey].WindowPlacement; + + if (windowPlacement.ShowCmd == ShowWindowCommands.Maximize) + { + // When restoring maximized windows, it occasionally switches res and when the maximized setting is restored + // the window thinks it's maxxed, but does not eat all the real estate. So we'll temporarily unmaximize then + // re-apply that + windowPlacement.ShowCmd = ShowWindowCommands.Normal; + User32.SetWindowPlacement(monitorApplications[displayKey][applicationKey].HWnd, ref windowPlacement); + windowPlacement.ShowCmd = ShowWindowCommands.Maximize; + } + var success = User32.SetWindowPlacement(monitorApplications[displayKey][applicationKey].HWnd, ref windowPlacement); + if(!success) + { + string error = new Win32Exception(Marshal.GetLastWin32Error()).Message; + Log.Error(error); + } + Log.Info("SetWindowPlacement({0} [{1}x{2}]-[{3}x{4}]) - {5}", + window.Process.ProcessName, + windowPlacement.NormalPosition.Left, + windowPlacement.NormalPosition.Top, + windowPlacement.NormalPosition.Width, + windowPlacement.NormalPosition.Height, + success); + } + } + } + } + } +} diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Properties/AssemblyInfo.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..def1fc9 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Properties/AssemblyInfo.cs @@ -0,0 +1,53 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Ninjacrab.PersistentWindows.WpfShell")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Ninjacrab.PersistentWindows.WpfShell")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.2.0")] +[assembly: AssemblyFileVersion("1.0.2.0")] diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Properties/Resources.Designer.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Properties/Resources.Designer.cs new file mode 100644 index 0000000..80b01a8 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34014 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Ninjacrab.PersistentWindows.WpfShell.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Ninjacrab.PersistentWindows.WpfShell.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Properties/Resources.resx b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Properties/Settings.Designer.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Properties/Settings.Designer.cs new file mode 100644 index 0000000..f231183 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34014 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Ninjacrab.PersistentWindows.WpfShell.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Properties/Settings.settings b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Properties/Settings.settings new file mode 100644 index 0000000..033d7a5 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/WinApiBridge/Display.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/WinApiBridge/Display.cs new file mode 100644 index 0000000..3cfd78d --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/WinApiBridge/Display.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using ManagedWinapi.Windows; + +namespace Ninjacrab.PersistentWindows.WpfShell.WinApiBridge +{ + public class Display + { + public int ScreenWidth { get; internal set; } + public int ScreenHeight { get; internal set; } + public int Left { get; internal set; } + public int Top { get; internal set; } + public uint Flags { get; internal set; } + + public static List GetDisplays() + { + List displays = new List(); + + User32.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, + delegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData) + { + MonitorInfo monitorInfo = new MonitorInfo(); + monitorInfo.StructureSize = Marshal.SizeOf(monitorInfo); + bool success = User32.GetMonitorInfo(hMonitor, ref monitorInfo); + if (success) + { + Display display = new Display(); + display.ScreenWidth = monitorInfo.Monitor.Width; + display.ScreenHeight = monitorInfo.Monitor.Height; + display.Left = monitorInfo.Monitor.Left; + display.Top = monitorInfo.Monitor.Top; + display.Flags = monitorInfo.Flags; + displays.Add(display); + } + return true; + }, IntPtr.Zero); + return displays; + } + } +} diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/WinApiBridge/MonitorInfo.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/WinApiBridge/MonitorInfo.cs new file mode 100644 index 0000000..bc8eb5d --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/WinApiBridge/MonitorInfo.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; +using ManagedWinapi.Windows; + +namespace Ninjacrab.PersistentWindows.WpfShell.WinApiBridge +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct MonitorInfo + { + // size of a device name string + private const int CCHDEVICENAME = 32; + + public int StructureSize; + public RECT Monitor; + public RECT WorkArea; + public uint Flags; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)] + public string DeviceName; + } +} diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/WinApiBridge/ShowWindowCommands.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/WinApiBridge/ShowWindowCommands.cs new file mode 100644 index 0000000..bb30e5f --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/WinApiBridge/ShowWindowCommands.cs @@ -0,0 +1,75 @@ + +namespace Ninjacrab.PersistentWindows.WpfShell.WinApiBridge +{ + public enum ShowWindowCommands + { + /// + /// Hides the window and activates another window. + /// + Hide = 0, + /// + /// Activates and displays a window. If the window is minimized or + /// maximized, the system restores it to its original size and position. + /// An application should specify this flag when displaying the window + /// for the first time. + /// + Normal = 1, + /// + /// Activates the window and displays it as a minimized window. + /// + ShowMinimized = 2, + /// + /// Maximizes the specified window. + /// + Maximize = 3, // is this the right value? + /// + /// Activates the window and displays it as a maximized window. + /// + ShowMaximized = 3, + /// + /// Displays a window in its most recent size and position. This value + /// is similar to , except + /// the window is not activated. + /// + ShowNoActivate = 4, + /// + /// Activates the window and displays it in its current size and position. + /// + Show = 5, + /// + /// Minimizes the specified window and activates the next top-level + /// window in the Z order. + /// + Minimize = 6, + /// + /// Displays the window as a minimized window. This value is similar to + /// , except the + /// window is not activated. + /// + ShowMinNoActive = 7, + /// + /// Displays the window in its current size and position. This value is + /// similar to , except the + /// window is not activated. + /// + ShowNA = 8, + /// + /// Activates and displays the window. If the window is minimized or + /// maximized, the system restores it to its original size and position. + /// An application should specify this flag when restoring a minimized window. + /// + Restore = 9, + /// + /// Sets the show state based on the SW_* value specified in the + /// STARTUPINFO structure passed to the CreateProcess function by the + /// program that started the application. + /// + ShowDefault = 10, + /// + /// Windows 2000/XP: Minimizes a window, even if the thread + /// that owns the window is not responding. This flag should only be + /// used when minimizing windows from a different thread. + /// + ForceMinimize = 11 + } +} diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/WinApiBridge/User32.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/WinApiBridge/User32.cs new file mode 100644 index 0000000..515f566 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/WinApiBridge/User32.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.InteropServices; +using ManagedWinapi.Windows; + +namespace Ninjacrab.PersistentWindows.WpfShell.WinApiBridge +{ + public class User32 + { + #region EnumDisplayMonitors + public delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData); + [DllImport("user32.dll")] + public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumDelegate lpfnEnum, IntPtr dwData); + #endregion + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + public static extern bool GetMonitorInfo(IntPtr hMonitor, ref MonitorInfo lpmi); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WindowPlacement lpwndpl); + } +} diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/WinApiBridge/WindowPlacement.cs b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/WinApiBridge/WindowPlacement.cs new file mode 100644 index 0000000..b3c2df5 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/WinApiBridge/WindowPlacement.cs @@ -0,0 +1,55 @@ +using System.Runtime.InteropServices; +using ManagedWinapi.Windows; + +namespace Ninjacrab.PersistentWindows.WpfShell.WinApiBridge +{ + [StructLayout(LayoutKind.Sequential)] + public struct WindowPlacement + { + /// + /// The length of the structure, in bytes. Before calling the GetWindowPlacement or SetWindowPlacement functions, set this member to sizeof(WINDOWPLACEMENT). + /// + /// GetWindowPlacement and SetWindowPlacement fail if this member is not set correctly. + /// + /// + public int Length; + + /// + /// Specifies flags that control the position of the minimized window and the method by which the window is restored. + /// + public int Flags; + + /// + /// The current show state of the window. + /// + public ShowWindowCommands ShowCmd; + + /// + /// The coordinates of the window's upper-left corner when the window is minimized. + /// + public POINT MinPosition; + + /// + /// The coordinates of the window's upper-left corner when the window is maximized. + /// + public POINT MaxPosition; + + /// + /// The window's coordinates when the window is in the restored position. + /// + public RECT NormalPosition; + + /// + /// Gets the default (empty) value. + /// + public static WindowPlacement Default + { + get + { + WindowPlacement result = new WindowPlacement(); + result.Length = Marshal.SizeOf(result); + return result; + } + } + } +} diff --git a/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/packages.config b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/packages.config new file mode 100644 index 0000000..c81b844 --- /dev/null +++ b/Ninjacrab.PersistentWindows.Solution/Ninjacrab.PersistentWindows.WpfShell/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file