diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml index 5e0c2e6e1..2a66032aa 100644 --- a/.github/workflows/artifacts.yml +++ b/.github/workflows/artifacts.yml @@ -179,6 +179,11 @@ jobs: # Build everything dotnet publish ErsatzTV/ErsatzTV.csproj --framework net6.0 --runtime "${{ matrix.target }}" -c Release -o "$release_name" -p:InformationalVersion="${{ inputs.release_version }}-${{ matrix.target }}" -p:EnableCompressionInSingleFile=true -p:DebugType=Embedded -p:PublishSingleFile=true --self-contained true + # Build Windows launcher + if [ "${{ matrix.kind }}" == "windows" ]; then + dotnet publish ErsatzTV-Windows/ErsatzTV-Windows.csproj --framework net6.0-windows --runtime "${{ matrix.target }}" -c Release -o "$release_name" -p:InformationalVersion="${{ inputs.release_version }}-${{ matrix.target }}" -p:EnableCompressionInSingleFile=true -p:DebugType=Embedded -p:PublishSingleFile=true --self-contained true + fi + # Pack files if [ "${{ matrix.kind }}" == "windows" ]; then 7z a -tzip "${release_name}.zip" "./${release_name}/*" diff --git a/CHANGELOG.md b/CHANGELOG.md index e8fa567b2..8ea9df9e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Jellyfin (web) and TiviMate (Android) were specifically tested ### Added -- Hide console window on macOS; tray menu can be used to access UI, logs and to stop the app +- Hide console window on macOS and Windows; tray menu can be used to access UI, logs and to stop the app - Also write logs to text files in the `logs` config subfolder - Add `added_date` to search index - This requires rebuilding the search index and search results may be empty or incomplete until the rebuild is complete diff --git a/ErsatzTV-Windows/ErsatzTV-Windows.csproj b/ErsatzTV-Windows/ErsatzTV-Windows.csproj new file mode 100644 index 000000000..db3d6b6f6 --- /dev/null +++ b/ErsatzTV-Windows/ErsatzTV-Windows.csproj @@ -0,0 +1,33 @@ + + + + WinExe + net6.0-windows + ErsatzTV_Windows + enable + true + enable + Ersatztv.ico + + + + + Always + + + + + + + + + + + + + + Always + + + + \ No newline at end of file diff --git a/ErsatzTV-Windows/Ersatztv.ico b/ErsatzTV-Windows/Ersatztv.ico new file mode 100644 index 000000000..92c5a0383 Binary files /dev/null and b/ErsatzTV-Windows/Ersatztv.ico differ diff --git a/ErsatzTV-Windows/Program.cs b/ErsatzTV-Windows/Program.cs new file mode 100644 index 000000000..870cb8eb3 --- /dev/null +++ b/ErsatzTV-Windows/Program.cs @@ -0,0 +1,14 @@ +namespace ErsatzTV_Windows; + +public static class Program +{ + /// + /// The main entry point for the application. + /// + [STAThread] + public static void Main() + { + ApplicationConfiguration.Initialize(); + Application.Run(new TrayApplicationContext()); + } +} diff --git a/ErsatzTV-Windows/TrayApplicationContext.cs b/ErsatzTV-Windows/TrayApplicationContext.cs new file mode 100644 index 000000000..18a4e3fa6 --- /dev/null +++ b/ErsatzTV-Windows/TrayApplicationContext.cs @@ -0,0 +1,77 @@ +using ErsatzTV.Core; +using System.Diagnostics; +using Asmichi.ProcessManagement; +using System.Reflection; + +namespace ErsatzTV_Windows; + +public class TrayApplicationContext : ApplicationContext +{ + private readonly NotifyIcon _trayIcon; + private readonly IChildProcess? _childProcess; + + public TrayApplicationContext() + { + _trayIcon = new NotifyIcon + { + Icon = new Icon("./Ersatztv.ico"), + ContextMenuStrip = new ContextMenuStrip(), + Visible = true + }; + + AddMenuItem("Launch Web UI", LaunchWebUI); + AddMenuItem("Show Logs", ShowLogs); + _trayIcon.ContextMenuStrip.Items.Add(new ToolStripSeparator()); + AddMenuItem("Exit", Exit); + + string folder = AppContext.BaseDirectory; + string exe = Path.Combine(folder, "ErsatzTV.exe"); + + if (File.Exists(exe)) + { + var si = new ChildProcessStartInfo(exe); + _childProcess = ChildProcess.Start(si); + } + } + + private void AddMenuItem(string name, EventHandler action) + { + var item = new ToolStripMenuItem(name); + item.Click += action; + _trayIcon.ContextMenuStrip.Items.Add(item); + } + + private void LaunchWebUI(object? sender, EventArgs e) + { + var process = new Process(); + process.StartInfo.UseShellExecute = true; + process.StartInfo.FileName = "http://localhost:8409"; + process.Start(); + } + + private void ShowLogs(object? sender, EventArgs e) + { + if (!Directory.Exists(FileSystemLayout.LogsFolder)) + { + Directory.CreateDirectory(FileSystemLayout.LogsFolder); + } + + var process = new Process(); + process.StartInfo.UseShellExecute = true; + process.StartInfo.FileName = FileSystemLayout.LogsFolder; + process.Start(); + } + + protected override void Dispose(bool disposing) + { + _childProcess?.Dispose(); + base.Dispose(disposing); + } + + private void Exit(object? sender, EventArgs e) + { + // Hide tray icon, otherwise it will remain shown until user mouses over it + _trayIcon.Visible = false; + Application.Exit(); + } +} diff --git a/ErsatzTV.Core/FileSystemLayout.cs b/ErsatzTV.Core/FileSystemLayout.cs index a6f57870c..96b7ca67a 100644 --- a/ErsatzTV.Core/FileSystemLayout.cs +++ b/ErsatzTV.Core/FileSystemLayout.cs @@ -18,10 +18,12 @@ namespace ErsatzTV.Core Environment.SpecialFolderOption.Create), "etv-transcode"); + public static readonly string LogsFolder = Path.Combine(AppDataFolder, "logs"); + public static readonly string DatabasePath = Path.Combine(AppDataFolder, "ersatztv.sqlite3"); public static readonly string LogDatabasePath = Path.Combine(AppDataFolder, "logs.sqlite3"); - public static readonly string LogFilePath = Path.Combine(AppDataFolder, "logs", "ersatztv.log"); + public static readonly string LogFilePath = Path.Combine(LogsFolder, "ersatztv.log"); public static readonly string LegacyImageCacheFolder = Path.Combine(AppDataFolder, "cache", "images"); public static readonly string ResourcesCacheFolder = Path.Combine(AppDataFolder, "cache", "resources"); diff --git a/ErsatzTV.sln b/ErsatzTV.sln index e9e62455a..a23602365 100644 --- a/ErsatzTV.sln +++ b/ErsatzTV.sln @@ -10,6 +10,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ErsatzTV.Application", "Ers EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ErsatzTV.Core.Tests", "ErsatzTV.Core.Tests\ErsatzTV.Core.Tests.csproj", "{CE7F1ACD-F286-4761-A7BC-A541A1E25C86}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ErsatzTV-Windows", "ErsatzTV-Windows\ErsatzTV-Windows.csproj", "{805DB439-1CC1-44E1-A39F-F472635CF94E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -36,6 +38,10 @@ Global {CE7F1ACD-F286-4761-A7BC-A541A1E25C86}.Debug|Any CPU.Build.0 = Debug|Any CPU {CE7F1ACD-F286-4761-A7BC-A541A1E25C86}.Release|Any CPU.ActiveCfg = Release|Any CPU {CE7F1ACD-F286-4761-A7BC-A541A1E25C86}.Release|Any CPU.Build.0 = Release|Any CPU + {805DB439-1CC1-44E1-A39F-F472635CF94E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {805DB439-1CC1-44E1-A39F-F472635CF94E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {805DB439-1CC1-44E1-A39F-F472635CF94E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {805DB439-1CC1-44E1-A39F-F472635CF94E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution EndGlobalSection diff --git a/artwork/Ersatztv.ico b/artwork/Ersatztv.ico new file mode 100644 index 000000000..92c5a0383 Binary files /dev/null and b/artwork/Ersatztv.ico differ