├── .gitattributes ├── .gitignore ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── Assets ├── 16.png ├── 20.png ├── 256.png ├── Icon.png ├── Icon128.png ├── Icon24.png ├── Icon32.png ├── Icon48.png ├── LauncherIcon.png ├── LauncherIcon.psd ├── LauncherIcon16.psd ├── LauncherIconSolido.psd ├── Warning.ai └── Warning_Icon.svg ├── CasparCG ├── CasparAction.cs ├── Configuration │ ├── Channel.cs │ ├── ConfigurationFile.cs │ ├── Consumers │ │ ├── ArtnetConsumer.cs │ │ ├── ArtnetFixture.cs │ │ ├── BluefishConsumer.cs │ │ ├── Consumer.cs │ │ ├── DecklinkConsumer.cs │ │ ├── DecklinkPort.cs │ │ ├── FfmpegConsumer.cs │ │ ├── FrameConsumer.cs │ │ ├── NdiConsumer.cs │ │ ├── NewtekIvgaConsumer.cs │ │ ├── ScreenConsumer.cs │ │ └── SystemAudioConsumer.cs │ ├── CustomVideoMode.cs │ ├── Enums │ │ ├── ArtnetFixtureType.cs │ │ ├── BluefishKeyer.cs │ │ ├── BluefishKeyerAudio.cs │ │ ├── BluefishUhdMode.cs │ │ ├── ChannelColorDepth.cs │ │ ├── ChannelColorSpace.cs │ │ ├── DecklinkDefaultColorSpace.cs │ │ ├── DecklinkKeyer.cs │ │ ├── DecklinkLatency.cs │ │ ├── DecklinkWaitForReference.cs │ │ ├── FfmpegDeinterlace.cs │ │ ├── HtmlAngleGraphicsBackend.cs │ │ ├── LogLevel.cs │ │ ├── ScreenAspectRatio.cs │ │ ├── ScreenColourSpace.cs │ │ ├── ScreenStretch.cs │ │ └── SystemAudioChannelLayout.cs │ ├── OscClient.cs │ ├── Producer.cs │ └── VideoMode.cs └── LogLine.cs ├── CasparLauncher.csproj ├── CasparLauncher.sln ├── GlobalUsings.cs ├── LICENSE ├── Languages.cs ├── Launcher ├── Command.cs ├── Executable.cs ├── ExecutableEventArgs.cs ├── Launchpad.cs └── TrayIcon.cs ├── Properties ├── PublishProfiles │ ├── FrameworkIndependent.pubxml │ └── SelfContained_x64.pubxml ├── Resources.Designer.cs ├── Resources.es.Designer.cs ├── Resources.es.resx └── Resources.resx ├── README.md ├── Resources ├── AppIcon.ico ├── NotifyIcon.ico ├── NotifyIconDark.ico └── NotifyIconLight.ico ├── Selectors └── TrayMenuStyleSelector.cs └── Windows ├── AboutWindow.xaml ├── AboutWindow.xaml.cs ├── ConfigEditor.xaml ├── ConfigEditor.xaml.cs ├── ExecutableOptions.xaml ├── ExecutableOptions.xaml.cs ├── StatusWindow.xaml ├── StatusWindow.xaml.cs ├── TrayMenu.xaml └── TrayMenu.xaml.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | /PasswordEncoder.cs 263 | -------------------------------------------------------------------------------- /App.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /App.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher; 2 | 3 | public partial class App : Application 4 | { 5 | private static readonly CultureInfo DefaultCulture = Thread.CurrentThread.CurrentCulture; 6 | public static TrayIcon Tray { get; } = new(); 7 | public static Launchpad Launchpad { get; } = new(); 8 | public StatusWindow? Window { get; set; } 9 | 10 | public static System.Drawing.Icon? AppIcon 11 | { 12 | get => Environment.ProcessPath is null ? null : System.Drawing.Icon.ExtractAssociatedIcon(Environment.ProcessPath); 13 | } 14 | 15 | public App() : base() 16 | { 17 | AppContext.SetSwitch("Switch.System.Windows.Controls.Text.UseAdornerForTextboxSelectionRendering", false); 18 | LocalizationHelper.Init(L.ResourceManager); 19 | if (Process.GetCurrentProcess() is Process p && 20 | p.MainModule is ProcessModule m && 21 | m.FileName is string f && 22 | Path.GetDirectoryName(f) is string d) 23 | { 24 | Directory.SetCurrentDirectory(d); 25 | } 26 | } 27 | 28 | protected override void OnStartup(StartupEventArgs e) 29 | { 30 | base.OnStartup(e); 31 | SetLanguageDictionary(); 32 | _ = AppInitHelper.SingleInstanceCheck("CasparLauncher"); 33 | AppInitHelper.RequestActivate += ActivateSingleInstance; 34 | SetDarkMode(Launchpad.DarkMode); 35 | Tray.SetupTray(); 36 | Launchpad.StartAll(true); 37 | } 38 | 39 | protected override void OnExit(ExitEventArgs e) 40 | { 41 | Launchpad.Exiting = true; 42 | Tray.HideIcon(); 43 | base.OnExit(e); 44 | } 45 | 46 | public static void Shutdown(bool prompt = false) 47 | { 48 | if (prompt) 49 | { 50 | MessageBoxResult close = MessageBox.Show(L.ClosePromptMessage, L.ClosePromptCaption, MessageBoxButton.YesNo, MessageBoxImage.Warning); 51 | switch (close) 52 | { 53 | case MessageBoxResult.Yes: 54 | break; 55 | case MessageBoxResult.No: 56 | default: 57 | return; 58 | } 59 | } 60 | Launchpad.StopAll(); 61 | Current.Shutdown(); 62 | } 63 | 64 | private void ActivateSingleInstance(object? sender, EventArgs e) 65 | { 66 | Dispatcher.BeginInvoke((() => 67 | { 68 | ShowWindow(); 69 | })); 70 | } 71 | 72 | internal void ShowWindow(int? tab = null) 73 | { 74 | if (Window is not null && Window.IsLoaded) 75 | { 76 | Window.Topmost = true; 77 | if (tab is int index) Launchpad.SelectedTab = index; 78 | Window.Activate(); 79 | Window.Topmost = false; 80 | } 81 | else 82 | { 83 | Window = new() 84 | { 85 | DataContext = Launchpad 86 | }; 87 | if (tab is int index) Launchpad.SelectedTab = index; 88 | Window.Show(); 89 | } 90 | } 91 | 92 | public static void SetLanguageDictionary() 93 | { 94 | try 95 | { 96 | L.Culture = Launchpad.ForcedLanguage switch 97 | { 98 | Languages.en => new CultureInfo("en-US"), 99 | Languages.es => new CultureInfo("es-ES"), 100 | _ => DefaultCulture.ToString() switch 101 | { 102 | "es-ES" or "es-MX" or "es-AR" => new("es-ES"), 103 | _ => new("en-US"), 104 | }, 105 | }; 106 | Thread.CurrentThread.CurrentCulture = L.Culture; 107 | Thread.CurrentThread.CurrentUICulture = L.Culture; 108 | Thread.CurrentThread.CurrentCulture.ClearCachedData(); 109 | Thread.CurrentThread.CurrentUICulture.ClearCachedData(); 110 | L.Culture.ClearCachedData(); 111 | LocalizationHelper.Instance.CurrentCulture = L.Culture; 112 | } 113 | catch (Exception) 114 | { 115 | L.Culture = new("en-US"); 116 | } 117 | } 118 | 119 | 120 | const string THEMES_KEYPATH = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"; 121 | const string THEMES_VALUEPATH = "SystemUsesLightTheme"; 122 | 123 | public static void SetDarkMode(bool enabled) 124 | { 125 | Current.Resources.MergedDictionaries[0].Source = enabled ? new Uri("pack://application:,,,/BaseUISupport;component/Styles/DarkColors.xaml", UriKind.RelativeOrAbsolute) : new Uri("pack://application:,,,/BaseUISupport;component/Styles/LightColors.xaml", UriKind.RelativeOrAbsolute); 126 | } 127 | 128 | public static bool IsSystemThemeLight() 129 | { 130 | using RegistryKey? key = Registry.CurrentUser.OpenSubKey(THEMES_KEYPATH); 131 | if (key is not null && key.GetValue(THEMES_VALUEPATH) is object obj) return (int)obj > 0; 132 | else return false; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | [assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)] 2 | -------------------------------------------------------------------------------- /Assets/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Assets/16.png -------------------------------------------------------------------------------- /Assets/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Assets/20.png -------------------------------------------------------------------------------- /Assets/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Assets/256.png -------------------------------------------------------------------------------- /Assets/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Assets/Icon.png -------------------------------------------------------------------------------- /Assets/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Assets/Icon128.png -------------------------------------------------------------------------------- /Assets/Icon24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Assets/Icon24.png -------------------------------------------------------------------------------- /Assets/Icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Assets/Icon32.png -------------------------------------------------------------------------------- /Assets/Icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Assets/Icon48.png -------------------------------------------------------------------------------- /Assets/LauncherIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Assets/LauncherIcon.png -------------------------------------------------------------------------------- /Assets/LauncherIcon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Assets/LauncherIcon.psd -------------------------------------------------------------------------------- /Assets/LauncherIcon16.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Assets/LauncherIcon16.psd -------------------------------------------------------------------------------- /Assets/LauncherIconSolido.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Assets/LauncherIconSolido.psd -------------------------------------------------------------------------------- /Assets/Warning.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Assets/Warning.ai -------------------------------------------------------------------------------- /Assets/Warning_Icon.svg: -------------------------------------------------------------------------------- 1 | Warning -------------------------------------------------------------------------------- /CasparCG/CasparAction.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG; 2 | 3 | public enum CasparAction 4 | { 5 | Rebuild, 6 | Diag, 7 | Grid 8 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Channel.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration; 2 | 3 | public class Channel : INotifyPropertyChanged 4 | { 5 | private VideoMode _videoMode; 6 | public VideoMode VideoMode 7 | { 8 | get 9 | { 10 | return _videoMode; 11 | } 12 | set 13 | { 14 | if (_videoMode != value) 15 | { 16 | _videoMode = value; 17 | OnPropertyChanged(nameof(VideoMode)); 18 | } 19 | } 20 | } 21 | 22 | private ChannelColorDepth? _colorDepth = null; 23 | public ChannelColorDepth? ColorDepth 24 | { 25 | get 26 | { 27 | return _colorDepth; 28 | } 29 | set 30 | { 31 | if (_colorDepth != value) 32 | { 33 | _colorDepth = value; 34 | OnPropertyChanged(nameof(ColorDepth)); 35 | } 36 | } 37 | } 38 | 39 | private ChannelColorSpace? _colorSpace = null; 40 | public ChannelColorSpace? ColorSpace 41 | { 42 | get 43 | { 44 | return _colorSpace; 45 | } 46 | set 47 | { 48 | if (_colorSpace != value) 49 | { 50 | _colorSpace = value; 51 | OnPropertyChanged(nameof(ColorSpace)); 52 | } 53 | } 54 | } 55 | 56 | private int _selectedConsumer = 0; 57 | public int SelectedConsumer 58 | { 59 | get 60 | { 61 | return _selectedConsumer; 62 | } 63 | set 64 | { 65 | if (_selectedConsumer != value) 66 | { 67 | _selectedConsumer = value; 68 | OnPropertyChanged(nameof(SelectedConsumer)); 69 | } 70 | } 71 | } 72 | 73 | public Channel() 74 | { 75 | _videoMode = ConfigFile.DefaultVideoModes.First(); 76 | } 77 | 78 | public ObservableCollection Producers { get; set; } = []; 79 | public ObservableCollection Consumers { get; set; } = []; 80 | 81 | public event PropertyChangedEventHandler? PropertyChanged; 82 | protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } 83 | } 84 | -------------------------------------------------------------------------------- /CasparCG/Configuration/Consumers/ArtnetConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Consumers; 2 | 3 | public class ArtnetConsumer : Consumer 4 | { 5 | public ObservableCollection Fixtures { get; } = []; 6 | 7 | private int _universe = 1; 8 | public int Universe 9 | { 10 | get 11 | { 12 | return _universe; 13 | } 14 | set 15 | { 16 | if (_universe != value) 17 | { 18 | _universe = value; 19 | OnPropertyChanged(nameof(Universe)); 20 | } 21 | } 22 | } 23 | 24 | private string _host = "127.0.0.1"; 25 | public string Host 26 | { 27 | get 28 | { 29 | return _host; 30 | } 31 | set 32 | { 33 | if (_host != value) 34 | { 35 | _host = value; 36 | OnPropertyChanged(nameof(Host)); 37 | } 38 | } 39 | } 40 | 41 | private int _port = 6454; 42 | public int Port 43 | { 44 | get 45 | { 46 | return _port; 47 | } 48 | set 49 | { 50 | if (_port != value) 51 | { 52 | _port = value; 53 | OnPropertyChanged(nameof(Port)); 54 | } 55 | } 56 | } 57 | 58 | private int _refreshRate = 30; 59 | public int RefreshRate 60 | { 61 | get 62 | { 63 | return _refreshRate; 64 | } 65 | set 66 | { 67 | if (_refreshRate != value) 68 | { 69 | _refreshRate = value; 70 | OnPropertyChanged(nameof(RefreshRate)); 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Consumers/ArtnetFixture.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Consumers; 2 | 3 | public class ArtnetFixture : INotifyPropertyChanged 4 | { 5 | private ArtnetFixtureType _type = ArtnetFixtureType._RGBW; 6 | public ArtnetFixtureType Type 7 | { 8 | get 9 | { 10 | return _type; 11 | } 12 | set 13 | { 14 | if (_type != value) 15 | { 16 | _type = value; 17 | OnPropertyChanged(nameof(Type)); 18 | } 19 | } 20 | } 21 | 22 | private int _startAddress = 1; 23 | public int StartAddress 24 | { 25 | get 26 | { 27 | return _startAddress; 28 | } 29 | set 30 | { 31 | if (_startAddress != value) 32 | { 33 | _startAddress = value; 34 | OnPropertyChanged(nameof(StartAddress)); 35 | } 36 | } 37 | } 38 | 39 | private int _fixtureCount = 10; 40 | public int FixtureCount 41 | { 42 | get 43 | { 44 | return _fixtureCount; 45 | } 46 | set 47 | { 48 | if (_fixtureCount != value) 49 | { 50 | _fixtureCount = value; 51 | OnPropertyChanged(nameof(FixtureCount)); 52 | } 53 | } 54 | } 55 | 56 | private int _fixtureChannels = 6; 57 | public int FixtureChannels 58 | { 59 | get 60 | { 61 | return _fixtureChannels; 62 | } 63 | set 64 | { 65 | if (_fixtureChannels != value) 66 | { 67 | _fixtureChannels = value; 68 | OnPropertyChanged(nameof(FixtureChannels)); 69 | } 70 | } 71 | } 72 | 73 | private int _x = 960; 74 | public int X 75 | { 76 | get 77 | { 78 | return _x; 79 | } 80 | set 81 | { 82 | if (_x != value) 83 | { 84 | _x = value; 85 | OnPropertyChanged(nameof(X)); 86 | } 87 | } 88 | } 89 | 90 | private int _y = 540; 91 | public int Y 92 | { 93 | get 94 | { 95 | return _y; 96 | } 97 | set 98 | { 99 | if (_y != value) 100 | { 101 | _y = value; 102 | OnPropertyChanged(nameof(Y)); 103 | } 104 | } 105 | } 106 | 107 | private int _width = 100; 108 | public int Width 109 | { 110 | get 111 | { 112 | return _width; 113 | } 114 | set 115 | { 116 | if (_width != value) 117 | { 118 | _width = value; 119 | OnPropertyChanged(nameof(Width)); 120 | } 121 | } 122 | } 123 | 124 | private int _height = 100; 125 | public int Height 126 | { 127 | get 128 | { 129 | return _height; 130 | } 131 | set 132 | { 133 | if (_height != value) 134 | { 135 | _height = value; 136 | OnPropertyChanged(nameof(Height)); 137 | } 138 | } 139 | } 140 | 141 | private int _rotation = 0; 142 | public int Rotation 143 | { 144 | get 145 | { 146 | return _rotation; 147 | } 148 | set 149 | { 150 | if (_rotation != value) 151 | { 152 | _rotation = value; 153 | OnPropertyChanged(nameof(Rotation)); 154 | } 155 | } 156 | } 157 | 158 | public event PropertyChangedEventHandler? PropertyChanged; 159 | protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } 160 | } 161 | -------------------------------------------------------------------------------- /CasparCG/Configuration/Consumers/BluefishConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Consumers; 2 | 3 | public class BluefishConsumer : Consumer 4 | { 5 | private int _device = 1; 6 | public int Device 7 | { 8 | get 9 | { 10 | return _device; 11 | } 12 | set 13 | { 14 | if (_device != value) 15 | { 16 | _device = value; 17 | OnPropertyChanged(nameof(Device)); 18 | } 19 | } 20 | } 21 | 22 | private int _sdiStream = 1; 23 | public int SdiStream 24 | { 25 | get 26 | { 27 | return _sdiStream; 28 | } 29 | set 30 | { 31 | if (_sdiStream != value) 32 | { 33 | _sdiStream = value; 34 | OnPropertyChanged(nameof(SdiStream)); 35 | } 36 | } 37 | } 38 | 39 | private bool _embeddedAudio = false; 40 | public bool EmbeddedAudio 41 | { 42 | get 43 | { 44 | return _embeddedAudio; 45 | } 46 | set 47 | { 48 | if (_embeddedAudio != value) 49 | { 50 | _embeddedAudio = value; 51 | OnPropertyChanged(nameof(EmbeddedAudio)); 52 | } 53 | } 54 | } 55 | 56 | private BluefishKeyer _keyer = BluefishKeyer._disabled; 57 | public BluefishKeyer Keyer 58 | { 59 | get 60 | { 61 | return _keyer; 62 | } 63 | set 64 | { 65 | if (_keyer != value) 66 | { 67 | _keyer = value; 68 | OnPropertyChanged(nameof(Keyer)); 69 | } 70 | } 71 | } 72 | 73 | private BluefishKeyerAudio _keyerAudio = BluefishKeyerAudio._videooutputchannel; 74 | public BluefishKeyerAudio KeyerAudio 75 | { 76 | get 77 | { 78 | return _keyerAudio; 79 | } 80 | set 81 | { 82 | if (_keyerAudio != value) 83 | { 84 | _keyerAudio = value; 85 | OnPropertyChanged(nameof(KeyerAudio)); 86 | } 87 | } 88 | } 89 | 90 | private int _watchdog = 2; 91 | public int Watchdog 92 | { 93 | get 94 | { 95 | return _watchdog; 96 | } 97 | set 98 | { 99 | if (_watchdog != value) 100 | { 101 | _watchdog = value; 102 | OnPropertyChanged("KeyDevice"); 103 | } 104 | } 105 | } 106 | 107 | private BluefishUhdMode _uhdMode = BluefishUhdMode._0; 108 | public BluefishUhdMode UhdMode 109 | { 110 | get 111 | { 112 | return _uhdMode; 113 | } 114 | set 115 | { 116 | if (_uhdMode != value) 117 | { 118 | _uhdMode = value; 119 | OnPropertyChanged(nameof(UhdMode)); 120 | } 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Consumers/Consumer.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Consumers; 2 | 3 | public abstract class Consumer : INotifyPropertyChanged 4 | { 5 | public event PropertyChangedEventHandler? PropertyChanged; 6 | protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } 7 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Consumers/DecklinkConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Consumers; 2 | 3 | public class DecklinkConsumer : FrameConsumer 4 | { 5 | public ObservableCollection Ports { get; } = []; 6 | 7 | private int _device = 1; 8 | public int Device 9 | { 10 | get 11 | { 12 | return _device; 13 | } 14 | set 15 | { 16 | if (_device != value) 17 | { 18 | _device = value; 19 | OnPropertyChanged(nameof(Device)); 20 | } 21 | } 22 | } 23 | 24 | private int _keyDevice = 2; 25 | public int KeyDevice 26 | { 27 | get 28 | { 29 | return _keyDevice; 30 | } 31 | set 32 | { 33 | if (_keyDevice != value) 34 | { 35 | _keyDevice = value; 36 | OnPropertyChanged(nameof(KeyDevice)); 37 | } 38 | } 39 | } 40 | 41 | private bool _embeddedAudio = false; 42 | public bool EmbeddedAudio 43 | { 44 | get 45 | { 46 | return _embeddedAudio; 47 | } 48 | set 49 | { 50 | if (_embeddedAudio != value) 51 | { 52 | _embeddedAudio = value; 53 | OnPropertyChanged(nameof(EmbeddedAudio)); 54 | } 55 | } 56 | } 57 | 58 | private DecklinkLatency _latency = DecklinkLatency._normal; 59 | public DecklinkLatency Latency 60 | { 61 | get 62 | { 63 | return _latency; 64 | } 65 | set 66 | { 67 | if (_latency != value) 68 | { 69 | _latency = value; 70 | OnPropertyChanged(nameof(Latency)); 71 | } 72 | } 73 | } 74 | 75 | private DecklinkKeyer _keyer = DecklinkKeyer._external; 76 | public DecklinkKeyer Keyer 77 | { 78 | get 79 | { 80 | return _keyer; 81 | } 82 | set 83 | { 84 | if (_keyer != value) 85 | { 86 | _keyer = value; 87 | OnPropertyChanged(nameof(Keyer)); 88 | } 89 | } 90 | } 91 | 92 | private bool _keyOnly = false; 93 | public bool KeyOnly 94 | { 95 | get 96 | { 97 | return _keyOnly; 98 | } 99 | set 100 | { 101 | if (_keyOnly != value) 102 | { 103 | _keyOnly = value; 104 | OnPropertyChanged(nameof(KeyOnly)); 105 | } 106 | } 107 | } 108 | 109 | private int _bufferDepth = 3; 110 | public int BufferDepth 111 | { 112 | get 113 | { 114 | return _bufferDepth; 115 | } 116 | set 117 | { 118 | if (_bufferDepth != value) 119 | { 120 | _bufferDepth = value; 121 | OnPropertyChanged(nameof(KeyDevice)); 122 | } 123 | } 124 | } 125 | 126 | private string _videoMode = ""; 127 | public string VideoMode 128 | { 129 | get 130 | { 131 | return _videoMode; 132 | } 133 | set 134 | { 135 | if (_videoMode != value) 136 | { 137 | _videoMode = value; 138 | OnPropertyChanged(nameof(VideoMode)); 139 | } 140 | } 141 | } 142 | 143 | private DecklinkWaitForReference _waitForReference = DecklinkWaitForReference._auto; 144 | public DecklinkWaitForReference WaitForReference 145 | { 146 | get 147 | { 148 | return _waitForReference; 149 | } 150 | set 151 | { 152 | if (_waitForReference != value) 153 | { 154 | _waitForReference = value; 155 | OnPropertyChanged(nameof(WaitForReference)); 156 | } 157 | } 158 | } 159 | 160 | private int _waitForReferenceDuration = 10; 161 | public int WaitForReferenceDuration 162 | { 163 | get 164 | { 165 | return _waitForReferenceDuration; 166 | } 167 | set 168 | { 169 | if (_waitForReferenceDuration != value) 170 | { 171 | _waitForReferenceDuration = value; 172 | OnPropertyChanged(nameof(WaitForReferenceDuration)); 173 | } 174 | } 175 | } 176 | 177 | private bool _hdrMetadata = false; 178 | public bool HdrMetadata 179 | { 180 | get 181 | { 182 | return _hdrMetadata; 183 | } 184 | set 185 | { 186 | if (_hdrMetadata != value) 187 | { 188 | _hdrMetadata = value; 189 | OnPropertyChanged(nameof(HdrMetadata)); 190 | } 191 | } 192 | } 193 | 194 | private int _hdrMaxCll = 1000; 195 | public int HdrMaxCll 196 | { 197 | get 198 | { 199 | return _hdrMaxCll; 200 | } 201 | set 202 | { 203 | if (_hdrMaxCll != value) 204 | { 205 | if (value < 1) _hdrMaxCll = 1; 206 | else if (value > 65535) _hdrMaxCll = 65535; 207 | else _hdrMaxCll = value; 208 | OnPropertyChanged(nameof(HdrMaxCll)); 209 | } 210 | } 211 | } 212 | 213 | private int _hdrMaxFall = 1000; 214 | public int HdrMaxFall 215 | { 216 | get 217 | { 218 | return _hdrMaxFall; 219 | } 220 | set 221 | { 222 | if (_hdrMaxFall != value) 223 | { 224 | if (value < 50) _hdrMaxFall = 50; 225 | else if (value > 65535) _hdrMaxFall = 65535; 226 | else _hdrMaxFall = value; 227 | OnPropertyChanged(nameof(HdrMaxFall)); 228 | } 229 | } 230 | } 231 | 232 | private double _hdrMinDml = 0.005; 233 | public double HdrMinDml 234 | { 235 | get 236 | { 237 | return _hdrMinDml; 238 | } 239 | set 240 | { 241 | if (_hdrMinDml != value) 242 | { 243 | if (value < 0.0001) _hdrMinDml = 0.0001; 244 | else if (value > 6.5535) _hdrMinDml = 6.5535; 245 | else _hdrMinDml = value; 246 | OnPropertyChanged(nameof(HdrMinDml)); 247 | } 248 | } 249 | } 250 | 251 | private double _hdrMaxDml = 1000; 252 | public double HdrMaxDml 253 | { 254 | get 255 | { 256 | return _hdrMaxDml; 257 | } 258 | set 259 | { 260 | if (_hdrMaxDml != value) 261 | { 262 | if (value < 1) _hdrMaxDml = 1; 263 | else if (value > 65535) _hdrMaxDml = 65535; 264 | else _hdrMaxDml = value; 265 | OnPropertyChanged(nameof(HdrMaxDml)); 266 | } 267 | } 268 | } 269 | 270 | private DecklinkDefaultColorSpace _defaultColorSpace = DecklinkDefaultColorSpace._bt709; 271 | public DecklinkDefaultColorSpace DefaultColorSpace 272 | { 273 | get 274 | { 275 | return _defaultColorSpace; 276 | } 277 | set 278 | { 279 | if (_defaultColorSpace != value) 280 | { 281 | _defaultColorSpace = value; 282 | OnPropertyChanged(nameof(DefaultColorSpace)); 283 | } 284 | } 285 | } 286 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Consumers/DecklinkPort.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Consumers; 2 | 3 | public class DecklinkPort : INotifyPropertyChanged 4 | { 5 | private int _device = 1; 6 | public int Device 7 | { 8 | get 9 | { 10 | return _device; 11 | } 12 | set 13 | { 14 | if (_device != value) 15 | { 16 | _device = value; 17 | OnPropertyChanged(nameof(Device)); 18 | } 19 | } 20 | } 21 | 22 | private bool _keyOnly = false; 23 | public bool KeyOnly 24 | { 25 | get 26 | { 27 | return _keyOnly; 28 | } 29 | set 30 | { 31 | if (_keyOnly != value) 32 | { 33 | _keyOnly = value; 34 | OnPropertyChanged(nameof(KeyOnly)); 35 | } 36 | } 37 | } 38 | 39 | private string _videoMode = ""; 40 | public string VideoMode 41 | { 42 | get 43 | { 44 | return _videoMode; 45 | } 46 | set 47 | { 48 | if (_videoMode != value) 49 | { 50 | _videoMode = value; 51 | OnPropertyChanged(nameof(VideoMode)); 52 | } 53 | } 54 | } 55 | 56 | 57 | private bool _subregionEnable = false; 58 | public bool SubregionEnable 59 | { 60 | get 61 | { 62 | return _subregionEnable; 63 | } 64 | set 65 | { 66 | if (_subregionEnable != value) 67 | { 68 | _subregionEnable = value; 69 | OnPropertyChanged(nameof(SubregionEnable)); 70 | } 71 | } 72 | } 73 | 74 | #region SUBREGION 75 | 76 | private int _sourceX = 0; 77 | public int SourceX 78 | { 79 | get 80 | { 81 | return _sourceX; 82 | } 83 | set 84 | { 85 | if (_sourceX != value) 86 | { 87 | _sourceX = value; 88 | OnPropertyChanged(nameof(SourceX)); 89 | } 90 | } 91 | } 92 | 93 | private int _sourceY = 0; 94 | public int SourceY 95 | { 96 | get 97 | { 98 | return _sourceY; 99 | } 100 | set 101 | { 102 | if (_sourceY != value) 103 | { 104 | _sourceY = value; 105 | OnPropertyChanged(nameof(SourceY)); 106 | } 107 | } 108 | } 109 | 110 | private int _destinationX = 0; 111 | public int DestinationX 112 | { 113 | get 114 | { 115 | return _destinationX; 116 | } 117 | set 118 | { 119 | if (_destinationX != value) 120 | { 121 | _destinationX = value; 122 | OnPropertyChanged(nameof(DestinationX)); 123 | } 124 | } 125 | } 126 | 127 | private int _destinationY = 0; 128 | public int DestinationY 129 | { 130 | get 131 | { 132 | return _destinationY; 133 | } 134 | set 135 | { 136 | if (_destinationY != value) 137 | { 138 | _destinationY = value; 139 | OnPropertyChanged(nameof(DestinationY)); 140 | } 141 | } 142 | } 143 | 144 | private int _subregionWidth = 0; 145 | public int SubregionWidth 146 | { 147 | get 148 | { 149 | return _subregionWidth; 150 | } 151 | set 152 | { 153 | if (_subregionWidth != value) 154 | { 155 | _subregionWidth = value; 156 | OnPropertyChanged(nameof(SubregionWidth)); 157 | } 158 | } 159 | } 160 | 161 | private int _subregionHeight = 0; 162 | public int SubregionHeight 163 | { 164 | get 165 | { 166 | return _subregionHeight; 167 | } 168 | set 169 | { 170 | if (_subregionHeight != value) 171 | { 172 | _subregionHeight = value; 173 | OnPropertyChanged(nameof(SubregionHeight)); 174 | } 175 | } 176 | } 177 | 178 | #endregion 179 | 180 | public event PropertyChangedEventHandler? PropertyChanged; 181 | protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } 182 | } 183 | -------------------------------------------------------------------------------- /CasparCG/Configuration/Consumers/FfmpegConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Consumers; 2 | 3 | public class FfmpegConsumer : Consumer 4 | { 5 | private string _path = ""; 6 | public string Path 7 | { 8 | get 9 | { 10 | return _path; 11 | } 12 | set 13 | { 14 | if (_path != value) 15 | { 16 | _path = value; 17 | OnPropertyChanged(nameof(Path)); 18 | } 19 | } 20 | } 21 | 22 | private string _arguments = ""; 23 | public string Arguments 24 | { 25 | get 26 | { 27 | return _arguments; 28 | } 29 | set 30 | { 31 | if (_arguments != value) 32 | { 33 | _arguments = value; 34 | OnPropertyChanged(nameof(Arguments)); 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Consumers/FrameConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Consumers; 2 | 3 | public abstract class FrameConsumer : Consumer 4 | { 5 | private bool _subregionEnable = false; 6 | public bool SubregionEnable 7 | { 8 | get 9 | { 10 | return _subregionEnable; 11 | } 12 | set 13 | { 14 | if (_subregionEnable != value) 15 | { 16 | _subregionEnable = value; 17 | OnPropertyChanged(nameof(SubregionEnable)); 18 | } 19 | } 20 | } 21 | 22 | private int _subregionWidth = 0; 23 | public int SubregionWidth 24 | { 25 | get 26 | { 27 | return _subregionWidth; 28 | } 29 | set 30 | { 31 | if (_subregionWidth != value) 32 | { 33 | _subregionWidth = value; 34 | OnPropertyChanged(nameof(SubregionWidth)); 35 | } 36 | } 37 | } 38 | 39 | private int _subregionHeight = 0; 40 | public int SubregionHeight 41 | { 42 | get 43 | { 44 | return _subregionHeight; 45 | } 46 | set 47 | { 48 | if (_subregionHeight != value) 49 | { 50 | _subregionHeight = value; 51 | OnPropertyChanged(nameof(SubregionHeight)); 52 | } 53 | } 54 | } 55 | 56 | private int _sourceX = 0; 57 | public int SourceX 58 | { 59 | get 60 | { 61 | return _sourceX; 62 | } 63 | set 64 | { 65 | if (_sourceX != value) 66 | { 67 | _sourceX = value; 68 | OnPropertyChanged(nameof(SourceX)); 69 | } 70 | } 71 | } 72 | 73 | private int _sourceY = 0; 74 | public int SourceY 75 | { 76 | get 77 | { 78 | return _sourceY; 79 | } 80 | set 81 | { 82 | if (_sourceY != value) 83 | { 84 | _sourceY = value; 85 | OnPropertyChanged(nameof(SourceY)); 86 | } 87 | } 88 | } 89 | 90 | private int _destinationX = 0; 91 | public int DestinationX 92 | { 93 | get 94 | { 95 | return _destinationX; 96 | } 97 | set 98 | { 99 | if (_destinationX != value) 100 | { 101 | _destinationX = value; 102 | OnPropertyChanged(nameof(DestinationX)); 103 | } 104 | } 105 | } 106 | 107 | private int _destinationY = 0; 108 | public int DestinationY 109 | { 110 | get 111 | { 112 | return _destinationY; 113 | } 114 | set 115 | { 116 | if (_destinationY != value) 117 | { 118 | _destinationY = value; 119 | OnPropertyChanged(nameof(DestinationY)); 120 | } 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Consumers/NdiConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Consumers; 2 | 3 | public class NdiConsumer : Consumer 4 | { 5 | private string _name = ""; 6 | public string Name 7 | { 8 | get 9 | { 10 | return _name; 11 | } 12 | set 13 | { 14 | if (_name != value) 15 | { 16 | _name = value; 17 | OnPropertyChanged(nameof(Name)); 18 | } 19 | } 20 | } 21 | 22 | private bool _allowFields = false; 23 | public bool AllowFields 24 | { 25 | get 26 | { 27 | return _allowFields; 28 | } 29 | set 30 | { 31 | if (_allowFields != value) 32 | { 33 | _allowFields = value; 34 | OnPropertyChanged(nameof(AllowFields)); 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Consumers/NewtekIvgaConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Consumers; 2 | 3 | public class NewtekIvgaConsumer : Consumer 4 | { 5 | 6 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Consumers/ScreenConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Consumers; 2 | 3 | public class ScreenConsumer : Consumer 4 | { 5 | private string _name = ""; 6 | public string Name 7 | { 8 | get 9 | { 10 | return _name; 11 | } 12 | set 13 | { 14 | if (_name != value) 15 | { 16 | _name = value; 17 | OnPropertyChanged(nameof(Name)); 18 | } 19 | } 20 | } 21 | 22 | private int _device = 1; 23 | public int Device 24 | { 25 | get 26 | { 27 | return _device; 28 | } 29 | set 30 | { 31 | if (_device != value) 32 | { 33 | _device = value; 34 | OnPropertyChanged(nameof(Device)); 35 | } 36 | } 37 | } 38 | 39 | private ScreenAspectRatio _aspect = ScreenAspectRatio._default; 40 | public ScreenAspectRatio Aspect 41 | { 42 | get 43 | { 44 | return _aspect; 45 | } 46 | set 47 | { 48 | if (_aspect != value) 49 | { 50 | _aspect = value; 51 | OnPropertyChanged(nameof(Aspect)); 52 | } 53 | } 54 | } 55 | 56 | private ScreenStretch _stretch = ScreenStretch._fill; 57 | public ScreenStretch Stretch 58 | { 59 | get 60 | { 61 | return _stretch; 62 | } 63 | set 64 | { 65 | if (_stretch != value) 66 | { 67 | _stretch = value; 68 | OnPropertyChanged(nameof(Stretch)); 69 | } 70 | } 71 | } 72 | 73 | private bool _windowed = true; 74 | public bool Windowed 75 | { 76 | get 77 | { 78 | return _windowed; 79 | } 80 | set 81 | { 82 | if (_windowed != value) 83 | { 84 | _windowed = value; 85 | OnPropertyChanged(nameof(Windowed)); 86 | } 87 | } 88 | } 89 | 90 | private bool _keyOnly = false; 91 | public bool KeyOnly 92 | { 93 | get 94 | { 95 | return _keyOnly; 96 | } 97 | set 98 | { 99 | if (_keyOnly != value) 100 | { 101 | _keyOnly = value; 102 | OnPropertyChanged(nameof(KeyOnly)); 103 | } 104 | } 105 | } 106 | 107 | private bool _vsync = false; 108 | public bool Vsync 109 | { 110 | get 111 | { 112 | return _vsync; 113 | } 114 | set 115 | { 116 | if (_vsync != value) 117 | { 118 | _vsync = value; 119 | OnPropertyChanged(nameof(Vsync)); 120 | } 121 | } 122 | } 123 | 124 | private bool _borderless = false; 125 | public bool Borderless 126 | { 127 | get 128 | { 129 | return _borderless; 130 | } 131 | set 132 | { 133 | if (_borderless != value) 134 | { 135 | _borderless = value; 136 | OnPropertyChanged(nameof(Borderless)); 137 | } 138 | } 139 | } 140 | 141 | private bool _interactive = true; 142 | public bool Interactive 143 | { 144 | get 145 | { 146 | return _interactive; 147 | } 148 | set 149 | { 150 | if (_interactive != value) 151 | { 152 | _interactive = value; 153 | OnPropertyChanged(nameof(Interactive)); 154 | } 155 | } 156 | } 157 | 158 | private bool _alwaysOn = false; 159 | public bool AlwaysOn 160 | { 161 | get 162 | { 163 | return _alwaysOn; 164 | } 165 | set 166 | { 167 | if (_alwaysOn != value) 168 | { 169 | _alwaysOn = value; 170 | OnPropertyChanged(nameof(AlwaysOn)); 171 | } 172 | } 173 | } 174 | 175 | private int _x = 0; 176 | public int X 177 | { 178 | get 179 | { 180 | return _x; 181 | } 182 | set 183 | { 184 | if (_x != value) 185 | { 186 | _x = value; 187 | OnPropertyChanged(nameof(X)); 188 | } 189 | } 190 | } 191 | 192 | private int _y = 0; 193 | public int Y 194 | { 195 | get 196 | { 197 | return _y; 198 | } 199 | set 200 | { 201 | if (_y != value) 202 | { 203 | _y = value; 204 | OnPropertyChanged(nameof(Y)); 205 | } 206 | } 207 | } 208 | 209 | private int _width = 0; 210 | public int Width 211 | { 212 | get 213 | { 214 | return _width; 215 | } 216 | set 217 | { 218 | if (_width != value) 219 | { 220 | _width = value; 221 | OnPropertyChanged(nameof(Width)); 222 | } 223 | } 224 | } 225 | 226 | private int _height = 0; 227 | public int Height 228 | { 229 | get 230 | { 231 | return _height; 232 | } 233 | set 234 | { 235 | if (_height != value) 236 | { 237 | _height = value; 238 | OnPropertyChanged(nameof(Height)); 239 | } 240 | } 241 | } 242 | 243 | private bool _sbsKey = false; 244 | public bool SbsKey 245 | { 246 | get 247 | { 248 | return _sbsKey; 249 | } 250 | set 251 | { 252 | if (_sbsKey != value) 253 | { 254 | _sbsKey = value; 255 | OnPropertyChanged(nameof(SbsKey)); 256 | } 257 | } 258 | } 259 | 260 | private ScreenColourSpace _colourSpace = ScreenColourSpace._rgb; 261 | public ScreenColourSpace ColourSpace 262 | { 263 | get 264 | { 265 | return _colourSpace; 266 | } 267 | set 268 | { 269 | if (_colourSpace != value) 270 | { 271 | _colourSpace = value; 272 | OnPropertyChanged(nameof(ColourSpace)); 273 | } 274 | } 275 | } 276 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Consumers/SystemAudioConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Consumers; 2 | 3 | public class SystemAudioConsumer : Consumer 4 | { 5 | private SystemAudioChannelLayout _channelLayout = SystemAudioChannelLayout._stereo; 6 | public SystemAudioChannelLayout ChannelLayout 7 | { 8 | get 9 | { 10 | return _channelLayout; 11 | } 12 | set 13 | { 14 | if (_channelLayout != value) 15 | { 16 | _channelLayout = value; 17 | OnPropertyChanged(nameof(ChannelLayout)); 18 | } 19 | } 20 | } 21 | 22 | private int _latency = 200; 23 | public int Latency 24 | { 25 | get 26 | { 27 | return _latency; 28 | } 29 | set 30 | { 31 | if (_latency != value) 32 | { 33 | _latency = value; 34 | OnPropertyChanged(nameof(Latency)); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /CasparCG/Configuration/CustomVideoMode.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration; 2 | 3 | public class CustomVideoMode : VideoMode 4 | { 5 | public CustomVideoMode() 6 | { 7 | 8 | } 9 | 10 | public override string Display => Id; 11 | 12 | private int _width = 1; 13 | public int Width 14 | { 15 | get 16 | { 17 | return _width; 18 | } 19 | set 20 | { 21 | if (_width != value) 22 | { 23 | _width = value; 24 | OnPropertyChanged(nameof(Width)); 25 | } 26 | } 27 | } 28 | 29 | private int _height = 1; 30 | public int Height 31 | { 32 | get 33 | { 34 | return _height; 35 | } 36 | set 37 | { 38 | if (_height != value) 39 | { 40 | _height = value; 41 | OnPropertyChanged(nameof(Height)); 42 | } 43 | } 44 | } 45 | 46 | private int _timeScale = 60000; 47 | public int TimeScale 48 | { 49 | get 50 | { 51 | return _timeScale; 52 | } 53 | set 54 | { 55 | if (_timeScale != value) 56 | { 57 | _timeScale = value; 58 | OnPropertyChanged(nameof(TimeScale)); 59 | } 60 | } 61 | } 62 | 63 | private int _duration = 1000; 64 | public int Duration 65 | { 66 | get 67 | { 68 | return _duration; 69 | } 70 | set 71 | { 72 | if (_duration != value) 73 | { 74 | _duration = value; 75 | OnPropertyChanged(nameof(Duration)); 76 | } 77 | } 78 | } 79 | 80 | private int _cadence = 0; 81 | public int Cadence 82 | { 83 | get 84 | { 85 | return _cadence; 86 | } 87 | set 88 | { 89 | if (_cadence != value) 90 | { 91 | _cadence = value; 92 | OnPropertyChanged(nameof(Cadence)); 93 | } 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /CasparCG/Configuration/Enums/ArtnetFixtureType.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Enums; 2 | 3 | public enum ArtnetFixtureType 4 | { 5 | [Display(Description = "ArtnetFixtureType_DIMMER", ResourceType = typeof(L))] 6 | _DIMMER, 7 | [Display(Description = "ArtnetFixtureType_RGB", ResourceType = typeof(L))] 8 | _RGB, 9 | [Display(Description = "ArtnetFixtureType_RGBW", ResourceType = typeof(L))] 10 | _RGBW 11 | } 12 | -------------------------------------------------------------------------------- /CasparCG/Configuration/Enums/BluefishKeyer.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Enums; 2 | 3 | public enum BluefishKeyer 4 | { 5 | [Display(Description = "BluefishKeyer_External", ResourceType = typeof(L))] 6 | _external, 7 | [Display(Description = "BluefishKeyer_Internal", ResourceType = typeof(L))] 8 | _internal, 9 | [Display(Description = "BluefishKeyer_Disabled", ResourceType = typeof(L))] 10 | _disabled 11 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Enums/BluefishKeyerAudio.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Enums; 2 | 3 | public enum BluefishKeyerAudio 4 | { 5 | [Display(Description = "BluefishKeyerAudio_Output", ResourceType = typeof(L))] 6 | _videooutputchannel, 7 | [Display(Description = "BluefishKeyerAudio_Input", ResourceType = typeof(L))] 8 | _sdivideoinput 9 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Enums/BluefishUhdMode.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Enums; 2 | 3 | public enum BluefishUhdMode 4 | { 5 | [Display(Description = "BluefishUhdMode_DisableBVC", ResourceType = typeof(L))] 6 | _0, 7 | [Display(Description = "BluefishUhdMode_Auto", ResourceType = typeof(L))] 8 | _1, 9 | [Display(Description = "BluefishUhdMode_Force2SI", ResourceType = typeof(L))] 10 | _2, 11 | [Display(Description = "BluefishUhdMode_ForceSQ", ResourceType = typeof(L))] 12 | _3 13 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Enums/ChannelColorDepth.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Enums; 2 | 3 | public enum ChannelColorDepth 4 | { 5 | [Display(Description = "ChannelColorDepth_8", ResourceType = typeof(L))] 6 | _8, 7 | [Display(Description = "ChannelColorDepth_16", ResourceType = typeof(L))] 8 | _16, 9 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Enums/ChannelColorSpace.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Enums; 2 | 3 | public enum ChannelColorSpace 4 | { 5 | [Display(Description = "ChannelColorSpace_BT709", ResourceType = typeof(L))] 6 | _bt709, 7 | [Display(Description = "ChannelColorSpace_BT2020", ResourceType = typeof(L))] 8 | _bt2020, 9 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Enums/DecklinkDefaultColorSpace.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Enums; 2 | 3 | public enum DecklinkDefaultColorSpace 4 | { 5 | [Display(Description = "DecklinkDefaultColorSpace_BT601", ResourceType = typeof(L))] 6 | _bt601, 7 | [Display(Description = "DecklinkDefaultColorSpace_BT709", ResourceType = typeof(L))] 8 | _bt709, 9 | [Display(Description = "DecklinkDefaultColorSpace_BT2020", ResourceType = typeof(L))] 10 | _bt2020, 11 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Enums/DecklinkKeyer.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Enums; 2 | 3 | public enum DecklinkKeyer 4 | { 5 | [Display(Description = "DecklinkKeyer_External", ResourceType = typeof(L))] 6 | _external, 7 | [Display(Description = "DecklinkKeyer_ExternalSeparateDevice", ResourceType = typeof(L))] 8 | _external_separate_device, 9 | [Display(Description = "DecklinkKeyer_Internal", ResourceType = typeof(L))] 10 | _internal, 11 | [Display(Description = "DecklinkKeyer_Default", ResourceType = typeof(L))] 12 | _default 13 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Enums/DecklinkLatency.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Enums; 2 | 3 | public enum DecklinkLatency 4 | { 5 | [Display(Description = "DecklinkLatency_Normal", ResourceType = typeof(L))] 6 | _normal, 7 | [Display(Description = "DecklinkLatency_Low", ResourceType = typeof(L))] 8 | _low, 9 | [Display(Description = "DecklinkLatency_Default", ResourceType = typeof(L))] 10 | _default 11 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Enums/DecklinkWaitForReference.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Enums; 2 | 3 | public enum DecklinkWaitForReference 4 | { 5 | [Display(Description = "DecklinkWaitForReference_Auto", ResourceType = typeof(L))] 6 | _auto, 7 | [Display(Description = "DecklinkWaitForReference_Enable", ResourceType = typeof(L))] 8 | _enable, 9 | [Display(Description = "DecklinkWaitForReference_Disable", ResourceType = typeof(L))] 10 | _disable 11 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Enums/FfmpegDeinterlace.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Enums; 2 | 3 | public enum FfmpegDeinterlace 4 | { 5 | [Display(Description = "FfmpegDeinterlace_None", ResourceType = typeof(L))] 6 | _none, 7 | [Display(Description = "FfmpegDeinterlace_Interlaced", ResourceType = typeof(L))] 8 | _interlaced, 9 | [Display(Description = "FfmpegDeinterlace_All", ResourceType = typeof(L))] 10 | _all 11 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Enums/HtmlAngleGraphicsBackend.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Enums; 2 | 3 | public enum HtmlAngleGraphicsBackend 4 | { 5 | [Display(Description = "HtmlAngleGraphicsBackend_OpenGL", ResourceType = typeof(L))] 6 | _gl, 7 | [Display(Description = "HtmlAngleGraphicsBackend_D3D11", ResourceType = typeof(L))] 8 | _d3d11, 9 | [Display(Description = "HtmlAngleGraphicsBackend_D3D9", ResourceType = typeof(L))] 10 | _d3d9 11 | } 12 | -------------------------------------------------------------------------------- /CasparCG/Configuration/Enums/LogLevel.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Enums; 2 | 3 | public enum LogLevel 4 | { 5 | [Display(Description = "LogLevel_Trace", ResourceType = typeof(L))] 6 | _trace, 7 | [Display(Description = "LogLevel_Debug", ResourceType = typeof(L))] 8 | _debug, 9 | [Display(Description = "LogLevel_Info", ResourceType = typeof(L))] 10 | _info, 11 | [Display(Description = "LogLevel_Warning", ResourceType = typeof(L))] 12 | _warning, 13 | [Display(Description = "LogLevel_Error", ResourceType = typeof(L))] 14 | _error, 15 | [Display(Description = "LogLevel_Fatal", ResourceType = typeof(L))] 16 | _fatal 17 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Enums/ScreenAspectRatio.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Enums; 2 | 3 | public enum ScreenAspectRatio 4 | { 5 | [Display(Description = "ScreenAspectRatio_Default", ResourceType = typeof(L))] 6 | _default, 7 | [Display(Description = "ScreenAspectRatio_4:3", ResourceType = typeof(L))] 8 | _4_3, 9 | [Display(Description = "ScreenAspectRatio_16:9", ResourceType = typeof(L))] 10 | _16_9, 11 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Enums/ScreenColourSpace.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Enums; 2 | 3 | public enum ScreenColourSpace 4 | { 5 | [Display(Description = "ScreenColourSpace_RGB", ResourceType = typeof(L))] 6 | _rgb, 7 | [Display(Description = "ScreenColourSpace_DataVideo_Full", ResourceType = typeof(L))] 8 | _datavideo_full, 9 | [Display(Description = "ScreenColourSpace_DataVideo_Limited", ResourceType = typeof(L))] 10 | _datavideo_limited 11 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Enums/ScreenStretch.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Enums; 2 | 3 | public enum ScreenStretch 4 | { 5 | [Display(Description = "ScreenStretch_None", ResourceType = typeof(L))] 6 | _none, 7 | [Display(Description = "ScreenStretch_Fill", ResourceType = typeof(L))] 8 | _fill, 9 | [Display(Description = "ScreenStretch_Uniform", ResourceType = typeof(L))] 10 | _uniform, 11 | [Display(Description = "ScreenStretch_UniformToFill", ResourceType = typeof(L))] 12 | _uniform_to_fill 13 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Enums/SystemAudioChannelLayout.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration.Enums; 2 | 3 | public enum SystemAudioChannelLayout 4 | { 5 | [Display(Description = "SystemAudioChannelLayout_Mono", ResourceType = typeof(L))] 6 | _mono, 7 | [Display(Description = "SystemAudioChannelLayout_Stereo", ResourceType = typeof(L))] 8 | _stereo, 9 | [Display(Description = "SystemAudioChannelLayout_Matrix", ResourceType = typeof(L))] 10 | _matrix 11 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/OscClient.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration; 2 | 3 | public class OscClient : INotifyPropertyChanged 4 | { 5 | private int _port = 5253; 6 | public int Port 7 | { 8 | get 9 | { 10 | return _port; 11 | } 12 | set 13 | { 14 | if (_port != value) 15 | { 16 | _port = value; 17 | OnPropertyChanged(nameof(Port)); 18 | } 19 | } 20 | } 21 | 22 | private string _address = "127.0.0.1"; 23 | public string Address 24 | { 25 | get 26 | { 27 | return _address; 28 | } 29 | set 30 | { 31 | if (_address != value) 32 | { 33 | _address = value; 34 | OnPropertyChanged(nameof(Address)); 35 | } 36 | } 37 | } 38 | 39 | public event PropertyChangedEventHandler? PropertyChanged; 40 | protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } 41 | } -------------------------------------------------------------------------------- /CasparCG/Configuration/Producer.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration; 2 | 3 | public class Producer : INotifyPropertyChanged 4 | { 5 | public Producer() 6 | { 7 | 8 | } 9 | 10 | private string _media = ""; 11 | public string Media 12 | { 13 | get 14 | { 15 | return _media; 16 | } 17 | set 18 | { 19 | if (_media != value) 20 | { 21 | _media = value; 22 | OnPropertyChanged(nameof(Media)); 23 | } 24 | } 25 | } 26 | 27 | private int _layer = 1; 28 | public int Layer 29 | { 30 | get 31 | { 32 | return _layer; 33 | } 34 | set 35 | { 36 | if (_layer != value) 37 | { 38 | _layer = value; 39 | OnPropertyChanged(nameof(Layer)); 40 | } 41 | } 42 | } 43 | 44 | public event PropertyChangedEventHandler? PropertyChanged; 45 | protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } 46 | } 47 | -------------------------------------------------------------------------------- /CasparCG/Configuration/VideoMode.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.CasparCG.Configuration; 2 | 3 | public class VideoMode : INotifyPropertyChanged 4 | { 5 | private string _display = ""; 6 | virtual public string Display 7 | { 8 | get 9 | { 10 | return _display; 11 | } 12 | set 13 | { 14 | if (_display != value) 15 | { 16 | _display = value; 17 | OnPropertyChanged(nameof(Display)); 18 | } 19 | } 20 | } 21 | 22 | private string _id = ""; 23 | public string Id 24 | { 25 | get 26 | { 27 | return _id; 28 | } 29 | set 30 | { 31 | if (_id != value) 32 | { 33 | _id = value; 34 | OnPropertyChanged(nameof(Id)); 35 | OnPropertyChanged(nameof(Display)); 36 | } 37 | } 38 | } 39 | 40 | public event PropertyChangedEventHandler? PropertyChanged; 41 | protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /CasparCG/LogLine.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace CasparLauncher.CasparCG; 4 | 5 | public class LogLine 6 | { 7 | public string Data { get; private set; } = ""; 8 | public string Message { get; private set; } = ""; 9 | public LogLevel Level { get; private set; } = LogLevel._info; 10 | public string Timestamp { get; private set; } = ""; 11 | public bool DirectOutput { get; private set; } = true; 12 | private static readonly Regex find = new(@"^\[([0-9]{4}-[0-9]{2}-[0-9]{2}\s[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+)\]\s\[(.+?)\]\s*(.*)"); 13 | 14 | public LogLine(string data, bool serverFormat) 15 | { 16 | Data = data; 17 | if (string.IsNullOrEmpty(data) || !serverFormat) return; 18 | bool has_content = data.Length > 36; 19 | 20 | string to_match = has_content ? data[..36] : data; 21 | Match linedata = find.Match(to_match); 22 | if (linedata.Groups.Count > 1) 23 | { 24 | Timestamp = linedata.Groups[1].Value; 25 | Level = GetLevel(linedata.Groups[2].Value); 26 | Message = has_content ? data[36..] : ""; 27 | DirectOutput = false; 28 | } 29 | } 30 | 31 | public static LogLevel GetLevel(string value) 32 | { 33 | var level = LogLevel._info; 34 | switch (value) 35 | { 36 | case "fatal": 37 | level = LogLevel._fatal; 38 | break; 39 | case "error": 40 | level = LogLevel._error; 41 | break; 42 | case "warning": 43 | level = LogLevel._warning; 44 | break; 45 | case "info": 46 | level = LogLevel._info; 47 | break; 48 | case "debug": 49 | level = LogLevel._debug; 50 | break; 51 | case "trace": 52 | level = LogLevel._trace; 53 | break; 54 | } 55 | return level; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /CasparLauncher.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net8.0-windows 6 | enable 7 | true 8 | true 9 | Resources\AppIcon.ico 10 | true 11 | 2.0.1 12 | 2.0.1 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | True 23 | True 24 | Resources.resx 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | Never 38 | 39 | 40 | Never 41 | 42 | 43 | Never 44 | 45 | 46 | Never 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | PublicResXFileCodeGenerator 56 | Resources.Designer.cs 57 | 58 | 59 | 60 | 61 | 62 | Always 63 | 64 | 65 | Always 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /CasparLauncher.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.852 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CasparLauncher", "CasparLauncher.csproj", "{9748052B-5D17-4542-974C-F4D60EBA5507}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Release|Any CPU = Release|Any CPU 13 | Release|x64 = Release|x64 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {9748052B-5D17-4542-974C-F4D60EBA5507}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {9748052B-5D17-4542-974C-F4D60EBA5507}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {9748052B-5D17-4542-974C-F4D60EBA5507}.Debug|x64.ActiveCfg = Debug|x64 19 | {9748052B-5D17-4542-974C-F4D60EBA5507}.Debug|x64.Build.0 = Debug|x64 20 | {9748052B-5D17-4542-974C-F4D60EBA5507}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {9748052B-5D17-4542-974C-F4D60EBA5507}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {9748052B-5D17-4542-974C-F4D60EBA5507}.Release|x64.ActiveCfg = Release|x64 23 | {9748052B-5D17-4542-974C-F4D60EBA5507}.Release|x64.Build.0 = Release|x64 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {A8BDD365-2E7E-44E1-94F8-90CDCC186E2C} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using BaseUISupport.Controls; 2 | global using BaseUISupport.Converters; 3 | global using BaseUISupport.Helpers; 4 | global using BaseUISupport.Settings; 5 | global using CasparLauncher.CasparCG; 6 | global using CasparLauncher.CasparCG.Configuration; 7 | global using CasparLauncher.CasparCG.Configuration.Consumers; 8 | global using CasparLauncher.CasparCG.Configuration.Enums; 9 | global using CasparLauncher.Launcher; 10 | global using Microsoft.Win32; 11 | global using System; 12 | global using System.Collections.Generic; 13 | global using System.Collections.ObjectModel; 14 | global using System.ComponentModel; 15 | global using System.ComponentModel.DataAnnotations; 16 | global using System.Data; 17 | global using System.Diagnostics; 18 | global using System.Drawing; 19 | global using System.Globalization; 20 | global using System.IO; 21 | global using System.Linq; 22 | global using System.Threading; 23 | global using System.Windows; 24 | global using System.Windows.Controls; 25 | global using System.Windows.Input; 26 | 27 | // Aliases 28 | global using MessageBox = BaseUISupport.Controls.MessageBox; 29 | global using L = CasparLauncher.Properties.Resources; 30 | global using S = BaseUISupport.Settings.SettingStore; 31 | global using Timer = System.Timers.Timer; 32 | global using WF = System.Windows.Forms; -------------------------------------------------------------------------------- /Languages.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher; 2 | 3 | public enum Languages 4 | { 5 | [Display(Description = "LanguagesDefault", ResourceType = typeof(L))] 6 | none, 7 | 8 | [Display(Description = "LanguagesEnglish", ResourceType = typeof(L))] 9 | en, 10 | 11 | [Display(Description = "LanguagesEspañol", ResourceType = typeof(L))] 12 | es 13 | } -------------------------------------------------------------------------------- /Launcher/Command.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.Launcher; 2 | 3 | public class Command : INotifyPropertyChanged 4 | { 5 | public event PropertyChangedEventHandler? PropertyChanged; 6 | protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } 7 | 8 | public Command() 9 | { 10 | 11 | } 12 | 13 | private string _value = ""; 14 | public string Value 15 | { 16 | get 17 | { 18 | return _value; 19 | } 20 | set 21 | { 22 | if (_value != value) 23 | { 24 | _value = value; 25 | OnPropertyChanged(nameof(Value)); 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Launcher/Executable.cs: -------------------------------------------------------------------------------- 1 | using System.Management; 2 | using System.Text; 3 | using System.Windows.Threading; 4 | using System.Collections.Specialized; 5 | 6 | namespace CasparLauncher.Launcher; 7 | 8 | public class Executable : INotifyPropertyChanged 9 | { 10 | const string LOG_LEVEL_START = "Logging ["; 11 | const string LOG_LEVEL_END = "] or higher severity to log"; 12 | 13 | private readonly Timer StartupTimer = new(); 14 | private readonly Timer CommandsTimer = new(); 15 | private readonly DispatcherTimer UptimeTimer = new(DispatcherPriority.Normal, Application.Current.Dispatcher); 16 | private int CurrentCommandIndex = 0; 17 | private DateTime StartupTime; 18 | private Encoding CurrentEncoding = Encoding.UTF8; 19 | private Thread? ExecutableThread; 20 | 21 | public Executable() 22 | { 23 | History.Add(""); 24 | StartupTimer.Elapsed += StartupTimer_Elapsed; 25 | StartupTimer.AutoReset = false; 26 | CommandsTimer.Elapsed += SendStartupCommand; 27 | CommandsTimer.AutoReset = true; 28 | UptimeTimer.Tick += UptimeTimer_Tick; 29 | PropertyChanged += Executable_PropertyChanged; 30 | Commands.CollectionChanged += Commands_CollectionChanged; 31 | } 32 | 33 | private void Commands_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) 34 | { 35 | foreach(Command c in Commands) 36 | { 37 | c.PropertyChanged -= CommandChanged; 38 | c.PropertyChanged += CommandChanged; 39 | } 40 | OnModified(); 41 | } 42 | 43 | private void CommandChanged(object? sender, PropertyChangedEventArgs e) 44 | { 45 | OnModified(); 46 | } 47 | 48 | private void Executable_PropertyChanged(object? sender, PropertyChangedEventArgs e) 49 | { 50 | if ( 51 | e.PropertyName == nameof(Path) || 52 | e.PropertyName == nameof(Name) || 53 | e.PropertyName == nameof(Args) || 54 | e.PropertyName == nameof(AutoStart) || 55 | e.PropertyName == nameof(StartupDelay) || 56 | e.PropertyName == nameof(CommandsDelay) || 57 | e.PropertyName == nameof(AllowCommands) || 58 | e.PropertyName == nameof(AllowMultipleInstances) || 59 | e.PropertyName == nameof(ConfigFile) || 60 | e.PropertyName == nameof(BufferLines) || 61 | e.PropertyName == nameof(SuppressEmptyLines) 62 | ) { 63 | OnModified(); 64 | } 65 | } 66 | 67 | private void UptimeTimer_Tick(object? sender, EventArgs e) 68 | { 69 | OnPropertyChanged(nameof(Uptime)); 70 | } 71 | 72 | #region PROPERTIES 73 | 74 | private string? _path = null; 75 | public string? Path 76 | { 77 | get 78 | { 79 | return _path; 80 | } 81 | set 82 | { 83 | if (_path != value) 84 | { 85 | _path = value; 86 | IsServer = false; 87 | IsScanner = false; 88 | OnPropertyChanged(nameof(Path)); 89 | OnPropertyChanged(nameof(Exists)); 90 | OnPropertyChanged(nameof(Icon)); 91 | OnPropertyChanged(nameof(IsServer)); 92 | OnPropertyChanged(nameof(IsScanner)); 93 | } 94 | } 95 | } 96 | 97 | private bool? _isServer; 98 | public bool IsServer 99 | { 100 | get 101 | { 102 | if(_isServer.HasValue) return _isServer.Value; 103 | if (_path is null || !Exists) return false; 104 | _isServer = System.IO.Path.GetFileNameWithoutExtension(_path).ToLower() == "casparcg"; 105 | return _isServer.Value; 106 | } 107 | private set 108 | { 109 | _isServer = null; 110 | _ = IsServer; 111 | } 112 | } 113 | 114 | private bool? _isScanner; 115 | public bool IsScanner 116 | { 117 | get 118 | { 119 | if (_isScanner.HasValue) return _isScanner.Value; 120 | if (_path is null || !Exists) return false; 121 | _isScanner = System.IO.Path.GetFileNameWithoutExtension(_path).ToLower() == "scanner"; 122 | return _isScanner ?? false; 123 | } 124 | private set 125 | { 126 | _isScanner = null; 127 | _ = IsScanner; 128 | } 129 | } 130 | 131 | private bool _isselected = false; 132 | public bool IsSelected 133 | { 134 | get 135 | { 136 | return _isselected; 137 | } 138 | set 139 | { 140 | if (_isselected != value) 141 | { 142 | _isselected = value; 143 | OnPropertyChanged(nameof(IsSelected)); 144 | } 145 | } 146 | } 147 | 148 | private bool _enabled = false; 149 | public bool Enabled 150 | { 151 | get 152 | { 153 | return _enabled; 154 | } 155 | set 156 | { 157 | if (_enabled != value) 158 | { 159 | _enabled = value; 160 | OnPropertyChanged(nameof(Enabled)); 161 | } 162 | } 163 | } 164 | 165 | private string _name = "Executable"; 166 | public string Name 167 | { 168 | get 169 | { 170 | return _name; 171 | } 172 | set 173 | { 174 | if (_name != value) 175 | { 176 | _name = value; 177 | OnPropertyChanged(nameof(Name)); 178 | } 179 | } 180 | } 181 | 182 | private string _configFile = ""; 183 | public string ConfigFile 184 | { 185 | get 186 | { 187 | return _configFile; 188 | } 189 | set 190 | { 191 | if (_configFile != value) 192 | { 193 | _configFile = value; 194 | OnPropertyChanged(nameof(ConfigFile)); 195 | } 196 | } 197 | } 198 | 199 | private int _scannerPort = 8000; 200 | public int ScannerPort 201 | { 202 | get 203 | { 204 | return _scannerPort; 205 | } 206 | set 207 | { 208 | if (_scannerPort != value) 209 | { 210 | _scannerPort = value; 211 | OnPropertyChanged(nameof(ScannerPort)); 212 | } 213 | } 214 | } 215 | 216 | private string _args = ""; 217 | public string Args 218 | { 219 | get 220 | { 221 | return _args; 222 | } 223 | set 224 | { 225 | if (_args != value) 226 | { 227 | _args = value; 228 | OnPropertyChanged(nameof(Args)); 229 | } 230 | } 231 | } 232 | 233 | private bool _autostart = true; 234 | public bool AutoStart 235 | { 236 | get 237 | { 238 | return _autostart; 239 | } 240 | set 241 | { 242 | if (_autostart != value) 243 | { 244 | _autostart = value; 245 | OnPropertyChanged(nameof(AutoStart)); 246 | } 247 | } 248 | } 249 | 250 | private int _startupdelay = 0; 251 | public int StartupDelay 252 | { 253 | get 254 | { 255 | return _startupdelay; 256 | } 257 | set 258 | { 259 | if (_startupdelay != value) 260 | { 261 | _startupdelay = value; 262 | OnPropertyChanged(nameof(StartupDelay)); 263 | } 264 | } 265 | } 266 | 267 | private int _commandsdelay = 5; 268 | public int CommandsDelay 269 | { 270 | get 271 | { 272 | return _commandsdelay; 273 | } 274 | set 275 | { 276 | if (_commandsdelay != value) 277 | { 278 | _commandsdelay = value; 279 | OnPropertyChanged(nameof(CommandsDelay)); 280 | } 281 | } 282 | } 283 | 284 | private bool _allowcommands = false; 285 | public bool AllowCommands 286 | { 287 | get 288 | { 289 | return _allowcommands; 290 | } 291 | set 292 | { 293 | if (_allowcommands != value) 294 | { 295 | _allowcommands = value; 296 | OnPropertyChanged(nameof(AllowCommands)); 297 | } 298 | } 299 | } 300 | 301 | private int _bufferLines = 1000; 302 | public int BufferLines 303 | { 304 | get 305 | { 306 | return _bufferLines; 307 | } 308 | set 309 | { 310 | if (_bufferLines != value) 311 | { 312 | _bufferLines = value; 313 | OnPropertyChanged(nameof(BufferLines)); 314 | } 315 | } 316 | } 317 | 318 | private bool _suppressEmptyLines = false; 319 | public bool SuppressEmptyLines 320 | { 321 | get 322 | { 323 | return _suppressEmptyLines; 324 | } 325 | set 326 | { 327 | if (_suppressEmptyLines != value) 328 | { 329 | _suppressEmptyLines = value; 330 | OnPropertyChanged(nameof(SuppressEmptyLines)); 331 | } 332 | } 333 | } 334 | 335 | private bool _isRunning = false; 336 | public bool IsRunning 337 | { 338 | get 339 | { 340 | return _isRunning; 341 | } 342 | set 343 | { 344 | if (_isRunning != value) 345 | { 346 | _isRunning = value; 347 | OnPropertyChanged(nameof(IsRunning)); 348 | } 349 | } 350 | } 351 | 352 | private LogLevel _currentLogLevel = LogLevel._warning; 353 | public LogLevel CurrentLogLevel 354 | { 355 | get 356 | { 357 | return _currentLogLevel; 358 | } 359 | set 360 | { 361 | if (_currentLogLevel != value) 362 | { 363 | _currentLogLevel = value; 364 | OnPropertyChanged(nameof(CurrentLogLevel)); 365 | if(_logLevelIsSet) ChangeCasparLogLevel(value); 366 | _logLevelIsSet = true; 367 | } 368 | } 369 | } 370 | 371 | private bool _logLevelIsSet = false; 372 | 373 | private bool _allowMultipleInstances = false; 374 | public bool AllowMultipleInstances 375 | { 376 | get 377 | { 378 | return _allowMultipleInstances; 379 | } 380 | set 381 | { 382 | if (_allowMultipleInstances != value) 383 | { 384 | _allowMultipleInstances = value; 385 | OnPropertyChanged(nameof(AllowMultipleInstances)); 386 | } 387 | } 388 | } 389 | 390 | private bool _killOnlyCurrentPath = false; 391 | public bool KillOnlyCurrentPath 392 | { 393 | get 394 | { 395 | return _killOnlyCurrentPath; 396 | } 397 | set 398 | { 399 | if (_killOnlyCurrentPath != value) 400 | { 401 | _killOnlyCurrentPath = value; 402 | OnPropertyChanged(nameof(KillOnlyCurrentPath)); 403 | } 404 | } 405 | } 406 | 407 | private int HistoryIndex 408 | { 409 | get 410 | { 411 | if (CurrentHistoryIndex >= History.Count) CurrentHistoryIndex = History.Count - 1; 412 | if (CurrentHistoryIndex < 0) CurrentHistoryIndex = 0; 413 | return History.Count - (CurrentHistoryIndex + 1); 414 | } 415 | } 416 | 417 | public int CurrentHistoryIndex { get; set; } = 0; 418 | 419 | private string _currentcommand = ""; 420 | public string CurrentCommand 421 | { 422 | get 423 | { 424 | return _currentcommand; 425 | } 426 | set 427 | { 428 | if(CurrentHistoryIndex>0) 429 | { 430 | if (value == History[HistoryIndex]) 431 | { 432 | _currentcommand = value; 433 | OnPropertyChanged(nameof(CurrentCommand)); 434 | } 435 | else 436 | { 437 | _currentcommand = value; 438 | History[^1] = value; 439 | CurrentHistoryIndex = 0; 440 | OnPropertyChanged(nameof(CurrentCommand)); 441 | } 442 | } 443 | else 444 | { 445 | if (_currentcommand != value) 446 | { 447 | _currentcommand = value; 448 | History[^1] = value; 449 | OnPropertyChanged(nameof(CurrentCommand)); 450 | } 451 | } 452 | } 453 | } 454 | 455 | public ObservableCollection Output { get; set; } = []; 456 | 457 | private Process? _process = null; 458 | public Process? Process 459 | { 460 | get 461 | { 462 | return _process; 463 | } 464 | set 465 | { 466 | if (_process != value) 467 | { 468 | _process = value; 469 | OnPropertyChanged(nameof(Process)); 470 | } 471 | } 472 | } 473 | 474 | public bool Exists 475 | { 476 | get 477 | { 478 | if (string.IsNullOrEmpty(_path)) return false; 479 | string path = System.IO.Path.GetFullPath(_path); 480 | return (Directory.Exists(System.IO.Path.GetDirectoryName(path)) && File.Exists(path)); 481 | } 482 | } 483 | 484 | public Icon? Icon 485 | { 486 | get 487 | { 488 | if (_path is null || !Exists) return null; 489 | return Icon.ExtractAssociatedIcon(_path); 490 | } 491 | } 492 | 493 | public TimeSpan Uptime => DateTime.Now - StartupTime; 494 | 495 | public ObservableCollection Commands { get; set; } = []; 496 | 497 | public ObservableCollection History { get; set; } = []; 498 | 499 | #endregion 500 | 501 | #region STATIC METHODS 502 | 503 | private static bool CheckIfRunning(string file) 504 | { 505 | try 506 | { 507 | return Process.GetProcessesByName(System.IO.Path.GetFileNameWithoutExtension(file)).Any(); 508 | } 509 | catch (Exception) 510 | { 511 | return false; 512 | } 513 | } 514 | 515 | private static void KillAllRunningInstances(string file, bool allInstances = false) 516 | { 517 | try 518 | { 519 | string absolutePath = System.IO.Path.GetFullPath(file); 520 | string processName = System.IO.Path.GetFileName(file); 521 | List runningProcesses = []; 522 | Dictionary parentProcesses = []; 523 | int count = 0; 524 | DateTime startTime = DateTime.Now; 525 | TimeSpan timeout = TimeSpan.FromSeconds(15); 526 | foreach (Process process in Process.GetProcessesByName(System.IO.Path.GetFileNameWithoutExtension(file))) 527 | { 528 | if ((allInstances && process.ProcessName == processName) || process.MainModule?.FileName == absolutePath) 529 | { 530 | parentProcesses.Add(process.Id, GetProcessParent(process)); 531 | runningProcesses.Add(process); 532 | process.Exited += (s, e) => { count--; }; 533 | count++; 534 | } 535 | } 536 | foreach (Process parent in runningProcesses.Where(p => !parentProcesses.ContainsKey((int)parentProcesses[p.Id]))) 537 | { 538 | if (!parent.HasExited) parent.Kill(true); 539 | } 540 | while (count > 0 && DateTime.Now - startTime < timeout) 541 | { 542 | foreach(Process runningProcess in runningProcesses.Where(p => p is not null && !p.HasExited)) 543 | { 544 | runningProcess.Kill(); 545 | } 546 | Thread.Sleep(10); 547 | } 548 | } 549 | catch { } 550 | } 551 | 552 | private static uint GetProcessParent(Process process) 553 | { 554 | using ManagementObjectSearcher searcher = new(new SelectQuery($"SELECT * FROM Win32_Process where ProcessId={process.Id}")); 555 | foreach (var item in searcher.Get()) return(uint)item["ParentProcessId"]; 556 | return 0; 557 | } 558 | 559 | private static List GetProcessChildren(Process process) 560 | { 561 | List children = []; 562 | using ManagementObjectSearcher searcher = new(new SelectQuery($"SELECT * FROM Win32_Process where ParentProcessId={process.Id}")); 563 | foreach (var item in searcher.Get()) children.Add(Process.GetProcessById((int)item["ProcessId"])); 564 | return children; 565 | } 566 | 567 | private static Encoding GetEncodingByVersion(string path) 568 | { 569 | try 570 | { 571 | if (path is not null) 572 | { 573 | FileVersionInfo version = FileVersionInfo.GetVersionInfo(path); 574 | float versionNumber = float.Parse(version.FileMajorPart + "." + version.FileMinorPart, System.Globalization.CultureInfo.InvariantCulture); 575 | if (versionNumber >= 2.4f) return Encoding.UTF8; 576 | else return Encoding.ASCII; 577 | } 578 | } 579 | catch (Exception) { } 580 | return Encoding.UTF8; 581 | } 582 | 583 | #endregion 584 | 585 | #region PRIVATE METHODS 586 | 587 | private void Setup() 588 | { 589 | if (_path is null || !Exists) 590 | { 591 | throw (new Exception("ExecutablePathNotFound")); 592 | } 593 | if (!AllowMultipleInstances && CheckIfRunning(_path)) 594 | { 595 | KillAllRunningInstances(_path); 596 | Thread.Sleep(100); 597 | } 598 | if (IsServer) CurrentEncoding = GetEncodingByVersion(_path); 599 | ProcessStartInfo info = new(); 600 | info.CreateNoWindow = true; 601 | info.UseShellExecute = false; 602 | info.WorkingDirectory = System.IO.Path.GetDirectoryName(_path); 603 | info.FileName = System.IO.Path.Combine(_path); 604 | info.RedirectStandardOutput = true; 605 | info.RedirectStandardError = true; 606 | info.RedirectStandardInput = true; 607 | info.StandardOutputEncoding = CurrentEncoding; 608 | info.Environment.Add("ERRORLEVEL", "0"); 609 | StringBuilder arguments = new(); 610 | if ((IsServer || IsScanner) 611 | && !string.IsNullOrEmpty(ConfigFile) 612 | && ConfigFile != "casparcg.config") 613 | { 614 | if (IsScanner) arguments.Append($@"--caspar.config "); 615 | arguments.Append($@"""{ConfigFile}"" "); 616 | } 617 | if (IsScanner && ScannerPort != 8000) 618 | { 619 | arguments.Append($@"--http.port {ScannerPort} "); 620 | } 621 | if (!string.IsNullOrEmpty(Args)) arguments.Append(Args); 622 | if (arguments.Length > 0) info.Arguments = arguments.ToString(); 623 | 624 | if (Process is not null) Process.Dispose(); 625 | Process = new() 626 | { 627 | StartInfo = info 628 | }; 629 | } 630 | 631 | private void StopProcess() 632 | { 633 | if (!Enabled) return; 634 | Enabled = false; 635 | Process?.Kill(); 636 | } 637 | 638 | private void StartProcess() 639 | { 640 | if (Launchpad.DisableStart) return; 641 | try 642 | { 643 | Setup(); 644 | Enabled = true; 645 | if (Process is null) return; 646 | Process.Start(); 647 | Process.OutputDataReceived += Process_OutputDataReceived; 648 | Process.ErrorDataReceived += Process_OutputDataReceived; 649 | Process.Exited += Process_Exited; 650 | Process.EnableRaisingEvents = true; 651 | Process.BeginOutputReadLine(); 652 | Process.BeginErrorReadLine(); 653 | } 654 | catch (Exception e) 655 | { 656 | if (e.Message == "ExecutablePathNotFound") OnProcessPathError(); 657 | else OnProcessError(e); 658 | return; 659 | } 660 | StartupTime = DateTime.Now; 661 | OnPropertyChanged(nameof(Uptime)); 662 | UptimeTimer.Interval = TimeSpan.FromSeconds(.2); 663 | UptimeTimer.Start(); 664 | IsRunning = true; 665 | OnProcessStarted(); 666 | if (AllowCommands && Commands.Any()) SendStartupCommands(); 667 | } 668 | 669 | private void StartupTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e) 670 | { 671 | StartProcess(); 672 | } 673 | 674 | private void SendStartupCommands() 675 | { 676 | if(CommandsDelay>0) 677 | { 678 | CurrentCommandIndex = 0; 679 | CommandsTimer.Interval = CommandsDelay; 680 | CommandsTimer.Start(); 681 | } 682 | else 683 | { 684 | foreach(Command c in Commands) 685 | { 686 | SendCommand(c.Value); 687 | } 688 | } 689 | } 690 | 691 | private void SendStartupCommand(object? sender, EventArgs e) 692 | { 693 | if(CurrentCommandIndex >= Commands.Count) 694 | { 695 | CommandsTimer.Stop(); 696 | return; 697 | } 698 | SendCommand(Commands[CurrentCommandIndex].Value); 699 | CurrentCommandIndex++; 700 | } 701 | 702 | private void SendCommand(string command) 703 | { 704 | if (Process is null || !IsRunning) return; 705 | try 706 | { 707 | if (IsServer && CurrentEncoding == Encoding.UTF8) 708 | { 709 | 710 | byte[] data = Encoding.Unicode.GetBytes(command + Process.StandardInput.NewLine); 711 | Process.StandardInput.BaseStream.Write(data, 0, data.Length); 712 | Process.StandardInput.BaseStream.Flush(); 713 | 714 | } 715 | else 716 | { 717 | Process.StandardInput.WriteLine(command); 718 | } 719 | } 720 | catch { } 721 | } 722 | 723 | private void Process_OutputDataReceived(object? sender, DataReceivedEventArgs e) 724 | { 725 | if (Application.Current is null || e.Data is null) return; 726 | bool remove_line = false; 727 | Application.Current.Dispatcher.BeginInvoke(() => 728 | { 729 | if (BufferLines > 0 && Output.Count == BufferLines) remove_line = true; 730 | var line = new LogLine(e.Data, IsServer); 731 | try 732 | { 733 | if (IsServer && line.Data is string data && line.DirectOutput && data.Length > LOG_LEVEL_START.Length + LOG_LEVEL_END.Length) 734 | { 735 | int start = LOG_LEVEL_START.Length; 736 | int end = data.IndexOf(LOG_LEVEL_END, start); 737 | 738 | if (data.IndexOf(LOG_LEVEL_START) == 0 && end > 0) 739 | { 740 | var level = LogLine.GetLevel(data[start..end]); 741 | CurrentLogLevel = level; 742 | } 743 | } 744 | } 745 | catch (Exception) {} 746 | if(SuppressEmptyLines) 747 | { 748 | if (line.DirectOutput && line.Data?.Length == 0) return; 749 | if (!line.DirectOutput && line.Message?.Length == 0) return; 750 | } 751 | if (remove_line) Output.RemoveAt(0); 752 | Output.Add(line); 753 | }); 754 | } 755 | 756 | private void Process_Exited(object? sender, EventArgs e) 757 | { 758 | if (sender is Process process) 759 | { 760 | if (process.ExitCode == 0) Enabled = false; 761 | Debug.WriteLine($"Process exit code: {process.ExitCode}"); 762 | } 763 | IsRunning = false; 764 | OnProcessExited(); 765 | if (!Enabled) return; 766 | Thread.Sleep(100); 767 | StartProcess(); 768 | } 769 | 770 | private void ChangeCasparLogLevel(LogLevel newLevel) 771 | { 772 | if(IsServer && IsRunning) SendCommand($"LOG LEVEL {newLevel.ToString()[1..]}"); 773 | } 774 | 775 | #endregion 776 | 777 | #region PUBLIC METHODS 778 | 779 | public void Start(bool auto = false) 780 | { 781 | ExecutableThread = new(() => 782 | { 783 | if (auto && StartupDelay > 0) 784 | { 785 | StartupTimer.Interval = StartupDelay; 786 | StartupTimer.Start(); 787 | } 788 | else 789 | { 790 | StartProcess(); 791 | } 792 | }); 793 | ExecutableThread.Start(); 794 | } 795 | 796 | public void Stop() 797 | { 798 | StopProcess(); 799 | } 800 | 801 | public string PreviousHistoryCommand() 802 | { 803 | CurrentHistoryIndex++; 804 | return CurrentCommand = History[HistoryIndex]; 805 | } 806 | 807 | public string NextHistoryCommand() 808 | { 809 | CurrentHistoryIndex--; 810 | return CurrentCommand = History[HistoryIndex]; 811 | } 812 | 813 | public void Write() 814 | { 815 | if (CurrentCommand.Length == 0) return; 816 | Write(CurrentCommand); 817 | if (CurrentHistoryIndex == 0) History.Add(""); 818 | else CurrentHistoryIndex = 0; 819 | CurrentCommand = ""; 820 | } 821 | 822 | public void Write(string command) 823 | { 824 | SendCommand(command); 825 | } 826 | 827 | #endregion 828 | 829 | #region SPECIAL COMMANDS 830 | 831 | internal void OpenDiag() 832 | { 833 | if (!IsRunning) return; 834 | Write("DIAG"); 835 | } 836 | 837 | internal void OpenGrid() 838 | { 839 | if (!IsRunning) return; 840 | Write("CHANNEL_GRID"); 841 | } 842 | 843 | internal void ClearScannerDatabases() 844 | { 845 | if (!Exists || IsRunning) return; 846 | 847 | string scanner_dir; 848 | try 849 | { 850 | if (System.IO.Path.GetDirectoryName(Path) is string relative_dir) 851 | scanner_dir = System.IO.Path.GetFullPath(relative_dir); 852 | else 853 | throw new Exception("Path is relative"); 854 | } 855 | catch (Exception) 856 | { 857 | scanner_dir = AppDomain.CurrentDomain.BaseDirectory; 858 | } 859 | string media_dir = System.IO.Path.Combine(scanner_dir, ".\\_media\\"); 860 | string pad_dir = System.IO.Path.Combine(scanner_dir, ".\\pouch__all_dbs__\\"); 861 | if (Directory.Exists(scanner_dir)) 862 | { 863 | try 864 | { 865 | if (Directory.Exists(media_dir)) Directory.Delete(media_dir, true); 866 | if (Directory.Exists(pad_dir)) Directory.Delete(pad_dir, true); 867 | } 868 | catch (Exception e) 869 | { 870 | MessageBox.Show(e.Message); 871 | } 872 | } 873 | } 874 | 875 | internal void RebuildMediaDb() 876 | { 877 | if (!Exists) return; 878 | if (IsRunning) 879 | { 880 | Stop(); 881 | ProcessExited += RebuildAndRestart; 882 | } 883 | else 884 | { 885 | ClearScannerDatabases(); 886 | } 887 | } 888 | 889 | private void RebuildAndRestart(object? sender, EventArgs e) 890 | { 891 | ProcessExited -= RebuildAndRestart; 892 | ClearScannerDatabases(); 893 | Start(); 894 | } 895 | 896 | #endregion 897 | 898 | #region EVENTS 899 | 900 | public event PropertyChangedEventHandler? PropertyChanged; 901 | protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new(propertyName)); } 902 | 903 | public event EventHandler? ProcessError; 904 | protected virtual void OnProcessError(Exception exception) { ProcessError?.Invoke(this, new(this, exception)); } 905 | 906 | public event EventHandler? ProcessPathError; 907 | protected virtual void OnProcessPathError() { ProcessPathError?.Invoke(this, new(this)); } 908 | 909 | public event EventHandler? ProcessExited; 910 | protected virtual void OnProcessExited() { ProcessExited?.Invoke(this, new(this)); } 911 | 912 | public event EventHandler? ProcessStarted; 913 | protected virtual void OnProcessStarted() { ProcessStarted?.Invoke(this, new(this)); } 914 | 915 | public event EventHandler? Modified; 916 | protected virtual void OnModified() { Modified?.Invoke(this, new()); } 917 | 918 | #endregion 919 | } -------------------------------------------------------------------------------- /Launcher/ExecutableEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.Launcher; 2 | 3 | public class ExecutableEventArgs : EventArgs 4 | { 5 | public Executable Executable { get; private set; } 6 | public Exception? Exception { get; private set; } 7 | 8 | public ExecutableEventArgs(Executable executable) 9 | { 10 | Executable = executable; 11 | } 12 | public ExecutableEventArgs(Executable executable, Exception exception) 13 | { 14 | Executable = executable; 15 | Exception = exception; 16 | } 17 | } -------------------------------------------------------------------------------- /Launcher/Launchpad.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher.Launcher; 2 | 3 | public class Launchpad : INotifyPropertyChanged 4 | { 5 | public event PropertyChangedEventHandler? PropertyChanged; 6 | protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new(propertyName)); } 7 | 8 | public event EventHandler? ExecutableError; 9 | protected virtual void OnExecutableError(Executable ex) { ExecutableError?.Invoke(this, new(ex)); } 10 | 11 | public event EventHandler? ExecutablePathError; 12 | protected virtual void OnExecutablePathError(Executable ex) { ExecutablePathError?.Invoke(this, new(ex)); } 13 | 14 | public event EventHandler? ExecutableStarted; 15 | protected virtual void OnExecutableStarted(Executable ex) { ExecutableStarted?.Invoke(this, new(ex)); } 16 | 17 | public event EventHandler? ExecutableStopped; 18 | protected virtual void OnExecutableStopped(Executable ex) { ExecutableStopped?.Invoke(this, new(ex)); } 19 | 20 | public Launchpad() 21 | { 22 | LoadSettings(); 23 | SaveChanges(); 24 | PropertyChanged += Launchpad_PropertyChanged; 25 | Executables.CollectionChanged += Executables_CollectionChanged; 26 | } 27 | 28 | private void Launchpad_PropertyChanged(object? sender, PropertyChangedEventArgs e) 29 | { 30 | SaveChanges(); 31 | } 32 | 33 | private void LoadSettings() 34 | { 35 | S.Init("casparlauncher"); 36 | S.WriteEnabled = true; 37 | S.Load(); 38 | ParseSettings(); 39 | if (Keyboard.Modifiers == ModifierKeys.Shift) DisableStart = true; 40 | InitExecutables(); 41 | } 42 | 43 | #region LAUNCHER SETTINGS 44 | 45 | public ObservableCollection Executables { get; set; } = []; 46 | 47 | public static bool DisableStart { get; set; } = false; 48 | public static bool Exiting { get; set; } = false; 49 | 50 | private Languages? _forcedLanguage; 51 | public Languages ForcedLanguage 52 | { 53 | get 54 | { 55 | _forcedLanguage ??= (Languages)S.GetOrSetValue("/ui/language", (int)Languages.none); 56 | return _forcedLanguage ?? 0; 57 | } 58 | set 59 | { 60 | if (_forcedLanguage != value) 61 | { 62 | _forcedLanguage = value; 63 | S.Set("/ui/language", (int)value); 64 | OnPropertyChanged(nameof(ForcedLanguage)); 65 | App.SetLanguageDictionary(); 66 | LocalizationHelper.Instance.CurrentCulture = L.Culture; 67 | } 68 | } 69 | } 70 | 71 | private bool? _darkMode; 72 | public bool DarkMode 73 | { 74 | get 75 | { 76 | _darkMode ??= S.GetOrSetValue("/ui/dark", true); 77 | return _darkMode ?? true; 78 | } 79 | set 80 | { 81 | if (_darkMode != value) 82 | { 83 | _darkMode = value; 84 | App.SetDarkMode(value); 85 | S.Set("/ui/dark", value); 86 | OnPropertyChanged(nameof(DarkMode)); 87 | } 88 | } 89 | } 90 | 91 | private bool? _openAtLogin; 92 | public bool OpenAtLogin 93 | { 94 | get 95 | { 96 | _openAtLogin ??= S.GetOrSetValue("/ui/open-at-login", false); 97 | return _openAtLogin ?? false; 98 | } 99 | set 100 | { 101 | if (_openAtLogin != value) 102 | { 103 | _openAtLogin = value; 104 | SetStartup(value); 105 | S.Set("/ui/open-at-login", value); 106 | OnPropertyChanged(nameof(OpenAtLogin)); 107 | } 108 | } 109 | } 110 | 111 | private bool? _notifyStart; 112 | public bool NotifyStart 113 | { 114 | get 115 | { 116 | _notifyStart ??= S.GetOrSetValue("/ui/notify-start", true); 117 | return _notifyStart ?? true; 118 | } 119 | set 120 | { 121 | if (_notifyStart != value) 122 | { 123 | _notifyStart = value; 124 | SetStartup(value); 125 | S.Set("/ui/notify-start", value); 126 | OnPropertyChanged(nameof(NotifyStart)); 127 | } 128 | } 129 | } 130 | 131 | private bool? _notifyStop; 132 | public bool NotifyStop 133 | { 134 | get 135 | { 136 | _notifyStop ??= S.GetOrSetValue("/ui/notify-stop", true); 137 | return _notifyStop ?? true; 138 | } 139 | set 140 | { 141 | if (_notifyStop != value) 142 | { 143 | _notifyStop = value; 144 | SetStartup(value); 145 | S.Set("/ui/notify-stop", value); 146 | OnPropertyChanged(nameof(NotifyStop)); 147 | } 148 | } 149 | } 150 | 151 | private bool? _notifyError; 152 | public bool NotifyError 153 | { 154 | get 155 | { 156 | _notifyError ??= S.GetOrSetValue("/ui/notify-error", true); 157 | return _notifyError ?? true; 158 | } 159 | set 160 | { 161 | if (_notifyError != value) 162 | { 163 | _notifyError = value; 164 | SetStartup(value); 165 | S.Set("/ui/notify-error", value); 166 | OnPropertyChanged(nameof(NotifyError)); 167 | } 168 | } 169 | } 170 | 171 | private int? _selectedTab; 172 | public int SelectedTab 173 | { 174 | get 175 | { 176 | _selectedTab ??= S.GetOrSetValue("/ui/tab", 0); 177 | return _selectedTab ?? 0; 178 | } 179 | set 180 | { 181 | if (_selectedTab != value) 182 | { 183 | _selectedTab = value; 184 | S.Set("/ui/tab", value); 185 | OnPropertyChanged(nameof(SelectedTab)); 186 | } 187 | } 188 | } 189 | 190 | private bool? _stylizeConsole; 191 | public bool StylizeConsole 192 | { 193 | get 194 | { 195 | _stylizeConsole ??= S.GetOrSetValue("/ui/console-stylize", true); 196 | return _stylizeConsole ?? true; 197 | } 198 | set 199 | { 200 | _stylizeConsole = value; 201 | S.Set("/ui/console-stylize", value); 202 | OnPropertyChanged(nameof(StylizeConsole)); 203 | } 204 | } 205 | 206 | private bool? _cropConsole; 207 | public bool CropConsole 208 | { 209 | get 210 | { 211 | _cropConsole ??= S.GetOrSetValue("/ui/console-crop", true); 212 | return _cropConsole ?? true; 213 | } 214 | set 215 | { 216 | _cropConsole = value; 217 | S.Set("/ui/console-crop", value); 218 | OnPropertyChanged(nameof(CropConsole)); 219 | OnPropertyChanged(nameof(CropConsoleWidth)); 220 | } 221 | } 222 | 223 | public DataGridLength CropConsoleWidth 224 | { 225 | get => CropConsole ? new(10d, DataGridLengthUnitType.Star) : new(10d, DataGridLengthUnitType.Auto); 226 | set { } 227 | } 228 | 229 | #endregion 230 | 231 | #region PRIVATE METHODS 232 | 233 | private void ParseSettings() 234 | { 235 | try 236 | { 237 | if (S.GetOrSet("/executables") is not SettingItem executables) return; 238 | 239 | if (executables.Settings.Count == 0) LoadDefaults(); 240 | else 241 | { 242 | foreach (SettingItem executable in executables.Settings.Values) 243 | { 244 | var path = executable.FullPath; 245 | Executable new_executable = new() 246 | { 247 | Path = S.GetOrSetValue(path + "path", ""), 248 | Name = S.GetOrSetValue(path + "name", ""), 249 | Args = S.GetOrSetValue(path + "args", ""), 250 | AutoStart = S.GetOrSetValue(path + "autostart", false), 251 | StartupDelay = S.GetOrSetValue(path + "sdel", 0), 252 | AllowCommands = S.GetOrSetValue(path + "acmd", false), 253 | AllowMultipleInstances = S.GetOrSetValue(path + "amin", false), 254 | KillOnlyCurrentPath = S.GetOrSetValue(path + "kocp", false), 255 | CommandsDelay = S.GetOrSetValue(path + "cdel", 0), 256 | BufferLines = S.GetOrSetValue(path + "buff", 1000), 257 | SuppressEmptyLines = S.GetOrSetValue(path + "seln", false), 258 | ConfigFile = S.GetOrSetValue(path + "config", ""), 259 | ScannerPort = S.GetOrSetValue(path + "sprt", 8000), 260 | }; 261 | 262 | if ((new_executable.IsServer || new_executable.IsScanner) 263 | && string.IsNullOrEmpty(new_executable.ConfigFile)) 264 | new_executable.ConfigFile = S.GetOrSetValue(path + "config", "casparcg.config"); 265 | 266 | if (S.GetOrSet(path + "commands") is SettingItem commands && commands.Values.Count > 0) 267 | { 268 | foreach (SettingValue command in commands.Values) 269 | { 270 | if (command.Value is not null) 271 | { 272 | Command new_command = new() 273 | { 274 | Value = command.Value 275 | }; 276 | new_executable.Commands.Add(new_command); 277 | } 278 | } 279 | } 280 | Executables.Add(new_executable); 281 | } 282 | } 283 | } 284 | catch(Exception) 285 | { 286 | LoadDefaults(); 287 | } 288 | } 289 | 290 | private void LoadDefaults() 291 | { 292 | Executable server = new() 293 | { 294 | Name = "Server", 295 | Path = "casparcg.exe", 296 | AllowCommands = true, 297 | ConfigFile = "casparcg.config" 298 | }; 299 | Executable scanner = new() 300 | { 301 | Name = "Scanner", 302 | Path = "scanner.exe", 303 | ConfigFile = "casparcg.config" 304 | }; 305 | Executables.Add(server); 306 | Executables.Add(scanner); 307 | } 308 | 309 | private void InitExecutables() 310 | { 311 | foreach (Executable executable in Executables) 312 | { 313 | executable.Modified -= ExecutableModified; 314 | executable.ProcessError -= Executable_ProcessError; 315 | executable.ProcessPathError -= Executable_ProcessPathError; 316 | executable.ProcessExited -= Executable_ProcessExited; 317 | 318 | executable.Modified += ExecutableModified; 319 | executable.ProcessError += Executable_ProcessError; 320 | executable.ProcessPathError += Executable_ProcessPathError; 321 | executable.ProcessStarted += Executable_ProcessStarted; 322 | executable.ProcessExited += Executable_ProcessExited; 323 | } 324 | } 325 | 326 | private void Executable_ProcessError(object? sender, ExecutableEventArgs e) 327 | { 328 | if (NotifyError) OnExecutableError(e.Executable); 329 | if (e.Exception is not null) 330 | { 331 | Debug.WriteLine(e.Exception?.Message); 332 | Debug.WriteLine(e.Exception?.Data); 333 | Debug.WriteLine(e.Exception?.StackTrace); 334 | } 335 | } 336 | 337 | private void Executable_ProcessPathError(object? sender, ExecutableEventArgs e) 338 | { 339 | if (NotifyError && !Exiting) OnExecutablePathError(e.Executable); 340 | } 341 | 342 | private void Executable_ProcessStarted(object? sender, ExecutableEventArgs e) 343 | { 344 | if (NotifyStart && !Exiting) OnExecutableStarted(e.Executable); 345 | } 346 | 347 | private void Executable_ProcessExited(object? sender, ExecutableEventArgs e) 348 | { 349 | if (NotifyStop && !Exiting) OnExecutableStopped(e.Executable); 350 | } 351 | 352 | private void Executables_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) 353 | { 354 | InitExecutables(); 355 | SaveChanges(); 356 | } 357 | 358 | private void ExecutableModified(object? sender, EventArgs e) 359 | { 360 | SaveChanges(); 361 | } 362 | 363 | private void SaveChanges() 364 | { 365 | if (S.GetOrSet("/executables") is not SettingItem executables) return; 366 | executables.Settings.Clear(); 367 | var count = 0; 368 | foreach (Executable executable in Executables) 369 | { 370 | count++; 371 | string path = $"{executables.FullPath}{count}"; 372 | if (S.GetOrSet(path) is SettingItem setting) 373 | { 374 | string s = setting.FullPath; 375 | S.Set(s + "path", executable.Path??""); 376 | S.Set(s + "name", executable.Name); 377 | S.Set(s + "args", executable.Args); 378 | S.Set(s + "autostart", executable.AutoStart); 379 | S.Set(s + "sdel", executable.StartupDelay); 380 | S.Set(s + "acmd", executable.AllowCommands); 381 | S.Set(s + "amin", executable.AllowMultipleInstances); 382 | S.Set(s + "kocp", executable.KillOnlyCurrentPath); 383 | S.Set(s + "cdel", executable.CommandsDelay); 384 | S.Set(s + "config", executable.ConfigFile); 385 | S.Set(s + "sprt", executable.ScannerPort); 386 | S.Set(s + "buff", executable.BufferLines); 387 | S.Set(s + "seln", executable.SuppressEmptyLines); 388 | 389 | if (executable.Commands.Count > 0) 390 | { 391 | SettingItem? commands = S.GetOrSet(s + "commands"); 392 | if (commands is not null) 393 | { 394 | if (commands.Values.Count > 0) commands.Values.Clear(); 395 | foreach (Command command in executable.Commands) 396 | commands.AddValue(command.Value); 397 | } 398 | } 399 | } 400 | } 401 | S.Save(); 402 | } 403 | 404 | private static void SetStartup(bool set = true) 405 | { 406 | if (Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true) is RegistryKey regKey) 407 | { 408 | if (set) regKey.SetValue("CasparLauncher", Process.GetCurrentProcess().MainModule?.FileName ?? ""); 409 | else regKey.DeleteValue("CasparLauncher", false); 410 | } 411 | 412 | } 413 | 414 | #endregion 415 | 416 | #region PUBLIC METHODS 417 | 418 | internal Executable AddExecutable(string? path = null) 419 | { 420 | Executable new_executable = new() 421 | { 422 | Name = L.NewExecutableName, 423 | Path = path, 424 | AllowCommands = false 425 | }; 426 | Executables.Add(new_executable); 427 | return new_executable; 428 | } 429 | 430 | internal bool RemoveExecutable(Executable ex) 431 | { 432 | if (!ex.Exists) 433 | { 434 | Executables.Remove(ex); 435 | return true; 436 | } 437 | MessageBoxResult remove = MessageBox.Show(L.DeleteExecutablePromptMessage, L.DeleteExecutablePromptCaption, MessageBoxButton.YesNo, MessageBoxImage.Question); 438 | switch (remove) 439 | { 440 | case MessageBoxResult.Yes: 441 | Executables.Remove(ex); 442 | return true; 443 | case MessageBoxResult.No: 444 | default: 445 | return false; 446 | } 447 | } 448 | 449 | internal static void StartAll(bool startup = false) 450 | { 451 | foreach (Executable ex in App.Launchpad.Executables.Where(ex => (!startup || ex.AutoStart) && !ex.IsRunning)) ex.Start(); 452 | } 453 | 454 | internal static void StopAll() 455 | { 456 | foreach (Executable ex in App.Launchpad.Executables.Where(ex => ex.IsRunning)) ex.Stop(); 457 | } 458 | 459 | internal static void RestartAll() 460 | { 461 | foreach (Executable ex in App.Launchpad.Executables.Where(ex => ex.IsRunning)) ex.Process?.Kill(); 462 | } 463 | 464 | internal static void Start(Executable executable) 465 | { 466 | executable.Start(); 467 | } 468 | 469 | internal static void Stop(Executable executable) 470 | { 471 | executable.Stop(); 472 | } 473 | 474 | internal static void Restart(Executable executable) 475 | { 476 | if (executable.IsRunning) executable.Process?.Kill(); 477 | } 478 | 479 | internal static void RebuildMediaDb(Executable executable) 480 | { 481 | if (executable.IsScanner) 482 | executable.RebuildMediaDb(); 483 | } 484 | 485 | internal static void OpenDiag(Executable executable) 486 | { 487 | if (executable.IsServer) 488 | executable.OpenDiag(); 489 | } 490 | 491 | internal static void OpenGrid(Executable executable) 492 | { 493 | if (executable.IsServer) 494 | executable.OpenGrid(); 495 | } 496 | 497 | #endregion 498 | } 499 | -------------------------------------------------------------------------------- /Launcher/TrayIcon.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Windows.Interop; 3 | 4 | namespace CasparLauncher.Launcher; 5 | 6 | public partial class TrayIcon 7 | { 8 | private const int GWL_EX_STYLE = -20; 9 | private const int WS_EX_APPWINDOW = 0x00040000, WS_EX_TOOLWINDOW = 0x00000080; 10 | 11 | [LibraryImport("user32.dll", SetLastError = true)] 12 | private static partial int GetWindowLongA(IntPtr hWnd, int nIndex); 13 | 14 | [LibraryImport("user32.dll")] 15 | private static partial int SetWindowLongA(IntPtr hWnd, int nIndex, int dwNewLong); 16 | 17 | private readonly WF.NotifyIcon Icon = new(); 18 | private TrayMenu Menu = new(); 19 | 20 | static bool IsColorLight(Color clr) => ((5 * clr.G) + (2 * clr.R) + clr.B) > (8 * 128); 21 | 22 | public TrayIcon() 23 | { 24 | } 25 | 26 | private void SystemEvents_DisplaySettingsChanged(object? sender, EventArgs e) 27 | { 28 | SetTrayIcon(); 29 | } 30 | 31 | internal void SetupTray() 32 | { 33 | SetEventHandlers(); 34 | SetTrayIcon(); 35 | SetTrayMenu(); 36 | } 37 | 38 | private void SetTrayIcon() 39 | { 40 | HideIcon(); 41 | int iconsize = 16; 42 | using (Graphics graphics = Graphics.FromHwnd(IntPtr.Zero)) 43 | { 44 | float dpiX = graphics.DpiX; 45 | float dpiY = graphics.DpiY; 46 | double size = 16 * (graphics.DpiX / 96); 47 | if (size >= 20) iconsize = 20; 48 | if (size >= 24) iconsize = 24; 49 | if (size >= 32) iconsize = 32; 50 | if (size >= 48) iconsize = 256; 51 | //size = 256; 52 | } 53 | string iconpath = App.IsSystemThemeLight() ? "NotifyIconLight.ico" : "NotifyIconDark.ico"; 54 | Stream IconStream = Application.GetResourceStream(new Uri($@"pack://application:,,,/Resources/{iconpath}")).Stream; 55 | Icon.Icon = new Icon(IconStream, new System.Drawing.Size(iconsize, iconsize)); 56 | Icon.Text = L.AppTitle; 57 | Icon.BalloonTipClicked += Icon_BalloonTipClicked; 58 | IconStream.Dispose(); 59 | ShowIcon(); 60 | } 61 | 62 | private void SetTrayMenu() 63 | { 64 | Menu.DataContext = App.Launchpad; 65 | Menu.Show(); 66 | IntPtr handle = new WindowInteropHelper(Menu).Handle; 67 | _ = SetWindowLongA(handle, GWL_EX_STYLE, (GetWindowLongA(handle, GWL_EX_STYLE) | WS_EX_TOOLWINDOW) & ~WS_EX_APPWINDOW); 68 | } 69 | 70 | internal void ShowIcon() => Icon.Visible = true; 71 | internal void HideIcon() => Icon.Visible = false; 72 | 73 | private void SetEventHandlers() 74 | { 75 | SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged; 76 | 77 | Icon.MouseDown += Icon_MouseDown; 78 | Icon.MouseUp += Icon_MouseUp; 79 | 80 | Menu.ActionExecuted += Menu_ActionExecuted; 81 | Menu.ConfigExecuted += Menu_ConfigExecuted; 82 | Menu.StartExecuted += Menu_StartExecuted; 83 | Menu.StopExecuted += Menu_StopExecuted; 84 | Menu.RestartExecuted += Menu_RestartExecuted; 85 | Menu.ExitExecuted += Menu_ExitExecuted; 86 | 87 | App.Launchpad.ExecutableError += Settings_ExecutableError; 88 | App.Launchpad.ExecutablePathError += Settings_ExecutablePathError; 89 | App.Launchpad.ExecutableStarted += Launchpad_ExecutableStarted; 90 | App.Launchpad.ExecutableStopped += Settings_ExecutableExited; 91 | } 92 | 93 | private void Menu_ActionExecuted(object? sender, (Executable, CasparAction) e) 94 | { 95 | switch (e.Item2) 96 | { 97 | case CasparAction.Diag: 98 | e.Item1.OpenDiag(); 99 | break; 100 | case CasparAction.Rebuild: 101 | e.Item1.RebuildMediaDb(); 102 | break; 103 | case CasparAction.Grid: 104 | e.Item1.OpenGrid(); 105 | break; 106 | } 107 | } 108 | private void Menu_ConfigExecuted(object? sender, Executable executable) 109 | { 110 | Window? currentWindow = null; 111 | foreach (Window window in Application.Current.Windows) 112 | { 113 | if (window.DataContext == executable) currentWindow = window; 114 | } 115 | if(currentWindow is not null) 116 | { 117 | currentWindow.Topmost = true; 118 | currentWindow.Activate(); 119 | currentWindow.Topmost = false; 120 | } 121 | else 122 | { 123 | ExecutableOptions executableOptions = new() 124 | { 125 | DataContext = executable, 126 | WindowStartupLocation = WindowStartupLocation.CenterScreen, 127 | Owner = Menu 128 | }; 129 | executableOptions.Show(); 130 | } 131 | } 132 | 133 | private void Menu_StartExecuted(object? sender, Executable? e) 134 | { 135 | if (e is not null) Launchpad.Start(e); 136 | else Launchpad.StartAll(); 137 | 138 | } 139 | 140 | private void Menu_StopExecuted(object? sender, Executable? e) 141 | { 142 | if (e is not null) Launchpad.Stop(e); 143 | else Launchpad.StopAll(); 144 | } 145 | 146 | private void Menu_RestartExecuted(object? sender, Executable? e) 147 | { 148 | if (e is not null) Launchpad.Restart(e); 149 | else Launchpad.RestartAll(); 150 | } 151 | 152 | private void Menu_ExitExecuted(object? sender, EventArgs e) 153 | { 154 | App.Shutdown(true); 155 | } 156 | 157 | 158 | private void Icon_MouseDown(object? sender, WF.MouseEventArgs e) 159 | { 160 | Launchpad.DisableStart = Keyboard.Modifiers == ModifierKeys.Shift; 161 | if (e.Button == WF.MouseButtons.Right) 162 | { 163 | Menu.Activate(); 164 | Menu.TrayContextMenu.PlacementTarget = Menu; 165 | Menu.TrayContextMenu.IsOpen = true; 166 | } 167 | } 168 | 169 | private void Icon_MouseUp(object? sender, WF.MouseEventArgs e) 170 | { 171 | Launchpad.DisableStart = Keyboard.Modifiers == ModifierKeys.Shift; 172 | if (e.Button == WF.MouseButtons.Left) 173 | { 174 | if (Application.Current is App app) app.ShowWindow(); 175 | } 176 | } 177 | 178 | private bool LastBalloonWasError = false; 179 | 180 | private void Icon_BalloonTipClicked(object? sender, EventArgs e) 181 | { 182 | if (Application.Current is App app) app.ShowWindow(LastBalloonWasError ? 0 : null); 183 | LastBalloonWasError = false; 184 | } 185 | 186 | private void Settings_ExecutablePathError(object? sender, ExecutableEventArgs e) 187 | { 188 | LastBalloonWasError = true; 189 | Icon.ShowBalloonTip(3000, string.Format("{0}: {1}", e.Executable.Name, L.ExecutableNotFoundWarningCaption), L.ExecutableNotFoundWarningMessage, WF.ToolTipIcon.Warning); 190 | } 191 | 192 | private void Settings_ExecutableError(object? sender, ExecutableEventArgs e) 193 | { 194 | LastBalloonWasError = true; 195 | if (Icon is null) return; 196 | Icon.ShowBalloonTip(3000, e.Executable.Name, L.ExecutableErrorMessage, WF.ToolTipIcon.Error); 197 | } 198 | 199 | private void Launchpad_ExecutableStarted(object? sender, ExecutableEventArgs e) 200 | { 201 | LastBalloonWasError = false; 202 | if (Icon is null) return; 203 | Icon.ShowBalloonTip(3000, e.Executable.Name, L.ExecutableStartedMessage, WF.ToolTipIcon.Info); 204 | } 205 | 206 | private void Settings_ExecutableExited(object? sender, ExecutableEventArgs e) 207 | { 208 | LastBalloonWasError = false; 209 | if (Icon is null) return; 210 | Icon.ShowBalloonTip(3000, e.Executable.Name, L.ExecutableStoppedMessage, WF.ToolTipIcon.Info); 211 | } 212 | } -------------------------------------------------------------------------------- /Properties/PublishProfiles/FrameworkIndependent.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\publish\ 10 | FileSystem 11 | <_TargetId>Folder 12 | net8.0-windows 13 | win-x64 14 | false 15 | true 16 | false 17 | 18 | -------------------------------------------------------------------------------- /Properties/PublishProfiles/SelfContained_x64.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\publish\SelfContained_x64 10 | FileSystem 11 | <_TargetId>Folder 12 | net8.0-windows 13 | win-x64 14 | true 15 | true 16 | false 17 | true 18 | 19 | -------------------------------------------------------------------------------- /Properties/Resources.es.Designer.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Properties/Resources.es.Designer.cs -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/README.md -------------------------------------------------------------------------------- /Resources/AppIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Resources/AppIcon.ico -------------------------------------------------------------------------------- /Resources/NotifyIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Resources/NotifyIcon.ico -------------------------------------------------------------------------------- /Resources/NotifyIconDark.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Resources/NotifyIconDark.ico -------------------------------------------------------------------------------- /Resources/NotifyIconLight.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rrebuffo/CasparLauncher/06b7608e240bb39f818cec15a4cbbcb2d936efcb/Resources/NotifyIconLight.ico -------------------------------------------------------------------------------- /Selectors/TrayMenuStyleSelector.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher; 2 | 3 | public class TrayMenuStyleSelector : StyleSelector 4 | { 5 | public Style? SeparatorStyle { get; set; } 6 | public Style? MenuItemStyle { get; set; } 7 | public Style? ExecutableItemStyle { get; set; } 8 | 9 | public override Style? SelectStyle(object item, DependencyObject container) 10 | { 11 | if (item is Separator) return SeparatorStyle; 12 | if (item is Executable) return ExecutableItemStyle; 13 | return MenuItemStyle; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Windows/AboutWindow.xaml: -------------------------------------------------------------------------------- 1 |  12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | rebuffo.ar 31 | 32 | GitHub 33 | 34 | CasparCG Forum 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Windows/AboutWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Windows.Media.Imaging; 3 | using IconConverter = BaseUISupport.Converters.IconConverter; 4 | 5 | namespace CasparLauncher; 6 | 7 | public partial class AboutWindow : DialogWindow 8 | { 9 | public AboutWindow() 10 | { 11 | InitializeComponent(); 12 | } 13 | 14 | public BitmapImage? AppIcon => IconConverter.GetJumboImageFromResource($@"pack://application:,,,/Resources/AppIcon.ico"); 15 | 16 | public string Version 17 | { 18 | get 19 | { 20 | FileVersionInfo? version = Process.GetCurrentProcess().MainModule?.FileVersionInfo; 21 | return version is not null ? string.Format(L.AboutWindowVersion, version.ProductVersion) : ""; 22 | } 23 | } 24 | 25 | private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) 26 | { 27 | LinkHelper.Open(e.Uri.ToString()); 28 | e.Handled = true; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Windows/ConfigEditor.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher; 2 | 3 | public partial class ConfigEditor : DialogWindow 4 | { 5 | public ConfigEditor() 6 | { 7 | InitializeComponent(); 8 | DataContextChanged += ConfigEditor_DataContextChanged; 9 | } 10 | 11 | private void ConfigEditor_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) 12 | { 13 | if (e.OldValue is null && e.NewValue is ConfigFile file) 14 | { 15 | file.Channels.CollectionChanged -= Channels_CollectionChanged; 16 | file.Channels.CollectionChanged += Channels_CollectionChanged; 17 | Debug.WriteLine("Initialized"); 18 | } 19 | } 20 | 21 | private void Channels_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) 22 | { 23 | OnPropertyChanged(nameof(ChannelsUpdated)); 24 | } 25 | 26 | protected override void OnClosing(CancelEventArgs e) 27 | { 28 | base.OnClosing(e); 29 | if (null != Owner) 30 | { 31 | Owner.Activate(); 32 | } 33 | } 34 | 35 | private void AddVideoMode(object sender, RoutedEventArgs e) 36 | { 37 | if (DataContext is not ConfigFile file) return; 38 | file.VideoModes.Add(new CustomVideoMode() { Id = L.ConfigWindowVideoModesNewModeId }); 39 | } 40 | 41 | private void RemVideoMode(object sender, RoutedEventArgs e) 42 | { 43 | if (DataContext is not ConfigFile file) return; 44 | if (VideoModesList.SelectedIndex >= 0) 45 | { 46 | CustomVideoMode target = (CustomVideoMode)VideoModesList.SelectedItem; 47 | 48 | foreach (Channel channel in file.Channels) 49 | if (channel.VideoMode == target) 50 | channel.VideoMode = ConfigFile.DefaultVideoModes.First(); 51 | 52 | file.VideoModes.Remove(target); 53 | } 54 | } 55 | 56 | private void AddChannel(object sender, RoutedEventArgs e) 57 | { 58 | if (DataContext is not ConfigFile file) return; 59 | file.Channels.Add(new Channel() { VideoMode = ConfigFile.DefaultVideoModes.First() }); 60 | } 61 | 62 | private void RemChannel(object sender, RoutedEventArgs e) 63 | { 64 | if (DataContext is not ConfigFile file) return; 65 | if (ChannelList.SelectedIndex >= 0) 66 | { 67 | file.Channels.Remove((Channel)ChannelList.SelectedItem); 68 | } 69 | } 70 | 71 | private void AddConsumer(object sender, RoutedEventArgs e) 72 | { 73 | Consumer consumer; 74 | switch (((FrameworkElement)e.OriginalSource).Name) 75 | { 76 | case "AddDecklinkMenuItem": 77 | consumer = new DecklinkConsumer(); 78 | break; 79 | case "AddBluefishMenuItem": 80 | consumer = new BluefishConsumer(); 81 | break; 82 | case "AddScreenMenuItem": 83 | consumer = new ScreenConsumer(); 84 | break; 85 | case "AddSystemAudioMenuItem": 86 | consumer = new SystemAudioConsumer(); 87 | break; 88 | case "AddIvgaMenuItem": 89 | consumer = new NewtekIvgaConsumer(); 90 | break; 91 | case "AddNdiMenuItem": 92 | consumer = new NdiConsumer(); 93 | break; 94 | case "AddFfmpegMenuItem": 95 | consumer = new FfmpegConsumer(); 96 | break; 97 | case "AddArtnetMenuItem": 98 | consumer = new ArtnetConsumer(); 99 | break; 100 | default: 101 | return; 102 | } 103 | if (ChannelList.SelectedIndex >= 0) 104 | { 105 | ((Channel)ChannelList.SelectedItem).Consumers.Add(consumer); 106 | } 107 | } 108 | 109 | private void RemConsumer(object sender, RoutedEventArgs e) 110 | { 111 | if (DataContext is not ConfigFile file) return; 112 | 113 | if (ChannelList.SelectedIndex >= 0 && ConsumerList.SelectedIndex >= 0) 114 | { 115 | file.Channels[ChannelList.SelectedIndex].Consumers.Remove(ConsumerList.SelectedItem); 116 | } 117 | } 118 | 119 | 120 | private void SaveFile(object sender, RoutedEventArgs e) 121 | { 122 | if (DataContext is not ConfigFile file) return; 123 | 124 | if (file.File is null) 125 | { 126 | if (ShowSaveDialog() is string filename) file.File = filename; 127 | else return; 128 | } 129 | 130 | try 131 | { 132 | file.SaveConfigFile(file.File); 133 | } 134 | catch (IOException) 135 | { 136 | MessageBox.Show(L.ConfigWindowStatusMessageSaveIOError, L.OpenConfigEditorErrorCaption, MessageBoxButton.OK, MessageBoxImage.Error); 137 | return; 138 | } 139 | catch (Exception ex) 140 | { 141 | MessageBox.Show($"{L.ConfigWindowStatusMessageSaveError}\n{ex.GetType()}", L.OpenConfigEditorErrorCaption, MessageBoxButton.OK, MessageBoxImage.Error); 142 | return; 143 | } 144 | Close(); 145 | } 146 | 147 | private static string? ShowSaveDialog() 148 | { 149 | SaveFileDialog saveFileDialog = new() { Filter = "Archivo de configuración |*.config" }; 150 | if (saveFileDialog.ShowDialog() == true) return saveFileDialog.FileName; 151 | else return null; 152 | } 153 | 154 | private static string? SelectFolder(string? path = null, string? caption = null, bool newFolder = false) 155 | { 156 | WF.FolderBrowserDialog Browser = new() { ShowNewFolderButton = newFolder }; 157 | if (string.IsNullOrEmpty(path)) path = Environment.CurrentDirectory; 158 | if (!Path.IsPathRooted(path)) path = Path.Combine(Environment.CurrentDirectory, path); 159 | if (Directory.Exists(path)) Browser.SelectedPath = path; 160 | else Browser.SelectedPath = ""; 161 | Browser.UseDescriptionForTitle = true; 162 | if (caption is not null) Browser.Description = caption; 163 | if (Browser.ShowDialog() == WF.DialogResult.OK) 164 | { 165 | string folder = Browser.SelectedPath; 166 | if (Directory.Exists(folder)) 167 | { 168 | return folder; 169 | } 170 | } 171 | return null; 172 | } 173 | 174 | private void PickMediaPathButton_Click(object sender, RoutedEventArgs e) 175 | { 176 | if (DataContext is not ConfigFile file) return; 177 | if (SelectFolder(file.MediaPath, string.Format(L.BrowseForPathDialogCaption,"media"), true) is string newFolder) file.MediaPath = newFolder; 178 | } 179 | 180 | private void PickDataPathButton_Click(object sender, RoutedEventArgs e) 181 | { 182 | if (DataContext is not ConfigFile file) return; 183 | if (SelectFolder(file.DataPath, string.Format(L.BrowseForPathDialogCaption, "data"), true) is string newFolder) file.DataPath = newFolder; 184 | } 185 | 186 | private void PickTemplatePathButton_Click(object sender, RoutedEventArgs e) 187 | { 188 | if (DataContext is not ConfigFile file) return; 189 | if (SelectFolder(file.TemplatePath, string.Format(L.BrowseForPathDialogCaption, "template"), true) is string newFolder) file.TemplatePath = newFolder; 190 | } 191 | 192 | private void PickFontPathButton_Click(object sender, RoutedEventArgs e) 193 | { 194 | if (DataContext is not ConfigFile file) return; 195 | if (SelectFolder(file.FontPath, string.Format(L.BrowseForPathDialogCaption, "font"), true) is string newFolder) file.FontPath = newFolder; 196 | } 197 | 198 | private void PickLogPathButton_Click(object sender, RoutedEventArgs e) 199 | { 200 | if (DataContext is not ConfigFile file) return; 201 | if (SelectFolder(file.LogPath, string.Format(L.BrowseForPathDialogCaption, "log"), true) is string newFolder) file.LogPath = newFolder; 202 | } 203 | 204 | private void PickHtmlCachePathButton_Click(object sender, RoutedEventArgs e) 205 | { 206 | if (DataContext is not ConfigFile file) return; 207 | if (SelectFolder(file.HtmlCachePath, string.Format(L.BrowseForPathDialogCaption, "HTML cache"), true) is string newFolder) file.HtmlCachePath = newFolder; 208 | } 209 | 210 | #region Drag & Drop 211 | 212 | private bool move = false; 213 | public bool Move 214 | { 215 | get 216 | { 217 | return move; 218 | } 219 | set 220 | { 221 | if (move != value) 222 | { 223 | move = value; 224 | OnPropertyChanged(nameof(Move)); 225 | } 226 | } 227 | } 228 | 229 | private void ChannelItem_Drop(object sender, DragEventArgs e) 230 | { 231 | e.Handled = false; 232 | if (sender is ListBoxItem item 233 | && item.DataContext is Channel target 234 | && e.Data.GetData(typeof(List)) is List droppedItems) 235 | { 236 | foreach (object dropped in droppedItems) 237 | { 238 | if (dropped is Consumer consumer 239 | && GetConsumerChannel(consumer) is Channel origin 240 | && origin != target) 241 | { 242 | origin.Consumers.Remove(consumer); 243 | target.Consumers.Add(consumer); 244 | Debug.WriteLine(item); 245 | } 246 | } 247 | } 248 | } 249 | 250 | private Channel? GetConsumerChannel(Consumer consumer) 251 | { 252 | if (DataContext is ConfigFile file) 253 | { 254 | return file.Channels.FirstOrDefault(c => c.Consumers.Contains(consumer)); 255 | } 256 | return null; 257 | } 258 | 259 | #endregion 260 | 261 | private void AddDecklinkPort(object sender, RoutedEventArgs e) 262 | { 263 | if (sender is FrameworkElement element && element.DataContext is DecklinkConsumer decklink) 264 | { 265 | decklink.Ports.Add(new()); 266 | } 267 | } 268 | 269 | private void RemDecklinkPort(object sender, RoutedEventArgs e) 270 | { 271 | if (sender is FrameworkElement element && element.DataContext is DecklinkPort port 272 | && element.FindParent() is ItemsControl parent && parent.DataContext is DecklinkConsumer decklink) 273 | { 274 | decklink.Ports.Remove(port); 275 | } 276 | } 277 | 278 | private void AddArtnetFixture(object sender, RoutedEventArgs e) 279 | { 280 | if (sender is FrameworkElement element && element.DataContext is ArtnetConsumer artnet) 281 | { 282 | artnet.Fixtures.Add(new()); 283 | } 284 | } 285 | 286 | private void RemArtnetFixture(object sender, RoutedEventArgs e) 287 | { 288 | if (sender is FrameworkElement element && element.DataContext is ArtnetFixture fixture 289 | && element.FindParent() is ItemsControl parent && parent.DataContext is ArtnetConsumer artnet) 290 | { 291 | artnet.Fixtures.Remove(fixture); 292 | } 293 | } 294 | 295 | private object? _selectedPort; 296 | public object? SelectedPort 297 | { 298 | get 299 | { 300 | return _selectedPort; 301 | } 302 | set 303 | { 304 | if (_selectedPort != value) 305 | { 306 | _selectedPort = value; 307 | OnPropertyChanged(nameof(SelectedPort)); 308 | } 309 | } 310 | } 311 | 312 | public bool ChannelsUpdated 313 | { 314 | get 315 | { 316 | return false; 317 | } 318 | set 319 | { 320 | OnPropertyChanged(nameof(ChannelsUpdated)); 321 | } 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /Windows/ExecutableOptions.xaml: -------------------------------------------------------------------------------- 1 |  24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 38 | 39 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 98 | 101 | 104 | 105 | 106 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 129 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /Windows/ExecutableOptions.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace CasparLauncher; 4 | 5 | public partial class ExecutableOptions : DialogWindow 6 | { 7 | public ObservableCollection ConfigFiles { get; set; } = []; 8 | 9 | public string CustomPath 10 | { 11 | get 12 | { 13 | return Executable.ConfigFile ?? ""; 14 | } 15 | set 16 | { 17 | if (!string.IsNullOrEmpty(value)) 18 | { 19 | Executable.ConfigFile = value; 20 | } 21 | } 22 | } 23 | 24 | private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) 25 | { 26 | OnPropertyChanged(nameof(CustomPath)); 27 | Keyboard.ClearFocus(); 28 | } 29 | 30 | public ExecutableOptions() 31 | { 32 | InitializeComponent(); 33 | DataContextChanged += ExecutableOptions_DataContextChanged; 34 | if (DataContext is Executable executable) 35 | { 36 | FindConfigFiles(); 37 | executable.PropertyChanged += PathChanged; 38 | } 39 | } 40 | 41 | private void PathChanged(object? sender, PropertyChangedEventArgs e) 42 | { 43 | if (e.PropertyName == nameof(Executable.Path)) 44 | { 45 | FindConfigFiles(); 46 | } 47 | } 48 | 49 | private void ExecutableOptions_DataContextChanged(object? sender, DependencyPropertyChangedEventArgs e) 50 | { 51 | if (e.OldValue is Executable old) 52 | { 53 | old.PropertyChanged -= PathChanged; 54 | } 55 | if (e.NewValue is Executable current) 56 | { 57 | current.PropertyChanged += PathChanged; 58 | } 59 | FindConfigFiles(); 60 | } 61 | 62 | private Executable Executable 63 | { 64 | get 65 | { 66 | return DataContext as Executable; 67 | } 68 | } 69 | 70 | private void FindConfigFiles() 71 | { 72 | try 73 | { 74 | if (Executable is null) return; 75 | ConfigFiles.Clear(); 76 | var path = Executable.Path; 77 | if (string.IsNullOrEmpty(path)) path = Environment.CurrentDirectory; 78 | if (!Path.IsPathRooted(path)) path = Path.Combine(Environment.CurrentDirectory, path); 79 | if (Path.GetDirectoryName(path) is string folder) 80 | { 81 | IEnumerable files = Directory.GetFiles(folder) 82 | .Select(f => Path.GetFileName(f).ToLower()) 83 | .Where(c => c.EndsWith(".config")); 84 | foreach (string file in files) ConfigFiles.Add(file); 85 | 86 | if (DataContext is Executable executable && (executable.IsServer || executable.IsScanner)) 87 | { 88 | if (string.IsNullOrEmpty(executable.ConfigFile) 89 | && ConfigFiles.Contains("casparcg.config")) 90 | { 91 | executable.ConfigFile = "casparcg.config"; 92 | } 93 | if (!ConfigFiles.Contains(executable.ConfigFile)) 94 | { 95 | ConfigFiles.Add(executable.ConfigFile); 96 | } 97 | } 98 | } 99 | } 100 | catch { } 101 | } 102 | 103 | private string? ShowSaveDialog(string filter, string? folder = null) 104 | { 105 | SaveFileDialog saveFileDialog = new() 106 | { 107 | Filter = filter, 108 | InitialDirectory = folder 109 | }; 110 | return saveFileDialog.ShowDialog() == true ? saveFileDialog.FileName : null; 111 | } 112 | 113 | private string? ShowOpenDialog(string filter, string? folder = null) 114 | { 115 | OpenFileDialog openFileDialog = new() 116 | { 117 | Multiselect = false, 118 | Filter = filter, 119 | InitialDirectory = folder 120 | }; 121 | return openFileDialog.ShowDialog() == true ? openFileDialog.FileName : null; 122 | } 123 | 124 | private void EditConfig(string path) 125 | { 126 | ConfigFile file = new(); 127 | 128 | if (!string.IsNullOrEmpty(path) && File.Exists(path)) file.File = path; 129 | else file.File = ShowOpenDialog(L.ConfigFileDialogFilterDescription + "|*.config"); 130 | if (file.File is null) return; 131 | 132 | try 133 | { 134 | file.LoadConfigFile(); 135 | } 136 | catch (IOException) 137 | { 138 | MessageBox.Show(L.OpenConfigEditorIOError, L.OpenConfigEditorErrorCaption, MessageBoxButton.OK, MessageBoxImage.Error); 139 | return; 140 | } 141 | catch (Exception) 142 | { 143 | MessageBox.Show(L.OpenConfigEditorFileError, L.OpenConfigEditorErrorCaption, MessageBoxButton.OK, MessageBoxImage.Error); 144 | } 145 | 146 | ConfigEditor configWindow = new(); 147 | configWindow.Owner = this; 148 | configWindow.DataContext = file; 149 | configWindow.ShowDialog(); 150 | } 151 | 152 | private void NewConfig(bool clone = false) 153 | { 154 | if (DataContext is Executable executable) 155 | { 156 | string? folder = Path.GetDirectoryName(executable.Path); 157 | if (folder is not null && ShowSaveDialog(L.ConfigFileDialogFilterDescription + "|*.config", folder) is string filename) 158 | { 159 | try 160 | { 161 | ConfigFile file = new(); 162 | if (clone) 163 | { 164 | try 165 | { 166 | file.File = Path.Combine(folder,executable.ConfigFile); 167 | file.LoadConfigFile(); 168 | file.File = filename; 169 | } 170 | catch (IOException) 171 | { 172 | MessageBox.Show(L.OpenConfigEditorIOError, L.OpenConfigEditorErrorCaption, MessageBoxButton.OK, MessageBoxImage.Error); 173 | } 174 | catch (Exception) 175 | { 176 | MessageBox.Show(L.OpenConfigEditorFileError, L.OpenConfigEditorErrorCaption, MessageBoxButton.OK, MessageBoxImage.Error); 177 | } 178 | } 179 | file.SaveConfigFile(filename); 180 | } 181 | catch (IOException) 182 | { 183 | MessageBox.Show(L.CreateConfigFileIOError, L.CreateConfigFileErrorCaption, MessageBoxButton.OK, MessageBoxImage.Error); 184 | } 185 | 186 | if (File.Exists(filename)) 187 | { 188 | EditConfig(filename); 189 | FindConfigFiles(); 190 | executable.ConfigFile = Path.GetFileName(filename); 191 | } 192 | } 193 | } 194 | } 195 | 196 | protected override void OnClosing(CancelEventArgs e) 197 | { 198 | base.OnClosing(e); 199 | if (Owner != null) 200 | { 201 | Owner.Activate(); 202 | } 203 | } 204 | 205 | private void ChangeLocation_Click(object? sender, RoutedEventArgs e) 206 | { 207 | if (Executable is null) return; 208 | 209 | if (ShowOpenDialog(L.FileDialogFilterDescription + " (*.EXE)|*.exe") is string path) 210 | { 211 | Executable.Path = path; 212 | } 213 | else 214 | { 215 | return; 216 | } 217 | } 218 | 219 | private void Copy(object? sender, ExecutedRoutedEventArgs e) 220 | { 221 | if (sender is DataGrid commands_dg) 222 | { 223 | StringBuilder commandList = new(); 224 | var first = false; 225 | foreach (Command c in commands_dg.SelectedItems) 226 | { 227 | if (!first) first = true; 228 | else commandList.Append('\n'); 229 | commandList.Append(c.Value); 230 | } 231 | Clipboard.SetData(DataFormats.UnicodeText, commandList.ToString()); 232 | } 233 | } 234 | 235 | private void CanCopy(object? sender, CanExecuteRoutedEventArgs e) 236 | { 237 | e.CanExecute = true; 238 | } 239 | 240 | private void CanPaste(object? sender, CanExecuteRoutedEventArgs e) 241 | { 242 | e.CanExecute = true; 243 | e.Handled = true; 244 | } 245 | 246 | private void Paste(object? sender, ExecutedRoutedEventArgs e) 247 | { 248 | try 249 | { 250 | List values = []; 251 | var data = ""; 252 | if (Clipboard.ContainsData(DataFormats.CommaSeparatedValue) || Clipboard.ContainsData(DataFormats.UnicodeText)) 253 | { 254 | if (Clipboard.ContainsData(DataFormats.CommaSeparatedValue)) data = (string)Clipboard.GetData(DataFormats.CommaSeparatedValue); 255 | else if (Clipboard.ContainsData(DataFormats.UnicodeText)) data = (string)Clipboard.GetData(DataFormats.UnicodeText); 256 | else return; 257 | data = data.Replace("\r\n", "\n"); 258 | values.AddRange(data.Split('\n')); 259 | } 260 | foreach (string value in values) if (value != "") Executable?.Commands.Add(new Command() { Value = value }); 261 | } 262 | catch { } 263 | } 264 | 265 | private void ExportCommands_Click(object sender, RoutedEventArgs e) 266 | { 267 | SaveFileDialog saveFileDialog = new() 268 | { 269 | Filter = "Text Files (*.txt)|*.txt|All files (*.*)|*.*", 270 | FileName = Environment.MachineName + "_CasparLauncher_Commands.txt", 271 | Title = L.ExportCommandsDialogTitle 272 | }; 273 | if (saveFileDialog.ShowDialog() == true) 274 | { 275 | try 276 | { 277 | File.WriteAllLines( 278 | saveFileDialog.FileName, 279 | Executable.Commands.Select(x => x.Value).ToArray() 280 | ); 281 | } 282 | catch { } 283 | } 284 | } 285 | private void ImportCommands_Click(object sender, RoutedEventArgs e) 286 | { 287 | OpenFileDialog openFileDialog = new() 288 | { 289 | Title = L.ImportCommandsDialogTitle, 290 | Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*", 291 | FilterIndex = 2, 292 | RestoreDirectory = true 293 | }; 294 | 295 | if (openFileDialog.ShowDialog() == true) 296 | { 297 | string filePath = openFileDialog.FileName; 298 | if (string.IsNullOrEmpty(filePath)) return; 299 | IEnumerable lines = []; 300 | try 301 | { 302 | lines = File.ReadLines(filePath); 303 | } 304 | catch { return; } 305 | Executable.Commands.Clear(); 306 | foreach (string line in lines) 307 | { 308 | Debug.WriteLine(line); 309 | Executable.Commands.Add(new() 310 | { 311 | Value = line 312 | }); 313 | } 314 | } 315 | } 316 | 317 | private void EditConfigButton_Click(object sender, RoutedEventArgs e) 318 | { 319 | if (DataContext is Executable executable) 320 | { 321 | string? folder = Path.GetDirectoryName(executable.Path); 322 | string? file = executable.ConfigFile; 323 | if (folder is not null && file is not null) 324 | { 325 | string? path = Path.Combine(folder, file); 326 | EditConfig(path); 327 | } 328 | } 329 | } 330 | 331 | private void RemoveConfigButton_Click(object sender, RoutedEventArgs e) 332 | { 333 | if (DataContext is Executable executable) 334 | { 335 | string? folder = Path.GetDirectoryName(executable.Path); 336 | string? file = executable.ConfigFile; 337 | if (folder is not null && file is not null) 338 | { 339 | string? path = Path.Combine(folder, file); 340 | if (File.Exists(path)) 341 | { 342 | MessageBoxResult result = MessageBox.Show(string.Format(L.ExecutableConfigWindowDeleteFilePromptMessage,file), L.ExecutableConfigWindowDeleteFilePromptCaption, MessageBoxButton.YesNo, MessageBoxImage.Exclamation); 343 | if (result == MessageBoxResult.Yes) 344 | { 345 | try 346 | { 347 | File.Delete(path); 348 | } 349 | catch (IOException) 350 | { 351 | MessageBox.Show(L.DeleteConfigFileIOError, L.DeleteConfigFileErrorCaption, MessageBoxButton.OK, MessageBoxImage.Error); 352 | } 353 | catch (Exception) 354 | { 355 | MessageBox.Show(L.DeleteConfigFileError, L.DeleteConfigFileErrorCaption, MessageBoxButton.OK, MessageBoxImage.Error); 356 | } 357 | FindConfigFiles(); 358 | } 359 | } 360 | } 361 | else 362 | { 363 | FindConfigFiles(); 364 | } 365 | } 366 | } 367 | 368 | private void NewConfigButton_Click(object sender, RoutedEventArgs e) 369 | { 370 | NewConfig(); 371 | } 372 | 373 | private void CloneConfigButton_Click(object sender, RoutedEventArgs e) 374 | { 375 | NewConfig(true); 376 | } 377 | 378 | private void RemoveExecutable_Click(object sender, RoutedEventArgs e) 379 | { 380 | if (DataContext is Executable ex) 381 | { 382 | bool deleted = App.Launchpad.RemoveExecutable(ex); 383 | if (deleted) Close(); 384 | } 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /Windows/StatusWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher; 2 | 3 | public partial class StatusWindow : AppWindow 4 | { 5 | public StatusWindow() 6 | { 7 | InitializeComponent(); 8 | Loaded += StatusWindow_Initialized; 9 | } 10 | 11 | private void StatusWindow_Initialized(object? sender, EventArgs e) 12 | { 13 | if (State == WindowState.Maximized) WindowState = WindowState.Maximized; 14 | KeyUp += StatusWindow_KeyUp; 15 | KeyDown += StatusWindow_KeyDown; 16 | } 17 | 18 | #region PROPERTIES 19 | 20 | private WindowState? _state; 21 | public WindowState State 22 | { 23 | get 24 | { 25 | _state ??= (WindowState)S.GetOrSetValue("/ui/state", (int)WindowState.Normal); 26 | return _state ?? (int)WindowState.Normal; 27 | } 28 | set 29 | { 30 | if (IsLoaded && _state != value && value != WindowState.Minimized) 31 | { 32 | _state = value; 33 | S.Set("/ui/state", (int)value); 34 | OnPropertyChanged(nameof(State)); 35 | } 36 | } 37 | } 38 | 39 | private int? _posX; 40 | public int PosX 41 | { 42 | get 43 | { 44 | _posX ??= S.GetOrSetValue("/ui/pos-x", 0); 45 | return _posX ?? 0; 46 | } 47 | set 48 | { 49 | if (WindowState == WindowState.Normal && _posX != value) 50 | { 51 | _posX = value; 52 | S.Set("/ui/pos-x", value); 53 | OnPropertyChanged(nameof(PosX)); 54 | } 55 | } 56 | } 57 | 58 | private int? _posY; 59 | public int PosY 60 | { 61 | get 62 | { 63 | _posY ??= S.GetOrSetValue("/ui/pos-y", 0); 64 | return _posY ?? 0; 65 | } 66 | set 67 | { 68 | if (WindowState == WindowState.Normal && _posY != value) 69 | { 70 | _posY = value; 71 | S.Set("/ui/pos-y", value); 72 | OnPropertyChanged(nameof(PosY)); 73 | } 74 | } 75 | } 76 | 77 | private int? _width; 78 | public int WindowWidth 79 | { 80 | get 81 | { 82 | _width ??= S.GetOrSetValue("/ui/width", 700); 83 | return _width ?? 0; 84 | } 85 | set 86 | { 87 | if (WindowState == WindowState.Normal && _width != value) 88 | { 89 | _width = value; 90 | S.Set("/ui/width", value); 91 | OnPropertyChanged(nameof(WindowWidth)); 92 | } 93 | } 94 | } 95 | 96 | private int? _height; 97 | public int WindowHeight 98 | { 99 | get 100 | { 101 | _height ??= S.GetOrSetValue("/ui/height", 400); 102 | return _height ?? 0; 103 | } 104 | set 105 | { 106 | if (WindowState == WindowState.Normal && _height != value) 107 | { 108 | _height = value; 109 | S.Set("/ui/height", value); 110 | OnPropertyChanged(nameof(WindowHeight)); 111 | } 112 | } 113 | } 114 | 115 | #endregion 116 | 117 | #region Event Handlers 118 | 119 | private void ForcedLanguage_Click(object sender, RoutedEventArgs e) 120 | { 121 | if (sender is FrameworkElement element && element.DataContext is Languages language) 122 | { 123 | App.Launchpad.ForcedLanguage = language; 124 | } 125 | } 126 | 127 | private void StatusWindow_KeyDown(object sender, KeyEventArgs e) 128 | { 129 | Launchpad.DisableStart = Keyboard.Modifiers == ModifierKeys.Shift; 130 | } 131 | 132 | private void StatusWindow_KeyUp(object sender, KeyEventArgs e) 133 | { 134 | Launchpad.DisableStart = Keyboard.Modifiers == ModifierKeys.Shift; 135 | IInputElement focused = FocusManager.GetFocusedElement(this); 136 | if (focused is TextBox target 137 | && target.Name == "ConsoleCommandTextBox" 138 | && target.DataContext is Executable ex 139 | && ex.AllowCommands) 140 | { 141 | if (e.Key == Key.Enter) 142 | { 143 | e.Handled = true; 144 | ex.Write(); 145 | } 146 | if (e.Key == Key.Up) 147 | { 148 | ex.PreviousHistoryCommand(); 149 | target.CaretIndex = target.Text.Length; 150 | } 151 | if (e.Key == Key.Down) 152 | { 153 | ex.NextHistoryCommand(); 154 | target.CaretIndex = target.Text.Length; 155 | } 156 | } 157 | } 158 | 159 | private void StartAll(object sender, RoutedEventArgs e) 160 | { 161 | Launchpad.StartAll(); 162 | } 163 | 164 | private void StopAll(object sender, RoutedEventArgs e) 165 | { 166 | Launchpad.StopAll(); 167 | } 168 | 169 | private void RestartAll(object sender, RoutedEventArgs e) 170 | { 171 | Launchpad.RestartAll(); 172 | } 173 | 174 | private static Executable? GetItemExecutable(object item) 175 | { 176 | if (item is FrameworkElement element && element.DataContext is Executable executable) 177 | return executable; 178 | else return null; 179 | } 180 | 181 | private void ExecutableButton_Click(object sender, RoutedEventArgs e) 182 | { 183 | if (GetItemExecutable(sender) is not Executable ex) return; 184 | if (ex.Exists) ex.IsSelected = true; 185 | else ConfigTab.IsSelected = true; 186 | } 187 | 188 | private void StartButton_Click(object sender, RoutedEventArgs e) 189 | { 190 | if (GetItemExecutable(sender) is Executable ex) 191 | { 192 | Launchpad.Start(ex); 193 | } 194 | } 195 | 196 | private void StopButton_Click(object sender, RoutedEventArgs e) 197 | { 198 | if (GetItemExecutable(sender) is Executable ex) 199 | { 200 | Launchpad.Stop(ex); 201 | } 202 | } 203 | 204 | private void RestartButton_Click(object sender, RoutedEventArgs e) 205 | { 206 | if (GetItemExecutable(sender) is Executable ex) 207 | { 208 | Launchpad.Restart(ex); 209 | } 210 | } 211 | 212 | private void RebuildMedia_Click(object sender, RoutedEventArgs e) 213 | { 214 | if (GetItemExecutable(sender) is Executable ex) 215 | { 216 | Launchpad.RebuildMediaDb(ex); 217 | } 218 | } 219 | 220 | private void OpenDiag_Click(object sender, RoutedEventArgs e) 221 | { 222 | if (GetItemExecutable(sender) is Executable ex) 223 | { 224 | Launchpad.OpenDiag(ex); 225 | } 226 | } 227 | 228 | private void OpenGrid_Click(object sender, RoutedEventArgs e) 229 | { 230 | if (GetItemExecutable(sender) is Executable ex) 231 | { 232 | Launchpad.OpenGrid(ex); 233 | } 234 | } 235 | 236 | private void ConsoleOutputDataGrid_ScrollChanged(object sender, ScrollChangedEventArgs e) 237 | { 238 | if (e.ExtentHeightChange > 0 && e.OriginalSource is ScrollViewer view) 239 | { 240 | if (view.ScrollableHeight <= view.VerticalOffset + e.ExtentHeightChange + 20) 241 | { 242 | view.ScrollToVerticalOffset(view.ScrollableHeight); 243 | } 244 | } 245 | } 246 | 247 | private void AddExecutableButton_Click(object sender, RoutedEventArgs e) 248 | { 249 | OpenExecutableConfig(App.Launchpad.AddExecutable()); 250 | } 251 | 252 | private void ExecutableConfig_Click(object sender, RoutedEventArgs e) 253 | { 254 | if (GetItemExecutable(sender) is not Executable ex) return; 255 | OpenExecutableConfig(ex); 256 | } 257 | 258 | private void OpenExecutableConfig(Executable executable) 259 | { 260 | ExecutableOptions executableOptions = new() 261 | { 262 | DataContext = executable, 263 | Owner = this 264 | }; 265 | executableOptions.ShowDialog(); 266 | } 267 | 268 | #endregion 269 | 270 | private void UpdateDataGridColumnWidth(DataGrid target) 271 | { 272 | target.Columns[0].Width = 0; 273 | target.UpdateLayout(); 274 | target.Columns[0].Width = App.Launchpad.CropConsole ? new(1, DataGridLengthUnitType.Star) : new(1, DataGridLengthUnitType.Auto); 275 | } 276 | 277 | private void ConsoleOutputDataGrid_Initialized(object sender, EventArgs e) 278 | { 279 | if (sender is DataGrid target) UpdateDataGridColumnWidth(target); 280 | } 281 | 282 | private void ConsoleOutputDataGrid_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) 283 | { 284 | if (sender is DataGrid target) UpdateDataGridColumnWidth(target); 285 | } 286 | 287 | private void CropConsoleLines_Checked(object sender, RoutedEventArgs e) 288 | { 289 | if (VisualTreeHelpers.FindChild(ExecutablesTabbedPanel) is DataGrid currentConsole) 290 | { 291 | currentConsole.Visibility = Visibility.Collapsed; 292 | ExecutablesTabbedPanel.UpdateLayout(); 293 | currentConsole.Visibility = Visibility.Visible; 294 | UpdateDataGridColumnWidth(currentConsole); 295 | } 296 | } 297 | 298 | private void ReportIssue_Click(object sender, RoutedEventArgs e) 299 | { 300 | LinkHelper.Open("https://github.com/rrebuffo/CasparLauncher/issues"); 301 | e.Handled = true; 302 | } 303 | 304 | private void CasparCGProject_Click(object sender, RoutedEventArgs e) 305 | { 306 | LinkHelper.Open("https://casparcg.com/"); 307 | e.Handled = true; 308 | } 309 | 310 | private void About_Click(object sender, RoutedEventArgs e) 311 | { 312 | AboutWindow about = new(); 313 | about.Owner = this; 314 | about.ShowDialog(); 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /Windows/TrayMenu.xaml: -------------------------------------------------------------------------------- 1 |  14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /Windows/TrayMenu.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace CasparLauncher; 2 | 3 | public partial class TrayMenu : Window 4 | { 5 | public TrayMenu() 6 | { 7 | InitializeComponent(); 8 | KeyDown += TrayMenu_KeyChange; 9 | KeyUp += TrayMenu_KeyChange; 10 | } 11 | 12 | private void TrayMenu_KeyChange(object sender, KeyEventArgs e) 13 | { 14 | Launchpad.DisableStart = Keyboard.Modifiers == ModifierKeys.Shift; 15 | } 16 | 17 | private void StartAll(object? sender, RoutedEventArgs e) 18 | { 19 | OnStart(null); 20 | } 21 | 22 | private void StopAll(object? sender, RoutedEventArgs e) 23 | { 24 | OnStop(null); 25 | } 26 | 27 | private void RestartAll(object? sender, RoutedEventArgs e) 28 | { 29 | OnRestart(null); 30 | } 31 | 32 | private Executable? GetItemExecutable(object? item) 33 | { 34 | if (item is FrameworkElement element && element.DataContext is Executable ex) return ex; 35 | else return null; 36 | } 37 | 38 | private void Start_item_Click(object? sender, RoutedEventArgs e) 39 | { 40 | if (GetItemExecutable(sender) is Executable ex) 41 | OnStart(ex); 42 | } 43 | 44 | private void Stop_item_Click(object? sender, RoutedEventArgs e) 45 | { 46 | if (GetItemExecutable(sender) is Executable ex) 47 | OnStop(ex); 48 | } 49 | 50 | private void Restart_item_Click(object? sender, RoutedEventArgs e) 51 | { 52 | if (GetItemExecutable(sender) is Executable ex) 53 | OnRestart(ex); 54 | } 55 | 56 | private void Config_item_Click(object? sender, RoutedEventArgs e) 57 | { 58 | if (GetItemExecutable(sender) is Executable ex) 59 | OnConfig(ex); 60 | } 61 | 62 | private void Rebuild_item_Click(object? sender, RoutedEventArgs e) 63 | { 64 | if (GetItemExecutable(sender) is Executable ex) 65 | OnAction(ex, CasparAction.Rebuild); 66 | } 67 | 68 | private void Diag_item_Click(object? sender, RoutedEventArgs e) 69 | { 70 | if (GetItemExecutable(sender) is Executable ex) 71 | OnAction(ex, CasparAction.Diag); 72 | } 73 | 74 | private void Grid_item_Click(object? sender, RoutedEventArgs e) 75 | { 76 | 77 | if (GetItemExecutable(sender) is Executable ex) 78 | OnAction(ex, CasparAction.Grid); 79 | } 80 | 81 | private void TrayMenu_ExitItem_Click(object? sender, RoutedEventArgs e) 82 | { 83 | OnExit(); 84 | } 85 | 86 | public event EventHandler? ExitExecuted; 87 | protected virtual void OnExit() { ExitExecuted?.Invoke(this, new()); } 88 | 89 | public event EventHandler? StartExecuted; 90 | protected virtual void OnStart(Executable? executable) { StartExecuted?.Invoke(this, executable); } 91 | 92 | public event EventHandler? StopExecuted; 93 | protected virtual void OnStop(Executable? executable) { StopExecuted?.Invoke(this, executable); } 94 | 95 | public event EventHandler? RestartExecuted; 96 | protected virtual void OnRestart(Executable? executable) { RestartExecuted?.Invoke(this, executable); } 97 | 98 | public event EventHandler? ConfigExecuted; 99 | protected virtual void OnConfig(Executable executable) { ConfigExecuted?.Invoke(this, executable); } 100 | 101 | public event EventHandler<(Executable, CasparAction)>? ActionExecuted; 102 | protected virtual void OnAction(Executable executable, CasparAction action) { ActionExecuted?.Invoke(this, (executable, action)); } 103 | } 104 | --------------------------------------------------------------------------------