First Commit

This commit is contained in:
0% [█ █ █ █ █ █ █ █ █ █] 100%
2026-07-18 23:05:42 -05:00
parent ac93c233fb
commit ab4144e833
12 changed files with 3282 additions and 0 deletions
+9
View File
@@ -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>
+114
View File
@@ -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
}
}
+170
View File
@@ -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();
}
}
}
+120
View File
@@ -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>
+20
View File
@@ -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
+194
View File
@@ -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
}
}
+256
View File
@@ -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);
}
}
}
+120
View File
@@ -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>
+84
View File
@@ -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
}
}
+91
View File
@@ -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();
}
}
}
+123
View File
@@ -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>