First Commit
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<Solution>
|
||||
<Configurations>
|
||||
<Platform Name="Any CPU" />
|
||||
<Platform Name="x64" />
|
||||
</Configurations>
|
||||
<Project Path="DisplayStreamer/DisplayStreamer.csproj">
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
</Solution>
|
||||
Generated
+114
@@ -0,0 +1,114 @@
|
||||
namespace DisplayStreamer
|
||||
{
|
||||
partial class ClientForm
|
||||
{
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
private System.Windows.Forms.Label lblServers;
|
||||
private System.Windows.Forms.ListBox lstServers;
|
||||
private System.Windows.Forms.Button btnConnect;
|
||||
private System.Windows.Forms.Button btnSwitchMode;
|
||||
private System.Windows.Forms.CheckBox chkLogging;
|
||||
private System.Windows.Forms.Timer updateTimer;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.lblServers = new System.Windows.Forms.Label();
|
||||
this.lstServers = new System.Windows.Forms.ListBox();
|
||||
this.btnConnect = new System.Windows.Forms.Button();
|
||||
this.btnSwitchMode = new System.Windows.Forms.Button();
|
||||
this.chkLogging = new System.Windows.Forms.CheckBox();
|
||||
this.updateTimer = new System.Windows.Forms.Timer(this.components);
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// lblServers
|
||||
//
|
||||
this.lblServers.AutoSize = true;
|
||||
this.lblServers.Location = new System.Drawing.Point(20, 20);
|
||||
this.lblServers.Name = "lblServers";
|
||||
this.lblServers.Size = new System.Drawing.Size(182, 13);
|
||||
this.lblServers.TabIndex = 0;
|
||||
this.lblServers.Text = "Discovered Servers (Auto-updating):";
|
||||
//
|
||||
// lstServers
|
||||
//
|
||||
this.lstServers.FormattingEnabled = true;
|
||||
this.lstServers.Location = new System.Drawing.Point(20, 40);
|
||||
this.lstServers.Name = "lstServers";
|
||||
this.lstServers.Size = new System.Drawing.Size(440, 160);
|
||||
this.lstServers.TabIndex = 1;
|
||||
this.lstServers.DoubleClick += new System.EventHandler(this.lstServers_DoubleClick);
|
||||
this.lstServers.KeyDown += new System.Windows.Forms.KeyEventHandler(this.lstServers_KeyDown);
|
||||
//
|
||||
// chkLogging
|
||||
//
|
||||
this.chkLogging.AutoSize = true;
|
||||
this.chkLogging.Location = new System.Drawing.Point(20, 210);
|
||||
this.chkLogging.Name = "chkLogging";
|
||||
this.chkLogging.Size = new System.Drawing.Size(100, 17);
|
||||
this.chkLogging.TabIndex = 4;
|
||||
this.chkLogging.Text = "Enable Logging";
|
||||
this.chkLogging.UseVisualStyleBackColor = true;
|
||||
this.chkLogging.CheckedChanged += new System.EventHandler(this.chkLogging_CheckedChanged);
|
||||
//
|
||||
// btnConnect
|
||||
//
|
||||
this.btnConnect.Font = new System.Drawing.Font("Arial", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.btnConnect.Location = new System.Drawing.Point(20, 240);
|
||||
this.btnConnect.Name = "btnConnect";
|
||||
this.btnConnect.Size = new System.Drawing.Size(210, 40);
|
||||
this.btnConnect.TabIndex = 2;
|
||||
this.btnConnect.Text = "Connect";
|
||||
this.btnConnect.UseVisualStyleBackColor = true;
|
||||
this.btnConnect.Click += new System.EventHandler(this.btnConnect_Click);
|
||||
//
|
||||
// btnSwitchMode
|
||||
//
|
||||
this.btnSwitchMode.Font = new System.Drawing.Font("Arial", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.btnSwitchMode.Location = new System.Drawing.Point(250, 240);
|
||||
this.btnSwitchMode.Name = "btnSwitchMode";
|
||||
this.btnSwitchMode.Size = new System.Drawing.Size(210, 40);
|
||||
this.btnSwitchMode.TabIndex = 3;
|
||||
this.btnSwitchMode.Text = "Switch to Server Mode";
|
||||
this.btnSwitchMode.UseVisualStyleBackColor = true;
|
||||
this.btnSwitchMode.Click += new System.EventHandler(this.btnSwitchMode_Click);
|
||||
//
|
||||
// updateTimer
|
||||
//
|
||||
this.updateTimer.Interval = 1000;
|
||||
this.updateTimer.Tick += new System.EventHandler(this.updateTimer_Tick);
|
||||
//
|
||||
// ClientForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(484, 311);
|
||||
this.Controls.Add(this.chkLogging);
|
||||
this.Controls.Add(this.btnSwitchMode);
|
||||
this.Controls.Add(this.btnConnect);
|
||||
this.Controls.Add(this.lstServers);
|
||||
this.Controls.Add(this.lblServers);
|
||||
this.Name = "ClientForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "Stream Client (Viewer)";
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ClientForm_FormClosing);
|
||||
this.Load += new System.EventHandler(this.ClientForm_Load);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Sockets;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace DisplayStreamer
|
||||
{
|
||||
public partial class ClientForm : Form
|
||||
{
|
||||
private UdpDiscoveryClient discoveryClient;
|
||||
private string autoConnectIp;
|
||||
private int autoConnectPort;
|
||||
|
||||
public ClientForm(string ip = null, int port = 0)
|
||||
{
|
||||
InitializeComponent();
|
||||
autoConnectIp = ip;
|
||||
autoConnectPort = port;
|
||||
this.Shown += ClientForm_Shown;
|
||||
}
|
||||
|
||||
private void ClientForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
chkLogging.Checked = Program.LoggingEnabled;
|
||||
discoveryClient = new UdpDiscoveryClient();
|
||||
discoveryClient.Start();
|
||||
updateTimer.Start();
|
||||
}
|
||||
|
||||
private void ClientForm_Shown(object sender, EventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(autoConnectIp))
|
||||
{
|
||||
ViewerForm viewer = new ViewerForm(autoConnectIp, autoConnectPort);
|
||||
this.Hide();
|
||||
viewer.ShowDialog();
|
||||
this.Show();
|
||||
autoConnectIp = null; // Clear it so it doesn't loop
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
var servers = discoveryClient.GetDiscoveredServers();
|
||||
|
||||
string selectedString = lstServers.SelectedItem?.ToString();
|
||||
|
||||
lstServers.BeginUpdate();
|
||||
lstServers.Items.Clear();
|
||||
foreach (var srv in servers)
|
||||
{
|
||||
string item = $"{srv.MachineName} - {srv.IPAddress}:{srv.Port}";
|
||||
lstServers.Items.Add(item);
|
||||
|
||||
if (item == selectedString)
|
||||
{
|
||||
lstServers.SelectedItem = item;
|
||||
}
|
||||
}
|
||||
lstServers.EndUpdate();
|
||||
}
|
||||
|
||||
private void btnConnect_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (lstServers.SelectedIndex == -1) return;
|
||||
string selectedString = lstServers.SelectedItem.ToString();
|
||||
|
||||
var servers = discoveryClient.GetDiscoveredServers();
|
||||
var selectedSrv = servers.FirstOrDefault(s => $"{s.MachineName} - {s.IPAddress}:{s.Port}" == selectedString);
|
||||
|
||||
if (selectedSrv == null) return;
|
||||
|
||||
// Auto-Update Check
|
||||
if (selectedSrv.Hash != Program.AppHash)
|
||||
{
|
||||
DialogResult res = MessageBox.Show("The server is running a different version of the streamer. Would you like to automatically update and reconnect?", "Update Required", MessageBoxButtons.YesNo, MessageBoxIcon.Information);
|
||||
if (res == DialogResult.Yes)
|
||||
{
|
||||
PerformAutoUpdate(selectedSrv);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ViewerForm viewer = new ViewerForm(selectedSrv.IPAddress, selectedSrv.Port);
|
||||
this.Hide();
|
||||
viewer.ShowDialog();
|
||||
this.Show();
|
||||
}
|
||||
|
||||
private void btnSwitchMode_Click(object sender, EventArgs e)
|
||||
{
|
||||
Program.NextForm = new ServerForm();
|
||||
this.Close();
|
||||
}
|
||||
|
||||
private void chkLogging_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
Program.LoggingEnabled = chkLogging.Checked;
|
||||
}
|
||||
|
||||
private void lstServers_DoubleClick(object sender, EventArgs e)
|
||||
{
|
||||
btnConnect_Click(sender, e);
|
||||
}
|
||||
|
||||
private void lstServers_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyCode == Keys.Enter)
|
||||
{
|
||||
btnConnect_Click(sender, e);
|
||||
e.Handled = true;
|
||||
e.SuppressKeyPress = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void PerformAutoUpdate(ServerInfo srv)
|
||||
{
|
||||
try
|
||||
{
|
||||
string exePath = Application.ExecutablePath;
|
||||
string exeName = Path.GetFileName(exePath);
|
||||
string tempExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update.exe");
|
||||
string batPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "update.bat");
|
||||
|
||||
using (var client = new TcpClient(srv.IPAddress, 21000))
|
||||
using (var stream = client.GetStream())
|
||||
using (var fs = File.Create(tempExe))
|
||||
{
|
||||
byte[] lenBuf = new byte[4];
|
||||
stream.Read(lenBuf, 0, 4);
|
||||
int len = BitConverter.ToInt32(lenBuf, 0);
|
||||
|
||||
byte[] buffer = new byte[8192];
|
||||
int totalRead = 0;
|
||||
while (totalRead < len)
|
||||
{
|
||||
int read = stream.Read(buffer, 0, Math.Min(buffer.Length, len - totalRead));
|
||||
if (read == 0) throw new Exception("Disconnected during update download.");
|
||||
fs.Write(buffer, 0, read);
|
||||
totalRead += read;
|
||||
}
|
||||
}
|
||||
|
||||
string bat = $@"
|
||||
@echo off
|
||||
timeout /t 2 /nobreak > NUL
|
||||
del ""{exeName}""
|
||||
ren ""update.exe"" ""{exeName}""
|
||||
start """" ""{exeName}"" -autoconnect {srv.IPAddress}:{srv.Port}
|
||||
del ""%~f0""
|
||||
";
|
||||
File.WriteAllText(batPath, bat);
|
||||
Process.Start(new ProcessStartInfo(batPath) { CreateNoWindow = true, UseShellExecute = false });
|
||||
Application.Exit();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show("Update failed: " + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClientForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
updateTimer.Stop();
|
||||
discoveryClient?.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SharpDX" Version="4.2.0" />
|
||||
<PackageReference Include="SharpDX.Direct3D11" Version="4.2.0" />
|
||||
<PackageReference Include="SharpDX.DXGI" Version="4.2.0" />
|
||||
<PackageReference Include="SharpDX.MediaFoundation" Version="4.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
File diff suppressed because it is too large
Load Diff
Generated
+194
@@ -0,0 +1,194 @@
|
||||
namespace DisplayStreamer
|
||||
{
|
||||
partial class ServerForm
|
||||
{
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
private System.Windows.Forms.Label lblAdapters;
|
||||
private System.Windows.Forms.CheckedListBox clbAdapters;
|
||||
private System.Windows.Forms.Label lblDisplays;
|
||||
private System.Windows.Forms.CheckedListBox clbDisplays;
|
||||
private System.Windows.Forms.Button btnStart;
|
||||
private System.Windows.Forms.Button btnSwitchMode;
|
||||
private System.Windows.Forms.Label lblStatus;
|
||||
private System.Windows.Forms.CheckBox chkLogging;
|
||||
private System.Windows.Forms.TextBox txtLog;
|
||||
private System.Windows.Forms.NotifyIcon trayIcon;
|
||||
private System.Windows.Forms.ContextMenuStrip trayMenu;
|
||||
private System.Windows.Forms.ToolStripMenuItem menuShow;
|
||||
private System.Windows.Forms.ToolStripMenuItem menuExit;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.lblAdapters = new System.Windows.Forms.Label();
|
||||
this.clbAdapters = new System.Windows.Forms.CheckedListBox();
|
||||
this.lblDisplays = new System.Windows.Forms.Label();
|
||||
this.clbDisplays = new System.Windows.Forms.CheckedListBox();
|
||||
this.btnStart = new System.Windows.Forms.Button();
|
||||
this.btnSwitchMode = new System.Windows.Forms.Button();
|
||||
this.lblStatus = new System.Windows.Forms.Label();
|
||||
this.chkLogging = new System.Windows.Forms.CheckBox();
|
||||
this.txtLog = new System.Windows.Forms.TextBox();
|
||||
this.trayIcon = new System.Windows.Forms.NotifyIcon(this.components);
|
||||
this.trayMenu = new System.Windows.Forms.ContextMenuStrip(this.components);
|
||||
this.menuShow = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.menuExit = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.trayMenu.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// lblAdapters
|
||||
//
|
||||
this.lblAdapters.AutoSize = true;
|
||||
this.lblAdapters.Location = new System.Drawing.Point(20, 20);
|
||||
this.lblAdapters.Name = "lblAdapters";
|
||||
this.lblAdapters.Size = new System.Drawing.Size(205, 13);
|
||||
this.lblAdapters.TabIndex = 0;
|
||||
this.lblAdapters.Text = "Select Network Adapters to Broadcast on:";
|
||||
//
|
||||
// clbAdapters
|
||||
//
|
||||
this.clbAdapters.FormattingEnabled = true;
|
||||
this.clbAdapters.Location = new System.Drawing.Point(20, 40);
|
||||
this.clbAdapters.Name = "clbAdapters";
|
||||
this.clbAdapters.Size = new System.Drawing.Size(440, 94);
|
||||
this.clbAdapters.TabIndex = 1;
|
||||
//
|
||||
// lblDisplays
|
||||
//
|
||||
this.lblDisplays.AutoSize = true;
|
||||
this.lblDisplays.Location = new System.Drawing.Point(20, 150);
|
||||
this.lblDisplays.Name = "lblDisplays";
|
||||
this.lblDisplays.Size = new System.Drawing.Size(278, 13);
|
||||
this.lblDisplays.TabIndex = 2;
|
||||
this.lblDisplays.Text = "Select Displays to Capture (Will downscale 4K to 1080p):";
|
||||
//
|
||||
// clbDisplays
|
||||
//
|
||||
this.clbDisplays.FormattingEnabled = true;
|
||||
this.clbDisplays.Location = new System.Drawing.Point(20, 170);
|
||||
this.clbDisplays.Name = "clbDisplays";
|
||||
this.clbDisplays.Size = new System.Drawing.Size(440, 94);
|
||||
this.clbDisplays.TabIndex = 3;
|
||||
//
|
||||
// btnStart
|
||||
//
|
||||
this.btnStart.Font = new System.Drawing.Font("Arial", 12F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.btnStart.Location = new System.Drawing.Point(20, 290);
|
||||
this.btnStart.Name = "btnStart";
|
||||
this.btnStart.Size = new System.Drawing.Size(210, 40);
|
||||
this.btnStart.TabIndex = 4;
|
||||
this.btnStart.Text = "Start Streaming";
|
||||
this.btnStart.UseVisualStyleBackColor = true;
|
||||
this.btnStart.Click += new System.EventHandler(this.btnStart_Click);
|
||||
//
|
||||
// btnSwitchMode
|
||||
//
|
||||
this.btnSwitchMode.Font = new System.Drawing.Font("Arial", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
this.btnSwitchMode.Location = new System.Drawing.Point(250, 290);
|
||||
this.btnSwitchMode.Name = "btnSwitchMode";
|
||||
this.btnSwitchMode.Size = new System.Drawing.Size(210, 40);
|
||||
this.btnSwitchMode.TabIndex = 7;
|
||||
this.btnSwitchMode.Text = "Switch to Client Mode";
|
||||
this.btnSwitchMode.UseVisualStyleBackColor = true;
|
||||
this.btnSwitchMode.Click += new System.EventHandler(this.btnSwitchMode_Click);
|
||||
//
|
||||
// lblStatus
|
||||
//
|
||||
this.lblStatus.AutoSize = true;
|
||||
this.lblStatus.ForeColor = System.Drawing.Color.Gray;
|
||||
this.lblStatus.Location = new System.Drawing.Point(20, 340);
|
||||
this.lblStatus.Name = "lblStatus";
|
||||
this.lblStatus.Size = new System.Drawing.Size(63, 13);
|
||||
this.lblStatus.TabIndex = 5;
|
||||
this.lblStatus.Text = "Status: Idle";
|
||||
//
|
||||
// chkLogging
|
||||
//
|
||||
this.chkLogging.AutoSize = true;
|
||||
this.chkLogging.Location = new System.Drawing.Point(350, 340);
|
||||
this.chkLogging.Name = "chkLogging";
|
||||
this.chkLogging.Size = new System.Drawing.Size(100, 17);
|
||||
this.chkLogging.TabIndex = 8;
|
||||
this.chkLogging.Text = "Enable Logging";
|
||||
this.chkLogging.UseVisualStyleBackColor = true;
|
||||
this.chkLogging.CheckedChanged += new System.EventHandler(this.chkLogging_CheckedChanged);
|
||||
//
|
||||
// txtLog
|
||||
//
|
||||
this.txtLog.Location = new System.Drawing.Point(20, 370);
|
||||
this.txtLog.Multiline = true;
|
||||
this.txtLog.Name = "txtLog";
|
||||
this.txtLog.ReadOnly = true;
|
||||
this.txtLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
|
||||
this.txtLog.Size = new System.Drawing.Size(440, 210);
|
||||
this.txtLog.TabIndex = 6;
|
||||
//
|
||||
// trayMenu
|
||||
//
|
||||
this.trayMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.menuShow,
|
||||
this.menuExit});
|
||||
this.trayMenu.Name = "trayMenu";
|
||||
this.trayMenu.Size = new System.Drawing.Size(104, 48);
|
||||
//
|
||||
// menuShow
|
||||
//
|
||||
this.menuShow.Name = "menuShow";
|
||||
this.menuShow.Size = new System.Drawing.Size(103, 22);
|
||||
this.menuShow.Text = "Show";
|
||||
this.menuShow.Click += new System.EventHandler(this.menuShow_Click);
|
||||
//
|
||||
// menuExit
|
||||
//
|
||||
this.menuExit.Name = "menuExit";
|
||||
this.menuExit.Size = new System.Drawing.Size(103, 22);
|
||||
this.menuExit.Text = "Exit";
|
||||
this.menuExit.Click += new System.EventHandler(this.menuExit_Click);
|
||||
//
|
||||
// trayIcon
|
||||
//
|
||||
this.trayIcon.ContextMenuStrip = this.trayMenu;
|
||||
this.trayIcon.Icon = System.Drawing.SystemIcons.Application;
|
||||
this.trayIcon.Text = "Display Streamer Server";
|
||||
this.trayIcon.DoubleClick += new System.EventHandler(this.trayIcon_DoubleClick);
|
||||
//
|
||||
// ServerForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(484, 600);
|
||||
this.Controls.Add(this.chkLogging);
|
||||
this.Controls.Add(this.btnSwitchMode);
|
||||
this.Controls.Add(this.txtLog);
|
||||
this.Controls.Add(this.lblStatus);
|
||||
this.Controls.Add(this.btnStart);
|
||||
this.Controls.Add(this.clbDisplays);
|
||||
this.Controls.Add(this.lblDisplays);
|
||||
this.Controls.Add(this.clbAdapters);
|
||||
this.Controls.Add(this.lblAdapters);
|
||||
this.Name = "ServerForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "Stream Server (DX12 / H.265)";
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ServerForm_FormClosing);
|
||||
this.Load += new System.EventHandler(this.ServerForm_Load);
|
||||
this.Resize += new System.EventHandler(this.ServerForm_Resize);
|
||||
this.trayMenu.ResumeLayout(false);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace DisplayStreamer
|
||||
{
|
||||
public partial class ServerForm : Form
|
||||
{
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
|
||||
|
||||
[FlagsAttribute]
|
||||
public enum EXECUTION_STATE : uint
|
||||
{
|
||||
ES_AWAYMODE_REQUIRED = 0x00000040,
|
||||
ES_CONTINUOUS = 0x80000000,
|
||||
ES_DISPLAY_REQUIRED = 0x00000002,
|
||||
ES_SYSTEM_REQUIRED = 0x00000001
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool SetProcessInformation(IntPtr hProcess, int processInformationClass, ref PROCESS_POWER_THROTTLING_STATE processInformation, uint processInformationSize);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct PROCESS_POWER_THROTTLING_STATE
|
||||
{
|
||||
public uint Version;
|
||||
public uint ControlMask;
|
||||
public uint StateMask;
|
||||
}
|
||||
|
||||
private UdpDiscoveryServer discoveryServer;
|
||||
private UpdateServer updateServer;
|
||||
private LogServer logServer;
|
||||
private List<StreamServer> streamServers = new List<StreamServer>();
|
||||
private bool isStreaming = false;
|
||||
|
||||
private Point normalLocation;
|
||||
private bool isHiddenOffScreen = false;
|
||||
|
||||
public ServerForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void ServerForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
chkLogging.Checked = Program.LoggingEnabled;
|
||||
Logger.OnLog += Logger_OnLog;
|
||||
LoadHardware();
|
||||
}
|
||||
|
||||
private void Logger_OnLog(string msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this.IsDisposed) return;
|
||||
|
||||
if (this.InvokeRequired)
|
||||
{
|
||||
this.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!this.IsDisposed && this.Visible)
|
||||
{
|
||||
if (txtLog.TextLength > 10000) txtLog.Clear();
|
||||
txtLog.AppendText(msg + Environment.NewLine);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!this.IsDisposed && this.Visible)
|
||||
{
|
||||
if (txtLog.TextLength > 10000) txtLog.Clear();
|
||||
txtLog.AppendText(msg + Environment.NewLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void LoadHardware()
|
||||
{
|
||||
foreach (var ni in NetworkInterface.GetAllNetworkInterfaces().Where(n => n.OperationalStatus == OperationalStatus.Up))
|
||||
{
|
||||
var ip = ni.GetIPProperties().UnicastAddresses.FirstOrDefault(a => a.Address.AddressFamily == AddressFamily.InterNetwork);
|
||||
if (ip != null) clbAdapters.Items.Add($"{ni.Name} ({ip.Address})", true);
|
||||
}
|
||||
|
||||
for (int i = 0; i < Screen.AllScreens.Length; i++)
|
||||
{
|
||||
var s = Screen.AllScreens[i];
|
||||
clbDisplays.Items.Add($"Display {i + 1}: {s.Bounds.Width}x{s.Bounds.Height}", true);
|
||||
}
|
||||
}
|
||||
|
||||
private void btnStart_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (!isStreaming)
|
||||
{
|
||||
if (clbDisplays.CheckedIndices.Count == 0)
|
||||
{
|
||||
MessageBox.Show("Select at least one display.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Elevate process priority to prevent Windows from throttling the app when minimized or out of focus
|
||||
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
|
||||
|
||||
// Disable Windows Power Throttling (EcoQoS) and Timer Resolution ignoring for this process
|
||||
try
|
||||
{
|
||||
PROCESS_POWER_THROTTLING_STATE state = new PROCESS_POWER_THROTTLING_STATE
|
||||
{
|
||||
Version = 1,
|
||||
ControlMask = 0x1 | 0x4, // EXECUTION_SPEED | IGNORE_TIMER_RESOLUTION
|
||||
StateMask = 0 // Turn off throttling
|
||||
};
|
||||
SetProcessInformation(Process.GetCurrentProcess().Handle, 77, ref state, (uint)Marshal.SizeOf(state));
|
||||
}
|
||||
catch { }
|
||||
|
||||
// Prevent the system from sleeping or turning off the display while streaming
|
||||
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS | EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED);
|
||||
|
||||
List<string> ips = new List<string>();
|
||||
foreach (var item in clbAdapters.CheckedItems)
|
||||
{
|
||||
string ip = item.ToString().Split('(')[1].TrimEnd(')');
|
||||
ips.Add(ip);
|
||||
}
|
||||
|
||||
int basePort = 20000;
|
||||
List<Tuple<string, int>> endpoints = new List<Tuple<string, int>>();
|
||||
|
||||
// Start a dedicated stream server for each selected display
|
||||
foreach (int idx in clbDisplays.CheckedIndices)
|
||||
{
|
||||
var srv = new StreamServer(idx, basePort);
|
||||
srv.Start();
|
||||
streamServers.Add(srv);
|
||||
endpoints.Add(new Tuple<string, int>($"Display {idx + 1}", basePort));
|
||||
basePort++;
|
||||
}
|
||||
|
||||
// Start the update server on port 21000
|
||||
updateServer = new UpdateServer();
|
||||
updateServer.Start(21000);
|
||||
|
||||
// Start the log server on port 22000
|
||||
logServer = new LogServer();
|
||||
logServer.Start(22000);
|
||||
|
||||
discoveryServer = new UdpDiscoveryServer(ips, endpoints);
|
||||
discoveryServer.Start();
|
||||
|
||||
isStreaming = true;
|
||||
btnStart.Text = "Stop Streaming";
|
||||
lblStatus.Text = $"Status: Broadcasting {streamServers.Count} display(s)";
|
||||
lblStatus.ForeColor = Color.Green;
|
||||
}
|
||||
else
|
||||
{
|
||||
discoveryServer?.Stop();
|
||||
updateServer?.Stop();
|
||||
logServer?.Stop();
|
||||
foreach (var srv in streamServers) srv.Stop();
|
||||
streamServers.Clear();
|
||||
|
||||
// Restore normal priority and allow sleep
|
||||
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.Normal;
|
||||
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS);
|
||||
|
||||
isStreaming = false;
|
||||
btnStart.Text = "Start Streaming";
|
||||
lblStatus.Text = "Status: Idle";
|
||||
lblStatus.ForeColor = Color.Gray;
|
||||
}
|
||||
}
|
||||
|
||||
private void btnSwitchMode_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (isStreaming)
|
||||
{
|
||||
btnStart_Click(null, null);
|
||||
}
|
||||
Program.NextForm = new ClientForm();
|
||||
this.Close();
|
||||
}
|
||||
|
||||
private void chkLogging_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
Program.LoggingEnabled = chkLogging.Checked;
|
||||
}
|
||||
|
||||
private void ServerForm_Resize(object sender, EventArgs e)
|
||||
{
|
||||
if (this.WindowState == FormWindowState.Minimized)
|
||||
{
|
||||
// Bypass Windows deep-sleep throttling for minimized windows by moving it off-screen instead of hiding it
|
||||
this.WindowState = FormWindowState.Normal;
|
||||
normalLocation = this.Location;
|
||||
this.Location = new Point(-32000, -32000);
|
||||
this.ShowInTaskbar = false;
|
||||
trayIcon.Visible = true;
|
||||
isHiddenOffScreen = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void trayIcon_DoubleClick(object sender, EventArgs e)
|
||||
{
|
||||
RestoreWindow();
|
||||
}
|
||||
|
||||
private void menuShow_Click(object sender, EventArgs e)
|
||||
{
|
||||
RestoreWindow();
|
||||
}
|
||||
|
||||
private void menuExit_Click(object sender, EventArgs e)
|
||||
{
|
||||
this.Close();
|
||||
}
|
||||
|
||||
private void RestoreWindow()
|
||||
{
|
||||
if (isHiddenOffScreen)
|
||||
{
|
||||
this.Location = normalLocation;
|
||||
this.ShowInTaskbar = true;
|
||||
trayIcon.Visible = false;
|
||||
this.Activate();
|
||||
isHiddenOffScreen = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ServerForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
discoveryServer?.Stop();
|
||||
updateServer?.Stop();
|
||||
logServer?.Stop();
|
||||
foreach (var srv in streamServers) srv.Stop();
|
||||
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
Generated
+84
@@ -0,0 +1,84 @@
|
||||
namespace DisplayStreamer
|
||||
{
|
||||
partial class ViewerForm
|
||||
{
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
private System.Windows.Forms.Label lblEsc;
|
||||
private System.Windows.Forms.Label lblMetrics;
|
||||
private System.Windows.Forms.Timer hideLabelTimer;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
components = new System.ComponentModel.Container();
|
||||
lblEsc = new Label();
|
||||
lblMetrics = new Label();
|
||||
hideLabelTimer = new System.Windows.Forms.Timer(components);
|
||||
SuspendLayout();
|
||||
//
|
||||
// lblEsc
|
||||
//
|
||||
lblEsc.AutoSize = true;
|
||||
lblEsc.BackColor = Color.FromArgb(128, 0, 0, 0);
|
||||
lblEsc.Font = new Font("Arial", 14.25F, FontStyle.Bold, GraphicsUnit.Point, 0);
|
||||
lblEsc.ForeColor = Color.White;
|
||||
lblEsc.Location = new Point(0, 0);
|
||||
lblEsc.Margin = new Padding(6, 0, 6, 0);
|
||||
lblEsc.Name = "lblEsc";
|
||||
lblEsc.Size = new Size(419, 40);
|
||||
lblEsc.TabIndex = 1;
|
||||
lblEsc.Text = "Press ESC to disconnect";
|
||||
//
|
||||
// lblMetrics
|
||||
//
|
||||
lblMetrics.AutoSize = true;
|
||||
lblMetrics.BackColor = Color.FromArgb(180, 0, 0, 0);
|
||||
lblMetrics.Font = new Font("Consolas", 14.25F, FontStyle.Bold, GraphicsUnit.Point, 0);
|
||||
lblMetrics.ForeColor = Color.Lime;
|
||||
lblMetrics.Location = new Point(0, 0);
|
||||
lblMetrics.Margin = new Padding(6, 0, 6, 0);
|
||||
lblMetrics.Name = "lblMetrics";
|
||||
lblMetrics.Size = new Size(0, 40);
|
||||
lblMetrics.TabIndex = 2;
|
||||
lblMetrics.Visible = false;
|
||||
//
|
||||
// hideLabelTimer
|
||||
//
|
||||
hideLabelTimer.Interval = 3000;
|
||||
hideLabelTimer.Tick += hideLabelTimer_Tick;
|
||||
//
|
||||
// ViewerForm
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(12F, 30F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
BackColor = Color.Black;
|
||||
ClientSize = new Size(1600, 1038);
|
||||
Controls.Add(lblMetrics);
|
||||
Controls.Add(lblEsc);
|
||||
FormBorderStyle = FormBorderStyle.None;
|
||||
KeyPreview = true;
|
||||
Margin = new Padding(6, 7, 6, 7);
|
||||
Name = "ViewerForm";
|
||||
Text = "ViewerForm";
|
||||
WindowState = FormWindowState.Maximized;
|
||||
FormClosing += ViewerForm_FormClosing;
|
||||
Load += ViewerForm_Load;
|
||||
KeyDown += ViewerForm_KeyDown;
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace DisplayStreamer
|
||||
{
|
||||
public partial class ViewerForm : Form
|
||||
{
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo);
|
||||
|
||||
private string serverIp;
|
||||
private int serverPort;
|
||||
private StreamClient streamClient;
|
||||
|
||||
public ViewerForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.Shown += ViewerForm_Shown;
|
||||
}
|
||||
|
||||
public ViewerForm(string ip, int port) : this()
|
||||
{
|
||||
serverIp = ip;
|
||||
serverPort = port;
|
||||
}
|
||||
|
||||
private void ViewerForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
// Force true fullscreen on the current monitor, covering the taskbar
|
||||
this.FormBorderStyle = FormBorderStyle.None;
|
||||
this.WindowState = FormWindowState.Normal;
|
||||
this.Bounds = Screen.FromControl(this).Bounds;
|
||||
this.TopMost = true;
|
||||
|
||||
// Hide the local host cursor while viewing the stream
|
||||
Cursor.Hide();
|
||||
|
||||
hideLabelTimer.Start();
|
||||
|
||||
if (!string.IsNullOrEmpty(serverIp))
|
||||
{
|
||||
// Pass 'this' (the form itself) to the StreamClient so it can bind the D3D11 SwapChain to it
|
||||
streamClient = new StreamClient(serverIp, serverPort, this, lblMetrics);
|
||||
streamClient.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void ViewerForm_Shown(object sender, EventArgs e)
|
||||
{
|
||||
// Simulate a momentary ALT key press to bypass Windows foreground lock restrictions.
|
||||
// This allows a background-launched process (like our auto-updater batch script) to steal focus.
|
||||
keybd_event(0x12, 0, 0, 0); // VK_MENU (ALT) down
|
||||
keybd_event(0x12, 0, 0x0002, 0); // VK_MENU (ALT) up
|
||||
|
||||
SetForegroundWindow(this.Handle);
|
||||
this.Activate();
|
||||
this.Focus();
|
||||
}
|
||||
|
||||
private void hideLabelTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
lblEsc.Visible = false;
|
||||
hideLabelTimer.Stop();
|
||||
}
|
||||
|
||||
private void ViewerForm_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyCode == Keys.Escape)
|
||||
{
|
||||
this.Close();
|
||||
}
|
||||
else if (e.KeyCode == Keys.F1)
|
||||
{
|
||||
lblMetrics.Visible = !lblMetrics.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
private void ViewerForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
// Restore the local host cursor when exiting the viewer
|
||||
Cursor.Show();
|
||||
|
||||
streamClient?.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="hideLabelTimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
</root>
|
||||
Reference in New Issue
Block a user