├── Duplicator ├── WinDuplicator │ ├── trace.bat │ ├── Duplicator.ico │ ├── trace64.bat │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── config.xml │ ├── Program.cs │ ├── ChooseAudioDevice.cs │ ├── About.cs │ ├── ChooseAdapter.cs │ ├── ChooseOutput.cs │ ├── Main.cs │ ├── WinDuplicator.csproj │ ├── About.resx │ ├── Main.resx │ ├── ChooseAdapter.resx │ ├── ChooseOutput.resx │ ├── ChooseAudioDevice.resx │ ├── About.Designer.cs │ ├── ChooseAudioDevice.Designer.cs │ ├── ChooseOutput.Designer.cs │ ├── ChooseAdapter.Designer.cs │ ├── Main.Designer.cs │ └── WinDuplicatorOptions.cs ├── Doc │ └── WinDuplicator.png ├── Duplicator │ ├── DuplicatorState.cs │ ├── DuplicatorInformationEventArgs.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Duplicator.csproj │ ├── H264Encoder.cs │ ├── DuplicatorOptions.cs │ ├── AudioCapture.cs │ └── DictionaryObject.cs └── Duplicator.sln ├── LICENSE ├── README.md └── .gitignore /Duplicator/WinDuplicator/trace.bat: -------------------------------------------------------------------------------- 1 | c:mftrace -c config.xml winduplicator.exe -------------------------------------------------------------------------------- /Duplicator/Doc/WinDuplicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shehryar/Duplicator/HEAD/Duplicator/Doc/WinDuplicator.png -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/Duplicator.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shehryar/Duplicator/HEAD/Duplicator/WinDuplicator/Duplicator.ico -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/trace64.bat: -------------------------------------------------------------------------------- 1 | "C:\Program Files (x86)\Windows Kits\10\bin\10.0.16299.0\x64\mftrace.exe" -c config.xml winduplicator.exe -------------------------------------------------------------------------------- /Duplicator/Duplicator/DuplicatorState.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace Duplicator 3 | { 4 | public enum DuplicatorState 5 | { 6 | Stopped, 7 | Starting, 8 | Started, 9 | Stopping, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Duplicator/Duplicator/DuplicatorInformationEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Duplicator 4 | { 5 | public class DuplicatorInformationEventArgs : EventArgs 6 | { 7 | internal DuplicatorInformationEventArgs(string information) 8 | { 9 | Information = information; 10 | } 11 | 12 | public string Information { get; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Duplicator/Duplicator/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("Duplicator")] 5 | [assembly: AssemblyProduct("Duplicator")] 6 | [assembly: AssemblyCopyright("Copyright © 2018 Simon Mourier. All rights reserved.")] 7 | [assembly: AssemblyCulture("")] 8 | [assembly: ComVisible(false)] 9 | [assembly: Guid("ad8cfc36-9dff-4429-8e08-698cedd35f1e")] 10 | [assembly: AssemblyVersion("1.0.0.0")] 11 | [assembly: AssemblyFileVersion("1.3.0.0")] 12 | [assembly: AssemblyInformationalVersion("1.3.0.0")] 13 | -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("WinDuplicator")] 5 | [assembly: AssemblyProduct("WinDuplicator")] 6 | [assembly: AssemblyCopyright("Copyright © 2018 Simon Mourier. All rights reserved.")] 7 | [assembly: AssemblyCulture("")] 8 | [assembly: ComVisible(false)] 9 | [assembly: Guid("2e9fc0e9-8589-4245-a222-f13cfa0ef29a")] 10 | [assembly: AssemblyVersion("1.0.0.0")] 11 | [assembly: AssemblyFileVersion("1.3.0.0")] 12 | [assembly: AssemblyInformationalVersion("1.3.0.0")] 13 | -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Simon Mourier 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Duplicator 2 | Screen Capture and Recording (Audio and Video) C# samples. There is only a Winform sample right now, but it should work with WPF or any Windows presentation technology because it only uses an Hwnd handle as a target. 3 | 4 | ## Key points: 5 | 6 | * Uses SharpDX exclusively for DirectX, DXGI, Direct2D and Media Foundation interop. 7 | * Uses Windows Desktop Duplication API for desktop duplication. It uses almost zero CPU and zero RAM. 8 | * Uses an integrated custom optimized interop layer (over Windows Core Audio) for sound capture (loopback and microphone). 9 | * Uses H264 + AAC for recording format. 10 | * Uses few resources for H264 encoding when a hardware encoder is available (Intel (C) Media SDK, etc.). 11 | * Never uses GDI nor GDI+ or legacy techology. 12 | 13 | ## Remarks 14 | 15 | * It requires a recent Windows version (10+). Untested on other Windows versions. 16 | * The Frame Rate choice (which is optional, you can choose to use the `` mode) may influence the encoder choice when more than one is available (this choice is automatic). Some hardware encoders support only some frame rates. The choosen encoder will be displayed by the app after recording has started. 17 | 18 | ![WinDuplicator.png](Duplicator/Doc/WinDuplicator.png?raw=true) 19 | -------------------------------------------------------------------------------- /Duplicator/Duplicator.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2027 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinDuplicator", "WinDuplicator\WinDuplicator.csproj", "{2E9FC0E9-8589-4245-A222-F13CFA0EF29A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicator", "Duplicator\Duplicator.csproj", "{AD8CFC36-9DFF-4429-8E08-698CEDD35F1E}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {2E9FC0E9-8589-4245-A222-F13CFA0EF29A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {2E9FC0E9-8589-4245-A222-F13CFA0EF29A}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {2E9FC0E9-8589-4245-A222-F13CFA0EF29A}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {2E9FC0E9-8589-4245-A222-F13CFA0EF29A}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {AD8CFC36-9DFF-4429-8E08-698CEDD35F1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {AD8CFC36-9DFF-4429-8E08-698CEDD35F1E}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {AD8CFC36-9DFF-4429-8E08-698CEDD35F1E}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {AD8CFC36-9DFF-4429-8E08-698CEDD35F1E}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {4A11A695-103B-46D2-8D32-7E67283FD688} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.InteropServices; 4 | using System.Windows.Forms; 5 | 6 | namespace WinDuplicator 7 | { 8 | static class Program 9 | { 10 | [STAThread] 11 | static void Main() 12 | { 13 | SetErrorMode(ErrorModes.SEM_NONE); 14 | if (!Debugger.IsAttached) 15 | { 16 | AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; 17 | Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); 18 | } 19 | 20 | Application.EnableVisualStyles(); 21 | Application.SetCompatibleTextRenderingDefault(false); 22 | Application.Run(new Main()); 23 | } 24 | 25 | private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) 26 | { 27 | var ex = e.ExceptionObject as Exception; 28 | if (ex == null) 29 | return; 30 | 31 | var dlg = new ThreadExceptionDialog(ex); 32 | dlg.ShowDialog(); 33 | } 34 | 35 | private enum ErrorModes : int 36 | { 37 | SYSTEM_DEFAULT = 0x0, 38 | SEM_FAILCRITICALERRORS = 0x0001, 39 | SEM_NOALIGNMENTFAULTEXCEPT = 0x0004, 40 | SEM_NOGPFAULTERRORBOX = 0x0002, 41 | SEM_NOOPENFILEERRORBOX = 0x8000, 42 | SEM_NONE = SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX 43 | } 44 | 45 | [DllImport("kernel32.dll")] 46 | private static extern ErrorModes SetErrorMode(ErrorModes uMode); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/ChooseAudioDevice.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Windows.Forms; 3 | using Duplicator; 4 | 5 | namespace WinDuplicator 6 | { 7 | public partial class ChooseAudioDevice : Form 8 | { 9 | public ChooseAudioDevice(string selectedAudioDevice, AudioCapture.DataFlow flow) 10 | { 11 | InitializeComponent(); 12 | Icon = Main.EmbeddedIcon; 13 | 14 | listViewMain.Select(); 15 | foreach (var device in AudioCapture.GetDevices(flow).Where(d => d.State == AudioCapture.AudioDeviceState.Active)) 16 | { 17 | var item = listViewMain.Items.Add(device.FriendlyName); 18 | item.Tag = device; 19 | if (selectedAudioDevice != null && device.FriendlyName == selectedAudioDevice) 20 | { 21 | Device = device; 22 | item.Selected = true; 23 | } 24 | } 25 | 26 | listViewMain.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize); 27 | UpdateControls(); 28 | } 29 | 30 | public AudioCapture.AudioDevice Device { get; private set; } 31 | 32 | private void ChooseAdapter_FormClosing(object sender, FormClosingEventArgs e) 33 | { 34 | if ((e.CloseReason == CloseReason.UserClosing || e.CloseReason == CloseReason.None) && DialogResult == DialogResult.OK) 35 | { 36 | if (listViewMain.SelectedItems.Count > 0) 37 | { 38 | Device = (AudioCapture.AudioDevice)listViewMain.SelectedItems[0].Tag; 39 | } 40 | } 41 | } 42 | 43 | private void UpdateControls() 44 | { 45 | buttonOk.Enabled = listViewMain.SelectedItems.Count > 0; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/About.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Text; 4 | using System.Windows.Forms; 5 | using Duplicator; 6 | 7 | namespace WinDuplicator 8 | { 9 | public partial class About : Form 10 | { 11 | public About() 12 | { 13 | InitializeComponent(); 14 | Icon = Main.EmbeddedIcon; 15 | string version = Assembly.GetExecutingAssembly().GetCustomAttribute().Version; 16 | string cr = Assembly.GetExecutingAssembly().GetCustomAttribute().Copyright; 17 | labelCopyright.Text = "Duplicator V" + version + Environment.NewLine + cr; 18 | 19 | var sb = new StringBuilder(); 20 | sb.AppendLine("* System:"); 21 | sb.AppendLine(); 22 | sb.AppendLine(" OS: " + Environment.OSVersion); 23 | sb.AppendLine(" Processors: " + Environment.ProcessorCount); 24 | sb.AppendLine(" Bitness: " + (Environment.Is64BitProcess ? "64" : "32") + "-bit"); 25 | sb.AppendLine(); 26 | sb.AppendLine("* Detected H264 encoders:"); 27 | sb.AppendLine(); 28 | foreach (var dec in H264Encoder.Enumerate()) 29 | { 30 | sb.AppendLine(" " + dec.FriendlyName); 31 | sb.AppendLine(" Clsid : " + dec.Clsid); 32 | sb.AppendLine(" DllPath : " + dec.DllPath); 33 | sb.AppendLine(" Flags : " + dec.Flags); 34 | sb.AppendLine(" D3D11 Aware: " + dec.IsDirect3D11Aware); 35 | sb.AppendLine(" Supported Input types:"); 36 | foreach (var type in dec.InputTypes) 37 | { 38 | sb.AppendLine(" " + type); 39 | } 40 | sb.AppendLine(); 41 | } 42 | 43 | textBoxInfo.Text = sb.ToString(); 44 | } 45 | 46 | private void buttonOk_Click(object sender, EventArgs e) 47 | { 48 | Close(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/ChooseAdapter.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | using SharpDX.DXGI; 3 | 4 | namespace WinDuplicator 5 | { 6 | public partial class ChooseAdapter : Form 7 | { 8 | public ChooseAdapter(string selectedAdapter) 9 | { 10 | InitializeComponent(); 11 | Icon = Main.EmbeddedIcon; 12 | 13 | using (var fac = new Factory1()) 14 | { 15 | listViewMain.Select(); 16 | foreach (var adapter in fac.Adapters1) 17 | { 18 | var item = listViewMain.Items.Add(adapter.Description1.Description); 19 | item.Tag = adapter; 20 | item.SubItems.Add(adapter.Description1.Flags.ToString()); 21 | item.SubItems.Add(adapter.Description1.Revision.ToString()); 22 | item.SubItems.Add(adapter.Description1.DedicatedVideoMemory.ToString()); 23 | if (selectedAdapter != null && adapter.Description.Description == selectedAdapter) 24 | { 25 | Adapter = adapter; 26 | item.Selected = true; 27 | } 28 | } 29 | } 30 | 31 | listViewMain.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize); 32 | } 33 | 34 | public Adapter1 Adapter { get; private set; } 35 | 36 | private void ChooseAdapter_FormClosing(object sender, FormClosingEventArgs e) 37 | { 38 | if ((e.CloseReason == CloseReason.UserClosing || e.CloseReason == CloseReason.None) && DialogResult == DialogResult.OK) 39 | { 40 | if (listViewMain.SelectedItems.Count > 0) 41 | { 42 | Adapter = (Adapter1)listViewMain.SelectedItems[0].Tag; 43 | } 44 | } 45 | } 46 | 47 | private void UpdateControls() 48 | { 49 | buttonOk.Enabled = listViewMain.SelectedItems.Count > 0; 50 | } 51 | 52 | private void listViewMain_SelectedIndexChanged(object sender, System.EventArgs e) 53 | { 54 | UpdateControls(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/ChooseOutput.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | using Duplicator; 3 | using SharpDX.DXGI; 4 | 5 | namespace WinDuplicator 6 | { 7 | public partial class ChooseOutput : Form 8 | { 9 | public ChooseOutput(Adapter1 adapter, string selectedDeviceName) 10 | { 11 | InitializeComponent(); 12 | Icon = Main.EmbeddedIcon; 13 | 14 | listViewMain.Select(); 15 | foreach (var output in adapter.Outputs) 16 | { 17 | string name = DuplicatorOptions.GetDisplayDeviceName(output.Description.DeviceName); 18 | var item = listViewMain.Items.Add(name); 19 | item.Tag = output.Description.DeviceName; 20 | int width = output.Description.DesktopBounds.Right - output.Description.DesktopBounds.Left; 21 | int height = output.Description.DesktopBounds.Bottom - output.Description.DesktopBounds.Top; 22 | item.SubItems.Add(width + " x " + height); 23 | item.SubItems.Add(output.Description.DesktopBounds.Left + ", " + output.Description.DesktopBounds.Top); 24 | item.SubItems.Add(output.Description.Rotation.ToString()); 25 | if (selectedDeviceName != null && output.Description.DeviceName == selectedDeviceName) 26 | { 27 | DeviceName = name; 28 | item.Selected = true; 29 | } 30 | } 31 | 32 | listViewMain.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize); 33 | UpdateControls(); 34 | Text = "Choose Monitor for " + adapter.Description.Description; 35 | } 36 | 37 | public string DeviceName { get; private set; } 38 | 39 | private void ChooseOutput_FormClosing(object sender, FormClosingEventArgs e) 40 | { 41 | if ((e.CloseReason == CloseReason.UserClosing || e.CloseReason == CloseReason.None) && DialogResult == DialogResult.OK) 42 | { 43 | if (listViewMain.SelectedItems.Count > 0) 44 | { 45 | DeviceName = (string)listViewMain.SelectedItems[0].Tag; 46 | } 47 | } 48 | } 49 | 50 | private void UpdateControls() 51 | { 52 | buttonOk.Enabled = listViewMain.SelectedItems.Count > 0; 53 | } 54 | 55 | private void listViewMain_SelectedIndexChanged(object sender, System.EventArgs e) 56 | { 57 | UpdateControls(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Duplicator/Duplicator/Duplicator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {AD8CFC36-9DFF-4429-8E08-698CEDD35F1E} 8 | Library 9 | Properties 10 | Duplicator 11 | Duplicator 12 | v4.6.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | true 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 4.0.1 53 | 54 | 55 | 4.0.1 56 | 57 | 58 | 4.0.1 59 | 60 | 61 | 4.0.1 62 | 63 | 64 | 4.0.1 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Drawing; 4 | using System.IO; 5 | using System.Reflection; 6 | using System.Windows.Forms; 7 | 8 | namespace WinDuplicator 9 | { 10 | public partial class Main : Form 11 | { 12 | private static Lazy _embeddedIcon = new Lazy(() => { using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(typeof(Main), "Duplicator.ico")) return new Icon(stream); }); 13 | public static Icon EmbeddedIcon => _embeddedIcon.Value; 14 | 15 | private WinDuplicatorOptions _options; 16 | private Duplicator.Duplicator _duplicator; 17 | 18 | public Main() 19 | { 20 | InitializeComponent(); 21 | MinimumSize = Size; 22 | Icon = EmbeddedIcon; 23 | _options = new WinDuplicatorOptions(); 24 | propertyGridMain.SelectedObject = _options; 25 | 26 | _duplicator = new Duplicator.Duplicator(_options); 27 | _duplicator.PropertyChanged += OnDuplicatorPropertyChanged; 28 | _duplicator.InformationAvailable += OnDuplicatorInformationAvailable; 29 | _duplicator.Size = new SharpDX.Size2(splitContainerMain.Panel1.Width, splitContainerMain.Panel1.Height); 30 | 31 | splitContainerMain.Panel1.SizeChanged += (sender, e) => 32 | { 33 | _duplicator.Size = new SharpDX.Size2(splitContainerMain.Panel1.Width, splitContainerMain.Panel1.Height); 34 | }; 35 | 36 | splitContainerMain.Panel1.HandleCreated += (sender, e) => 37 | { 38 | _duplicator.Hwnd = splitContainerMain.Panel1.Handle; 39 | }; 40 | 41 | splitContainerMain.Panel1.HandleDestroyed += (sender, e) => 42 | { 43 | _duplicator.Hwnd = IntPtr.Zero; 44 | }; 45 | 46 | #if DEBUG 47 | Text += " - DEBUG"; 48 | #endif 49 | } 50 | 51 | private void OnDuplicatorInformationAvailable(object sender, Duplicator.DuplicatorInformationEventArgs e) 52 | { 53 | BeginInvoke((Action)(() => 54 | { 55 | if (!string.IsNullOrEmpty(textBoxStatus.Text)) 56 | { 57 | textBoxStatus.AppendText(Environment.NewLine); 58 | } 59 | textBoxStatus.AppendText(e.Information); 60 | })); 61 | } 62 | 63 | private void OnDuplicatorPropertyChanged(object sender, PropertyChangedEventArgs e) 64 | { 65 | // update chekboxes accordingly with duplicator state 66 | // note: these events arrive on another thread 67 | switch (e.PropertyName) 68 | { 69 | case nameof(_duplicator.DuplicatingState): 70 | BeginInvoke((Action)(() => 71 | { 72 | bool dup = _duplicator.DuplicatingState == Duplicator.DuplicatorState.Started || _duplicator.DuplicatingState == Duplicator.DuplicatorState.Starting; 73 | checkBoxDuplicate.Checked = dup; 74 | checkBoxRecord.Enabled = dup; 75 | })); 76 | break; 77 | 78 | case nameof(_duplicator.RecordingState): 79 | BeginInvoke((Action)(() => 80 | { 81 | checkBoxRecord.Checked = _duplicator.RecordingState == Duplicator.DuplicatorState.Started || _duplicator.RecordingState == Duplicator.DuplicatorState.Starting; 82 | })); 83 | break; 84 | 85 | case nameof(_duplicator.RecordFilePath): 86 | string mode = _duplicator.IsUsingHardwareBasedEncoder ? "Hardware" : "Software"; 87 | BeginInvoke((Action)(() => 88 | { 89 | if (_duplicator.RecordingState == Duplicator.DuplicatorState.Started && !string.IsNullOrEmpty(_duplicator.RecordFilePath)) 90 | { 91 | Text = "Duplicator - " + mode + " Recording " + Path.GetFileName(_duplicator.RecordFilePath); 92 | } 93 | else 94 | { 95 | Text = "Duplicator"; 96 | } 97 | })); 98 | break; 99 | } 100 | } 101 | 102 | protected override void Dispose(bool disposing) 103 | { 104 | if (disposing) 105 | { 106 | _duplicator?.Dispose(); 107 | } 108 | base.Dispose(disposing); 109 | } 110 | 111 | private void buttonQuit_Click(object sender, EventArgs e) 112 | { 113 | Close(); 114 | } 115 | 116 | private void checkBoxDuplicate_CheckedChanged(object sender, EventArgs e) 117 | { 118 | if (checkBoxDuplicate.Checked) 119 | { 120 | _duplicator.StartDuplicating(); 121 | } 122 | else 123 | { 124 | _duplicator.StopDuplicating(); 125 | } 126 | } 127 | 128 | private void buttonAbout_Click(object sender, EventArgs e) 129 | { 130 | var about = new About(); 131 | about.ShowDialog(this); 132 | } 133 | 134 | private void checkBoxRecord_CheckedChanged(object sender, EventArgs e) 135 | { 136 | if (checkBoxRecord.Checked) 137 | { 138 | _duplicator.StartRecording(); 139 | } 140 | else 141 | { 142 | _duplicator.StopRecording(); 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/WinDuplicator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2E9FC0E9-8589-4245-A222-F13CFA0EF29A} 8 | WinExe 9 | WinDuplicator 10 | WinDuplicator 11 | v4.6.1 12 | 512 13 | true 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | false 25 | true 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | false 36 | 37 | 38 | Duplicator.ico 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | Form 52 | 53 | 54 | About.cs 55 | 56 | 57 | Form 58 | 59 | 60 | ChooseAudioDevice.cs 61 | 62 | 63 | Form 64 | 65 | 66 | ChooseOutput.cs 67 | 68 | 69 | Form 70 | 71 | 72 | ChooseAdapter.cs 73 | 74 | 75 | Form 76 | 77 | 78 | Main.cs 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | {ad8cfc36-9dff-4429-8e08-698cedd35f1e} 87 | Duplicator 88 | 89 | 90 | 91 | 92 | About.cs 93 | 94 | 95 | ChooseAudioDevice.cs 96 | 97 | 98 | ChooseOutput.cs 99 | 100 | 101 | ChooseAdapter.cs 102 | 103 | 104 | Main.cs 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 4.0.1 113 | 114 | 115 | 4.0.1 116 | 117 | 118 | 4.0.1 119 | 120 | 121 | 4.0.1 122 | 123 | 124 | 4.0.1 125 | 126 | 127 | 128 | 129 | PreserveNewest 130 | 131 | 132 | 133 | 134 | PreserveNewest 135 | 136 | 137 | PreserveNewest 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/About.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/Main.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/ChooseAdapter.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/ChooseOutput.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/ChooseAudioDevice.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/About.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace WinDuplicator 2 | { 3 | partial class About 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.tableLayoutPanelMain = new System.Windows.Forms.TableLayoutPanel(); 32 | this.buttonOk = new System.Windows.Forms.Button(); 33 | this.labelCopyright = new System.Windows.Forms.Label(); 34 | this.textBoxInfo = new System.Windows.Forms.TextBox(); 35 | this.tableLayoutPanelMain.SuspendLayout(); 36 | this.SuspendLayout(); 37 | // 38 | // tableLayoutPanelMain 39 | // 40 | this.tableLayoutPanelMain.ColumnCount = 1; 41 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); 42 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); 43 | this.tableLayoutPanelMain.Controls.Add(this.buttonOk, 0, 2); 44 | this.tableLayoutPanelMain.Controls.Add(this.labelCopyright, 0, 0); 45 | this.tableLayoutPanelMain.Controls.Add(this.textBoxInfo, 0, 1); 46 | this.tableLayoutPanelMain.Dock = System.Windows.Forms.DockStyle.Fill; 47 | this.tableLayoutPanelMain.Location = new System.Drawing.Point(0, 0); 48 | this.tableLayoutPanelMain.Name = "tableLayoutPanelMain"; 49 | this.tableLayoutPanelMain.RowCount = 3; 50 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 50F)); 51 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); 52 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F)); 53 | this.tableLayoutPanelMain.Size = new System.Drawing.Size(607, 308); 54 | this.tableLayoutPanelMain.TabIndex = 0; 55 | // 56 | // buttonOk 57 | // 58 | this.buttonOk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 59 | this.buttonOk.DialogResult = System.Windows.Forms.DialogResult.OK; 60 | this.buttonOk.Location = new System.Drawing.Point(529, 281); 61 | this.buttonOk.Name = "buttonOk"; 62 | this.buttonOk.Size = new System.Drawing.Size(75, 23); 63 | this.buttonOk.TabIndex = 0; 64 | this.buttonOk.Text = "Close"; 65 | this.buttonOk.UseVisualStyleBackColor = true; 66 | this.buttonOk.Click += new System.EventHandler(this.buttonOk_Click); 67 | // 68 | // labelCopyright 69 | // 70 | this.labelCopyright.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 71 | | System.Windows.Forms.AnchorStyles.Left) 72 | | System.Windows.Forms.AnchorStyles.Right))); 73 | this.labelCopyright.AutoSize = true; 74 | this.labelCopyright.Location = new System.Drawing.Point(3, 0); 75 | this.labelCopyright.Name = "labelCopyright"; 76 | this.labelCopyright.Size = new System.Drawing.Size(601, 50); 77 | this.labelCopyright.TabIndex = 1; 78 | this.labelCopyright.Text = "Copyright"; 79 | this.labelCopyright.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; 80 | // 81 | // textBoxInfo 82 | // 83 | this.textBoxInfo.BackColor = System.Drawing.SystemColors.HighlightText; 84 | this.textBoxInfo.Dock = System.Windows.Forms.DockStyle.Fill; 85 | this.textBoxInfo.Font = new System.Drawing.Font("Lucida Console", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 86 | this.textBoxInfo.Location = new System.Drawing.Point(3, 53); 87 | this.textBoxInfo.Multiline = true; 88 | this.textBoxInfo.Name = "textBoxInfo"; 89 | this.textBoxInfo.ReadOnly = true; 90 | this.textBoxInfo.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; 91 | this.textBoxInfo.Size = new System.Drawing.Size(601, 222); 92 | this.textBoxInfo.TabIndex = 2; 93 | // 94 | // About 95 | // 96 | this.AcceptButton = this.buttonOk; 97 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 98 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 99 | this.CancelButton = this.buttonOk; 100 | this.ClientSize = new System.Drawing.Size(607, 308); 101 | this.Controls.Add(this.tableLayoutPanelMain); 102 | this.MaximizeBox = false; 103 | this.MinimizeBox = false; 104 | this.Name = "About"; 105 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 106 | this.Text = "About Duplicator"; 107 | this.tableLayoutPanelMain.ResumeLayout(false); 108 | this.tableLayoutPanelMain.PerformLayout(); 109 | this.ResumeLayout(false); 110 | 111 | } 112 | 113 | #endregion 114 | 115 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanelMain; 116 | private System.Windows.Forms.Button buttonOk; 117 | private System.Windows.Forms.Label labelCopyright; 118 | private System.Windows.Forms.TextBox textBoxInfo; 119 | } 120 | } -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/ChooseAudioDevice.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace WinDuplicator 2 | { 3 | partial class ChooseAudioDevice 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.tableLayoutPanelMain = new System.Windows.Forms.TableLayoutPanel(); 32 | this.panelCommands = new System.Windows.Forms.Panel(); 33 | this.buttonCancel = new System.Windows.Forms.Button(); 34 | this.buttonOk = new System.Windows.Forms.Button(); 35 | this.listViewMain = new System.Windows.Forms.ListView(); 36 | this.columnName = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 37 | this.tableLayoutPanelMain.SuspendLayout(); 38 | this.panelCommands.SuspendLayout(); 39 | this.SuspendLayout(); 40 | // 41 | // tableLayoutPanelMain 42 | // 43 | this.tableLayoutPanelMain.ColumnCount = 1; 44 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); 45 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); 46 | this.tableLayoutPanelMain.Controls.Add(this.panelCommands, 0, 1); 47 | this.tableLayoutPanelMain.Controls.Add(this.listViewMain, 0, 0); 48 | this.tableLayoutPanelMain.Dock = System.Windows.Forms.DockStyle.Fill; 49 | this.tableLayoutPanelMain.Location = new System.Drawing.Point(0, 0); 50 | this.tableLayoutPanelMain.Name = "tableLayoutPanelMain"; 51 | this.tableLayoutPanelMain.RowCount = 2; 52 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); 53 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F)); 54 | this.tableLayoutPanelMain.Size = new System.Drawing.Size(509, 255); 55 | this.tableLayoutPanelMain.TabIndex = 0; 56 | // 57 | // panelCommands 58 | // 59 | this.panelCommands.Controls.Add(this.buttonCancel); 60 | this.panelCommands.Controls.Add(this.buttonOk); 61 | this.panelCommands.Dock = System.Windows.Forms.DockStyle.Fill; 62 | this.panelCommands.Location = new System.Drawing.Point(0, 225); 63 | this.panelCommands.Margin = new System.Windows.Forms.Padding(0); 64 | this.panelCommands.Name = "panelCommands"; 65 | this.panelCommands.Size = new System.Drawing.Size(509, 30); 66 | this.panelCommands.TabIndex = 0; 67 | // 68 | // buttonCancel 69 | // 70 | this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; 71 | this.buttonCancel.Location = new System.Drawing.Point(341, 3); 72 | this.buttonCancel.Name = "buttonCancel"; 73 | this.buttonCancel.Size = new System.Drawing.Size(75, 23); 74 | this.buttonCancel.TabIndex = 0; 75 | this.buttonCancel.Text = "&Cancel"; 76 | this.buttonCancel.UseVisualStyleBackColor = true; 77 | // 78 | // buttonOk 79 | // 80 | this.buttonOk.DialogResult = System.Windows.Forms.DialogResult.OK; 81 | this.buttonOk.Location = new System.Drawing.Point(422, 3); 82 | this.buttonOk.Name = "buttonOk"; 83 | this.buttonOk.Size = new System.Drawing.Size(75, 23); 84 | this.buttonOk.TabIndex = 1; 85 | this.buttonOk.Text = "OK"; 86 | this.buttonOk.UseVisualStyleBackColor = true; 87 | // 88 | // listViewMain 89 | // 90 | this.listViewMain.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { 91 | this.columnName}); 92 | this.listViewMain.Dock = System.Windows.Forms.DockStyle.Fill; 93 | this.listViewMain.FullRowSelect = true; 94 | this.listViewMain.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; 95 | this.listViewMain.Location = new System.Drawing.Point(3, 3); 96 | this.listViewMain.MultiSelect = false; 97 | this.listViewMain.Name = "listViewMain"; 98 | this.listViewMain.Size = new System.Drawing.Size(503, 219); 99 | this.listViewMain.TabIndex = 0; 100 | this.listViewMain.UseCompatibleStateImageBehavior = false; 101 | this.listViewMain.View = System.Windows.Forms.View.Details; 102 | // 103 | // columnName 104 | // 105 | this.columnName.Text = "Name"; 106 | this.columnName.Width = 211; 107 | // 108 | // ChooseAudioDevice 109 | // 110 | this.AcceptButton = this.buttonOk; 111 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 112 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 113 | this.CancelButton = this.buttonCancel; 114 | this.ClientSize = new System.Drawing.Size(509, 255); 115 | this.Controls.Add(this.tableLayoutPanelMain); 116 | this.MaximizeBox = false; 117 | this.MinimizeBox = false; 118 | this.Name = "ChooseAudioDevice"; 119 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 120 | this.Text = "Choose Audio Device"; 121 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ChooseAdapter_FormClosing); 122 | this.tableLayoutPanelMain.ResumeLayout(false); 123 | this.panelCommands.ResumeLayout(false); 124 | this.ResumeLayout(false); 125 | 126 | } 127 | 128 | #endregion 129 | 130 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanelMain; 131 | private System.Windows.Forms.Panel panelCommands; 132 | private System.Windows.Forms.Button buttonCancel; 133 | private System.Windows.Forms.Button buttonOk; 134 | private System.Windows.Forms.ListView listViewMain; 135 | private System.Windows.Forms.ColumnHeader columnName; 136 | } 137 | } -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/ChooseOutput.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace WinDuplicator 2 | { 3 | partial class ChooseOutput 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.tableLayoutPanelMain = new System.Windows.Forms.TableLayoutPanel(); 32 | this.panelCommands = new System.Windows.Forms.Panel(); 33 | this.buttonCancel = new System.Windows.Forms.Button(); 34 | this.buttonOk = new System.Windows.Forms.Button(); 35 | this.listViewMain = new System.Windows.Forms.ListView(); 36 | this.columnName = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 37 | this.columnSize = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 38 | this.columnPosition = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 39 | this.columnRotation = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 40 | this.tableLayoutPanelMain.SuspendLayout(); 41 | this.panelCommands.SuspendLayout(); 42 | this.SuspendLayout(); 43 | // 44 | // tableLayoutPanelMain 45 | // 46 | this.tableLayoutPanelMain.ColumnCount = 1; 47 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); 48 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); 49 | this.tableLayoutPanelMain.Controls.Add(this.panelCommands, 0, 1); 50 | this.tableLayoutPanelMain.Controls.Add(this.listViewMain, 0, 0); 51 | this.tableLayoutPanelMain.Dock = System.Windows.Forms.DockStyle.Fill; 52 | this.tableLayoutPanelMain.Location = new System.Drawing.Point(0, 0); 53 | this.tableLayoutPanelMain.Name = "tableLayoutPanelMain"; 54 | this.tableLayoutPanelMain.RowCount = 2; 55 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); 56 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F)); 57 | this.tableLayoutPanelMain.Size = new System.Drawing.Size(509, 255); 58 | this.tableLayoutPanelMain.TabIndex = 0; 59 | // 60 | // panelCommands 61 | // 62 | this.panelCommands.Controls.Add(this.buttonCancel); 63 | this.panelCommands.Controls.Add(this.buttonOk); 64 | this.panelCommands.Dock = System.Windows.Forms.DockStyle.Fill; 65 | this.panelCommands.Location = new System.Drawing.Point(0, 225); 66 | this.panelCommands.Margin = new System.Windows.Forms.Padding(0); 67 | this.panelCommands.Name = "panelCommands"; 68 | this.panelCommands.Size = new System.Drawing.Size(509, 30); 69 | this.panelCommands.TabIndex = 0; 70 | // 71 | // buttonCancel 72 | // 73 | this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; 74 | this.buttonCancel.Location = new System.Drawing.Point(341, 3); 75 | this.buttonCancel.Name = "buttonCancel"; 76 | this.buttonCancel.Size = new System.Drawing.Size(75, 23); 77 | this.buttonCancel.TabIndex = 0; 78 | this.buttonCancel.Text = "&Cancel"; 79 | this.buttonCancel.UseVisualStyleBackColor = true; 80 | // 81 | // buttonOk 82 | // 83 | this.buttonOk.DialogResult = System.Windows.Forms.DialogResult.OK; 84 | this.buttonOk.Location = new System.Drawing.Point(422, 3); 85 | this.buttonOk.Name = "buttonOk"; 86 | this.buttonOk.Size = new System.Drawing.Size(75, 23); 87 | this.buttonOk.TabIndex = 1; 88 | this.buttonOk.Text = "OK"; 89 | this.buttonOk.UseVisualStyleBackColor = true; 90 | // 91 | // listViewMain 92 | // 93 | this.listViewMain.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { 94 | this.columnName, 95 | this.columnSize, 96 | this.columnPosition, 97 | this.columnRotation}); 98 | this.listViewMain.Dock = System.Windows.Forms.DockStyle.Fill; 99 | this.listViewMain.FullRowSelect = true; 100 | this.listViewMain.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; 101 | this.listViewMain.Location = new System.Drawing.Point(3, 3); 102 | this.listViewMain.MultiSelect = false; 103 | this.listViewMain.Name = "listViewMain"; 104 | this.listViewMain.Size = new System.Drawing.Size(503, 219); 105 | this.listViewMain.TabIndex = 0; 106 | this.listViewMain.UseCompatibleStateImageBehavior = false; 107 | this.listViewMain.View = System.Windows.Forms.View.Details; 108 | this.listViewMain.SelectedIndexChanged += new System.EventHandler(this.listViewMain_SelectedIndexChanged); 109 | // 110 | // columnName 111 | // 112 | this.columnName.Text = "Name"; 113 | this.columnName.Width = 211; 114 | // 115 | // columnSize 116 | // 117 | this.columnSize.Text = "Size"; 118 | // 119 | // columnPosition 120 | // 121 | this.columnPosition.Text = "Position"; 122 | this.columnPosition.Width = 120; 123 | // 124 | // columnRotation 125 | // 126 | this.columnRotation.Text = "Rotation"; 127 | // 128 | // ChooseOutput 129 | // 130 | this.AcceptButton = this.buttonOk; 131 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 132 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 133 | this.CancelButton = this.buttonCancel; 134 | this.ClientSize = new System.Drawing.Size(509, 255); 135 | this.Controls.Add(this.tableLayoutPanelMain); 136 | this.MaximizeBox = false; 137 | this.MinimizeBox = false; 138 | this.Name = "ChooseOutput"; 139 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 140 | this.Text = "Choose Output"; 141 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ChooseOutput_FormClosing); 142 | this.tableLayoutPanelMain.ResumeLayout(false); 143 | this.panelCommands.ResumeLayout(false); 144 | this.ResumeLayout(false); 145 | 146 | } 147 | 148 | #endregion 149 | 150 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanelMain; 151 | private System.Windows.Forms.Panel panelCommands; 152 | private System.Windows.Forms.Button buttonCancel; 153 | private System.Windows.Forms.Button buttonOk; 154 | private System.Windows.Forms.ListView listViewMain; 155 | private System.Windows.Forms.ColumnHeader columnName; 156 | private System.Windows.Forms.ColumnHeader columnSize; 157 | private System.Windows.Forms.ColumnHeader columnPosition; 158 | private System.Windows.Forms.ColumnHeader columnRotation; 159 | } 160 | } -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/ChooseAdapter.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace WinDuplicator 2 | { 3 | partial class ChooseAdapter 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.tableLayoutPanelMain = new System.Windows.Forms.TableLayoutPanel(); 32 | this.panelCommands = new System.Windows.Forms.Panel(); 33 | this.buttonCancel = new System.Windows.Forms.Button(); 34 | this.buttonOk = new System.Windows.Forms.Button(); 35 | this.listViewMain = new System.Windows.Forms.ListView(); 36 | this.columnDescription = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 37 | this.columnFlags = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 38 | this.columnRevision = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 39 | this.columnVRAM = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 40 | this.tableLayoutPanelMain.SuspendLayout(); 41 | this.panelCommands.SuspendLayout(); 42 | this.SuspendLayout(); 43 | // 44 | // tableLayoutPanelMain 45 | // 46 | this.tableLayoutPanelMain.ColumnCount = 1; 47 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); 48 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); 49 | this.tableLayoutPanelMain.Controls.Add(this.panelCommands, 0, 1); 50 | this.tableLayoutPanelMain.Controls.Add(this.listViewMain, 0, 0); 51 | this.tableLayoutPanelMain.Dock = System.Windows.Forms.DockStyle.Fill; 52 | this.tableLayoutPanelMain.Location = new System.Drawing.Point(0, 0); 53 | this.tableLayoutPanelMain.Name = "tableLayoutPanelMain"; 54 | this.tableLayoutPanelMain.RowCount = 2; 55 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); 56 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F)); 57 | this.tableLayoutPanelMain.Size = new System.Drawing.Size(509, 255); 58 | this.tableLayoutPanelMain.TabIndex = 0; 59 | // 60 | // panelCommands 61 | // 62 | this.panelCommands.Controls.Add(this.buttonCancel); 63 | this.panelCommands.Controls.Add(this.buttonOk); 64 | this.panelCommands.Dock = System.Windows.Forms.DockStyle.Fill; 65 | this.panelCommands.Location = new System.Drawing.Point(0, 225); 66 | this.panelCommands.Margin = new System.Windows.Forms.Padding(0); 67 | this.panelCommands.Name = "panelCommands"; 68 | this.panelCommands.Size = new System.Drawing.Size(509, 30); 69 | this.panelCommands.TabIndex = 0; 70 | // 71 | // buttonCancel 72 | // 73 | this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; 74 | this.buttonCancel.Location = new System.Drawing.Point(341, 3); 75 | this.buttonCancel.Name = "buttonCancel"; 76 | this.buttonCancel.Size = new System.Drawing.Size(75, 23); 77 | this.buttonCancel.TabIndex = 0; 78 | this.buttonCancel.Text = "&Cancel"; 79 | this.buttonCancel.UseVisualStyleBackColor = true; 80 | // 81 | // buttonOk 82 | // 83 | this.buttonOk.DialogResult = System.Windows.Forms.DialogResult.OK; 84 | this.buttonOk.Location = new System.Drawing.Point(422, 3); 85 | this.buttonOk.Name = "buttonOk"; 86 | this.buttonOk.Size = new System.Drawing.Size(75, 23); 87 | this.buttonOk.TabIndex = 1; 88 | this.buttonOk.Text = "OK"; 89 | this.buttonOk.UseVisualStyleBackColor = true; 90 | // 91 | // listViewMain 92 | // 93 | this.listViewMain.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { 94 | this.columnDescription, 95 | this.columnFlags, 96 | this.columnRevision, 97 | this.columnVRAM}); 98 | this.listViewMain.Dock = System.Windows.Forms.DockStyle.Fill; 99 | this.listViewMain.FullRowSelect = true; 100 | this.listViewMain.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; 101 | this.listViewMain.Location = new System.Drawing.Point(3, 3); 102 | this.listViewMain.MultiSelect = false; 103 | this.listViewMain.Name = "listViewMain"; 104 | this.listViewMain.Size = new System.Drawing.Size(503, 219); 105 | this.listViewMain.TabIndex = 0; 106 | this.listViewMain.UseCompatibleStateImageBehavior = false; 107 | this.listViewMain.View = System.Windows.Forms.View.Details; 108 | this.listViewMain.SelectedIndexChanged += new System.EventHandler(this.listViewMain_SelectedIndexChanged); 109 | // 110 | // columnDescription 111 | // 112 | this.columnDescription.Text = "Description"; 113 | this.columnDescription.Width = 211; 114 | // 115 | // columnFlags 116 | // 117 | this.columnFlags.Text = "Flags"; 118 | // 119 | // columnRevision 120 | // 121 | this.columnRevision.Text = "Revision"; 122 | this.columnRevision.Width = 120; 123 | // 124 | // columnVRAM 125 | // 126 | this.columnVRAM.Text = "VRAM"; 127 | // 128 | // ChooseAdapter 129 | // 130 | this.AcceptButton = this.buttonOk; 131 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 132 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 133 | this.CancelButton = this.buttonCancel; 134 | this.ClientSize = new System.Drawing.Size(509, 255); 135 | this.Controls.Add(this.tableLayoutPanelMain); 136 | this.MaximizeBox = false; 137 | this.MinimizeBox = false; 138 | this.Name = "ChooseAdapter"; 139 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 140 | this.Text = "Choose Adapter"; 141 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ChooseAdapter_FormClosing); 142 | this.tableLayoutPanelMain.ResumeLayout(false); 143 | this.panelCommands.ResumeLayout(false); 144 | this.ResumeLayout(false); 145 | 146 | } 147 | 148 | #endregion 149 | 150 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanelMain; 151 | private System.Windows.Forms.Panel panelCommands; 152 | private System.Windows.Forms.Button buttonCancel; 153 | private System.Windows.Forms.Button buttonOk; 154 | private System.Windows.Forms.ListView listViewMain; 155 | private System.Windows.Forms.ColumnHeader columnDescription; 156 | private System.Windows.Forms.ColumnHeader columnFlags; 157 | private System.Windows.Forms.ColumnHeader columnRevision; 158 | private System.Windows.Forms.ColumnHeader columnVRAM; 159 | } 160 | } -------------------------------------------------------------------------------- /Duplicator/Duplicator/H264Encoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using Microsoft.Win32; 7 | using SharpDX.MediaFoundation; 8 | using SharpDX.Multimedia; 9 | 10 | namespace Duplicator 11 | { 12 | public class H264Encoder : IDisposable 13 | { 14 | private H264Encoder(Activate activate) 15 | { 16 | Activate = activate; 17 | FriendlyName = activate.Get(TransformAttributeKeys.MftFriendlyNameAttribute); 18 | Clsid = activate.Get(TransformAttributeKeys.MftTransformClsidAttribute); 19 | Flags = (TransformEnumFlag)activate.Get(TransformAttributeKeys.TransformFlagsAttribute); 20 | var list = new List(); 21 | var inputTypes = activate.Get(TransformAttributeKeys.MftInputTypesAttributes); 22 | for (int j = 0; j < inputTypes.Length; j += 32) // two guids 23 | { 24 | var majorType = new Guid(Enumerable.Range(0, 16).Select(index => Marshal.ReadByte(inputTypes, j + index)).ToArray()); // Should be video in this context 25 | var subType = new Guid(Enumerable.Range(0, 16).Select(index => Marshal.ReadByte(inputTypes, j + 16 + index)).ToArray()); 26 | list.Add(GetFourCC(subType)); 27 | } 28 | 29 | list.Sort(); 30 | InputTypes = list; 31 | try 32 | { 33 | using (var tf = activate.ActivateObject()) 34 | { 35 | IsBuiltin = IsBuiltinEncoder(tf); 36 | IsDirect3D11Aware = IsDirect3D11AwareEncoder(tf); 37 | IsHardwareBased = IsHardwareBasedEncoder(tf); 38 | } 39 | } 40 | catch 41 | { 42 | // do nothing 43 | } 44 | 45 | using (var key = Registry.ClassesRoot.OpenSubKey(Path.Combine("CLSID", Clsid.ToString("B"), "InprocServer32"))) 46 | { 47 | if (key != null) 48 | { 49 | DllPath = key.GetValue(null) as string; 50 | } 51 | } 52 | } 53 | 54 | public Activate Activate { get; } 55 | public string FriendlyName { get; } 56 | public Guid Clsid { get; } 57 | public IEnumerable InputTypes { get; } 58 | public TransformEnumFlag Flags { get; } 59 | public bool IsBuiltin { get; } 60 | public bool IsDirect3D11Aware { get; } 61 | public bool IsHardwareBased { get; } 62 | public string DllPath { get; } 63 | public override string ToString() => FriendlyName; 64 | 65 | public Transform GetTransform() => Activate.ActivateObject(); 66 | 67 | public void Dispose() => Activate.Dispose(); 68 | 69 | private static IntPtr GetTransformPtr(SinkWriter writer, int streamIndex) 70 | { 71 | if (writer == null) 72 | throw new ArgumentNullException(nameof(writer)); 73 | 74 | var tf = IntPtr.Zero; 75 | try 76 | { 77 | writer.GetServiceForStream(streamIndex, Guid.Empty, typeof(Transform).GUID, out tf); 78 | } 79 | catch 80 | { 81 | // do nothing 82 | } 83 | return tf; 84 | } 85 | 86 | public static Transform GetTransform(SinkWriter writer, int streamIndex) 87 | { 88 | var ptr = GetTransformPtr(writer, streamIndex); 89 | return ptr != IntPtr.Zero ? new Transform(ptr) : null; 90 | } 91 | 92 | public static bool IsBuiltinEncoder(SinkWriter writer, int streamIndex) 93 | { 94 | var ptr = GetTransformPtr(writer, streamIndex); 95 | if (ptr == IntPtr.Zero) 96 | return false; 97 | 98 | return Marshal.GetObjectForIUnknown(ptr) as IMFObjectInformation != null; 99 | } 100 | 101 | public static TOutputStreamInformation GetOutputStreamInfo(SinkWriter writer, int streamIndex) 102 | { 103 | using (var transform = GetTransform(writer, streamIndex)) 104 | { 105 | if (transform == null) 106 | return new TOutputStreamInformation(); 107 | 108 | transform.GetOutputStreamInfo(streamIndex, out TOutputStreamInformation info); 109 | return info; 110 | } 111 | } 112 | 113 | public static bool IsDirect3D11AwareEncoder(SinkWriter writer, int streamIndex) 114 | { 115 | using (var transform = GetTransform(writer, streamIndex)) 116 | { 117 | if (transform == null) 118 | return false; 119 | 120 | return IsDirect3D11AwareEncoder(transform); 121 | } 122 | } 123 | 124 | public static string GetEncoderFriendlyName(SinkWriter writer, int streamIndex) 125 | { 126 | try 127 | { 128 | using (var transform = GetTransform(writer, streamIndex)) 129 | { 130 | if (transform != null) 131 | { 132 | if (IsBuiltinEncoder(transform)) 133 | return Enumerate().First(e => e.IsBuiltin).FriendlyName; 134 | 135 | var clsid = transform.Attributes.Get(TransformAttributeKeys.MftTransformClsidAttribute); 136 | return Enumerate().First(e => e.Clsid == clsid).FriendlyName; 137 | } 138 | } 139 | } 140 | catch 141 | { 142 | // continue 143 | } 144 | return "Unknown"; 145 | } 146 | 147 | public static bool IsHardwareBasedEncoder(SinkWriter writer, int streamIndex) 148 | { 149 | using (var transform = GetTransform(writer, streamIndex)) 150 | { 151 | if (transform == null) 152 | return false; 153 | 154 | return IsHardwareBasedEncoder(transform); 155 | } 156 | } 157 | 158 | public static bool IsHardwareBasedEncoder(Transform transform) 159 | { 160 | if (transform == null) 161 | throw new ArgumentNullException(nameof(transform)); 162 | 163 | return EnumerateAttributes(transform.Attributes).Any(a => a.Key == TransformAttributeKeys.MftEnumHardwareUrlAttribute.Guid); 164 | } 165 | 166 | public static bool IsDirect3D11AwareEncoder(Transform transform) 167 | { 168 | if (transform == null) 169 | throw new ArgumentNullException(nameof(transform)); 170 | 171 | return EnumerateAttributes(transform.Attributes).Any(a => a.Key == TransformAttributeKeys.D3D11Aware.Guid && a.Value.Equals(1)); 172 | } 173 | 174 | public static IReadOnlyDictionary GetAttributes(MediaAttributes atts) 175 | { 176 | var dic = new Dictionary(); 177 | if (atts != null) 178 | { 179 | for (int i = 0; i < atts.Count; i++) 180 | { 181 | object value = atts.GetByIndex(i, out Guid guid); 182 | dic[guid] = value; 183 | } 184 | } 185 | return dic; 186 | } 187 | 188 | internal static IEnumerable> EnumerateAttributes(MediaAttributes atts) 189 | { 190 | for (int i = 0; i < atts.Count; i++) 191 | { 192 | object value = atts.GetByIndex(i, out Guid guid); 193 | yield return new KeyValuePair(guid, value); 194 | } 195 | } 196 | 197 | public static bool IsBuiltinEncoder(Transform transform) 198 | { 199 | if (transform == null) 200 | throw new ArgumentNullException(nameof(transform)); 201 | 202 | return Marshal.GetObjectForIUnknown(transform.NativePointer) as IMFObjectInformation != null; 203 | } 204 | 205 | public static IEnumerable Enumerate() => Enumerate(TransformEnumFlag.All); 206 | public static IEnumerable Enumerate(TransformEnumFlag flags) 207 | { 208 | var output = new TRegisterTypeInformation(); 209 | output.GuidMajorType = MediaTypeGuids.Video; 210 | output.GuidSubtype = VideoFormatGuids.FromFourCC(new FourCC("H264")); 211 | foreach (var activate in MediaFactory.FindTransform(TransformCategoryGuids.VideoEncoder, flags, null, output)) 212 | { 213 | yield return new H264Encoder(activate); 214 | } 215 | } 216 | 217 | private static string GetFourCC(Guid guid) 218 | { 219 | var s = guid.ToString(); 220 | if (s.EndsWith("0000-0010-8000-00aa00389b71")) 221 | { 222 | var bytes = guid.ToByteArray(); 223 | if (bytes.Take(4).Any(b => b < 32 || b > 127)) 224 | return s; 225 | 226 | return new string(bytes.Take(4).Select(b => (char)b).ToArray()); 227 | } 228 | 229 | return s; 230 | } 231 | 232 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("CE6BE8E7-D757-435F-9DE9-BE3EF330B805")] 233 | private interface IMFObjectInformation { } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/Main.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace WinDuplicator 2 | { 3 | partial class Main 4 | { 5 | #region Windows Form Designer generated code 6 | 7 | /// 8 | /// Required method for Designer support - do not modify 9 | /// the contents of this method with the code editor. 10 | /// 11 | private void InitializeComponent() 12 | { 13 | this.splitContainerMain = new System.Windows.Forms.SplitContainer(); 14 | this.tableLayoutPanelMain = new System.Windows.Forms.TableLayoutPanel(); 15 | this.panelCommands = new System.Windows.Forms.Panel(); 16 | this.buttonAbout = new System.Windows.Forms.Button(); 17 | this.checkBoxRecord = new System.Windows.Forms.CheckBox(); 18 | this.checkBoxDuplicate = new System.Windows.Forms.CheckBox(); 19 | this.buttonQuit = new System.Windows.Forms.Button(); 20 | this.propertyGridMain = new System.Windows.Forms.PropertyGrid(); 21 | this.textBoxStatus = new System.Windows.Forms.TextBox(); 22 | ((System.ComponentModel.ISupportInitialize)(this.splitContainerMain)).BeginInit(); 23 | this.splitContainerMain.Panel2.SuspendLayout(); 24 | this.splitContainerMain.SuspendLayout(); 25 | this.tableLayoutPanelMain.SuspendLayout(); 26 | this.panelCommands.SuspendLayout(); 27 | this.SuspendLayout(); 28 | // 29 | // splitContainerMain 30 | // 31 | this.splitContainerMain.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; 32 | this.splitContainerMain.Dock = System.Windows.Forms.DockStyle.Fill; 33 | this.splitContainerMain.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; 34 | this.splitContainerMain.Location = new System.Drawing.Point(0, 0); 35 | this.splitContainerMain.Name = "splitContainerMain"; 36 | // 37 | // splitContainerMain.Panel2 38 | // 39 | this.splitContainerMain.Panel2.Controls.Add(this.tableLayoutPanelMain); 40 | this.splitContainerMain.Size = new System.Drawing.Size(806, 513); 41 | this.splitContainerMain.SplitterDistance = 455; 42 | this.splitContainerMain.TabIndex = 0; 43 | // 44 | // tableLayoutPanelMain 45 | // 46 | this.tableLayoutPanelMain.ColumnCount = 1; 47 | this.tableLayoutPanelMain.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); 48 | this.tableLayoutPanelMain.Controls.Add(this.panelCommands, 0, 2); 49 | this.tableLayoutPanelMain.Controls.Add(this.propertyGridMain, 0, 0); 50 | this.tableLayoutPanelMain.Controls.Add(this.textBoxStatus, 0, 1); 51 | this.tableLayoutPanelMain.Dock = System.Windows.Forms.DockStyle.Fill; 52 | this.tableLayoutPanelMain.Location = new System.Drawing.Point(0, 0); 53 | this.tableLayoutPanelMain.Name = "tableLayoutPanelMain"; 54 | this.tableLayoutPanelMain.RowCount = 3; 55 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); 56 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 80F)); 57 | this.tableLayoutPanelMain.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F)); 58 | this.tableLayoutPanelMain.Size = new System.Drawing.Size(345, 511); 59 | this.tableLayoutPanelMain.TabIndex = 0; 60 | // 61 | // panelCommands 62 | // 63 | this.panelCommands.Controls.Add(this.buttonAbout); 64 | this.panelCommands.Controls.Add(this.checkBoxRecord); 65 | this.panelCommands.Controls.Add(this.checkBoxDuplicate); 66 | this.panelCommands.Controls.Add(this.buttonQuit); 67 | this.panelCommands.Dock = System.Windows.Forms.DockStyle.Fill; 68 | this.panelCommands.Location = new System.Drawing.Point(0, 481); 69 | this.panelCommands.Margin = new System.Windows.Forms.Padding(0); 70 | this.panelCommands.Name = "panelCommands"; 71 | this.panelCommands.Size = new System.Drawing.Size(345, 30); 72 | this.panelCommands.TabIndex = 2; 73 | // 74 | // buttonAbout 75 | // 76 | this.buttonAbout.DialogResult = System.Windows.Forms.DialogResult.OK; 77 | this.buttonAbout.Location = new System.Drawing.Point(3, 4); 78 | this.buttonAbout.Name = "buttonAbout"; 79 | this.buttonAbout.Size = new System.Drawing.Size(75, 23); 80 | this.buttonAbout.TabIndex = 0; 81 | this.buttonAbout.Text = "&About..."; 82 | this.buttonAbout.UseVisualStyleBackColor = true; 83 | this.buttonAbout.Click += new System.EventHandler(this.buttonAbout_Click); 84 | // 85 | // checkBoxRecord 86 | // 87 | this.checkBoxRecord.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 88 | this.checkBoxRecord.AutoSize = true; 89 | this.checkBoxRecord.Location = new System.Drawing.Point(101, 8); 90 | this.checkBoxRecord.Name = "checkBoxRecord"; 91 | this.checkBoxRecord.Size = new System.Drawing.Size(61, 17); 92 | this.checkBoxRecord.TabIndex = 1; 93 | this.checkBoxRecord.Text = "&Record"; 94 | this.checkBoxRecord.UseVisualStyleBackColor = true; 95 | this.checkBoxRecord.CheckedChanged += new System.EventHandler(this.checkBoxRecord_CheckedChanged); 96 | // 97 | // checkBoxDuplicate 98 | // 99 | this.checkBoxDuplicate.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 100 | this.checkBoxDuplicate.AutoSize = true; 101 | this.checkBoxDuplicate.Location = new System.Drawing.Point(180, 8); 102 | this.checkBoxDuplicate.Name = "checkBoxDuplicate"; 103 | this.checkBoxDuplicate.Size = new System.Drawing.Size(71, 17); 104 | this.checkBoxDuplicate.TabIndex = 2; 105 | this.checkBoxDuplicate.Text = "&Duplicate"; 106 | this.checkBoxDuplicate.UseVisualStyleBackColor = true; 107 | this.checkBoxDuplicate.CheckedChanged += new System.EventHandler(this.checkBoxDuplicate_CheckedChanged); 108 | // 109 | // buttonQuit 110 | // 111 | this.buttonQuit.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 112 | this.buttonQuit.DialogResult = System.Windows.Forms.DialogResult.OK; 113 | this.buttonQuit.Location = new System.Drawing.Point(266, 4); 114 | this.buttonQuit.Name = "buttonQuit"; 115 | this.buttonQuit.Size = new System.Drawing.Size(75, 23); 116 | this.buttonQuit.TabIndex = 3; 117 | this.buttonQuit.Text = "&Quit"; 118 | this.buttonQuit.UseVisualStyleBackColor = true; 119 | this.buttonQuit.Click += new System.EventHandler(this.buttonQuit_Click); 120 | // 121 | // propertyGridMain 122 | // 123 | this.propertyGridMain.Dock = System.Windows.Forms.DockStyle.Fill; 124 | this.propertyGridMain.HelpVisible = false; 125 | this.propertyGridMain.Location = new System.Drawing.Point(3, 3); 126 | this.propertyGridMain.Name = "propertyGridMain"; 127 | this.propertyGridMain.Size = new System.Drawing.Size(339, 395); 128 | this.propertyGridMain.TabIndex = 0; 129 | this.propertyGridMain.ToolbarVisible = false; 130 | // 131 | // textBoxStatus 132 | // 133 | this.textBoxStatus.Dock = System.Windows.Forms.DockStyle.Fill; 134 | this.textBoxStatus.Font = new System.Drawing.Font("Lucida Console", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 135 | this.textBoxStatus.Location = new System.Drawing.Point(3, 404); 136 | this.textBoxStatus.Multiline = true; 137 | this.textBoxStatus.Name = "textBoxStatus"; 138 | this.textBoxStatus.ReadOnly = true; 139 | this.textBoxStatus.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; 140 | this.textBoxStatus.Size = new System.Drawing.Size(339, 74); 141 | this.textBoxStatus.TabIndex = 3; 142 | // 143 | // Main 144 | // 145 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 146 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 147 | this.ClientSize = new System.Drawing.Size(806, 513); 148 | this.Controls.Add(this.splitContainerMain); 149 | this.Name = "Main"; 150 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 151 | this.Text = "Duplicator"; 152 | this.splitContainerMain.Panel2.ResumeLayout(false); 153 | ((System.ComponentModel.ISupportInitialize)(this.splitContainerMain)).EndInit(); 154 | this.splitContainerMain.ResumeLayout(false); 155 | this.tableLayoutPanelMain.ResumeLayout(false); 156 | this.tableLayoutPanelMain.PerformLayout(); 157 | this.panelCommands.ResumeLayout(false); 158 | this.panelCommands.PerformLayout(); 159 | this.ResumeLayout(false); 160 | 161 | } 162 | 163 | #endregion 164 | 165 | private System.Windows.Forms.SplitContainer splitContainerMain; 166 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanelMain; 167 | private System.Windows.Forms.PropertyGrid propertyGridMain; 168 | private System.Windows.Forms.Panel panelCommands; 169 | private System.Windows.Forms.Button buttonAbout; 170 | private System.Windows.Forms.CheckBox checkBoxRecord; 171 | private System.Windows.Forms.CheckBox checkBoxDuplicate; 172 | private System.Windows.Forms.Button buttonQuit; 173 | private System.Windows.Forms.TextBox textBoxStatus; 174 | } 175 | } 176 | 177 | -------------------------------------------------------------------------------- /Duplicator/Duplicator/DuplicatorOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | using SharpDX.DXGI; 8 | 9 | namespace Duplicator 10 | { 11 | public class DuplicatorOptions : DictionaryObject 12 | { 13 | public const string DisplayCategory = "Duplicated Display"; 14 | public const string RecordingCategory = "Recording"; 15 | public const string SoundRecordingCategory = "Sound Recording"; 16 | public const string InputCategory = "Input"; 17 | public const string DiagnosticsCategory = "Diagnostics"; 18 | private const string DefaultFileFormat = "Capture_{0:yyyy_MM_dd_HH_mm_ss}"; 19 | 20 | public DuplicatorOptions() 21 | { 22 | Adapter1 adapter; 23 | using (var fac = new Factory1()) 24 | { 25 | adapter = fac.Adapters1.FirstOrDefault(a => !a.Description1.Flags.HasFlag(AdapterFlags.Software)); 26 | if (adapter == null) 27 | { 28 | adapter = fac.Adapters1.First(); 29 | } 30 | } 31 | 32 | Adapter = adapter.Description.Description; 33 | Output = adapter.Outputs.First().Description.DeviceName; 34 | FrameAcquisitionTimeout = 500; 35 | AudioAcquisitionTimeout = 500; 36 | ShowCursor = true; 37 | PreserveRatio = true; 38 | RecordingFrameRate = 0; 39 | OutputFileFormat = DefaultFileFormat; 40 | OutputDirectoryPath = GetDefaultOutputDirectoryPath(); 41 | EnableHardwareTransforms = true; 42 | CaptureSound = true; 43 | CaptureMicrophone = false; 44 | SoundDevice = AudioCapture.GetSpeakersDevice()?.FriendlyName; 45 | MicrophoneDevice = AudioCapture.GetMicrophoneDevice()?.FriendlyName; 46 | UseRecordingQueue = false; 47 | } 48 | 49 | [DisplayName("File Format")] 50 | [Category(RecordingCategory)] 51 | [DefaultValue(DefaultFileFormat)] 52 | public virtual string OutputFileFormat { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); } 53 | 54 | [DisplayName("Directory Path")] 55 | [Category(RecordingCategory)] 56 | public virtual string OutputDirectoryPath { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); } 57 | 58 | [Browsable(false)] // doesn't work 59 | [DisplayName("Use Intermediate Queue")] 60 | [Category(RecordingCategory)] 61 | [DefaultValue(false)] 62 | public virtual bool UseRecordingQueue { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); } 63 | 64 | //[Browsable(false)] // not used 65 | [DisplayName("Frame Rate")] 66 | [Category(RecordingCategory)] 67 | [DefaultValue(0f)] 68 | public virtual float RecordingFrameRate { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(Math.Max(0f, value)); } 69 | 70 | [DisplayName("Enable Hardware Encoding")] 71 | [Category(RecordingCategory)] 72 | [DefaultValue(true)] 73 | public virtual bool EnableHardwareTransforms { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); } 74 | 75 | [DisplayName("Capture Sound")] 76 | [Category(SoundRecordingCategory)] 77 | [DefaultValue(true)] 78 | public virtual bool CaptureSound { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); } 79 | 80 | [DisplayName("Capture Microphone")] 81 | [Category(SoundRecordingCategory)] 82 | [DefaultValue(false)] 83 | public virtual bool CaptureMicrophone { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); } 84 | 85 | [DisplayName("Sound Device")] 86 | [Category(SoundRecordingCategory)] 87 | public virtual string SoundDevice { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); } 88 | 89 | [DisplayName("Microphone Device")] 90 | [Category(SoundRecordingCategory)] 91 | public virtual string MicrophoneDevice { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); } 92 | 93 | [DisplayName("Disable Throttling")] 94 | [Category(RecordingCategory)] 95 | [DefaultValue(false)] 96 | public virtual bool DisableThrottling { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); } 97 | 98 | [DisplayName("Low Latency")] 99 | [Category(RecordingCategory)] 100 | [DefaultValue(false)] 101 | public virtual bool LowLatency { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); } 102 | 103 | [DisplayName("Video Adapter")] 104 | [Category(InputCategory)] 105 | public virtual string Adapter { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); } 106 | 107 | [DisplayName("Video Monitor")] 108 | [Category(InputCategory)] 109 | [TypeConverter(typeof(DisplayDeviceTypeConverter))] 110 | public virtual string Output { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); } 111 | 112 | [DisplayName("Show Cursor")] 113 | [Category(DisplayCategory)] 114 | [DefaultValue(true)] 115 | public virtual bool ShowCursor { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); } 116 | 117 | [DisplayName("Proportional Cursor")] 118 | [Category(DisplayCategory)] 119 | [DefaultValue(false)] 120 | public virtual bool IsCursorProportional { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); } 121 | 122 | [DisplayName("Show Acquisition Rate")] 123 | [Category(DiagnosticsCategory)] 124 | [DefaultValue(false)] 125 | public virtual bool ShowInputFps { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); } 126 | 127 | [DisplayName("Preserve Input Ratio")] 128 | [Category(DisplayCategory)] 129 | [DefaultValue(true)] 130 | public virtual bool PreserveRatio { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); } 131 | 132 | [DisplayName("Show Accumulated Frames")] 133 | [Category(DiagnosticsCategory)] 134 | [DefaultValue(false)] 135 | public virtual bool ShowAccumulatedFrames { get => DictionaryObjectGetPropertyValue(); set => DictionaryObjectSetPropertyValue(value); } 136 | 137 | [DisplayName("Frame Acquisition Timeout")] 138 | [Category(InputCategory)] 139 | [DefaultValue(500)] 140 | public virtual int FrameAcquisitionTimeout 141 | { 142 | get => DictionaryObjectGetPropertyValue(); 143 | set 144 | { 145 | // we don't want infinite 146 | DictionaryObjectSetPropertyValue(Math.Max(0, value)); 147 | } 148 | } 149 | 150 | [DisplayName("Audio Acquisition Timeout")] 151 | [Category(InputCategory)] 152 | [DefaultValue(500)] 153 | public virtual int AudioAcquisitionTimeout 154 | { 155 | get => DictionaryObjectGetPropertyValue(); 156 | set 157 | { 158 | // we don't want infinite 159 | DictionaryObjectSetPropertyValue(Math.Max(0, value)); 160 | } 161 | } 162 | 163 | public AudioCapture.AudioDevice GetSoundDevice() => AudioCapture.GetDevices(AudioCapture.DataFlow.All).FirstOrDefault(d => d.FriendlyName == SoundDevice); 164 | public AudioCapture.AudioDevice GetMicrophoneDevice() => AudioCapture.GetDevices(AudioCapture.DataFlow.All).FirstOrDefault(d => d.FriendlyName == MicrophoneDevice); 165 | 166 | public Adapter1 GetAdapter() 167 | { 168 | using (var fac = new Factory1()) 169 | { 170 | return fac.Adapters1.FirstOrDefault(a => a.Description.Description == Adapter); 171 | } 172 | } 173 | 174 | public Output1 GetOutput() 175 | { 176 | using (var adapter = GetAdapter()) 177 | { 178 | var output = adapter.Outputs.FirstOrDefault(o => o.Description.DeviceName == Output); 179 | if (output == null) 180 | return null; // this can happen if the adapter is not connected to a display 181 | 182 | return output.QueryInterface(); 183 | } 184 | } 185 | 186 | public static string GetDefaultOutputDirectoryPath() 187 | { 188 | var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Duplicator"); 189 | if (!Directory.Exists(dir)) 190 | { 191 | Directory.CreateDirectory(dir); 192 | } 193 | return dir; 194 | } 195 | 196 | public string GetNewFilePath() 197 | { 198 | var dir = OutputDirectoryPath; 199 | if (string.IsNullOrWhiteSpace(dir)) 200 | { 201 | dir = GetDefaultOutputDirectoryPath(); 202 | } 203 | 204 | string format = OutputFileFormat; 205 | if (string.IsNullOrEmpty(OutputFileFormat)) 206 | { 207 | format = DefaultFileFormat; 208 | } 209 | 210 | string fileName = string.Format(format, DateTime.Now); 211 | return Path.Combine(dir, fileName); 212 | } 213 | 214 | public static string GetDisplayDeviceName(string deviceName) 215 | { 216 | if (deviceName == null) 217 | throw new ArgumentNullException(nameof(deviceName)); 218 | 219 | var dd = new DISPLAY_DEVICE(); 220 | dd.cb = Marshal.SizeOf(); 221 | if (!EnumDisplayDevices(deviceName, 0, ref dd, 0)) 222 | return deviceName; 223 | 224 | return dd.DeviceString; 225 | } 226 | 227 | private class DisplayDeviceTypeConverter : TypeConverter 228 | { 229 | public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) 230 | { 231 | var name = value as string; 232 | if (name != null) 233 | return GetDisplayDeviceName(name); 234 | 235 | return base.ConvertTo(context, culture, value, destinationType); 236 | } 237 | } 238 | 239 | [Flags] 240 | private enum DISPLAY_DEVICE_FLAGS 241 | { 242 | DISPLAY_DEVICE_ATTACHED_TO_DESKTOP = 0x00000001, 243 | DISPLAY_DEVICE_MULTI_DRIVER = 0x00000002, 244 | DISPLAY_DEVICE_PRIMARY_DEVICE = 0x00000004, 245 | DISPLAY_DEVICE_MIRRORING_DRIVER = 0x00000008, 246 | DISPLAY_DEVICE_VGA_COMPATIBLE = 0x00000010, 247 | DISPLAY_DEVICE_REMOVABLE = 0x00000020, 248 | DISPLAY_DEVICE_ACC_DRIVER = 0x00000040, 249 | DISPLAY_DEVICE_MODESPRUNED = 0x08000000, 250 | DISPLAY_DEVICE_RDPUDD = 0x01000000, 251 | DISPLAY_DEVICE_REMOTE = 0x04000000, 252 | DISPLAY_DEVICE_DISCONNECT = 0x02000000, 253 | DISPLAY_DEVICE_TS_COMPATIBLE = 0x00200000, 254 | DISPLAY_DEVICE_UNSAFE_MODES_ON = 0x00080000, 255 | DISPLAY_DEVICE_ACTIVE = 0x00000001, 256 | DISPLAY_DEVICE_ATTACHED = 0x00000002, 257 | } 258 | 259 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 260 | private struct DISPLAY_DEVICE 261 | { 262 | public int cb; 263 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] 264 | public string DeviceName; 265 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] 266 | public string DeviceString; 267 | public DISPLAY_DEVICE_FLAGS StateFlags; 268 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] 269 | public string DeviceID; 270 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] 271 | public string DeviceKey; 272 | } 273 | 274 | [DllImport("user32", CharSet = CharSet.Auto)] 275 | private static extern bool EnumDisplayDevices(string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /Duplicator/WinDuplicator/WinDuplicatorOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Drawing.Design; 5 | using System.Globalization; 6 | using System.Runtime.InteropServices; 7 | using System.Windows.Forms; 8 | using System.Windows.Forms.Design; 9 | using Duplicator; 10 | 11 | namespace WinDuplicator 12 | { 13 | public class WinDuplicatorOptions : DuplicatorOptions 14 | { 15 | [Editor(typeof(OutputEditor), typeof(UITypeEditor))] 16 | public override string Output { get => base.Output; set => base.Output = value; } 17 | 18 | [Editor(typeof(AdapterEditor), typeof(UITypeEditor))] 19 | public override string Adapter { get => base.Adapter; set => base.Adapter = value; } 20 | 21 | [Editor(typeof(RenderAudioDeviceEditor), typeof(UITypeEditor))] 22 | public override string SoundDevice { get => base.SoundDevice; set => base.SoundDevice = value; } 23 | 24 | [Editor(typeof(CaptureAudioDeviceEditor), typeof(UITypeEditor))] 25 | public override string MicrophoneDevice { get => base.MicrophoneDevice; set => base.MicrophoneDevice = value; } 26 | 27 | [TypeConverter(typeof(FrameRateConverter))] 28 | public override float RecordingFrameRate { get => base.RecordingFrameRate; set => base.RecordingFrameRate = value; } 29 | 30 | [Editor(typeof(FolderNameEditor), typeof(UITypeEditor))] 31 | public override string OutputDirectoryPath { get => base.OutputDirectoryPath; set => base.OutputDirectoryPath = value; } 32 | 33 | private class FrameRateConverter : TypeConverter 34 | { 35 | private const string Automatic = ""; 36 | 37 | public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) => true; 38 | 39 | public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) 40 | { 41 | if (Automatic.Equals(value)) 42 | return 0f; 43 | 44 | return float.Parse((string)value); 45 | } 46 | 47 | public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) 48 | { 49 | if (0f.Equals(value)) 50 | return Automatic; 51 | 52 | return base.ConvertTo(context, culture, value, destinationType); 53 | } 54 | 55 | public override bool GetStandardValuesSupported(ITypeDescriptorContext context) => true; 56 | public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) 57 | { 58 | var list = new List(); 59 | list.Add(0f); 60 | list.Add(23.976f); 61 | list.Add(24f); 62 | list.Add(25); 63 | list.Add(29.97f); 64 | list.Add(30); 65 | list.Add(60); 66 | return new StandardValuesCollection(list); 67 | } 68 | } 69 | 70 | private class OutputEditor : UITypeEditor 71 | { 72 | public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.Modal; 73 | 74 | public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) 75 | { 76 | var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)); 77 | if (editorService == null) 78 | return base.EditValue(context, provider, value); 79 | 80 | var options = context.Instance as DuplicatorOptions; 81 | if (options == null) 82 | return base.EditValue(context, provider, value); 83 | 84 | var adapter = options.GetAdapter(); 85 | if (adapter == null) 86 | return base.EditValue(context, provider, value); 87 | 88 | var form = new ChooseOutput(adapter, value as string); 89 | if (editorService.ShowDialog(form) == DialogResult.OK) 90 | return form.DeviceName; 91 | 92 | return base.EditValue(context, provider, value); 93 | } 94 | } 95 | 96 | private class AdapterEditor : UITypeEditor 97 | { 98 | public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.Modal; 99 | 100 | public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) 101 | { 102 | var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)); 103 | if (editorService == null) 104 | return base.EditValue(context, provider, value); 105 | 106 | var form = new ChooseAdapter(value as string); 107 | if (editorService.ShowDialog(form) == DialogResult.OK) 108 | return form.Adapter.Description1.Description; 109 | 110 | return base.EditValue(context, provider, value); 111 | } 112 | } 113 | 114 | private class RenderAudioDeviceEditor : UITypeEditor 115 | { 116 | public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.Modal; 117 | 118 | public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) 119 | { 120 | var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)); 121 | if (editorService == null) 122 | return base.EditValue(context, provider, value); 123 | 124 | var form = new ChooseAudioDevice(value as string, AudioCapture.DataFlow.Render); 125 | if (editorService.ShowDialog(form) == DialogResult.OK) 126 | return form.Device.FriendlyName; 127 | 128 | return base.EditValue(context, provider, value); 129 | } 130 | } 131 | 132 | private class CaptureAudioDeviceEditor : UITypeEditor 133 | { 134 | public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.Modal; 135 | 136 | public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) 137 | { 138 | var editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService)); 139 | if (editorService == null) 140 | return base.EditValue(context, provider, value); 141 | 142 | var form = new ChooseAudioDevice(value as string, AudioCapture.DataFlow.Capture); 143 | if (editorService.ShowDialog(form) == DialogResult.OK) 144 | return form.Device.FriendlyName; 145 | 146 | return base.EditValue(context, provider, value); 147 | } 148 | } 149 | 150 | private class FolderNameEditor : UITypeEditor 151 | { 152 | public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.Modal; 153 | 154 | public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) 155 | { 156 | var browser = new FolderBrowser(); 157 | if (value != null) 158 | { 159 | browser.DirectoryPath = string.Format("{0}", value); 160 | } 161 | 162 | if (browser.ShowDialog(null) == DialogResult.OK) 163 | return browser.DirectoryPath; 164 | 165 | return value; 166 | } 167 | } 168 | 169 | public class FolderBrowser 170 | { 171 | public string DirectoryPath { get; set; } 172 | 173 | public DialogResult ShowDialog(IWin32Window owner) 174 | { 175 | var hwndOwner = owner != null ? owner.Handle : GetActiveWindow(); 176 | var dialog = (IFileOpenDialog)new FileOpenDialog(); 177 | IShellItem item; 178 | if (!string.IsNullOrEmpty(DirectoryPath)) 179 | { 180 | SHCreateItemFromParsingName(DirectoryPath, IntPtr.Zero, typeof(IShellItem).GUID, out item); 181 | if (item != null) 182 | { 183 | dialog.SetFolder(item); 184 | } 185 | } 186 | 187 | dialog.SetOptions(FOS.FOS_PICKFOLDERS | FOS.FOS_FORCEFILESYSTEM); 188 | int hr = dialog.Show(hwndOwner); 189 | if (hr == ERROR_CANCELLED) 190 | return DialogResult.Cancel; 191 | 192 | if (hr != 0) 193 | return DialogResult.Abort; 194 | 195 | dialog.GetResult(out item); 196 | item.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out string path); 197 | DirectoryPath = path; 198 | return DialogResult.OK; 199 | } 200 | 201 | [DllImport("shell32")] 202 | private static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IShellItem ppv); 203 | 204 | [DllImport("user32")] 205 | private static extern IntPtr GetActiveWindow(); 206 | 207 | private const int ERROR_CANCELLED = unchecked((int)0x800704C7); 208 | 209 | [ComImport] 210 | [Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")] 211 | private class FileOpenDialog { } 212 | 213 | [Guid("42f85136-db7e-439c-85f1-e4075d135fc8")] 214 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 215 | private interface IFileOpenDialog 216 | { 217 | [PreserveSig] 218 | int Show(IntPtr parent); // IModalWindow 219 | void SetFileTypes(); // not fully defined 220 | void SetFileTypeIndex([In] uint iFileType); 221 | void GetFileTypeIndex(out uint piFileType); 222 | void Advise(); // not fully defined 223 | void Unadvise(); 224 | void SetOptions(FOS fos); 225 | void GetOptions(out FOS pfos); 226 | void SetDefaultFolder(IShellItem psi); 227 | void SetFolder(IShellItem psi); 228 | void GetFolder(out IShellItem ppsi); 229 | void GetCurrentSelection(out IShellItem ppsi); 230 | void SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName); 231 | void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); 232 | void SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle); 233 | void SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText); 234 | void SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel); 235 | void GetResult(out IShellItem ppsi); 236 | void AddPlace(IShellItem psi, int alignment); 237 | void SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); 238 | void Close(int hr); 239 | void SetClientGuid(); // not fully defined 240 | void ClearClientData(); 241 | void SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); 242 | void GetResults([MarshalAs(UnmanagedType.Interface)] out IntPtr ppenum); // not fully defined 243 | void GetSelectedItems([MarshalAs(UnmanagedType.Interface)] out IntPtr ppsai); // not fully defined 244 | } 245 | 246 | [Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE")] 247 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 248 | private interface IShellItem 249 | { 250 | void BindToHandler(); // not fully defined 251 | void GetParent(); // not fully defined 252 | void GetDisplayName(SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName); 253 | void GetAttributes(); // not fully defined 254 | void Compare(); // not fully defined 255 | } 256 | 257 | private enum SIGDN : uint 258 | { 259 | SIGDN_FILESYSPATH = 0x80058000, 260 | SIGDN_NORMALDISPLAY = 0, 261 | SIGDN_PARENTRELATIVE = 0x80080001, 262 | SIGDN_PARENTRELATIVEEDITING = 0x80031001, 263 | SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007c001, 264 | SIGDN_PARENTRELATIVEPARSING = 0x80018001, 265 | SIGDN_URL = 0x80068000 266 | } 267 | 268 | [Flags] 269 | private enum FOS 270 | { 271 | FOS_ALLNONSTORAGEITEMS = 0x80, 272 | FOS_ALLOWMULTISELECT = 0x200, 273 | FOS_CREATEPROMPT = 0x2000, 274 | FOS_DEFAULTNOMINIMODE = 0x20000000, 275 | FOS_DONTADDTORECENT = 0x2000000, 276 | FOS_FILEMUSTEXIST = 0x1000, 277 | FOS_FORCEFILESYSTEM = 0x40, 278 | FOS_FORCESHOWHIDDEN = 0x10000000, 279 | FOS_HIDEMRUPLACES = 0x20000, 280 | FOS_HIDEPINNEDPLACES = 0x40000, 281 | FOS_NOCHANGEDIR = 8, 282 | FOS_NODEREFERENCELINKS = 0x100000, 283 | FOS_NOREADONLYRETURN = 0x8000, 284 | FOS_NOTESTFILECREATE = 0x10000, 285 | FOS_NOVALIDATE = 0x100, 286 | FOS_OVERWRITEPROMPT = 2, 287 | FOS_PATHMUSTEXIST = 0x800, 288 | FOS_PICKFOLDERS = 0x20, 289 | FOS_SHAREAWARE = 0x4000, 290 | FOS_STRICTFILETYPES = 4 291 | } 292 | } 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /Duplicator/Duplicator/AudioCapture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | using System.Threading; 8 | using Microsoft.Win32.SafeHandles; 9 | 10 | namespace Duplicator 11 | { 12 | public sealed class AudioCapture : IDisposable 13 | { 14 | private AutoResetEvent _dataEvent = new AutoResetEvent(false); 15 | private AutoResetEvent _stopEvent; 16 | private AudioDevice _device; 17 | private bool _stopping; 18 | private int _waitTimeout; 19 | 20 | public event EventHandler NativeDataReady; 21 | public event EventHandler DataReady; 22 | 23 | public AudioCapture() 24 | { 25 | RaiseDataEvents = true; 26 | WaitTimeout = 100; 27 | } 28 | 29 | public bool RaiseNativeDataEvents { get; set; } 30 | public bool RaiseDataEvents { get; set; } 31 | public AudioDevice Device => _device; 32 | 33 | public int WaitTimeout 34 | { 35 | get => _waitTimeout; 36 | set => _waitTimeout = Math.Max(1, value); 37 | } 38 | 39 | public WaveFormat GetFormat() 40 | { 41 | using (var device = GetSpeakersDevice()) 42 | { 43 | return GetFormat(device); 44 | } 45 | } 46 | 47 | public WaveFormat GetFormat(AudioDevice device) 48 | { 49 | if (device == null) 50 | throw new ArgumentNullException(nameof(device)); 51 | 52 | var audioClient = device.ActivateClient(); 53 | try 54 | { 55 | audioClient.GetMixFormat(out IntPtr format); 56 | var fex = Marshal.PtrToStructure(format); 57 | Marshal.FreeCoTaskMem(format); 58 | return new WaveFormat(fex); 59 | 60 | } 61 | finally 62 | { 63 | Marshal.ReleaseComObject(audioClient); 64 | } 65 | } 66 | 67 | public void Start() => Start(GetSpeakersDevice()); 68 | public void Start(AudioDevice device) => Start(device, null); 69 | public void Start(AudioDevice device, string threadTaskName) 70 | { 71 | if (_device != null) 72 | return; 73 | 74 | var thread = new Thread(ThreadLoop); 75 | thread.Name = nameof(AudioCapture) + DateTime.Now.TimeOfDay; 76 | thread.IsBackground = true; 77 | //thread.Priority = ThreadPriority.Lowest; 78 | var state = new ThreadState(); 79 | state.Device = device; 80 | state.TaskName = threadTaskName; 81 | thread.Start(state); 82 | } 83 | 84 | private class ThreadState 85 | { 86 | public string TaskName; 87 | public AudioDevice Device; 88 | } 89 | 90 | public void Stop() 91 | { 92 | if (_device == null) 93 | return; 94 | 95 | _stopEvent.Set(); 96 | _stopping = true; 97 | } 98 | 99 | private void ThreadLoop(object obj) 100 | { 101 | var state = (ThreadState)obj; 102 | _stopEvent?.Dispose(); 103 | _stopEvent = new AutoResetEvent(false); 104 | 105 | if (state.Device == null) 106 | { 107 | Loop(_stopEvent, state.TaskName); 108 | } 109 | else 110 | { 111 | Loop(state.Device, _stopEvent, state.TaskName); 112 | } 113 | } 114 | 115 | private static bool IsRenderDevice(AudioDevice device) => GetDevices(DataFlow.Render).Any(d => d.Id == device.Id); 116 | 117 | // loops is public in case someone wants to handle his own threading/task stuff 118 | public void Loop(WaitHandle stopHandle) => Loop(stopHandle, null); 119 | public void Loop(WaitHandle stopHandle, string threadTaskName) 120 | { 121 | using (var device = GetSpeakersDevice()) 122 | { 123 | Loop(device, stopHandle, threadTaskName); 124 | } 125 | } 126 | 127 | public void Loop(AudioDevice device, WaitHandle stopHandle) => Loop(device, stopHandle, null); 128 | public void Loop(AudioDevice device, WaitHandle stopHandle, string threadTaskName) 129 | { 130 | if (device == null) 131 | throw new ArgumentNullException(nameof(device)); 132 | 133 | if (stopHandle == null) 134 | throw new ArgumentNullException(nameof(stopHandle)); 135 | 136 | if (_device != null) 137 | throw new InvalidOperationException("Capture loop was already started."); 138 | 139 | _device = device; 140 | bool renderDevice = IsRenderDevice(_device); 141 | 142 | if (string.IsNullOrEmpty(threadTaskName)) 143 | { 144 | threadTaskName = "Audio"; 145 | } 146 | 147 | var audioClient = device.ActivateClient(); 148 | try 149 | { 150 | audioClient.GetMixFormat(out IntPtr format); 151 | var fex = Marshal.PtrToStructure(format); 152 | Marshal.FreeCoTaskMem(format); 153 | 154 | // ask MF to do the resampling work to PCM 16 for us 155 | fex.SubFormat = CoreAudio.KSDATAFORMAT_SUBTYPE_PCM; 156 | fex.wValidBitsPerSample = 16; 157 | fex.Format.wBitsPerSample = 16; 158 | fex.Format.nBlockAlign = (short)(fex.Format.nChannels * fex.Format.wBitsPerSample / 8); 159 | fex.Format.nAvgBytesPerSec = fex.Format.nBlockAlign * fex.Format.nSamplesPerSec; 160 | 161 | format = Marshal.AllocCoTaskMem(Marshal.SizeOf()); 162 | Marshal.StructureToPtr(fex, format, false); 163 | 164 | var initFlags = CoreAudio.AUDCLNT_FLAGS.AUDCLNT_STREAMFLAGS_EVENTCALLBACK; 165 | if (renderDevice) 166 | { 167 | initFlags |= CoreAudio.AUDCLNT_FLAGS.AUDCLNT_STREAMFLAGS_LOOPBACK; 168 | } 169 | 170 | try 171 | { 172 | audioClient.Initialize(CoreAudio.AUDCLNT_SHAREMODE.AUDCLNT_SHAREMODE_SHARED, initFlags, 0, 0, format, Guid.Empty); 173 | } 174 | finally 175 | { 176 | Marshal.FreeCoTaskMem(format); 177 | } 178 | 179 | int blockAlign = fex.Format.nBlockAlign; 180 | 181 | audioClient.SetEventHandle(_dataEvent.SafeWaitHandle); 182 | audioClient.GetService(typeof(CoreAudio.IAudioCaptureClient).GUID, out object acc); 183 | var captureClient = (CoreAudio.IAudioCaptureClient)acc; 184 | try 185 | { 186 | // profiles names are stored as sub keys of HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Multimedia\SystemProfile\Tasks 187 | var task = CoreAudio.AvSetMmThreadCharacteristics(threadTaskName, out int taskIndex); 188 | if (task == IntPtr.Zero) 189 | throw new Win32Exception(Marshal.GetLastWin32Error()); 190 | 191 | // reuse the same buffer 192 | byte[] data = null; 193 | try 194 | { 195 | audioClient.Start(); 196 | RaiseEvents(IntPtr.Zero, 0, ref data); 197 | do 198 | { 199 | if (_stopping || _dataEvent == null) // we've been disposed 200 | break; 201 | 202 | do 203 | { 204 | int size = captureClient.GetNextPacketSize(); 205 | if (size == 0) 206 | break; 207 | 208 | captureClient.GetBuffer(out IntPtr dataPtr, out int frames, out CoreAudio.AUDCLNT_BUFFERFLAGS flags, out long devPosition, out long qpcPosition); 209 | //Duplicator.Trace("frames:" + frames + " flags: " + flags); 210 | int bytesCount; 211 | if (flags.HasFlag(CoreAudio.AUDCLNT_BUFFERFLAGS.AUDCLNT_BUFFERFLAGS_SILENT)) 212 | { 213 | bytesCount = 0; 214 | } 215 | else 216 | { 217 | bytesCount = frames * blockAlign; 218 | } 219 | RaiseEvents(dataPtr, bytesCount, ref data); 220 | captureClient.ReleaseBuffer(frames); 221 | } 222 | while (true); 223 | 224 | int index; 225 | try 226 | { 227 | index = WaitHandle.WaitAny(new[] { stopHandle, _dataEvent }, WaitTimeout); 228 | } 229 | catch (ObjectDisposedException) 230 | { 231 | index = 0; 232 | } 233 | 234 | if (index == WaitHandle.WaitTimeout) 235 | continue; 236 | 237 | if (index == 0) // stop 238 | break; 239 | 240 | } 241 | while (true); 242 | audioClient.Stop(); 243 | } 244 | finally 245 | { 246 | CoreAudio.AvRevertMmThreadCharacteristics(task); 247 | } 248 | } 249 | finally 250 | { 251 | Marshal.ReleaseComObject(captureClient); 252 | } 253 | } 254 | finally 255 | { 256 | Marshal.ReleaseComObject(audioClient); 257 | } 258 | 259 | _device?.Dispose(); 260 | _device = null; 261 | _stopping = false; 262 | } 263 | 264 | private void RaiseEvents(IntPtr dataPtr, int bytesCount, ref byte[] data) 265 | { 266 | long ticks = Stopwatch.GetTimestamp(); 267 | bool handled = false; 268 | if (RaiseNativeDataEvents) 269 | { 270 | var ne = new AudioCaptureNativeDataEventArgs(dataPtr, bytesCount, ticks); 271 | NativeDataReady?.Invoke(this, ne); 272 | handled = ne.Handled; 273 | } 274 | 275 | if (!handled && RaiseDataEvents) 276 | { 277 | if (bytesCount > 0 && (data == null || data.Length < bytesCount)) 278 | { 279 | data = new byte[bytesCount]; 280 | } 281 | 282 | if (bytesCount > 0) 283 | { 284 | Marshal.Copy(dataPtr, data, 0, bytesCount); 285 | } 286 | 287 | var e = new AudioCaptureDataEventArgs(data, bytesCount, ticks); 288 | DataReady?.Invoke(this, e); 289 | } 290 | } 291 | 292 | public void Dispose() 293 | { 294 | // we want to handling looping in another thread 295 | var dataEvent = Interlocked.Exchange(ref _dataEvent, null); 296 | if (dataEvent != null) 297 | { 298 | _stopping = true; 299 | dataEvent.Set(); 300 | dataEvent.Dispose(); 301 | while (_device != null && _stopping) 302 | { 303 | Thread.Sleep(10); 304 | } 305 | } 306 | } 307 | 308 | public enum AudioDeviceState 309 | { 310 | Active = 0x00000001, 311 | Disabled = 0x00000002, 312 | NotPresent = 0x00000004, 313 | Unplugged = 0x00000008, 314 | } 315 | 316 | public static AudioDevice GetSpeakersDevice() => CreateDevice(GetSpeakers()); 317 | public static AudioDevice GetMicrophoneDevice() => CreateDevice(GetMicrophone()); 318 | 319 | public static IReadOnlyList GetDevices(DataFlow flow) 320 | { 321 | var list = new List(); 322 | CoreAudio.IMMDeviceEnumerator deviceEnumerator = null; 323 | try 324 | { 325 | deviceEnumerator = (CoreAudio.IMMDeviceEnumerator)(new CoreAudio.MMDeviceEnumerator()); 326 | } 327 | catch 328 | { 329 | } 330 | 331 | if (deviceEnumerator != null) 332 | { 333 | const int DEVICE_STATEMASK_ALL = 0x0000000f; 334 | deviceEnumerator.EnumAudioEndpoints(flow, (AudioDeviceState)DEVICE_STATEMASK_ALL, out CoreAudio.IMMDeviceCollection collection); 335 | if (collection != null) 336 | { 337 | int count = collection.GetCount(); 338 | for (int i = 0; i < count; i++) 339 | { 340 | var adev = CreateDevice(collection.Item(i)); 341 | if (adev != null) 342 | { 343 | list.Add(adev); 344 | } 345 | } 346 | } 347 | } 348 | return list; 349 | } 350 | 351 | private static AudioDevice CreateDevice(CoreAudio.IMMDevice dev) 352 | { 353 | if (dev == null) 354 | return null; 355 | 356 | dev.GetId(out string id); 357 | var state = dev.GetState(); 358 | var store = dev.OpenPropertyStore(CoreAudio.STGM.STGM_READ); 359 | string friendlyName = GetValue(store, new CoreAudio.PROPERTYKEY { fmtid = new Guid("a45c254e-df1c-4efd-8020-67d146a850e0"), pid = 14 }); 360 | string description = GetValue(store, new CoreAudio.PROPERTYKEY { fmtid = new Guid("a45c254e-df1c-4efd-8020-67d146a850e0"), pid = 2 }); 361 | return new AudioDevice(dev, id, state, friendlyName, description); 362 | } 363 | 364 | private static string GetValue(CoreAudio.IPropertyStore ps, CoreAudio.PROPERTYKEY pk) 365 | { 366 | if (ps == null) 367 | return null; 368 | 369 | var pv = Marshal.AllocCoTaskMem(IntPtr.Size == 8 ? 24 : 16); 370 | if (ps.GetValue(ref pk, pv) != 0) 371 | return null; 372 | 373 | try 374 | { 375 | CoreAudio.PropVariantToStringAlloc(pv, out IntPtr ptr); 376 | if (ptr == IntPtr.Zero) 377 | return null; 378 | 379 | var str = Marshal.PtrToStringUni(ptr); 380 | Marshal.FreeCoTaskMem(ptr); 381 | return str; 382 | } 383 | finally 384 | { 385 | Marshal.FreeCoTaskMem(pv); 386 | } 387 | } 388 | 389 | private static CoreAudio.IMMDevice GetSpeakers() 390 | { 391 | // get the speakers (1st render + multimedia) device 392 | try 393 | { 394 | var deviceEnumerator = (CoreAudio.IMMDeviceEnumerator)(new CoreAudio.MMDeviceEnumerator()); 395 | deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, CoreAudio.ERole.eMultimedia, out CoreAudio.IMMDevice speakers); 396 | return speakers; 397 | } 398 | catch 399 | { 400 | // huh? not on vista? 401 | return null; 402 | } 403 | } 404 | 405 | private static CoreAudio.IMMDevice GetMicrophone() 406 | { 407 | try 408 | { 409 | var deviceEnumerator = (CoreAudio.IMMDeviceEnumerator)(new CoreAudio.MMDeviceEnumerator()); 410 | deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Capture, CoreAudio.ERole.eMultimedia, out CoreAudio.IMMDevice mic); 411 | return mic; 412 | } 413 | catch 414 | { 415 | // huh? not on vista? 416 | return null; 417 | } 418 | } 419 | 420 | public enum DataFlow 421 | { 422 | Render, 423 | Capture, 424 | All, 425 | } 426 | 427 | // this is public so the client can choose the device 428 | public sealed class AudioDevice : IDisposable 429 | { 430 | private CoreAudio.IMMDevice _device; 431 | 432 | internal AudioDevice(CoreAudio.IMMDevice device, string id, AudioDeviceState state, string friendlyName, string description) 433 | { 434 | _device = device; 435 | Id = id; 436 | State = state; 437 | FriendlyName = friendlyName; 438 | Description = description; 439 | } 440 | 441 | public string Id { get; } 442 | public AudioDeviceState State { get; } 443 | public string FriendlyName { get; } 444 | public string Description { get; } 445 | 446 | internal CoreAudio.IAudioClient ActivateClient() 447 | { 448 | var o = _device.Activate(typeof(CoreAudio.IAudioClient).GUID, CoreAudio.CLSCTX.CLSCTX_ALL, IntPtr.Zero); 449 | return (CoreAudio.IAudioClient)o; 450 | } 451 | 452 | public override string ToString() => FriendlyName; 453 | 454 | public void Dispose() => Marshal.ReleaseComObject(_device); 455 | } 456 | 457 | internal static class CoreAudio 458 | { 459 | public static readonly Guid KSDATAFORMAT_SUBTYPE_PCM = new Guid("00000001-0000-0010-8000-00aa00389b71"); 460 | 461 | [ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] 462 | public class MMDeviceEnumerator { } 463 | 464 | public enum AUDCLNT_SHAREMODE 465 | { 466 | AUDCLNT_SHAREMODE_SHARED, 467 | AUDCLNT_SHAREMODE_EXCLUSIVE 468 | } 469 | 470 | [Flags] 471 | public enum AUDCLNT_BUFFERFLAGS 472 | { 473 | AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY = 0x1, 474 | AUDCLNT_BUFFERFLAGS_SILENT = 0x2, 475 | AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR = 0x4 476 | } 477 | 478 | [Flags] 479 | public enum AUDCLNT_FLAGS 480 | { 481 | AUDCLNT_STREAMFLAGS_CROSSPROCESS = 0x00010000, 482 | AUDCLNT_STREAMFLAGS_LOOPBACK = 0x00020000, 483 | AUDCLNT_STREAMFLAGS_EVENTCALLBACK = 0x00040000, 484 | AUDCLNT_STREAMFLAGS_NOPERSIST = 0x00080000, 485 | AUDCLNT_STREAMFLAGS_RATEADJUST = 0x00100000, 486 | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY = 0x08000000, 487 | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM = unchecked((int)0x80000000), 488 | AUDCLNT_SESSIONFLAGS_EXPIREWHENUNOWNED = 0x10000000, 489 | AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE = 0x20000000, 490 | AUDCLNT_SESSIONFLAGS_DISPLAY_HIDEWHENEXPIRED = 0x40000000, 491 | } 492 | 493 | [StructLayout(LayoutKind.Sequential, Pack = 2)] 494 | public struct WAVEFORMATEX 495 | { 496 | public ushort wFormatTag; 497 | public short nChannels; 498 | public int nSamplesPerSec; 499 | public int nAvgBytesPerSec; 500 | public short nBlockAlign; 501 | public ushort wBitsPerSample; 502 | public ushort cbSize; 503 | } 504 | 505 | [StructLayout(LayoutKind.Sequential, Pack = 2)] 506 | public struct WAVEFORMATEXTENSIBLE 507 | { 508 | public WAVEFORMATEX Format; 509 | public ushort wValidBitsPerSample; 510 | public uint dwChannelMask; 511 | public Guid SubFormat; 512 | } 513 | 514 | [StructLayout(LayoutKind.Sequential)] 515 | public struct PROPERTYKEY 516 | { 517 | public Guid fmtid; 518 | public int pid; 519 | public override string ToString() => fmtid.ToString("B") + " " + pid; 520 | } 521 | 522 | public enum STGM 523 | { 524 | STGM_READ = 0x00000000, 525 | } 526 | 527 | [Flags] 528 | public enum CLSCTX 529 | { 530 | CLSCTX_INPROC_SERVER = 0x1, 531 | CLSCTX_INPROC_HANDLER = 0x2, 532 | CLSCTX_LOCAL_SERVER = 0x4, 533 | CLSCTX_REMOTE_SERVER = 0x10, 534 | CLSCTX_ALL = CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER 535 | } 536 | 537 | public enum ERole 538 | { 539 | eConsole, 540 | eMultimedia, 541 | eCommunications, 542 | } 543 | 544 | [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 545 | public interface IMMDeviceEnumerator 546 | { 547 | [PreserveSig] 548 | int EnumAudioEndpoints(DataFlow dataFlow, AudioDeviceState dwStateMask, out IMMDeviceCollection ppDevices); 549 | 550 | [PreserveSig] 551 | int GetDefaultAudioEndpoint(DataFlow dataFlow, ERole role, out IMMDevice ppEndpoint); 552 | 553 | [PreserveSig] 554 | int GetDevice([MarshalAs(UnmanagedType.LPWStr)] string pwstrId, out IMMDevice ppDevice); 555 | } 556 | 557 | [Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 558 | public interface IMMDeviceCollection 559 | { 560 | int GetCount(); 561 | IMMDevice Item(int nDevice); 562 | } 563 | 564 | [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 565 | public interface IMMDevice 566 | { 567 | [return: MarshalAs(UnmanagedType.IUnknown)] 568 | object Activate([MarshalAs(UnmanagedType.LPStruct)] Guid riid, CLSCTX dwClsCtx, IntPtr pActivationParams); 569 | 570 | IPropertyStore OpenPropertyStore(STGM stgmAccess); 571 | 572 | [PreserveSig] 573 | int GetId([MarshalAs(UnmanagedType.LPWStr)] out string ppstrId); 574 | 575 | AudioDeviceState GetState(); 576 | } 577 | 578 | [Guid("1CB9AD4C-DBFA-4c32-B178-C2F568A703B2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 579 | public interface IAudioClient 580 | { 581 | void Initialize(AUDCLNT_SHAREMODE ShareMode, AUDCLNT_FLAGS StreamFlags, long hnsBufferDuration, long hnsPeriodicity, /*ref WAVEFORMATEX*/ IntPtr pFormat, [MarshalAs(UnmanagedType.LPStruct)] Guid AudioSessionGuid); 582 | int GetBufferSize(); 583 | long GetStreamLatency(); 584 | int GetCurrentPadding(); 585 | 586 | [PreserveSig] 587 | int IsFormatSupported(AUDCLNT_SHAREMODE ShareMode, ref WAVEFORMATEX pFormat, out WAVEFORMATEX ppClosestMatch); 588 | 589 | void GetMixFormat(out IntPtr ppDeviceFormat); 590 | 591 | void GetDevicePeriod(out long phnsDefaultDevicePeriod, out long phnsMinimumDevicePeriod); 592 | 593 | void Start(); 594 | void Stop(); 595 | void Reset(); 596 | void SetEventHandle(SafeWaitHandle eventHandle); 597 | void GetService([MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppv); 598 | } 599 | 600 | [Guid("C8ADBD64-E71E-48a0-A4DE-185C395CD317"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 601 | public interface IAudioCaptureClient 602 | { 603 | void GetBuffer(out IntPtr ppData, out int NumFramesToRead, out AUDCLNT_BUFFERFLAGS pdwFlags, out long pu64DevicePosition, out long pu64QPCPosition); 604 | void ReleaseBuffer(int NumFramesRead); 605 | int GetNextPacketSize(); 606 | } 607 | 608 | [Guid("886d8eeb-8cf2-4446-8d02-cdba1dbdcf99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 609 | public interface IPropertyStore 610 | { 611 | int GetCount(); 612 | 613 | [PreserveSig] 614 | int GetAt(int iProp, out PROPERTYKEY pkey); 615 | 616 | [PreserveSig] 617 | int GetValue(ref PROPERTYKEY key, IntPtr pv); 618 | } 619 | 620 | [DllImport("propsys")] 621 | public static extern int PropVariantToStringAlloc(IntPtr propvar, out IntPtr ppszOut); 622 | 623 | [DllImport("avrt", SetLastError = true, CharSet = CharSet.Unicode)] 624 | public static extern IntPtr AvSetMmThreadCharacteristics([MarshalAs(UnmanagedType.LPWStr)] string TaskName, out int TaskIndex); 625 | 626 | [DllImport("avrt", SetLastError = true)] 627 | public static extern bool AvRevertMmThreadCharacteristics(IntPtr AvrtHandle); 628 | } 629 | } 630 | 631 | public class WaveFormat 632 | { 633 | internal WaveFormat(AudioCapture.CoreAudio.WAVEFORMATEXTENSIBLE fex) 634 | { 635 | ChannelsCount = fex.Format.nChannels; 636 | SamplesPerSecond = fex.Format.nSamplesPerSec; 637 | AverageBytesPerSecond = fex.Format.nAvgBytesPerSec; 638 | BitsPerSample = fex.Format.wBitsPerSample; 639 | ChannelMask = (int)fex.dwChannelMask; 640 | } 641 | 642 | public int ChannelsCount { get; } 643 | public int SamplesPerSecond { get; } 644 | public int AverageBytesPerSecond { get; } 645 | public int BitsPerSample { get; } 646 | public int ChannelMask { get; } 647 | public Guid Format { get; } 648 | } 649 | 650 | public class AudioCaptureNativeDataEventArgs : HandledEventArgs 651 | { 652 | internal AudioCaptureNativeDataEventArgs(IntPtr data, int size, long time) 653 | { 654 | Data = data; 655 | Size = size; 656 | Time = time; 657 | } 658 | 659 | public IntPtr Data { get; } 660 | public int Size { get; } 661 | public long Time { get; } 662 | } 663 | 664 | public class AudioCaptureDataEventArgs : EventArgs 665 | { 666 | internal AudioCaptureDataEventArgs(byte[] data, int size, long time) 667 | { 668 | Data = data; 669 | Size = size; 670 | Time = time; 671 | } 672 | 673 | public byte[] Data { get; } 674 | public int Size { get; } 675 | public long Time { get; } 676 | } 677 | } 678 | -------------------------------------------------------------------------------- /Duplicator/Duplicator/DictionaryObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Globalization; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Runtime.CompilerServices; 10 | 11 | namespace Duplicator 12 | { 13 | // all properties and methods start with DictionaryObject and are protected so they won't interfere with super type 14 | public abstract class DictionaryObject : INotifyPropertyChanged, INotifyPropertyChanging, IDataErrorInfo, INotifyDataErrorInfo 15 | { 16 | private ConcurrentDictionary _properties = new ConcurrentDictionary(); 17 | 18 | protected DictionaryObject() 19 | { 20 | DictionaryObjectRaiseOnPropertyChanging = true; 21 | DictionaryObjectRaiseOnPropertyChanged = true; 22 | DictionaryObjectRaiseOnErrorsChanged = true; 23 | } 24 | 25 | protected virtual ConcurrentDictionary DictionaryObjectProperties => _properties; 26 | 27 | // these PropertyChangxxx are public and don't start with BaseObject because used by everyone 28 | public event PropertyChangingEventHandler PropertyChanging; 29 | public event PropertyChangedEventHandler PropertyChanged; 30 | public event EventHandler ErrorsChanged; 31 | public event EventHandler PropertyRollback; 32 | 33 | protected virtual bool DictionaryObjectRaiseOnPropertyChanging { get; set; } 34 | protected virtual bool DictionaryObjectRaiseOnPropertyChanged { get; set; } 35 | protected virtual bool DictionaryObjectRaiseOnErrorsChanged { get; set; } 36 | 37 | protected string DictionaryObjectError => DictionaryObjectGetError(null); 38 | protected bool DictionaryObjectHasErrors => (DictionaryObjectGetErrors(null)?.Cast().Any()).GetValueOrDefault(); 39 | 40 | protected virtual string DictionaryObjectGetError(string propertyName) 41 | { 42 | var errors = DictionaryObjectGetErrors(propertyName); 43 | if (errors == null) 44 | return null; 45 | 46 | string error = string.Join(Environment.NewLine, errors.Cast().Select(e => string.Format("{0}", e))); 47 | return !string.IsNullOrEmpty(error) ? error : null; 48 | } 49 | 50 | protected virtual IEnumerable DictionaryObjectGetErrors(string propertyName) => null; 51 | 52 | protected void OnErrorsChanged(string name) 53 | { 54 | if (name == null) 55 | throw new ArgumentNullException(nameof(name)); 56 | 57 | OnErrorsChanged(this, new DataErrorsChangedEventArgs(name)); 58 | } 59 | 60 | protected virtual void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e) => ErrorsChanged?.Invoke(sender, e); 61 | protected virtual void OnPropertyRollback(object sender, DictionaryObjectPropertyRollbackEventArgs e) => PropertyRollback?.Invoke(sender, e); 62 | protected virtual void OnPropertyChanging(object sender, PropertyChangingEventArgs e) => PropertyChanging?.Invoke(sender, e); 63 | protected virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e) => PropertyChanged?.Invoke(sender, e); 64 | 65 | protected T DictionaryObjectGetPropertyValue([CallerMemberName] string name = null) => DictionaryObjectGetPropertyValue(default(T), name); 66 | protected virtual T DictionaryObjectGetPropertyValue(T defaultValue, [CallerMemberName] string name = null) 67 | { 68 | if (name == null) 69 | throw new ArgumentNullException(nameof(name)); 70 | 71 | DictionaryObjectProperties.TryGetValue(name, out DictionaryObjectProperty property); 72 | if (property == null) 73 | return defaultValue; 74 | 75 | if (!DictionaryObjectConversions.TryChangeType(property.Value, out T value)) 76 | return defaultValue; 77 | 78 | return value; 79 | } 80 | 81 | protected virtual bool DictionaryObjectAreValuesEqual(object value1, object value2) 82 | { 83 | if (value1 == null) 84 | return value2 == null; 85 | 86 | if (value2 == null) 87 | return false; 88 | 89 | return value1.Equals(value2); 90 | } 91 | 92 | private class ObjectComparer : IEqualityComparer 93 | { 94 | private DictionaryObject _dob; 95 | 96 | public ObjectComparer(DictionaryObject dob) 97 | { 98 | _dob = dob; 99 | } 100 | 101 | public new bool Equals(object x, object y) => _dob.DictionaryObjectAreValuesEqual(x, y); 102 | public int GetHashCode(object obj) => (obj?.GetHashCode()).GetValueOrDefault(); 103 | } 104 | 105 | protected virtual bool DictionaryObjectAreErrorsEqual(IEnumerable errors1, IEnumerable errors2) 106 | { 107 | if (errors1 == null && errors2 == null) 108 | return true; 109 | 110 | var dic = new Dictionary(new ObjectComparer(this)); 111 | IEnumerable left = errors1 != null ? errors1.Cast() : Enumerable.Empty(); 112 | foreach (var obj in left) 113 | { 114 | if (dic.ContainsKey(obj)) 115 | { 116 | dic[obj]++; 117 | } 118 | else 119 | { 120 | dic.Add(obj, 1); 121 | } 122 | } 123 | 124 | if (errors2 == null) 125 | return dic.Count == 0; 126 | 127 | foreach (var obj in errors2) 128 | { 129 | if (dic.ContainsKey(obj)) 130 | { 131 | dic[obj]--; 132 | } 133 | else 134 | return false; 135 | } 136 | return dic.Values.All(c => c == 0); 137 | } 138 | 139 | protected virtual DictionaryObjectProperty DictionaryObjectUpdatingProperty(DictionaryObjectPropertySetOptions options, string name, DictionaryObjectProperty oldProperty, DictionaryObjectProperty newProperty) => null; 140 | protected virtual DictionaryObjectProperty DictionaryObjectUpdatedProperty(DictionaryObjectPropertySetOptions options, string name, DictionaryObjectProperty oldProperty, DictionaryObjectProperty newProperty) => null; 141 | protected virtual DictionaryObjectProperty DictionaryObjectRollbackProperty(DictionaryObjectPropertySetOptions options, string name, DictionaryObjectProperty oldProperty, DictionaryObjectProperty newProperty) => null; 142 | protected virtual DictionaryObjectProperty DictionaryObjectCreateProperty() => new DictionaryObjectProperty(); 143 | 144 | protected bool DictionaryObjectSetPropertyValue(object value, [CallerMemberName] string name = null) => DictionaryObjectSetPropertyValue(value, DictionaryObjectPropertySetOptions.None, name); 145 | protected virtual bool DictionaryObjectSetPropertyValue(object value, DictionaryObjectPropertySetOptions options, [CallerMemberName] string name = null) 146 | { 147 | if (name == null) 148 | throw new ArgumentNullException(nameof(name)); 149 | 150 | IEnumerable oldErrors = null; 151 | bool rollbackOnError = (options & DictionaryObjectPropertySetOptions.RollbackChangeOnError) == DictionaryObjectPropertySetOptions.RollbackChangeOnError; 152 | bool onErrorsChanged = (options & DictionaryObjectPropertySetOptions.DontRaiseOnErrorsChanged) != DictionaryObjectPropertySetOptions.DontRaiseOnErrorsChanged; 153 | if (!DictionaryObjectRaiseOnErrorsChanged) 154 | { 155 | onErrorsChanged = false; 156 | } 157 | 158 | if (onErrorsChanged || rollbackOnError) 159 | { 160 | oldErrors = DictionaryObjectGetErrors(name); 161 | } 162 | 163 | bool forceChanged = (options & DictionaryObjectPropertySetOptions.ForceRaiseOnPropertyChanged) == DictionaryObjectPropertySetOptions.ForceRaiseOnPropertyChanged; 164 | bool onChanged = (options & DictionaryObjectPropertySetOptions.DontRaiseOnPropertyChanged) != DictionaryObjectPropertySetOptions.DontRaiseOnPropertyChanged; 165 | if (!DictionaryObjectRaiseOnPropertyChanged) 166 | { 167 | onChanged = false; 168 | forceChanged = false; 169 | } 170 | 171 | var newProp = DictionaryObjectCreateProperty(); 172 | newProp.Value = value; 173 | DictionaryObjectProperty oldProp = null; 174 | var finalProp = DictionaryObjectProperties.AddOrUpdate(name, newProp, (k, o) => 175 | { 176 | oldProp = o; 177 | var updating = DictionaryObjectUpdatingProperty(options, k, o, newProp); 178 | if (updating != null) 179 | return updating; 180 | 181 | bool testEquality = (options & DictionaryObjectPropertySetOptions.DontTestValuesForEquality) != DictionaryObjectPropertySetOptions.DontTestValuesForEquality; 182 | if (testEquality && o != null && DictionaryObjectAreValuesEqual(value, o.Value)) 183 | return o; 184 | 185 | bool onChanging = (options & DictionaryObjectPropertySetOptions.DontRaiseOnPropertyChanging) != DictionaryObjectPropertySetOptions.DontRaiseOnPropertyChanging; 186 | if (!DictionaryObjectRaiseOnPropertyChanging) 187 | { 188 | onChanging = false; 189 | } 190 | 191 | if (onChanging) 192 | { 193 | var e = new DictionaryObjectPropertyChangingEventArgs(name, oldProp, newProp); 194 | OnPropertyChanging(this, e); 195 | if (e.Cancel) 196 | return o; 197 | } 198 | 199 | var updated = DictionaryObjectUpdatedProperty(options, k, o, newProp); 200 | if (updated != null) 201 | return updated; 202 | 203 | return newProp; 204 | }); 205 | 206 | if (forceChanged || (onChanged && ReferenceEquals(finalProp, newProp))) 207 | { 208 | bool rollbacked = false; 209 | if (rollbackOnError) 210 | { 211 | if ((DictionaryObjectGetErrors(name)?.Cast().Any()).GetValueOrDefault()) 212 | { 213 | var rolled = DictionaryObjectRollbackProperty(options, name, oldProp, newProp); 214 | if (rolled == null) 215 | { 216 | rolled = oldProp; 217 | } 218 | 219 | if (rolled == null) 220 | { 221 | DictionaryObjectProperties.TryRemove(name, out DictionaryObjectProperty dop); 222 | } 223 | else 224 | { 225 | DictionaryObjectProperties.AddOrUpdate(name, rolled, (k, o) => rolled); 226 | } 227 | 228 | var e = new DictionaryObjectPropertyRollbackEventArgs(name, rolled, value); 229 | OnPropertyRollback(this, e); 230 | rollbacked = true; 231 | } 232 | } 233 | 234 | if (!rollbacked) 235 | { 236 | var e = new DictionaryObjectPropertyChangedEventArgs(name, oldProp, newProp); 237 | OnPropertyChanged(this, e); 238 | 239 | if (onErrorsChanged) 240 | { 241 | var newErrors = DictionaryObjectGetErrors(name); 242 | if (!DictionaryObjectAreErrorsEqual(oldErrors, newErrors)) 243 | { 244 | OnErrorsChanged(name); 245 | } 246 | } 247 | return true; 248 | } 249 | } 250 | 251 | return false; 252 | } 253 | 254 | string IDataErrorInfo.Error => DictionaryObjectError; 255 | string IDataErrorInfo.this[string columnName] => DictionaryObjectGetError(columnName); 256 | bool INotifyDataErrorInfo.HasErrors => DictionaryObjectHasErrors; 257 | IEnumerable INotifyDataErrorInfo.GetErrors(string propertyName) => DictionaryObjectGetErrors(propertyName); 258 | } 259 | 260 | public class DictionaryObjectProperty 261 | { 262 | public object Value { get; set; } 263 | 264 | public override string ToString() 265 | { 266 | var value = Value; 267 | if (value == null) 268 | return null; 269 | 270 | if (value is string svalue) 271 | return svalue; 272 | 273 | return string.Format("{0}", value); 274 | } 275 | } 276 | 277 | [Flags] 278 | public enum DictionaryObjectPropertySetOptions 279 | { 280 | None = 0x0, 281 | DontRaiseOnPropertyChanging = 0x1, 282 | DontRaiseOnPropertyChanged = 0x2, 283 | DontTestValuesForEquality = 0x4, 284 | DontRaiseOnErrorsChanged = 0x8, 285 | ForceRaiseOnPropertyChanged = 0x10, 286 | TrackChanges = 0x20, 287 | RollbackChangeOnError = 0x40, 288 | } 289 | 290 | public class DictionaryObjectPropertyChangingEventArgs : PropertyChangingEventArgs 291 | { 292 | public DictionaryObjectPropertyChangingEventArgs(string propertyName, DictionaryObjectProperty existingProperty, DictionaryObjectProperty newProperty) 293 | : base(propertyName) 294 | { 295 | if (propertyName == null) 296 | throw new ArgumentNullException(nameof(propertyName)); 297 | 298 | if (newProperty == null) 299 | throw new ArgumentNullException(nameof(newProperty)); 300 | 301 | // existingProperty may be null 302 | 303 | ExistingProperty = existingProperty; 304 | NewProperty = newProperty; 305 | } 306 | 307 | public DictionaryObjectProperty ExistingProperty { get; } 308 | public DictionaryObjectProperty NewProperty { get; } 309 | public bool Cancel { get; set; } 310 | } 311 | 312 | public class DictionaryObjectPropertyChangedEventArgs : PropertyChangedEventArgs 313 | { 314 | public DictionaryObjectPropertyChangedEventArgs(string propertyName, DictionaryObjectProperty existingProperty, DictionaryObjectProperty newProperty) 315 | : base(propertyName) 316 | { 317 | if (propertyName == null) 318 | throw new ArgumentNullException(nameof(propertyName)); 319 | 320 | if (newProperty == null) 321 | throw new ArgumentNullException(nameof(newProperty)); 322 | 323 | // existingProperty may be null 324 | 325 | ExistingProperty = existingProperty; 326 | NewProperty = newProperty; 327 | } 328 | 329 | public DictionaryObjectProperty ExistingProperty { get; } 330 | public DictionaryObjectProperty NewProperty { get; } 331 | } 332 | 333 | public class DictionaryObjectPropertyRollbackEventArgs : EventArgs 334 | { 335 | public DictionaryObjectPropertyRollbackEventArgs(string propertyName, DictionaryObjectProperty existingProperty, object invalidValue) 336 | { 337 | if (propertyName == null) 338 | throw new ArgumentNullException(nameof(propertyName)); 339 | 340 | // existingProperty may be null 341 | PropertyName = propertyName; 342 | ExistingProperty = existingProperty; 343 | InvalidValue = invalidValue; 344 | } 345 | 346 | public string PropertyName { get; } 347 | public DictionaryObjectProperty ExistingProperty { get; } 348 | public object InvalidValue { get; } 349 | } 350 | 351 | internal static class DictionaryObjectConversions 352 | { 353 | public static object ChangeType(object input, Type conversionType) => ChangeType(input, conversionType, null, null); 354 | public static object ChangeType(object input, Type conversionType, object defaultValue) => ChangeType(input, conversionType, defaultValue, null); 355 | public static object ChangeType(object input, Type conversionType, object defaultValue, IFormatProvider provider) 356 | { 357 | if (!TryChangeType(input, conversionType, provider, out object value)) 358 | return defaultValue; 359 | 360 | return value; 361 | } 362 | 363 | public static T ChangeType(object input) => ChangeType(input, default(T)); 364 | public static T ChangeType(object input, T defaultValue) => ChangeType(input, defaultValue, null); 365 | public static T ChangeType(object input, T defaultValue, IFormatProvider provider) 366 | { 367 | if (!TryChangeType(input, provider, out T value)) 368 | return defaultValue; 369 | 370 | return value; 371 | } 372 | 373 | public static bool TryChangeType(object input, out T value) => TryChangeType(input, null, out value); 374 | public static bool TryChangeType(object input, IFormatProvider provider, out T value) 375 | { 376 | if (!TryChangeType(input, typeof(T), provider, out object tvalue)) 377 | { 378 | value = default(T); 379 | return false; 380 | } 381 | 382 | value = (T)tvalue; 383 | return true; 384 | } 385 | 386 | public static bool TryChangeType(object input, Type conversionType, out object value) => TryChangeType(input, conversionType, null, out value); 387 | public static bool TryChangeType(object input, Type conversionType, IFormatProvider provider, out object value) 388 | { 389 | if (conversionType == null) 390 | throw new ArgumentNullException(nameof(conversionType)); 391 | 392 | if (conversionType == typeof(object)) 393 | { 394 | value = input; 395 | return true; 396 | } 397 | 398 | Type nullableType = null; 399 | if (conversionType.IsNullable()) 400 | { 401 | nullableType = conversionType.GenericTypeArguments[0]; 402 | if (input == null) 403 | { 404 | value = null; 405 | return true; 406 | } 407 | 408 | return TryChangeType(input, nullableType, provider, out value); 409 | } 410 | 411 | value = conversionType.IsValueType ? Activator.CreateInstance(conversionType) : null; 412 | if (input == null) 413 | return !conversionType.IsValueType; 414 | 415 | var inputType = input.GetType(); 416 | if (inputType.IsAssignableFrom(conversionType)) 417 | { 418 | value = input; 419 | return true; 420 | } 421 | 422 | if (conversionType.IsEnum) 423 | return EnumTryParse(conversionType, input, out value); 424 | 425 | if (conversionType == typeof(Guid)) 426 | { 427 | if (inputType == typeof(byte[])) 428 | { 429 | var bytes = (byte[])input; 430 | if (bytes.Length != 16) 431 | return false; 432 | 433 | value = new Guid(bytes); 434 | return true; 435 | } 436 | 437 | string svalue = string.Format(provider, "{0}", input).Nullify(); 438 | if (svalue != null && Guid.TryParse(svalue, out Guid guid)) 439 | { 440 | value = guid; 441 | return true; 442 | } 443 | return false; 444 | } 445 | 446 | if (conversionType == typeof(IntPtr)) 447 | { 448 | if (IntPtr.Size == 8) 449 | { 450 | if (TryChangeType(input, provider, out long l)) 451 | { 452 | value = new IntPtr(l); 453 | return true; 454 | } 455 | } 456 | else if (TryChangeType(input, provider, out int i)) 457 | { 458 | value = new IntPtr(i); 459 | return true; 460 | } 461 | return false; 462 | } 463 | 464 | if (conversionType == typeof(bool)) 465 | { 466 | if (inputType == typeof(byte[])) 467 | { 468 | var bytes = (byte[])input; 469 | if (bytes.Length != 1) 470 | return false; 471 | 472 | value = BitConverter.ToBoolean(bytes, 0); 473 | return true; 474 | } 475 | } 476 | 477 | if (conversionType == typeof(int)) 478 | { 479 | if (inputType == typeof(uint)) 480 | { 481 | value = unchecked((int)(uint)input); 482 | return true; 483 | } 484 | 485 | if (inputType == typeof(ulong)) 486 | { 487 | value = unchecked((int)(ulong)input); 488 | return true; 489 | } 490 | 491 | if (inputType == typeof(ushort)) 492 | { 493 | value = unchecked((int)(ushort)input); 494 | return true; 495 | } 496 | 497 | if (inputType == typeof(byte)) 498 | { 499 | value = unchecked((int)(byte)input); 500 | return true; 501 | } 502 | 503 | if (inputType == typeof(byte[])) 504 | { 505 | var bytes = (byte[])input; 506 | if (bytes.Length != 4) 507 | return false; 508 | 509 | value = BitConverter.ToInt32(bytes, 0); 510 | return true; 511 | } 512 | } 513 | 514 | if (conversionType == typeof(long)) 515 | { 516 | if (inputType == typeof(uint)) 517 | { 518 | value = unchecked((long)(uint)input); 519 | return true; 520 | } 521 | 522 | if (inputType == typeof(ulong)) 523 | { 524 | value = unchecked((long)(ulong)input); 525 | return true; 526 | } 527 | 528 | if (inputType == typeof(ushort)) 529 | { 530 | value = unchecked((long)(ushort)input); 531 | return true; 532 | } 533 | 534 | if (inputType == typeof(byte)) 535 | { 536 | value = unchecked((long)(byte)input); 537 | return true; 538 | } 539 | 540 | if (inputType == typeof(DateTime)) 541 | { 542 | value = ((DateTime)input).Ticks; 543 | return true; 544 | } 545 | 546 | if (inputType == typeof(TimeSpan)) 547 | { 548 | value = ((TimeSpan)input).Ticks; 549 | return true; 550 | } 551 | 552 | if (inputType == typeof(DateTimeOffset)) 553 | { 554 | value = ((DateTimeOffset)input).Ticks; 555 | return true; 556 | } 557 | 558 | if (inputType == typeof(byte[])) 559 | { 560 | var bytes = (byte[])input; 561 | if (bytes.Length != 8) 562 | return false; 563 | 564 | value = BitConverter.ToInt64(bytes, 0); 565 | return true; 566 | } 567 | } 568 | 569 | if (conversionType == typeof(short)) 570 | { 571 | if (inputType == typeof(uint)) 572 | { 573 | value = unchecked((short)(uint)input); 574 | return true; 575 | } 576 | 577 | if (inputType == typeof(ulong)) 578 | { 579 | value = unchecked((short)(ulong)input); 580 | return true; 581 | } 582 | 583 | if (inputType == typeof(ushort)) 584 | { 585 | value = unchecked((short)(ushort)input); 586 | return true; 587 | } 588 | 589 | if (inputType == typeof(byte)) 590 | { 591 | value = unchecked((short)(byte)input); 592 | return true; 593 | } 594 | 595 | if (inputType == typeof(byte[])) 596 | { 597 | var bytes = (byte[])input; 598 | if (bytes.Length != 2) 599 | return false; 600 | 601 | value = BitConverter.ToInt16(bytes, 0); 602 | return true; 603 | } 604 | } 605 | 606 | if (conversionType == typeof(sbyte)) 607 | { 608 | if (inputType == typeof(uint)) 609 | { 610 | value = unchecked((sbyte)(uint)input); 611 | return true; 612 | } 613 | 614 | if (inputType == typeof(ulong)) 615 | { 616 | value = unchecked((sbyte)(ulong)input); 617 | return true; 618 | } 619 | 620 | if (inputType == typeof(ushort)) 621 | { 622 | value = unchecked((sbyte)(ushort)input); 623 | return true; 624 | } 625 | 626 | if (inputType == typeof(byte)) 627 | { 628 | value = unchecked((sbyte)(byte)input); 629 | return true; 630 | } 631 | 632 | if (inputType == typeof(byte[])) 633 | { 634 | var bytes = (byte[])input; 635 | if (bytes.Length != 1) 636 | return false; 637 | 638 | value = unchecked((sbyte)bytes[0]); 639 | return true; 640 | } 641 | } 642 | 643 | if (conversionType == typeof(uint)) 644 | { 645 | if (inputType == typeof(int)) 646 | { 647 | value = unchecked((uint)(int)input); 648 | return true; 649 | } 650 | 651 | if (inputType == typeof(long)) 652 | { 653 | value = unchecked((uint)(long)input); 654 | return true; 655 | } 656 | 657 | if (inputType == typeof(short)) 658 | { 659 | value = unchecked((uint)(short)input); 660 | return true; 661 | } 662 | 663 | if (inputType == typeof(sbyte)) 664 | { 665 | value = unchecked((uint)(sbyte)input); 666 | return true; 667 | } 668 | 669 | if (inputType == typeof(byte[])) 670 | { 671 | var bytes = (byte[])input; 672 | if (bytes.Length != 4) 673 | return false; 674 | 675 | value = BitConverter.ToUInt32(bytes, 0); 676 | return true; 677 | } 678 | } 679 | 680 | if (conversionType == typeof(ulong)) 681 | { 682 | if (inputType == typeof(int)) 683 | { 684 | value = unchecked((ulong)(int)input); 685 | return true; 686 | } 687 | 688 | if (inputType == typeof(long)) 689 | { 690 | value = unchecked((ulong)(long)input); 691 | return true; 692 | } 693 | 694 | if (inputType == typeof(short)) 695 | { 696 | value = unchecked((ulong)(short)input); 697 | return true; 698 | } 699 | 700 | if (inputType == typeof(sbyte)) 701 | { 702 | value = unchecked((ulong)(sbyte)input); 703 | return true; 704 | } 705 | 706 | if (inputType == typeof(byte[])) 707 | { 708 | var bytes = (byte[])input; 709 | if (bytes.Length != 8) 710 | return false; 711 | 712 | value = BitConverter.ToUInt64(bytes, 0); 713 | return true; 714 | } 715 | } 716 | 717 | if (conversionType == typeof(ushort)) 718 | { 719 | if (inputType == typeof(int)) 720 | { 721 | value = unchecked((ushort)(int)input); 722 | return true; 723 | } 724 | 725 | if (inputType == typeof(long)) 726 | { 727 | value = unchecked((ushort)(long)input); 728 | return true; 729 | } 730 | 731 | if (inputType == typeof(short)) 732 | { 733 | value = unchecked((ushort)(short)input); 734 | return true; 735 | } 736 | 737 | if (inputType == typeof(sbyte)) 738 | { 739 | value = unchecked((ushort)(sbyte)input); 740 | return true; 741 | } 742 | 743 | if (inputType == typeof(byte[])) 744 | { 745 | var bytes = (byte[])input; 746 | if (bytes.Length != 2) 747 | return false; 748 | 749 | value = BitConverter.ToUInt16(bytes, 0); 750 | return true; 751 | } 752 | } 753 | 754 | if (conversionType == typeof(byte)) 755 | { 756 | if (inputType == typeof(int)) 757 | { 758 | value = unchecked((byte)(int)input); 759 | return true; 760 | } 761 | 762 | if (inputType == typeof(long)) 763 | { 764 | value = unchecked((byte)(long)input); 765 | return true; 766 | } 767 | 768 | if (inputType == typeof(short)) 769 | { 770 | value = unchecked((byte)(short)input); 771 | return true; 772 | } 773 | 774 | if (inputType == typeof(sbyte)) 775 | { 776 | value = unchecked((byte)(sbyte)input); 777 | return true; 778 | } 779 | 780 | if (inputType == typeof(byte[])) 781 | { 782 | var bytes = (byte[])input; 783 | if (bytes.Length != 1) 784 | return false; 785 | 786 | value = bytes[0]; 787 | return true; 788 | } 789 | } 790 | 791 | if (conversionType == typeof(decimal)) 792 | { 793 | if (inputType == typeof(byte[])) 794 | { 795 | var bytes = (byte[])input; 796 | if (bytes.Length != 16) 797 | return false; 798 | 799 | value = ToDecimal(bytes); 800 | return true; 801 | } 802 | } 803 | 804 | if (conversionType == typeof(DateTime)) 805 | { 806 | if (inputType == typeof(long)) 807 | { 808 | value = new DateTime((long)input); 809 | return true; 810 | } 811 | 812 | if (inputType == typeof(DateTimeOffset)) 813 | { 814 | value = ((DateTimeOffset)input).DateTime; 815 | return true; 816 | } 817 | } 818 | 819 | if (conversionType == typeof(TimeSpan)) 820 | { 821 | if (inputType == typeof(long)) 822 | { 823 | value = new TimeSpan((long)input); 824 | return true; 825 | } 826 | } 827 | 828 | if (conversionType == typeof(char)) 829 | { 830 | if (inputType == typeof(byte[])) 831 | { 832 | var bytes = (byte[])input; 833 | if (bytes.Length != 2) 834 | return false; 835 | 836 | value = BitConverter.ToChar(bytes, 0); 837 | return true; 838 | } 839 | } 840 | 841 | if (conversionType == typeof(float)) 842 | { 843 | if (inputType == typeof(byte[])) 844 | { 845 | var bytes = (byte[])input; 846 | if (bytes.Length != 4) 847 | return false; 848 | 849 | value = BitConverter.ToSingle(bytes, 0); 850 | return true; 851 | } 852 | } 853 | 854 | if (conversionType == typeof(double)) 855 | { 856 | if (inputType == typeof(byte[])) 857 | { 858 | var bytes = (byte[])input; 859 | if (bytes.Length != 8) 860 | return false; 861 | 862 | value = BitConverter.ToDouble(bytes, 0); 863 | return true; 864 | } 865 | } 866 | 867 | if (conversionType == typeof(DateTimeOffset)) 868 | { 869 | if (inputType == typeof(DateTime)) 870 | { 871 | value = new DateTimeOffset((DateTime)input); 872 | return true; 873 | } 874 | 875 | if (inputType == typeof(long)) 876 | { 877 | value = new DateTimeOffset(new DateTime((long)input)); 878 | return true; 879 | } 880 | } 881 | 882 | if (conversionType == typeof(byte[])) 883 | { 884 | if (inputType == typeof(int)) 885 | { 886 | value = BitConverter.GetBytes((int)input); 887 | return true; 888 | } 889 | 890 | if (inputType == typeof(long)) 891 | { 892 | value = BitConverter.GetBytes((long)input); 893 | return true; 894 | } 895 | 896 | if (inputType == typeof(short)) 897 | { 898 | value = BitConverter.GetBytes((short)input); 899 | return true; 900 | } 901 | 902 | if (inputType == typeof(uint)) 903 | { 904 | value = BitConverter.GetBytes((uint)input); 905 | return true; 906 | } 907 | 908 | if (inputType == typeof(ulong)) 909 | { 910 | value = BitConverter.GetBytes((ulong)input); 911 | return true; 912 | } 913 | 914 | if (inputType == typeof(ushort)) 915 | { 916 | value = BitConverter.GetBytes((ushort)input); 917 | return true; 918 | } 919 | 920 | if (inputType == typeof(bool)) 921 | { 922 | value = BitConverter.GetBytes((bool)input); 923 | return true; 924 | } 925 | 926 | if (inputType == typeof(char)) 927 | { 928 | value = BitConverter.GetBytes((char)input); 929 | return true; 930 | } 931 | 932 | if (inputType == typeof(float)) 933 | { 934 | value = BitConverter.GetBytes((float)input); 935 | return true; 936 | } 937 | 938 | if (inputType == typeof(double)) 939 | { 940 | value = BitConverter.GetBytes((double)input); 941 | return true; 942 | } 943 | 944 | if (inputType == typeof(byte)) 945 | { 946 | value = new byte[] { (byte)input }; 947 | return true; 948 | } 949 | 950 | if (inputType == typeof(sbyte)) 951 | { 952 | value = new byte[] { unchecked((byte)(sbyte)input) }; 953 | return true; 954 | } 955 | 956 | if (inputType == typeof(decimal)) 957 | { 958 | value = ((decimal)value).ToBytes(); 959 | return true; 960 | } 961 | 962 | if (inputType == typeof(Guid)) 963 | { 964 | value = ((Guid)input).ToByteArray(); 965 | return true; 966 | } 967 | } 968 | 969 | var tc = TypeDescriptor.GetConverter(conversionType); 970 | if (tc != null && tc.CanConvertFrom(inputType)) 971 | { 972 | try 973 | { 974 | value = tc.ConvertFrom(null, provider as CultureInfo, input); 975 | return true; 976 | } 977 | catch 978 | { 979 | // continue; 980 | } 981 | } 982 | 983 | tc = TypeDescriptor.GetConverter(inputType); 984 | if (tc != null && tc.CanConvertTo(conversionType)) 985 | { 986 | try 987 | { 988 | value = tc.ConvertTo(null, provider as CultureInfo, input, conversionType); 989 | return true; 990 | } 991 | catch 992 | { 993 | // continue; 994 | } 995 | } 996 | 997 | if (input is IConvertible convertible) 998 | { 999 | try 1000 | { 1001 | value = convertible.ToType(conversionType, provider); 1002 | return true; 1003 | } 1004 | catch 1005 | { 1006 | // continue 1007 | } 1008 | } 1009 | 1010 | if (conversionType == typeof(string)) 1011 | { 1012 | value = string.Format(provider, "{0}", input); 1013 | return true; 1014 | } 1015 | 1016 | return false; 1017 | } 1018 | 1019 | public static decimal ToDecimal(this byte[] bytes) 1020 | { 1021 | if (bytes == null || bytes.Length != 16) 1022 | throw new ArgumentException(null, nameof(bytes)); 1023 | 1024 | var ints = new int[4]; 1025 | Buffer.BlockCopy(bytes, 0, ints, 0, 16); 1026 | return new decimal(ints); 1027 | } 1028 | 1029 | public static byte[] ToBytes(this decimal dec) 1030 | { 1031 | var bytes = new byte[16]; 1032 | Buffer.BlockCopy(decimal.GetBits(dec), 0, bytes, 0, 16); 1033 | return bytes; 1034 | } 1035 | 1036 | public static bool IsNullable(this Type type) 1037 | { 1038 | if (type == null) 1039 | throw new ArgumentNullException(nameof(type)); 1040 | 1041 | return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); 1042 | } 1043 | 1044 | public static string Nullify(this string text) 1045 | { 1046 | if (text == null) 1047 | return null; 1048 | 1049 | if (string.IsNullOrWhiteSpace(text)) 1050 | return null; 1051 | 1052 | string t = text.Trim(); 1053 | return t.Length == 0 ? null : t; 1054 | } 1055 | 1056 | private static Lazy _enumTryParse = new Lazy(() => typeof(Enum).GetMethods(BindingFlags.Public | BindingFlags.Static).First(m => m.Name == nameof(Enum.TryParse) && m.GetParameters().Length == 3)); 1057 | 1058 | public static bool EnumTryParse(Type enumType, object value, out object enumValue) => EnumTryParse(enumType, value, true, out enumValue); 1059 | public static bool EnumTryParse(Type enumType, object value, bool ignoreCase, out object enumValue) 1060 | { 1061 | if (enumType == null) 1062 | throw new ArgumentNullException(nameof(enumType)); 1063 | 1064 | if (!enumType.IsEnum) 1065 | throw new ArgumentException(null, nameof(enumType)); 1066 | 1067 | var svalue = value as string; 1068 | if (svalue == null && value != null) 1069 | { 1070 | svalue = string.Format("{0}", value); 1071 | } 1072 | 1073 | var tryParse = _enumTryParse.Value.MakeGenericMethod(enumType); 1074 | var args = new object[] { svalue, ignoreCase, Enum.ToObject(enumType, 0) }; 1075 | bool result = (bool)tryParse.Invoke(null, args); 1076 | enumValue = args[2]; 1077 | return result; 1078 | } 1079 | } 1080 | } 1081 | --------------------------------------------------------------------------------