Created
January 16, 2026 14:37
-
-
Save Reiner030/3261f12a9cc008518ac8c530715c883e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # FlipControl-GUI.ps1 - All-in-one Samsung Flip control GUI | |
| # Requirements: | |
| # - Windows 10/11 with PowerShell 5.1 or newer | |
| # - A WSL distribution (e.g. Debian 12 "Trixie" or Ubuntu) installed and configured | |
| # - Inside WSL, the package "python3-samsung-mdc" must be installed and the command "samsung-mdc" callable | |
| # Example (Debian/Ubuntu in WSL): sudo apt-get update && sudo apt-get install -y python3-samsung-mdc | |
| # - Network connectivity from WSL to the Samsung Flip displays (TCP port 1515) | |
| # Device configuration (per Samsung Flip): | |
| # - On the Flip: Settings => System => Advanced System => Remote Management: | |
| # * Remote Management : Active | |
| # * Display ID : 0 (must match the "0@" prefix in the target) | |
| # * Connection : RJ45 / Ethernet (instead of RS232) for IP-based MDC | |
| # * TLS : On for secure MDC + PIN (default is Off) | |
| # - Default PIN on Samsung Flip is usually 000000; change it on the device if needed. | |
| # - If TLS is Off and no PIN is configured, set "pin" to "none" or "" in the profile to call samsung-mdc without --pin. | |
| # | |
| # Optional convenience: | |
| # - Save this script as, e.g., C:\Tools\FlipControl-GUI.ps1 | |
| # - Create a shortcut with target: | |
| # powershell.exe -NoProfile -NoLogo -WindowStyle Hidden -ExecutionPolicy Bypass -File "C:\Tools\FlipControl-GUI.ps1" | |
| # - Set "Start in" in the shortcut properties to: | |
| # C:\Tools | |
| # - Choose an icon from the standard Windows icon libraries, for example: | |
| # %SystemRoot%\System32\shell32.dll | |
| # %SystemRoot%\System32\imageres.dll | |
| # (open the shortcut properties -> Change Icon... -> paste one of the paths above) | |
| # | |
| # - The selected icon is internally stored as "path, index", e.g.: | |
| # %SystemRoot%\System32\imageres.dll, 146 | |
| # but the GUI normally only shows the path; the index is managed by Windows. | |
| # - Place the shortcut on the Desktop or in the Start menu. | |
| # | |
| # PS 5.1 one-liner for current user's desktop: | |
| # $ws=New-Object -ComObject WScript.Shell;$sc=$ws.CreateShortcut([IO.Path]::Combine([Environment]::GetFolderPath('Desktop'),'FlipControl-GUI.lnk'));$sc.TargetPath='powershell.exe';$sc.Arguments='-NoProfile -NoLogo -WindowStyle Hidden -ExecutionPolicy Bypass -File "C:\Tools\FlipControl-GUI.ps1"';$sc.WorkingDirectory='C:\Tools';$sc.IconLocation="$env:SystemRoot\System32\imageres.dll,146";$sc.Save() | |
| # | |
| # PS 5.1 one-liner for all users' desktops (admin rights needed): | |
| # $ws=New-Object -ComObject WScript.Shell;$sc=$ws.CreateShortcut("$env:Public\Desktop\FlipControl-GUI.lnk");$sc.TargetPath='powershell.exe';$sc.Arguments='-NoProfile -NoLogo -WindowStyle Hidden -ExecutionPolicy Bypass -File "C:\Tools\FlipControl-GUI.ps1"';$sc.WorkingDirectory='C:\Tools';$sc.IconLocation="$env:SystemRoot\System32\imageres.dll,146";$sc.Save() | |
| # FlipControl-GUI.ps1 - ASCII-only, PS5.1-safe WinForms GUI | |
| Add-Type -AssemblyName System.Windows.Forms | |
| Add-Type -AssemblyName System.Drawing | |
| # Embedded profile, notifications about setup in notes bottom of json | |
| $cfgJson = @' | |
| { | |
| "pin": "000000", | |
| "timeout": 5, | |
| "wslDistro": "", | |
| "targets": [ | |
| { "label": "Flip #1 - Room 1", "target": "0@192.168.178.11:1515" }, | |
| { "label": "Flip #2 - Room 2", "target": "0@192.168.178.12:1515" }, | |
| { "label": "Flip #3 - Room 3", "target": "0@192.168.178.13:1515" } | |
| ], | |
| "capabilities": { | |
| "power": { | |
| "get": true, | |
| "set": [ | |
| "ON", | |
| "OFF" | |
| ] | |
| }, | |
| "input": { | |
| "get": true, | |
| "set": [ | |
| "IWB", | |
| "HDMI1", | |
| "HDMI2", | |
| "DISPLAY_PORT_1", | |
| "WIDI_SCREEN_MIRRORING", | |
| "WEB_BROWSER", | |
| "REMOTE_WORKSPACE" | |
| ] | |
| }, | |
| "volume": { | |
| "get": true, | |
| "setRange": [ | |
| 0, | |
| 100 | |
| ] | |
| }, | |
| "mute": { | |
| "get": true, | |
| "set": [ | |
| "ON", | |
| "OFF" | |
| ] | |
| } | |
| }, | |
| "notes": [ | |
| "wslDistro can be set for multiple WSL instances else default is used", | |
| "input_source/volume/mute only valid when POWER_STATE.ON", | |
| "Display ID must match device configuration", | |
| "TLS/PIN is used when pin is not 'none' or empty; otherwise samsung-mdc is called without --pin", | |
| "Port 1515/TCP must allow bidirectional traffic" | |
| ] | |
| } | |
| '@ | |
| $cfg = $cfgJson | ConvertFrom-Json | |
| # Embedded helpers from MdcHelpers.ps1 | |
| # MdcHelpers.ps1 - ASCII-only, PS5.1-safe helpers for samsung-mdc via WSL | |
| function Invoke-Mdc { | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory=$true)][string] $Target, # "ID@HOST:PORT" | |
| [string] $Pin = "000000", | |
| [int] $Timeout = 8, | |
| [string] $Distro = "", | |
| [Parameter(Mandatory=$true)][string] $Command, # power / input_source / volume / mute | |
| [string[]] $Args, | |
| [int] $Retry = 1 | |
| ) | |
| $usePin = -not [string]::IsNullOrWhiteSpace($Pin) -and $Pin -ne "none" | |
| $base = @("--","samsung-mdc","-t",$Timeout.ToString()) | |
| if ($usePin) { | |
| $base += @("--pin",$Pin) | |
| } | |
| $base += @($Target,$Command) | |
| if ($Args) { | |
| $base += $Args | |
| } | |
| if ([string]::IsNullOrWhiteSpace($Distro)) { | |
| $psi = $base | |
| } else { | |
| $psi = @("-d",$Distro) + $base | |
| } | |
| $output = & wsl $psi 2>&1 | |
| # SSLError / UNEXPECTED_MESSAGE / MDCReadTimeoutError -> short retry | |
| if ($Retry -gt 0 -and ($output -match "SSLError" -or $output -match "UNEXPECTED_MESSAGE" -or $output -match "MDCReadTimeoutError")) { | |
| Start-Sleep -Milliseconds 600 | |
| $output = & wsl $psi 2>&1 | |
| } | |
| return $output | |
| } | |
| function Get-MdcStatus { | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory=$true)][string] $Target, | |
| [string] $Pin = "000000", | |
| [int] $Timeout = 8, | |
| [string] $Distro = "" | |
| ) | |
| $power = Invoke-Mdc -Target $Target -Pin $Pin -Timeout $Timeout -Distro $Distro -Command power | |
| $pstate = "UNKNOWN" | |
| if ($power -match "POWER_STATE\.(\w+):\d+") { $pstate = $Matches[1] } | |
| $input = $null; $vol = $null; $mute = $null | |
| if ($pstate -eq "ON") { | |
| $input = Invoke-Mdc -Target $Target -Pin $Pin -Timeout $Timeout -Distro $Distro -Command input_source | |
| if ($input -match "INPUT_SOURCE_STATE\.(\w+):\d+") { $input = $Matches[1] } | |
| $vol = Invoke-Mdc -Target $Target -Pin $Pin -Timeout $Timeout -Distro $Distro -Command volume | |
| if ($vol -match "(\d+)\s*$") { $vol = $Matches[1] } else { $vol = $null } | |
| $mute = Invoke-Mdc -Target $Target -Pin $Pin -Timeout $Timeout -Distro $Distro -Command mute | |
| if ($mute -match "MUTE_STATE\.(\w+):\d+") { $mute = $Matches[1] } | |
| } | |
| [pscustomobject]@{ | |
| Target = $Target | |
| Power = $pstate | |
| Input = $input | |
| Volume = $vol | |
| Mute = $mute | |
| } | |
| } | |
| # Form windows | |
| $form = New-Object System.Windows.Forms.Form | |
| $form.Text = "Samsung Whiteboard Flip Control" | |
| $form.Size = New-Object System.Drawing.Size(540,520) # Width, Height | |
| $form.StartPosition = "CenterScreen" | |
| # Controls | |
| # target input_sources selection | |
| $lblTarget = New-Object System.Windows.Forms.Label | |
| $lblTarget.Text = "Target" | |
| $lblTarget.Location = New-Object System.Drawing.Point(10,15) | |
| $form.Controls.Add($lblTarget) | |
| $cboTarget = New-Object System.Windows.Forms.ComboBox | |
| $cboTarget.Location = New-Object System.Drawing.Point(120,10) | |
| $cboTarget.Width = 380 | |
| $cboTarget.DropDownStyle = "DropDownList" | |
| # build from JSON (Label, Target, Display) | |
| $items = New-Object System.Collections.ArrayList | |
| foreach ($t in $cfg.targets) { | |
| $null = $items.Add([pscustomobject]@{ | |
| Label = $t.label | |
| Target = $t.target | |
| Display = ("{0} - {1}" -f $t.label, $t.target) | |
| }) | |
| } | |
| $cboTarget.DisplayMember = "Display" # UI labeling | |
| $cboTarget.ValueMember = "Target" # value usage | |
| $cboTarget.DataSource = $items | |
| $form.Controls.Add($cboTarget) | |
| # Power status/update | |
| $lblPower = New-Object System.Windows.Forms.Label | |
| $lblPower.Text = "Power" | |
| $lblPower.Location = New-Object System.Drawing.Point(10,50) | |
| $form.Controls.Add($lblPower) | |
| $cboPower = New-Object System.Windows.Forms.ComboBox | |
| $cboPower.Location = New-Object System.Drawing.Point(120,45) | |
| $cboPower.Width = 60 | |
| $cboPower.DropDownStyle = "DropDownList" | |
| $cboPower.Items.AddRange(@("ON","OFF")) | |
| $form.Controls.Add($cboPower) | |
| # Status label for last Refresh/Apply result | |
| $lblStatus = New-Object System.Windows.Forms.Label | |
| $lblStatus.Text = "Status: -" | |
| $lblStatus.Location = New-Object System.Drawing.Point(210,50) | |
| $lblStatus.AutoSize = $true | |
| $lblStatus.ForeColor = [System.Drawing.Color]::Gray | |
| $form.Controls.Add($lblStatus) | |
| # Input status/update | |
| $lblInput = New-Object System.Windows.Forms.Label | |
| $lblInput.Text = "Input" | |
| $lblInput.Location = New-Object System.Drawing.Point(10,85) | |
| $form.Controls.Add($lblInput) | |
| $cboInput = New-Object System.Windows.Forms.ComboBox | |
| $cboInput.Location = New-Object System.Drawing.Point(120,80) | |
| $cboInput.Width = 175 | |
| $cboInput.DropDownStyle = "DropDownList" | |
| $cboInput.Items.AddRange($cfg.capabilities.input.set) | |
| $form.Controls.Add($cboInput) | |
| # Volume status/update with value and slider | |
| $lblVol = New-Object System.Windows.Forms.Label | |
| $lblVol.Text = "Volume" | |
| $lblVol.Location = New-Object System.Drawing.Point(10,120) | |
| $form.Controls.Add($lblVol) | |
| $txtVol = New-Object System.Windows.Forms.TextBox | |
| $txtVol.Location = New-Object System.Drawing.Point(120,115) | |
| $txtVol.Width = 60 | |
| $form.Controls.Add($txtVol) | |
| $trkVol = New-Object System.Windows.Forms.TrackBar | |
| $trkVol.Location = New-Object System.Drawing.Point(200,110) | |
| $trkVol.Width = 300 | |
| $trkVol.Height = 30 | |
| $trkVol.Minimum = [int]$cfg.capabilities.volume.setRange[0] | |
| $trkVol.Maximum = [int]$cfg.capabilities.volume.setRange[1] | |
| $trkVol.TickFrequency = 10 # markers all 10 | |
| $trkVol.SmallChange = 1 # Cursor-Keys for small hops | |
| $trkVol.LargeChange = 10 # PageUp/Down for big hops | |
| $form.Controls.Add($trkVol) | |
| # Mute status/update | |
| $lblMute = New-Object System.Windows.Forms.Label | |
| $lblMute.Text = "Mute" | |
| $lblMute.Location = New-Object System.Drawing.Point(10,155) | |
| $form.Controls.Add($lblMute) | |
| $cboMute = New-Object System.Windows.Forms.ComboBox | |
| $cboMute.Location = New-Object System.Drawing.Point(120,150) | |
| $cboMute.Width = 60 | |
| $cboMute.DropDownStyle = "DropDownList" | |
| $cboMute.Items.AddRange(@("ON","OFF")) | |
| $form.Controls.Add($cboMute) | |
| # Refresh Button for Status Update | |
| $btnRefresh = New-Object System.Windows.Forms.Button | |
| $btnRefresh.Text = "Refresh" | |
| $btnRefresh.Location = New-Object System.Drawing.Point(50,200) | |
| $form.Controls.Add($btnRefresh) | |
| # Apply-Button to push changes | |
| $btnApply = New-Object System.Windows.Forms.Button | |
| $btnApply.Text = "Apply" | |
| $btnApply.Location = New-Object System.Drawing.Point(160,200) | |
| $form.Controls.Add($btnApply) | |
| # Ping-Button for test network reachability | |
| $btnPing = New-Object System.Windows.Forms.Button | |
| $btnPing.Text = "Ping" | |
| $btnPing.Location = New-Object System.Drawing.Point(270,200) | |
| $form.Controls.Add($btnPing) | |
| # Exit-Button | |
| $btnExit = New-Object System.Windows.Forms.Button | |
| $btnExit.Text = "Exit" | |
| $btnExit.Location = New-Object System.Drawing.Point(380,200) | |
| $btnExit.Add_Click({ $form.Close() }) | |
| $form.Controls.Add($btnExit) | |
| # Log-Textbox | |
| $txtLog = New-Object System.Windows.Forms.TextBox | |
| # Use monospace font for better log alignment | |
| $txtLog.Font = New-Object System.Drawing.Font("Consolas", 8, [System.Drawing.FontStyle]::Regular) | |
| $txtLog.Multiline = $true | |
| $txtLog.ReadOnly = $true | |
| $txtLog.ScrollBars = "Both" # Vertical, Horizontal | |
| $txtLog.WordWrap = $false # important for horizontal ScrollBars | |
| $txtLog.Location = New-Object System.Drawing.Point(10,235) | |
| $txtLog.Width = 490 | |
| $txtLog.Height = 220 # deutlich größer | |
| # unten breit andocken | |
| $txtLog.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom ` | |
| -bor [System.Windows.Forms.AnchorStyles]::Left ` | |
| -bor [System.Windows.Forms.AnchorStyles]::Right | |
| $form.Controls.Add($txtLog) | |
| function Add-LogLine([string]$msg) { | |
| # Leerzeile ohne Timestamp | |
| if ([string]::IsNullOrWhiteSpace($msg)) { | |
| $txtLog.AppendText("`r`n") | |
| } | |
| else { | |
| # Normale Zeile mit Timestamp | |
| $ts = (Get-Date).ToString("HH:mm:ss") | |
| $txtLog.AppendText("[${ts}] $msg`r`n") | |
| } | |
| # Gemeinsamer Scroll/Auto-Scroll | |
| $txtLog.SelectionStart = $txtLog.Text.Length | |
| $txtLog.ScrollToCaret() | |
| } | |
| function LogCmd([string]$cmdLine) { Add-LogLine("CMD : " + $cmdLine) } | |
| function LogOut([string]$m) { Add-LogLine("RESP: " + $m) } | |
| function Log([string]$m) { Add-LogLine($m) } | |
| function Set-UiStatus { | |
| param( | |
| [string] $Text, | |
| [string] $State # "ok", "warn", "error", "none" | |
| ) | |
| if (-not $lblStatus) { return } | |
| if ([string]::IsNullOrWhiteSpace($Text)) { | |
| $lblStatus.Text = "Status: -" | |
| } else { | |
| $lblStatus.Text = "Status: " + $Text | |
| } | |
| switch ($State) { | |
| "ok" { $lblStatus.ForeColor = [System.Drawing.Color]::Green } | |
| "warn" { $lblStatus.ForeColor = [System.Drawing.Color]::Goldenrod } | |
| "error" { $lblStatus.ForeColor = [System.Drawing.Color]::Red } | |
| default { $lblStatus.ForeColor = [System.Drawing.Color]::Gray } | |
| } | |
| } | |
| function Format-MdcCmdString { | |
| param( | |
| [string] $Target, | |
| [string] $Pin, | |
| [string] $Command, | |
| [string[]] $Args | |
| ) | |
| $usePin = -not [string]::IsNullOrWhiteSpace($Pin) -and $Pin -ne "none" | |
| $pinPart = "" | |
| if ($usePin) { | |
| $pinPart = " --pin *****" | |
| } | |
| $argText = "" | |
| if ($Args -and $Args.Count -gt 0) { | |
| $argText = " " + ($Args -join " ") | |
| } | |
| return "wsl -- samsung-mdc" + $pinPart + " " + $Target + " " + $Command + $argText | |
| } | |
| function Get-MdcErrorFromOutput { | |
| param( | |
| [object] $Output | |
| ) | |
| if ($null -eq $Output) { return $null } | |
| $flat = "" | |
| if ($Output -is [System.Array]) { | |
| $flat = ($Output -join " ") | |
| } else { | |
| $flat = [string]$Output | |
| } | |
| if ([string]::IsNullOrWhiteSpace($flat)) { | |
| return $null | |
| } | |
| if ($flat -match "MDCTLSAuthFailed") { | |
| return "PIN error (MDCTLSAuthFailed)" | |
| } | |
| elseif ($flat -match "MDCTLSRequired") { | |
| return "TLS/PIN required (MDCTLSRequired)" | |
| } | |
| elseif ($flat -match "MDCSTART<<TLS>>") { | |
| return "TLS handshake required (MDCSTART<<TLS>>)" | |
| } | |
| return $null | |
| } | |
| function Get-SelectedTarget { | |
| if ($cboTarget.SelectedItem -and $cboTarget.SelectedItem.Target) { | |
| return $cboTarget.SelectedItem.Target | |
| } | |
| return $null | |
| } | |
| function Set-Busy([bool]$busy) { | |
| $form.UseWaitCursor = $busy | |
| if ($busy) { [System.Windows.Forms.Cursor]::Current = [System.Windows.Forms.Cursors]::WaitCursor } | |
| else { [System.Windows.Forms.Cursor]::Current = [System.Windows.Forms.Cursors]::Default } | |
| $cboTarget.Enabled = -not $busy | |
| $cboPower.Enabled = -not $busy | |
| $cboInput.Enabled = (-not $busy) -and $cboInput.Enabled | |
| $txtVol.Enabled = (-not $busy) -and $txtVol.Enabled | |
| $trkVol.Enabled = (-not $busy) -and $trkVol.Enabled | |
| $cboMute.Enabled = (-not $busy) -and $cboMute.Enabled | |
| $btnRefresh.Enabled = -not $busy | |
| $btnApply.Enabled = -not $busy | |
| if ($btnExit) { $btnExit.Enabled = -not $busy } | |
| $form.Refresh() | |
| } | |
| $updateUI = { | |
| $t = Get-SelectedTarget | |
| if (-not $t) { return } | |
| Add-LogLine "" # leere Zeile als Trennung zwischen Aktionen | |
| try { | |
| Set-Busy $true | |
| $hadError = $false | |
| $lastError = $null | |
| # POWER | |
| $cmd = Format-MdcCmdString -Target $t -Pin $cfg.pin -Command "power" | |
| LogCmd $cmd | |
| $powerOut = Invoke-Mdc -Target $t -Pin $cfg.pin -Timeout $cfg.timeout -Distro $cfg.wslDistro -Command power | |
| LogOut ($powerOut -join " ") | |
| $pstate = "UNKNOWN" | |
| if ($powerOut -match "POWER_STATE\.(\w+):\d+") { $pstate = $Matches[1] } | |
| $cboPower.SelectedItem = $pstate | |
| $err = Get-MdcErrorFromOutput $powerOut | |
| if ($err) { | |
| $hadError = $true | |
| $lastError = $err | |
| } | |
| $input = $null | |
| $vol = $null | |
| $mute = $null | |
| if ($pstate -eq "ON") { | |
| # INPUT_SOURCE | |
| $cmd = Format-MdcCmdString -Target $t -Pin $cfg.pin -Command "input_source" | |
| LogCmd $cmd | |
| $inOut = Invoke-Mdc -Target $t -Pin $cfg.pin -Timeout $cfg.timeout -Distro $cfg.wslDistro -Command input_source | |
| LogOut ($inOut -join " ") | |
| if ($inOut -match "INPUT_SOURCE_STATE\.(\w+):\d+") { $input = $Matches[1] } | |
| $err = Get-MdcErrorFromOutput $inOut | |
| if ($err) { | |
| $hadError = $true | |
| $lastError = $err | |
| } | |
| # VOLUME | |
| $cmd = Format-MdcCmdString -Target $t -Pin $cfg.pin -Command "volume" | |
| LogCmd $cmd | |
| $volOut = Invoke-Mdc -Target $t -Pin $cfg.pin -Timeout $cfg.timeout -Distro $cfg.wslDistro -Command volume | |
| LogOut ($volOut -join " ") | |
| if ($volOut -match "(\d+)\s*$") { $vol = $Matches[1] } | |
| $err = Get-MdcErrorFromOutput $inOut | |
| if ($err) { | |
| $hadError = $true | |
| $lastError = $err | |
| } | |
| # MUTE | |
| $cmd = Format-MdcCmdString -Target $t -Pin $cfg.pin -Command "mute" | |
| LogCmd $cmd | |
| $muteOut = Invoke-Mdc -Target $t -Pin $cfg.pin -Timeout $cfg.timeout -Distro $cfg.wslDistro -Command mute | |
| LogOut ($muteOut -join " ") | |
| if ($muteOut -match "MUTE_STATE\.(\w+):\d+") { $mute = $Matches[1] } | |
| $err = Get-MdcErrorFromOutput $inOut | |
| if ($err) { | |
| $hadError = $true | |
| $lastError = $err | |
| } | |
| $cboInput.Enabled = $true | |
| $txtVol.Enabled = $true | |
| $trkVol.Enabled = $true | |
| $cboMute.Enabled = $true | |
| } else { | |
| $cboInput.Enabled = $false | |
| $txtVol.Enabled = $false | |
| $trkVol.Enabled = $false | |
| $cboMute.Enabled = $false | |
| } | |
| if ($input) { | |
| $cboInput.SelectedItem = $input | |
| } | |
| if ($vol) { | |
| $txtVol.Text = [string]$vol | |
| $v = 0 | |
| if ([int]::TryParse([string]$vol, [ref]$v) -and $v -ge $trkVol.Minimum -and $v -le $trkVol.Maximum) { | |
| $trkVol.Value = $v | |
| } | |
| } | |
| if ($mute) { | |
| $cboMute.SelectedItem = $mute | |
| } | |
| if ($hadError -and $lastError) { | |
| Log ("WARN: " + $lastError) | |
| Set-UiStatus $lastError "error" | |
| } | |
| else { | |
| Log "OK: status updated" | |
| Set-UiStatus "OK (Refresh)" "ok" | |
| } | |
| } | |
| catch { | |
| LogOut ("ERROR: " + $_.Exception.Message) | |
| Set-UiStatus "Error (Refresh)" "error" | |
| } | |
| finally { | |
| Set-Busy $false | |
| } | |
| } | |
| $cboTarget.Add_SelectedIndexChanged({ & $updateUI }) | |
| $btnRefresh.Add_Click({ & $updateUI }) | |
| $trkVol.Add_Scroll({ $txtVol.Text = $trkVol.Value.ToString() }) | |
| $btnApply.Add_Click({ | |
| $t = Get-SelectedTarget | |
| if (-not $t) { return } | |
| Add-LogLine "" # leere Zeile als Trennung zwischen Aktionen | |
| try { | |
| Set-Busy $true | |
| $hadError = $false | |
| $lastError = $null | |
| # Power zuerst | |
| $wantPower = $cboPower.SelectedItem | |
| if ($wantPower) { | |
| $cmd = Format-MdcCmdString -Target $t -Pin $cfg.pin -Command "power" -Args @($wantPower.ToLower()) | |
| LogCmd $cmd | |
| $res = Invoke-Mdc -Target $t -Pin $cfg.pin -Timeout $cfg.timeout -Distro $cfg.wslDistro -Command power -Args @($wantPower.ToLower()) | |
| LogOut ($res -join " ") | |
| $err = Get-MdcErrorFromOutput $res | |
| if ($err) { | |
| $hadError = $true | |
| $lastError = $err | |
| } | |
| Start-Sleep -Seconds 1 | |
| } | |
| # Neu lesen, damit wir wissen ob jetzt ON | |
| $s = Get-MdcStatus -Target $t -Pin $cfg.pin -Timeout $cfg.timeout -Distro $cfg.wslDistro | |
| if ($s.Power -eq "ON") { | |
| if ($cboInput.SelectedItem) { | |
| $cmd = Format-MdcCmdString -Target $t -Pin $cfg.pin -Command "input_source" -Args @($cboInput.SelectedItem) | |
| LogCmd $cmd | |
| $res = Invoke-Mdc -Target $t -Pin $cfg.pin -Timeout $cfg.timeout -Distro $cfg.wslDistro -Command input_source -Args @($cboInput.SelectedItem) | |
| LogOut ($res -join " ") | |
| $err = Get-MdcErrorFromOutput $res | |
| if ($err) { | |
| $hadError = $true | |
| $lastError = $err | |
| } | |
| } | |
| if ($txtVol.Text -match "^\d+$") { | |
| $v = [int]$txtVol.Text | |
| if ($v -lt $trkVol.Minimum) { $v = $trkVol.Minimum } | |
| if ($v -gt $trkVol.Maximum) { $v = $trkVol.Maximum } | |
| $trkVol.Value = $v | |
| $cmd = Format-MdcCmdString -Target $t -Pin $cfg.pin -Command "volume" -Args @($v.ToString()) | |
| LogCmd $cmd | |
| $res = Invoke-Mdc -Target $t -Pin $cfg.pin -Timeout $cfg.timeout -Distro $cfg.wslDistro -Command volume -Args @($v.ToString()) | |
| LogOut ($res -join " ") | |
| $err = Get-MdcErrorFromOutput $res | |
| if ($err) { | |
| $hadError = $true | |
| $lastError = $err | |
| } | |
| } | |
| if ($cboMute.SelectedItem) { | |
| $cmd = Format-MdcCmdString -Target $t -Pin $cfg.pin -Command "mute" -Args @($cboMute.SelectedItem.ToLower()) | |
| LogCmd $cmd | |
| $res = Invoke-Mdc -Target $t -Pin $cfg.pin -Timeout $cfg.timeout -Distro $cfg.wslDistro -Command mute -Args @($cboMute.SelectedItem.ToLower()) | |
| LogOut ($res -join " ") | |
| $err = Get-MdcErrorFromOutput $res | |
| if ($err) { | |
| $hadError = $true | |
| $lastError = $err | |
| } | |
| } | |
| } | |
| # Auto-Refresh am Ende | |
| & $updateUI | |
| if ($hadError -and $lastError) { | |
| Set-UiStatus $lastError "error" | |
| } | |
| else { | |
| Set-UiStatus "OK (Apply)" "ok" | |
| } | |
| } | |
| catch { | |
| LogOut ("ERROR (Apply): " + $_.Exception.Message) | |
| Set-UiStatus "Error (Apply)" "error" | |
| } | |
| finally { | |
| Set-Busy $false | |
| } | |
| }) | |
| $btnPing.Add_Click({ | |
| $t = Get-SelectedTarget | |
| if (-not $t) { return } | |
| Add-LogLine "" | |
| # Host aus DISPLAYID@HOST:PORT extrahieren | |
| $flipHost = $null | |
| $atIdx = $t.IndexOf("@") | |
| if ($atIdx -ge 0) { | |
| $rest = $t.Substring($atIdx + 1) | |
| } else { | |
| $rest = $t | |
| } | |
| $colonIdx = $rest.IndexOf(":") | |
| if ($colonIdx -ge 0) { | |
| $flipHost = $rest.Substring(0, $colonIdx) | |
| } else { | |
| $flipHost = $rest | |
| } | |
| Add-LogLine ("Ping test for " + $flipHost) | |
| # Windows Ping | |
| $winOk = $false | |
| try { | |
| $winOk = [bool](Test-Connection -ComputerName $flipHost -Count 1 -Quiet -ErrorAction Stop) | |
| } catch { | |
| $winOk = $false | |
| } | |
| if ($winOk) { | |
| Add-LogLine "Ping (Windows): OK" | |
| } else { | |
| Add-LogLine "Ping (Windows): FAILED" | |
| } | |
| # WSL Ping | |
| try { | |
| $wslPing = & wsl ping -c 1 $flipHost 2>&1 | |
| if ($wslPing -is [System.Array]) { $wslPing = $wslPing -join " " } | |
| if ($wslPing -match "1 received" -or $wslPing -match ", 0% packet loss") { | |
| Add-LogLine "Ping (WSL): OK" | |
| } else { | |
| Add-LogLine ("Ping (WSL): FAILED - " + $wslPing) | |
| } | |
| } catch { | |
| Add-LogLine ("Ping (WSL): ERROR - " + $_.Exception.Message) | |
| } | |
| }) | |
| # --- Startup WSL diagnostics --- | |
| try { | |
| Add-LogLine "" | |
| $distId = "" | |
| $pretty = "" | |
| # ID=... (z.B. debian, ubuntu) | |
| try { | |
| $distIdRaw = & wsl -- sed -ne 's/^ID=//p' /etc/os-release 2>&1 | |
| if ($distIdRaw -is [System.Array]) { $distIdRaw = $distIdRaw -join " " } | |
| $distId = ($distIdRaw | Out-String).Trim() | |
| } | |
| catch { | |
| # ignore | |
| } | |
| # PRETTY_NAME=... (z.B. "Debian GNU/Linux 13 (trixie)") | |
| try { | |
| $prettyRaw = & wsl -- sed -ne 's/^PRETTY_NAME=//p' /etc/os-release 2>&1 | |
| if ($prettyRaw -is [System.Array]) { $prettyRaw = $prettyRaw -join " " } | |
| $pretty = ($prettyRaw | Out-String).Trim() | |
| } | |
| catch { | |
| # ignore | |
| } | |
| # Label bauen: bevorzugt PRETTY_NAME, sonst ID, sonst Config-Fallback | |
| $label = $null | |
| if (-not [string]::IsNullOrWhiteSpace($pretty)) { | |
| if (-not [string]::IsNullOrWhiteSpace($distId)) { | |
| $label = $pretty + " (" + $distId + ")" | |
| } else { | |
| $label = $pretty | |
| } | |
| } | |
| elseif (-not [string]::IsNullOrWhiteSpace($distId)) { | |
| $label = $distId | |
| } | |
| elseif ($cfg -and -not [string]::IsNullOrWhiteSpace($cfg.wslDistro)) { | |
| # Fallback auf Config-Wert, z.B. "Debian" | |
| $label = $cfg.wslDistro | |
| } | |
| if ($label) { | |
| Add-LogLine ("WSL default distro: " + $label) | |
| } else { | |
| Add-LogLine "WSL default distro: (could not detect)" | |
| } | |
| Add-LogLine "" | |
| # samsung-mdc Version wie gewohnt loggen | |
| LogCmd "wsl -- samsung-mdc --version" | |
| $ver = & wsl -- samsung-mdc --version 2>&1 | |
| if ($ver -is [System.Array]) { $ver = $ver -join " " } | |
| LogOut ($ver) | |
| } | |
| catch { | |
| LogOut ("ERROR (WSL/samsung-mdc): " + $_.Exception.Message) | |
| } | |
| # Erstes Display vorauswählen + initialen Status holen, sobald das Formular angezeigt wird | |
| $form.Add_Shown({ | |
| try { | |
| if ($cboTarget.Items.Count -gt 0) { | |
| $cboTarget.SelectedIndex = 0 | |
| & $updateUI | |
| } | |
| } | |
| catch { | |
| LogOut ("ERROR (initial updateUI): " + $_.Exception.Message) | |
| } | |
| }) | |
| Set-UiStatus "" "none" | |
| [void]$form.ShowDialog() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment