commit a9fa99cf53ff47aa446ca04ce93594f488edd4ec Author: kirill.labutin Date: Sat Feb 7 23:23:15 2026 +0300 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5eaf580 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +EthernetSwitcher/bin +EthernetSwitcher/obj diff --git a/EthernetSwitcher.sln b/EthernetSwitcher.sln new file mode 100644 index 0000000..e0f1534 --- /dev/null +++ b/EthernetSwitcher.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EthernetSwitcher", "EthernetSwitcher\EthernetSwitcher.csproj", "{0463CEC3-E6DB-4698-BA12-6166940A143A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0463CEC3-E6DB-4698-BA12-6166940A143A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0463CEC3-E6DB-4698-BA12-6166940A143A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0463CEC3-E6DB-4698-BA12-6166940A143A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0463CEC3-E6DB-4698-BA12-6166940A143A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/EthernetSwitcher.sln.DotSettings.user b/EthernetSwitcher.sln.DotSettings.user new file mode 100644 index 0000000..83a3131 --- /dev/null +++ b/EthernetSwitcher.sln.DotSettings.user @@ -0,0 +1,3 @@ + + True + True \ No newline at end of file diff --git a/EthernetSwitcher/App.config b/EthernetSwitcher/App.config new file mode 100644 index 0000000..2d7731d --- /dev/null +++ b/EthernetSwitcher/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/EthernetSwitcher/CurrentSettings.cs b/EthernetSwitcher/CurrentSettings.cs new file mode 100644 index 0000000..3c4ff3c --- /dev/null +++ b/EthernetSwitcher/CurrentSettings.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.NetworkInformation; +using System.Diagnostics; + +namespace EthernetSwitcher +{ + public static class CurrentSettings + { + private static Settings _settings; + + public static Settings Instance + { + get { return _settings ?? (_settings = Settings.Load()); } + } + + public static void Save() + { + Instance.Save(); + } + + public static void Reload() + { + _settings = Settings.Load(); + } + + public static List GetNetworkInterfaces() + { + try + { + // Use PowerShell to get consistent interface names + var output = ExecutePowerShell("Get-NetAdapter | Select-Object Name, Status | ForEach-Object { $_.Name + ' (' + $_.Status + ')' }"); + + if (string.IsNullOrEmpty(output)) + { + // Fallback to NetworkInterface + return NetworkInterface.GetAllNetworkInterfaces() + .Select(ni => $"{ni.Name} ({ni.OperationalStatus})") + .OrderBy(name => name) + .ToList(); + } + + var interfaces = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) + .Where(line => !string.IsNullOrWhiteSpace(line.Trim())) + .Select(line => line.Trim()) + .OrderBy(name => name) + .ToList(); + + return interfaces; + } + catch (Exception) + { + return new List(); + } + } + + private static string ExecutePowerShell(string command) + { + try + { + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "powershell.exe", + Arguments = $"-NoProfile -ExecutionPolicy Bypass -Command \"& {{{command}}}\"", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + } + }; + + process.Start(); + var output = process.StandardOutput.ReadToEnd(); + var error = process.StandardError.ReadToEnd(); + process.WaitForExit(); + + if (process.ExitCode != 0 && !string.IsNullOrEmpty(error)) + { + Console.WriteLine($"PowerShell error in GetNetworkInterfaces: {error}"); + } + + return output; + } + catch (Exception ex) + { + Console.WriteLine($"Error executing PowerShell in GetNetworkInterfaces: {ex.Message}"); + return ""; + } + } + + public static string GetCleanInterfaceName(string displayName) + { + if (string.IsNullOrEmpty(displayName)) + return ""; + + var lastSpaceIndex = displayName.LastIndexOf(" ("); + if (lastSpaceIndex > 0) + { + return displayName.Substring(0, lastSpaceIndex); + } + return displayName; + } + } +} \ No newline at end of file diff --git a/EthernetSwitcher/EthernetSwitcher.csproj b/EthernetSwitcher/EthernetSwitcher.csproj new file mode 100644 index 0000000..d16872c --- /dev/null +++ b/EthernetSwitcher/EthernetSwitcher.csproj @@ -0,0 +1,94 @@ + + + + + Debug + AnyCPU + {0463CEC3-E6DB-4698-BA12-6166940A143A} + WinExe + EthernetSwitcher + EthernetSwitcher + v4.0 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + Form + + + Form1.cs + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + + + + + + app.manifest + + + \ No newline at end of file diff --git a/EthernetSwitcher/EthernetSwitcherService.cs b/EthernetSwitcher/EthernetSwitcherService.cs new file mode 100644 index 0000000..689005d --- /dev/null +++ b/EthernetSwitcher/EthernetSwitcherService.cs @@ -0,0 +1,264 @@ +using System; +using System.Threading; +using System.Drawing; +using System.Windows.Forms; + +namespace EthernetSwitcher +{ + public class EthernetSwitcherService + { + private Thread _workerThread; + private bool _isRunning; + private NotifyIcon _trayIcon; + private string _lastSelectedInterface = ""; + + public void Start(NotifyIcon trayIcon) + { + _trayIcon = trayIcon; + _isRunning = true; + _workerThread = new Thread(WorkerLoop); + _workerThread.IsBackground = true; + _workerThread.Start(); + } + + public void Stop() + { + _isRunning = false; + if (_workerThread != null && _workerThread.IsAlive) + { + _workerThread.Join(1000); // Wait up to 1 second for thread to finish + } + } + + public void UpdateMode(EthernetMode newMode) + { + // Force immediate execution of the new mode + var settings = CurrentSettings.Instance; + + switch (newMode) + { + case EthernetMode.Primary: + SelectInterface(settings.PrimaryName); + UpdateIconFromResource("icon_yellow.ico"); + break; + + case EthernetMode.Secondary: + SelectInterface(settings.SecondaryName); + UpdateIconFromResource("icon_yellow.ico"); + break; + + case EthernetMode.Auto: + HandleAutoMode(settings); + break; + + case EthernetMode.Disabled: + UpdateIconFromResource("icon_yellow.ico"); + _lastSelectedInterface = "Нет активного интерфейса"; + break; + } + + UpdateTrayTooltip(newMode, _lastSelectedInterface); + } + + private void WorkerLoop() + { + while (_isRunning) + { + try + { + var settings = CurrentSettings.Instance; + + switch (settings.Mode) + { + case EthernetMode.Primary: + SelectInterface(settings.PrimaryName); + UpdateIconFromResource("icon_yellow.ico"); + break; + + case EthernetMode.Secondary: + SelectInterface(settings.SecondaryName); + UpdateIconFromResource("icon_yellow.ico"); + break; + + case EthernetMode.Auto: + HandleAutoMode(settings); + break; + + case EthernetMode.Disabled: + // Do nothing + UpdateIconFromResource("icon_yellow.ico"); + _lastSelectedInterface = "Нет активного интерфейса"; + break; + } + + // Update tray tooltip after all operations + UpdateTrayTooltip(settings.Mode, _lastSelectedInterface); + + // Wait for the specified interval + Thread.Sleep(settings.CheckInterval * 1000); + } + catch (Exception ex) + { + Thread.Sleep(5000); // Wait 5 seconds on error before retrying + } + } + } + + private void UpdateIconFromResource(string resourceName) + { + try + { + var icon = new Icon(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream($"EthernetSwitcher.{resourceName}")); + if (_trayIcon.Icon?.Handle != icon.Handle) + { + _trayIcon.Icon = icon; + } + } + catch (Exception ex) + { + // Fallback to default icon + var defaultIcon = Icon.FromHandle(System.Drawing.SystemIcons.WinLogo.Handle); + if (_trayIcon.Icon?.Handle != defaultIcon.Handle) + { + _trayIcon.Icon = defaultIcon; + } + } + } + + private void HandleAutoMode(Settings settings) + { + bool primaryLinkExists = CheckInterfaceLink(settings.PrimaryName); + + // Update tray icon based on link status + if (primaryLinkExists) + { + UpdateIconFromResource("icon_green.ico"); + } + else + { + UpdateIconFromResource("icon_red.ico"); + } + + if (primaryLinkExists) + { + SelectInterface(settings.PrimaryName); + DeselectInterface(settings.SecondaryName); + } + else + { + SelectInterface(settings.SecondaryName); + DeselectInterface(settings.PrimaryName); + } + } + + private bool CheckInterfaceLink(string interfaceName) + { + try + { + return NetworkManager.GetInterfaceStatus(interfaceName); + } + catch (Exception ex) + { + return false; + } + } + + private void DeselectInterface(string interfaceName) + { + if (string.IsNullOrEmpty(interfaceName)) + return; + + try + { + var settings = CurrentSettings.Instance; + + if (!NetworkManager.InterfaceExists(interfaceName)) + { + _lastSelectedInterface = $"Интерфейс не найден: {interfaceName}"; + UpdateIconFromResource("icon_red.ico"); + return; + } + + switch (settings.Protocol) + { + case IpProtocol.IPv4: + NetworkManager.SetIPv4Status(interfaceName, false); + break; + case IpProtocol.IPv6: + NetworkManager.SetIPv6Status(interfaceName, false); + break; + case IpProtocol.IPv4_IPv6: + NetworkManager.SetIPv4Status(interfaceName, false); + NetworkManager.SetIPv6Status(interfaceName, false); + break; + } + } + catch (Exception ex) + { + // Error handling without logging + } + } + + private void SelectInterface(string interfaceName) + { + if (string.IsNullOrEmpty(interfaceName)) + return; + + try + { + var settings = CurrentSettings.Instance; + + if (!NetworkManager.InterfaceExists(interfaceName)) + { + _lastSelectedInterface = $"Интерфейс не найден: {interfaceName}"; + UpdateIconFromResource("icon_red.ico"); + return; + } + + switch (settings.Protocol) + { + case IpProtocol.IPv4: + NetworkManager.SetIPv4Status(interfaceName, true); + break; + case IpProtocol.IPv6: + NetworkManager.SetIPv6Status(interfaceName, true); + break; + case IpProtocol.IPv4_IPv6: + NetworkManager.SetIPv4Status(interfaceName, true); + NetworkManager.SetIPv6Status(interfaceName, true); + break; + } + + _lastSelectedInterface = interfaceName; + } + catch (Exception ex) + { + // Error handling without logging + } + } + + private void UpdateTrayTooltip(EthernetMode mode, string interfaceName) + { + try + { + var tooltip = $"Режим: {mode}"; + + if (!string.IsNullOrEmpty(interfaceName)) + { + // Shorten interface name to fit in tooltip (Windows 64 char limit) + var shortInterfaceName = interfaceName.Length > 25 ? interfaceName.Substring(0, 22) + "..." : interfaceName; + tooltip += $"\nИнтерфейс: {shortInterfaceName}"; + } + + if (_trayIcon.Text != tooltip) + { + _trayIcon.Text = tooltip; + } + } + catch (Exception ex) + { + // Error handling without logging + } + } + } +} \ No newline at end of file diff --git a/EthernetSwitcher/Form1.Designer.cs b/EthernetSwitcher/Form1.Designer.cs new file mode 100644 index 0000000..3641a72 --- /dev/null +++ b/EthernetSwitcher/Form1.Designer.cs @@ -0,0 +1,193 @@ +using System.Drawing; +using System.Windows.Forms; +using System.Reflection; + +namespace EthernetSwitcher +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.comboBoxMode = new System.Windows.Forms.ComboBox(); + this.comboBoxProtocol = new System.Windows.Forms.ComboBox(); + this.comboBoxPrimary = new System.Windows.Forms.ComboBox(); + this.comboBoxSecondary = new System.Windows.Forms.ComboBox(); + this.trackBarInterval = new System.Windows.Forms.TrackBar(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(20, 20); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(99, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Режим работы:"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(20, 60); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(82, 13); + this.label2.TabIndex = 1; + this.label2.Text = "Основной интерфейс:"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(20, 100); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(103, 13); + this.label3.TabIndex = 2; + this.label3.Text = "Резервный интерфейс:"; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(20, 145); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(78, 13); + this.label5.TabIndex = 4; + this.label5.Text = "IP протокол:"; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(20, 185); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(126, 13); + this.label4.TabIndex = 3; + this.label4.Text = "Интервал проверки: 5 сек"; + // + // comboBoxMode + // + this.comboBoxMode.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxMode.FormattingEnabled = true; + this.comboBoxMode.Items.AddRange(new object[] { + "Auto", + "Primary", + "Secondary", + "Disabled"}); + this.comboBoxMode.Location = new System.Drawing.Point(150, 17); + this.comboBoxMode.Name = "comboBoxMode"; + this.comboBoxMode.Size = new System.Drawing.Size(200, 21); + this.comboBoxMode.TabIndex = 4; + // + // comboBoxPrimary + // + this.comboBoxPrimary.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxPrimary.FormattingEnabled = true; + this.comboBoxPrimary.Location = new System.Drawing.Point(150, 57); + this.comboBoxPrimary.Name = "comboBoxPrimary"; + this.comboBoxPrimary.Size = new System.Drawing.Size(200, 21); + this.comboBoxPrimary.TabIndex = 5; + // + // comboBoxSecondary + // + this.comboBoxSecondary.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxSecondary.FormattingEnabled = true; + this.comboBoxSecondary.Location = new System.Drawing.Point(150, 97); + this.comboBoxSecondary.Name = "comboBoxSecondary"; + this.comboBoxSecondary.Size = new System.Drawing.Size(200, 21); + this.comboBoxSecondary.TabIndex = 6; + // + // comboBoxProtocol + // + this.comboBoxProtocol.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxProtocol.FormattingEnabled = true; + this.comboBoxProtocol.Items.AddRange(new object[] { + "IPv4", + "IPv6", + "IPv4 + IPv6"}); + this.comboBoxProtocol.Location = new System.Drawing.Point(150, 142); + this.comboBoxProtocol.Name = "comboBoxProtocol"; + this.comboBoxProtocol.Size = new System.Drawing.Size(200, 21); + this.comboBoxProtocol.TabIndex = 8; + // + // trackBarInterval + // + this.trackBarInterval.AutoSize = false; + this.trackBarInterval.LargeChange = 5; + this.trackBarInterval.Location = new System.Drawing.Point(150, 180); + this.trackBarInterval.Maximum = 30; + this.trackBarInterval.Minimum = 1; + this.trackBarInterval.Name = "trackBarInterval"; + this.trackBarInterval.Size = new System.Drawing.Size(200, 45); + this.trackBarInterval.SmallChange = 1; + this.trackBarInterval.TabIndex = 7; + this.trackBarInterval.TickFrequency = 1; + this.trackBarInterval.Value = 5; + this.trackBarInterval.ValueChanged += new System.EventHandler(this.TrackBarInterval_ValueChanged); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(400, 270); + this.Controls.Add(this.comboBoxProtocol); + this.Controls.Add(this.trackBarInterval); + this.Controls.Add(this.comboBoxSecondary); + this.Controls.Add(this.comboBoxPrimary); + this.Controls.Add(this.comboBoxMode); + this.Controls.Add(this.label4); + this.Controls.Add(this.label3); + this.Controls.Add(this.label2); + this.Controls.Add(this.label5); + this.Controls.Add(this.label1); + this.Text = "Ethernet Switcher Settings"; + this.WindowState = System.Windows.Forms.FormWindowState.Minimized; + this.ShowInTaskbar = false; + this.Icon = new Icon(Assembly.GetExecutingAssembly().GetManifestResourceStream("EthernetSwitcher.icon.ico")); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.ResumeLayout(false); + this.PerformLayout(); + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.ComboBox comboBoxMode; + private System.Windows.Forms.ComboBox comboBoxPrimary; + private System.Windows.Forms.ComboBox comboBoxSecondary; + private System.Windows.Forms.TrackBar trackBarInterval; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.ComboBox comboBoxProtocol; + private NotifyIcon trayIcon; + } +} \ No newline at end of file diff --git a/EthernetSwitcher/Form1.cs b/EthernetSwitcher/Form1.cs new file mode 100644 index 0000000..43eb6dd --- /dev/null +++ b/EthernetSwitcher/Form1.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Reflection; + +namespace EthernetSwitcher +{ + public partial class Form1 : Form + { + private static EthernetSwitcherService _service; + + public Form1() + { + InitializeComponent(); + InitializeTrayIcon(); + LoadNetworkInterfaces(); + LoadSettings(); + + // Start the background service + _service = new EthernetSwitcherService(); + _service.Start(trayIcon); + } + + private void InitializeTrayIcon() + { + var contextMenu = new ContextMenuStrip(); + + // Add Mode submenu + var modeMenuItem = new ToolStripMenuItem("Режим"); + foreach (EthernetMode mode in Enum.GetValues(typeof(EthernetMode))) + { + var modeItem = new ToolStripMenuItem(GetModeDisplayName(mode)); + modeItem.Tag = mode; + modeItem.Click += (sender, e) => SetModeFromTray((EthernetMode)((ToolStripMenuItem)sender).Tag); + modeMenuItem.DropDownItems.Add(modeItem); + } + contextMenu.Items.Add(modeMenuItem); + + contextMenu.Items.Add("Настройки", null, ShowSettings); + contextMenu.Items.Add("Выход", null, ExitApplication); + + var networkIcon = new Icon(Assembly.GetExecutingAssembly().GetManifestResourceStream("EthernetSwitcher.icon.ico")); + + trayIcon = new NotifyIcon() + { + Icon = networkIcon, + Text = "Ethernet Switcher", + Visible = true, + ContextMenuStrip = contextMenu + }; + + trayIcon.Click += TrayIcon_Click; + + UpdateModeMenuCheck(); + } + + private void ShowSettings(object sender, EventArgs e) + { + this.Show(); + this.WindowState = FormWindowState.Normal; + this.BringToFront(); + } + + private void ExitApplication(object sender, EventArgs e) + { + trayIcon.Visible = false; + Application.Exit(); + } + + private void SetModeFromTray(EthernetMode mode) + { + var settings = CurrentSettings.Instance; + settings.Mode = mode; + CurrentSettings.Save(); + + // Update the service if needed + _service?.UpdateMode(mode); + + // Update menu check marks + UpdateModeMenuCheck(); + } + + private string GetModeDisplayName(EthernetMode mode) + { + switch (mode) + { + case EthernetMode.Auto: + return "Авто"; + case EthernetMode.Primary: + return "Основной"; + case EthernetMode.Secondary: + return "Резервный"; + case EthernetMode.Disabled: + return "Отключен"; + default: + return mode.ToString(); + } + } + + private void UpdateModeMenuCheck() + { + if (trayIcon?.ContextMenuStrip == null) return; + + var currentMode = CurrentSettings.Instance.Mode; + var modeMenuItem = trayIcon.ContextMenuStrip.Items[0] as ToolStripMenuItem; + if (modeMenuItem?.DropDownItems != null) + { + foreach (ToolStripMenuItem item in modeMenuItem.DropDownItems) + { + item.Checked = ((EthernetMode)item.Tag) == currentMode; + } + } + } + + private void LoadNetworkInterfaces() + { + var interfaces = CurrentSettings.GetNetworkInterfaces(); + + comboBoxPrimary.Items.Clear(); + comboBoxSecondary.Items.Clear(); + + comboBoxPrimary.Items.AddRange(interfaces.ToArray()); + comboBoxSecondary.Items.AddRange(interfaces.ToArray()); + + if (interfaces.Count > 0) + { + comboBoxPrimary.SelectedIndex = 0; + comboBoxSecondary.SelectedIndex = Math.Min(1, interfaces.Count - 1); + } + } + + private void LoadSettings() + { + var settings = CurrentSettings.Instance; + + comboBoxMode.SelectedItem = settings.Mode.ToString(); + + // Find the display names that match the saved clean names + var primaryDisplay = FindInterfaceDisplay(settings.PrimaryName); + var secondaryDisplay = FindInterfaceDisplay(settings.SecondaryName); + + comboBoxPrimary.SelectedItem = primaryDisplay; + comboBoxSecondary.SelectedItem = secondaryDisplay; + comboBoxProtocol.SelectedItem = settings.Protocol.ToString().Replace("_", " + "); + trackBarInterval.Value = settings.CheckInterval; + + // Update tray menu check marks + UpdateModeMenuCheck(); + } + + private string FindInterfaceDisplay(string cleanName) + { + foreach (var item in comboBoxPrimary.Items) + { + if (item.ToString().StartsWith(cleanName + " (")) + { + return item.ToString(); + } + } + return cleanName; // Fallback if not found + } + + private void SaveSettings() + { + var settings = CurrentSettings.Instance; + + EthernetMode parsedMode; + if (Enum.TryParse(comboBoxMode.SelectedItem?.ToString(), out parsedMode)) + { + settings.Mode = parsedMode; + } + + settings.PrimaryName = CurrentSettings.GetCleanInterfaceName(comboBoxPrimary.SelectedItem?.ToString() ?? ""); + settings.SecondaryName = CurrentSettings.GetCleanInterfaceName(comboBoxSecondary.SelectedItem?.ToString() ?? ""); + + var protocolString = comboBoxProtocol.SelectedItem?.ToString().Replace(" + ", "_") ?? "IPv4_IPv6"; + IpProtocol parsedProtocol; + if (Enum.TryParse(protocolString, out parsedProtocol)) + { + settings.Protocol = parsedProtocol; + } + + settings.CheckInterval = trackBarInterval.Value; + + CurrentSettings.Save(); + } + + private void TrackBarInterval_ValueChanged(object sender, EventArgs e) + { + label4.Text = $"Интервал проверки: {trackBarInterval.Value} сек"; + } + + protected override void OnVisibleChanged(EventArgs e) + { + base.OnVisibleChanged(e); + + if (this.Visible) + { + LoadNetworkInterfaces(); + LoadSettings(); + } + } + + private void TrayIcon_Click(object sender, EventArgs e) + { + var mouseEvent = e as MouseEventArgs; + if (mouseEvent != null && mouseEvent.Button == MouseButtons.Left) + { + this.Show(); + this.WindowState = FormWindowState.Normal; + this.BringToFront(); + } + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + + if (WindowState == FormWindowState.Minimized) + { + this.Hide(); + } + } + + protected override void OnFormClosing(FormClosingEventArgs e) + { + if (e.CloseReason == CloseReason.UserClosing) + { + SaveSettings(); + e.Cancel = true; + this.Hide(); + } + base.OnFormClosing(e); + } + + protected override void OnFormClosed(FormClosedEventArgs e) + { + _service?.Stop(); + trayIcon?.Dispose(); + base.OnFormClosed(e); + } + } +} \ No newline at end of file diff --git a/EthernetSwitcher/NetworkManager.cs b/EthernetSwitcher/NetworkManager.cs new file mode 100644 index 0000000..5b13e28 --- /dev/null +++ b/EthernetSwitcher/NetworkManager.cs @@ -0,0 +1,196 @@ +using System; +using System.Diagnostics; +using System.Linq; + +namespace EthernetSwitcher +{ + public static class NetworkManager + { + public static bool InterfaceExists(string interfaceName) + { + try + { + var escapedName = EscapePowerShellString(interfaceName); + var output = ExecutePowerShell($"Get-NetAdapter -Name '{escapedName}' -ErrorAction SilentlyContinue"); + return !string.IsNullOrEmpty(output) && !output.Contains("Get-NetAdapter") && output.Contains("Name"); + } + catch (Exception ex) + { + return false; + } + } + + public static void EnableInterface(string interfaceName) + { + try + { + var status = GetInterfaceStatus(interfaceName); + if (!status) + { + var escapedName = EscapePowerShellString(interfaceName); + ExecutePowerShell($"Enable-NetAdapter -Name '{escapedName}' -Confirm:$false"); + Console.WriteLine($"Interface enabled: {interfaceName}"); + } + else + { + Console.WriteLine($"Interface already enabled: {interfaceName}"); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error enabling interface {interfaceName}: {ex.Message}"); + } + } + + public static void SetIPv4Status(string interfaceName, bool status) + { + try + { + var isCurrentlyEnabled = IsIPv4Enabled(interfaceName); + if (isCurrentlyEnabled == status) + { + return; + } + + var escapedName = EscapePowerShellString(interfaceName); + var command = status + ? $"Enable-NetAdapterBinding -Name '{escapedName}' -ComponentID ms_tcpip -Confirm:$false" + : $"Disable-NetAdapterBinding -Name '{escapedName}' -ComponentID ms_tcpip -Confirm:$false"; + + ExecutePowerShell(command); + Console.WriteLine($"IPv4 {(status ? "enabled" : "disabled")} for interface: {interfaceName}"); + } + catch (Exception ex) + { + // Error handling without logging + } + } + + public static void SetIPv6Status(string interfaceName, bool status) + { + try + { + var isCurrentlyEnabled = IsIPv6Enabled(interfaceName); + if (isCurrentlyEnabled == status) + { + return; + } + + var escapedName = EscapePowerShellString(interfaceName); + var command = status + ? $"Enable-NetAdapterBinding -Name '{escapedName}' -ComponentID ms_tcpip6 -Confirm:$false" + : $"Disable-NetAdapterBinding -Name '{escapedName}' -ComponentID ms_tcpip6 -Confirm:$false"; + + ExecutePowerShell(command); + Console.WriteLine($"IPv6 {(status ? "enabled" : "disabled")} for interface: {interfaceName}"); + } + catch (Exception ex) + { + // Error handling without logging + } + } + + public static bool IsIPv4Enabled(string interfaceName) + { + try + { + var escapedName = EscapePowerShellString(interfaceName); + var command = $"Get-NetAdapterBinding -Name '{escapedName}' -ComponentID ms_tcpip -ErrorAction SilentlyContinue"; + var output = ExecutePowerShell(command); + + var lines = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) + { + if (line.Contains("ms_tcpip")) + { + return line.Contains("True"); + } + } + return false; + } + catch (Exception ex) + { + return false; + } + } + + public static bool IsIPv6Enabled(string interfaceName) + { + try + { + var escapedName = EscapePowerShellString(interfaceName); + var command = $"Get-NetAdapterBinding -Name '{escapedName}' -ComponentID ms_tcpip6 -ErrorAction SilentlyContinue"; + var output = ExecutePowerShell(command); + + var lines = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) + { + if (line.Contains("ms_tcpip6")) + { + return line.Contains("True"); + } + } + return false; + } + catch (Exception ex) + { + return false; + } + } + + public static bool GetInterfaceStatus(string interfaceName) + { + try + { + var escapedName = EscapePowerShellString(interfaceName); + var output = ExecutePowerShell($"Get-NetAdapter -Name '{escapedName}' | Select-Object -ExpandProperty Status -ErrorAction SilentlyContinue"); + return output.Trim() == "Up"; + } + catch (Exception ex) + { + return false; + } + } + + public static string EscapePowerShellString(string input) + { + return input.Replace("'", "''"); + } + + private static string ExecutePowerShell(string command) + { + try + { + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "powershell.exe", + Arguments = $"-NoProfile -ExecutionPolicy Bypass -Command \"& {{{command}}}\"", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + Verb = "runas" + } + }; + + process.Start(); + var output = process.StandardOutput.ReadToEnd(); + var error = process.StandardError.ReadToEnd(); + process.WaitForExit(); + + if (process.ExitCode != 0 && !string.IsNullOrEmpty(error)) + { + throw new Exception($"PowerShell command failed: {error}"); + } + + return output; + } + catch (Exception ex) + { + throw new Exception($"Error executing PowerShell command '{command}': {ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/EthernetSwitcher/Program.cs b/EthernetSwitcher/Program.cs new file mode 100644 index 0000000..050a05a --- /dev/null +++ b/EthernetSwitcher/Program.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace EthernetSwitcher +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + // Load settings on startup + var settings = CurrentSettings.Instance; + Application.Run(new Form1()); + } + } +} \ No newline at end of file diff --git a/EthernetSwitcher/Properties/AssemblyInfo.cs b/EthernetSwitcher/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b3b8613 --- /dev/null +++ b/EthernetSwitcher/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("EthernetSwitcher")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("EthernetSwitcher")] +[assembly: AssemblyCopyright("Copyright © 2026")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("0463CEC3-E6DB-4698-BA12-6166940A143A")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/EthernetSwitcher/Properties/Resources.Designer.cs b/EthernetSwitcher/Properties/Resources.Designer.cs new file mode 100644 index 0000000..438b5a5 --- /dev/null +++ b/EthernetSwitcher/Properties/Resources.Designer.cs @@ -0,0 +1,64 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace EthernetSwitcher.Properties +{ + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = + new global::System.Resources.ResourceManager("EthernetSwitcher.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get { return resourceCulture; } + set { resourceCulture = value; } + } + } +} \ No newline at end of file diff --git a/EthernetSwitcher/Properties/Resources.resx b/EthernetSwitcher/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/EthernetSwitcher/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/EthernetSwitcher/Properties/Settings.Designer.cs b/EthernetSwitcher/Properties/Settings.Designer.cs new file mode 100644 index 0000000..81a63da --- /dev/null +++ b/EthernetSwitcher/Properties/Settings.Designer.cs @@ -0,0 +1,24 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace EthernetSwitcher.Properties +{ + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + private static Settings defaultInstance = ((Settings) (global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get { return defaultInstance; } + } + } +} \ No newline at end of file diff --git a/EthernetSwitcher/Properties/Settings.settings b/EthernetSwitcher/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/EthernetSwitcher/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/EthernetSwitcher/Settings.cs b/EthernetSwitcher/Settings.cs new file mode 100644 index 0000000..9a6f2c1 --- /dev/null +++ b/EthernetSwitcher/Settings.cs @@ -0,0 +1,94 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace EthernetSwitcher +{ + public enum EthernetMode + { + Auto, + Primary, + Secondary, + Disabled + } + + public enum IpProtocol + { + IPv4, + IPv6, + IPv4_IPv6 + } + + public class Settings + { + private static readonly string SettingsFilePath = Path.Combine(Application.StartupPath, "settings.ini"); + + public EthernetMode Mode { get; set; } = EthernetMode.Auto; + public string PrimaryName { get; set; } = ""; + public string SecondaryName { get; set; } = ""; + public int CheckInterval { get; set; } = 5; + public IpProtocol Protocol { get; set; } = IpProtocol.IPv4_IPv6; + + public static Settings Load() + { + var settings = new Settings(); + + if (File.Exists(SettingsFilePath)) + { + var mode = ReadIniValue("Settings", "Mode", "Auto"); + EthernetMode parsedMode; + if (Enum.TryParse(mode, out parsedMode)) + { + settings.Mode = parsedMode; + } + + settings.PrimaryName = ReadIniValue("Settings", "PrimaryName", ""); + settings.SecondaryName = ReadIniValue("Settings", "SecondaryName", ""); + + var interval = ReadIniValue("Settings", "CheckInterval", "5"); + int parsedInterval; + if (int.TryParse(interval, out parsedInterval)) + { + settings.CheckInterval = parsedInterval; + } + + var protocol = ReadIniValue("Settings", "Protocol", "IPv4_IPv6"); + IpProtocol parsedProtocol; + if (Enum.TryParse(protocol, out parsedProtocol)) + { + settings.Protocol = parsedProtocol; + } + } + + return settings; + } + + public void Save() + { + WriteIniValue("Settings", "Mode", Mode.ToString()); + WriteIniValue("Settings", "PrimaryName", PrimaryName); + WriteIniValue("Settings", "SecondaryName", SecondaryName); + WriteIniValue("Settings", "CheckInterval", CheckInterval.ToString()); + WriteIniValue("Settings", "Protocol", Protocol.ToString()); + } + + [DllImport("kernel32", CharSet = CharSet.Unicode)] + private static extern long WritePrivateProfileString(string section, string key, string value, string filePath); + + [DllImport("kernel32", CharSet = CharSet.Unicode)] + private static extern int GetPrivateProfileString(string section, string key, string defaultValue, string returnedString, int size, string filePath); + + private static void WriteIniValue(string section, string key, string value) + { + WritePrivateProfileString(section, key, value, SettingsFilePath); + } + + private static string ReadIniValue(string section, string key, string defaultValue) + { + var buffer = new string('\0', 256); + var length = GetPrivateProfileString(section, key, defaultValue, buffer, buffer.Length, SettingsFilePath); + return buffer.Substring(0, length); + } + } +} \ No newline at end of file diff --git a/EthernetSwitcher/app.manifest b/EthernetSwitcher/app.manifest new file mode 100644 index 0000000..4807a5b --- /dev/null +++ b/EthernetSwitcher/app.manifest @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + PerMonitorV2 + + + \ No newline at end of file diff --git a/EthernetSwitcher/icon.ico b/EthernetSwitcher/icon.ico new file mode 100644 index 0000000..e4ca05f Binary files /dev/null and b/EthernetSwitcher/icon.ico differ diff --git a/EthernetSwitcher/icon_green.ico b/EthernetSwitcher/icon_green.ico new file mode 100644 index 0000000..35a3a7d Binary files /dev/null and b/EthernetSwitcher/icon_green.ico differ diff --git a/EthernetSwitcher/icon_red.ico b/EthernetSwitcher/icon_red.ico new file mode 100644 index 0000000..eef12ec Binary files /dev/null and b/EthernetSwitcher/icon_red.ico differ diff --git a/EthernetSwitcher/icon_yellow.ico b/EthernetSwitcher/icon_yellow.ico new file mode 100644 index 0000000..efe4094 Binary files /dev/null and b/EthernetSwitcher/icon_yellow.ico differ diff --git a/EthernetSwitcher/log.txt b/EthernetSwitcher/log.txt new file mode 100644 index 0000000..5eef771 --- /dev/null +++ b/EthernetSwitcher/log.txt @@ -0,0 +1,10 @@ +ExecutePowerShell: Running command: Get-NetAdapterBinding -Name 'vEthernet (ethernet1)' -ComponentID ms_tcpip6 -ErrorAction SilentlyContinue +ExecutePowerShell: Exit code = 0 +ExecutePowerShell: Output = 'Name DisplayName ComponentID Enabled +---- ----------- ----------- ------- +vEthernet (ethernet1) Internet Protocol Version 6 (TCP/IPv6) ms_tcpip6 False' +IsIPv6Enabled: Output: 'Name DisplayName ComponentID Enabled +---- ----------- ----------- ------- +vEthernet (ethernet1) Internet Protocol Version 6 (TCP/IPv6) ms_tcpip6 False' +IsIPv6Enabled: Result = True +EnableIPv6: IPv6Enabled check returned True