Added in Windows toast notifications

This commit is contained in:
minster586
2026-09-03 00:20:33 -04:00
parent 34c80bda7b
commit 7b67c03270
6 changed files with 426 additions and 31 deletions
+345
View File
@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@@ -12,6 +14,10 @@ using System.Threading;
using System.IO;
using System.Resources;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Security;
namespace RadioDJViewer
{
@@ -294,6 +300,28 @@ namespace RadioDJViewer
{
_currentTrackKey = newKey;
ShowTemporaryStatus("Song Change Detected", 2000);
bool toastEnabled = IsToastEnabledForActiveProfile();
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Track changed. Enabled={toastEnabled}, Profile={loadedProfile?.Name ?? "(null)"}");
#endif
if (toastEnabled)
{
try
{
string outputImagePath = GetProfileOutputImagePath();
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Trigger with title='{marqueeTextTitle}', artist='{marqueeTextArtist}', imagePath='{outputImagePath}'");
#endif
ShowTrackChangeToast(marqueeTextTitle, marqueeTextArtist, outputImagePath);
}
catch (Exception ex)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Trigger error: {ex}");
MessageBox.Show($"Toast trigger failed: {ex.Message}", "Toast Debug", MessageBoxButtons.OK, MessageBoxIcon.Warning);
#endif
}
}
}
}
catch { }
@@ -443,6 +471,7 @@ namespace RadioDJViewer
{
var resized = new Bitmap(fallbackImg, pictureBox1.Size);
pictureBox1.Image = resized;
currentSongImagePath = defaultImagePath;
// Save fallback image to output folder as PNG
if (!string.IsNullOrEmpty(outputFolderPath) && !string.IsNullOrEmpty(outputImageName))
{
@@ -453,6 +482,7 @@ namespace RadioDJViewer
else
{
pictureBox1.Image = null;
currentSongImagePath = defaultImagePath;
}
return;
}
@@ -462,6 +492,7 @@ namespace RadioDJViewer
{
var resized = new Bitmap(img, pictureBox1.Size);
pictureBox1.Image = resized;
currentSongImagePath = imagePath;
// Save to output folder as PNG
if (!string.IsNullOrEmpty(outputFolderPath) && !string.IsNullOrEmpty(outputImageName))
{
@@ -478,6 +509,7 @@ namespace RadioDJViewer
{
var resized = new Bitmap(fallbackImg, pictureBox1.Size);
pictureBox1.Image = resized;
currentSongImagePath = defaultImagePath;
if (!string.IsNullOrEmpty(outputFolderPath) && !string.IsNullOrEmpty(outputImageName))
{
string destPath = Path.Combine(outputFolderPath, Path.ChangeExtension(outputImageName, ".png"));
@@ -487,6 +519,7 @@ namespace RadioDJViewer
else
{
pictureBox1.Image = null;
currentSongImagePath = defaultImagePath;
}
}
}
@@ -970,5 +1003,317 @@ namespace RadioDJViewer
int secs = seconds % 60;
return $"{minutes}:{secs:D2}";
}
private bool IsToastEnabledForActiveProfile()
{
return loadedProfile != null && loadedProfile.ToastNotificationsEnabled;
}
private string GetProfileOutputImagePath()
{
if (string.IsNullOrWhiteSpace(outputFolderPath) || string.IsNullOrWhiteSpace(loadedProfile?.OutputImageName))
return string.Empty;
try
{
string imageName = Path.ChangeExtension(loadedProfile.OutputImageName, ".png");
string fullPath = Path.GetFullPath(Path.Combine(outputFolderPath, imageName));
return fullPath;
}
catch
{
return string.Empty;
}
}
private void ShowTrackChangeToast(string title, string artist, string outputImagePath)
{
string safeTitle = string.IsNullOrWhiteSpace(title) ? "No title" : title;
string safeArtist = string.IsNullOrWhiteSpace(artist) ? "No artist" : artist;
DateTime now = DateTime.Now;
string attribution = $"Date: {now:MM/dd/yyyy} | Time: {now:hh:mm tt}";
try
{
ToastNotificationService.ShowTrackChangeToast(safeTitle, safeArtist, attribution, outputImagePath);
}
catch (Exception ex)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] ShowTrackChangeToast failed: {ex}");
MessageBox.Show($"Toast display failed: {ex.Message}", "Toast Debug", MessageBoxButtons.OK, MessageBoxIcon.Warning);
#endif
}
}
}
internal static class ToastNotificationService
{
private const string AppId = "RadioDJViewer.App";
private static bool _initialized;
public static void ShowTrackChangeToast(string trackTitle, string artistName, string attributionLine, string outputImagePath)
{
try
{
EnsureInitialized();
string toastXml = BuildToastXml(trackTitle, artistName, attributionLine, outputImagePath);
var xmlDocumentType = Type.GetType("Windows.Data.Xml.Dom.XmlDocument, Windows, ContentType=WindowsRuntime");
var toastType = Type.GetType("Windows.UI.Notifications.ToastNotification, Windows, ContentType=WindowsRuntime");
var managerType = Type.GetType("Windows.UI.Notifications.ToastNotificationManager, Windows, ContentType=WindowsRuntime");
if (xmlDocumentType == null || toastType == null || managerType == null)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine("[Toast] Windows Runtime toast types are unavailable.");
#endif
return;
}
object xmlDocument = Activator.CreateInstance(xmlDocumentType);
MethodInfo loadXmlMethod = xmlDocumentType.GetMethod(
"LoadXml",
BindingFlags.Public | BindingFlags.Instance,
null,
new[] { typeof(string) },
null);
if (loadXmlMethod == null)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine("[Toast] LoadXml(string) method not found.");
#endif
return;
}
loadXmlMethod.Invoke(xmlDocument, new object[] { toastXml });
object toast = Activator.CreateInstance(toastType, new[] { xmlDocument });
MethodInfo createNotifierMethod = managerType.GetMethod(
"CreateToastNotifier",
BindingFlags.Public | BindingFlags.Static,
null,
new[] { typeof(string) },
null);
if (createNotifierMethod == null)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine("[Toast] CreateToastNotifier(string) method not found.");
#endif
return;
}
object notifier = createNotifierMethod.Invoke(null, new object[] { AppId });
if (notifier == null)
return;
MethodInfo showMethod = notifier.GetType().GetMethod(
"Show",
BindingFlags.Public | BindingFlags.Instance,
null,
new[] { toastType },
null);
if (showMethod == null)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine("[Toast] Show(ToastNotification) method not found.");
#endif
return;
}
showMethod.Invoke(notifier, new[] { toast });
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Toast shown for AppId='{AppId}'.");
#endif
}
catch (Exception ex)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Service error: {ex}");
MessageBox.Show($"Toast service error: {ex.Message}", "Toast Debug", MessageBoxButtons.OK, MessageBoxIcon.Warning);
#endif
}
}
private static void EnsureInitialized()
{
if (_initialized)
return;
DesktopToastRegistrar.EnsureStartMenuShortcut(AppId);
_initialized = true;
}
private static string BuildToastXml(string trackTitle, string artistName, string attributionLine, string outputImagePath)
{
string safeTitle = SecurityElement.Escape(string.IsNullOrWhiteSpace(trackTitle) ? "No title" : trackTitle);
string safeArtist = SecurityElement.Escape(string.IsNullOrWhiteSpace(artistName) ? "No artist" : artistName);
string safeAttribution = SecurityElement.Escape(attributionLine ?? string.Empty);
string imageUri = string.Empty;
if (!string.IsNullOrWhiteSpace(outputImagePath))
{
try
{
string fullPath = Path.GetFullPath(outputImagePath);
if (File.Exists(fullPath))
{
var localUri = new Uri(fullPath);
imageUri = localUri.AbsoluteUri;
}
#if DEBUG
else
{
System.Diagnostics.Debug.WriteLine($"[Toast] Output image file missing: '{fullPath}'");
}
#endif
}
catch (Exception ex)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Invalid output image path '{outputImagePath}': {ex.Message}");
#endif
}
}
string safeImage = SecurityElement.Escape(imageUri);
string imageNode = string.IsNullOrWhiteSpace(safeImage)
? string.Empty
: $"<image placement='appLogoOverride' hint-crop='none' src='{safeImage}'/>";
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Image URI used: '{imageUri}'");
#endif
return $"<toast><visual><binding template='ToastGeneric'>{imageNode}<text>{safeTitle}</text><text>{safeArtist}</text><text>{safeAttribution}</text></binding></visual></toast>";
}
}
internal static class DesktopToastRegistrar
{
[ComImport]
[Guid("00021401-0000-0000-C000-000000000046")]
private class CShellLink { }
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214F9-0000-0000-C000-000000000046")]
private interface IShellLinkW
{
void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cch, out WIN32_FIND_DATAW pfd, uint fFlags);
void GetIDList(out IntPtr ppidl);
void SetIDList(IntPtr pidl);
void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cch);
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cch);
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cch);
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
void GetHotkey(out short pwHotkey);
void SetHotkey(short wHotkey);
void GetShowCmd(out int piShowCmd);
void SetShowCmd(int iShowCmd);
void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cch, out int piIcon);
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, uint dwReserved);
void Resolve(IntPtr hwnd, uint fFlags);
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct WIN32_FIND_DATAW
{
public uint dwFileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
public uint nFileSizeHigh;
public uint nFileSizeLow;
public uint dwReserved0;
public uint dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string cFileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string cAlternateFileName;
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")]
private interface IPropertyStore
{
uint GetCount(out uint cProps);
uint GetAt(uint iProp, out PROPERTYKEY pkey);
uint GetValue(ref PROPERTYKEY key, out PROPVARIANT pv);
uint SetValue(ref PROPERTYKEY key, ref PROPVARIANT pv);
uint Commit();
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
private struct PROPERTYKEY
{
public Guid fmtid;
public uint pid;
}
[StructLayout(LayoutKind.Explicit)]
private struct PROPVARIANT
{
[FieldOffset(0)]
public ushort vt;
[FieldOffset(8)]
public IntPtr pointerValue;
public static PROPVARIANT FromString(string value)
{
var pv = new PROPVARIANT();
pv.vt = 31;
pv.pointerValue = Marshal.StringToCoTaskMemUni(value);
return pv;
}
}
private static PROPERTYKEY PKEY_AppUserModel_ID = new PROPERTYKEY
{
fmtid = new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"),
pid = 5
};
public static void EnsureStartMenuShortcut(string appId)
{
try
{
string shortcutPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.StartMenu),
"Programs",
"RadioDJ Viewer.lnk");
string exePath = Assembly.GetEntryAssembly()?.Location ?? Application.ExecutablePath;
string workDir = Path.GetDirectoryName(exePath) ?? AppDomain.CurrentDomain.BaseDirectory;
string iconPath = Path.Combine(workDir, "icon.ico");
if (!File.Exists(iconPath))
iconPath = exePath;
var shellLink = (IShellLinkW)new CShellLink();
shellLink.SetPath(exePath);
shellLink.SetWorkingDirectory(workDir);
shellLink.SetIconLocation(iconPath, 0);
var propertyStore = (IPropertyStore)shellLink;
var value = PROPVARIANT.FromString(appId);
propertyStore.SetValue(ref PKEY_AppUserModel_ID, ref value);
propertyStore.Commit();
((IPersistFile)shellLink).Save(shortcutPath, true);
Marshal.FreeCoTaskMem(value.pointerValue);
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Shortcut registered: '{shortcutPath}', AppId='{appId}'");
#endif
}
catch (Exception ex)
{
#if DEBUG
System.Diagnostics.Debug.WriteLine($"[Toast] Shortcut registration error: {ex}");
MessageBox.Show($"Toast registration failed: {ex.Message}", "Toast Debug", MessageBoxButtons.OK, MessageBoxIcon.Warning);
#endif
}
}
}
}
+1
View File
@@ -25,6 +25,7 @@ namespace RadioDJViewer
public int WebServerPort { get; set; }
// Clock visibility setting
public bool ClockVisibilityEnabled { get; set; }
public bool ToastNotificationsEnabled { get; set; }
}
public static class ProfileStorage
+10 -3
View File
@@ -1,19 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace RadioDJViewer
{
internal static class Program
{
[DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern int SetCurrentProcessExplicitAppUserModelID(string AppID);
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
try
{
SetCurrentProcessExplicitAppUserModelID("RadioDJViewer.App");
}
catch { }
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Main());
+2 -2
View File
@@ -29,5 +29,5 @@ using System.Runtime.InteropServices;
// Build Number
// Revision
//
[assembly: AssemblyVersion("1.10.0")]
[assembly: AssemblyFileVersion("1.10.0")]
[assembly: AssemblyVersion("1.14.0")]
[assembly: AssemblyFileVersion("1.14.0")]
+65 -25
View File
@@ -56,7 +56,12 @@
this.label7 = new System.Windows.Forms.Label();
this.textBox7 = new System.Windows.Forms.TextBox();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.toast_box_group_box = new System.Windows.Forms.GroupBox();
this.toast_check_box_enable = new System.Windows.Forms.CheckBox();
this.toast_label = new System.Windows.Forms.Label();
this.web_server_settings_groupbox = new System.Windows.Forms.GroupBox();
this.clock_visibility_checkBox = new System.Windows.Forms.CheckBox();
this.clock_label = new System.Windows.Forms.Label();
this.web_server_port_textbox = new System.Windows.Forms.TextBox();
this.web_server_port_label = new System.Windows.Forms.Label();
this.web_server_label = new System.Windows.Forms.Label();
@@ -69,11 +74,10 @@
this.label9 = new System.Windows.Forms.Label();
this.label8 = new System.Windows.Forms.Label();
this.label12 = new System.Windows.Forms.Label();
this.clock_label = new System.Windows.Forms.Label();
this.clock_visibility_checkBox = new System.Windows.Forms.CheckBox();
this.groupBox1.SuspendLayout();
this.groupBox3.SuspendLayout();
this.groupBox2.SuspendLayout();
this.toast_box_group_box.SuspendLayout();
this.web_server_settings_groupbox.SuspendLayout();
this.SuspendLayout();
//
@@ -106,7 +110,7 @@
//
// ok_button_master
//
this.ok_button_master.Location = new System.Drawing.Point(65, 694);
this.ok_button_master.Location = new System.Drawing.Point(54, 729);
this.ok_button_master.Name = "ok_button_master";
this.ok_button_master.Size = new System.Drawing.Size(157, 23);
this.ok_button_master.TabIndex = 1;
@@ -115,7 +119,7 @@
//
// button2
//
this.button2.Location = new System.Drawing.Point(305, 694);
this.button2.Location = new System.Drawing.Point(302, 729);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(157, 23);
this.button2.TabIndex = 2;
@@ -304,6 +308,7 @@
//
// groupBox2
//
this.groupBox2.Controls.Add(this.toast_box_group_box);
this.groupBox2.Controls.Add(this.web_server_settings_groupbox);
this.groupBox2.Controls.Add(this.label13);
this.groupBox2.Controls.Add(this.comboBoxScrollSpeed);
@@ -331,11 +336,41 @@
this.groupBox2.Controls.Add(this.comboBox1);
this.groupBox2.Location = new System.Drawing.Point(12, 100);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(519, 575);
this.groupBox2.Size = new System.Drawing.Size(519, 623);
this.groupBox2.TabIndex = 3;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "Profiles";
//
// toast_box_group_box
//
this.toast_box_group_box.Controls.Add(this.toast_check_box_enable);
this.toast_box_group_box.Controls.Add(this.toast_label);
this.toast_box_group_box.Location = new System.Drawing.Point(133, 575);
this.toast_box_group_box.Name = "toast_box_group_box";
this.toast_box_group_box.Size = new System.Drawing.Size(248, 42);
this.toast_box_group_box.TabIndex = 4;
this.toast_box_group_box.TabStop = false;
this.toast_box_group_box.Text = "Windows Toast";
//
// toast_check_box_enable
//
this.toast_check_box_enable.AutoSize = true;
this.toast_check_box_enable.Location = new System.Drawing.Point(130, 17);
this.toast_check_box_enable.Name = "toast_check_box_enable";
this.toast_check_box_enable.Size = new System.Drawing.Size(59, 17);
this.toast_check_box_enable.TabIndex = 1;
this.toast_check_box_enable.Text = "Enable";
this.toast_check_box_enable.UseVisualStyleBackColor = true;
//
// toast_label
//
this.toast_label.AutoSize = true;
this.toast_label.Location = new System.Drawing.Point(40, 20);
this.toast_label.Name = "toast_label";
this.toast_label.Size = new System.Drawing.Size(73, 13);
this.toast_label.TabIndex = 0;
this.toast_label.Text = "Toast Control:";
//
// web_server_settings_groupbox
//
this.web_server_settings_groupbox.Controls.Add(this.clock_visibility_checkBox);
@@ -351,6 +386,25 @@
this.web_server_settings_groupbox.TabStop = false;
this.web_server_settings_groupbox.Text = "Web Server Settings";
//
// clock_visibility_checkBox
//
this.clock_visibility_checkBox.AutoSize = true;
this.clock_visibility_checkBox.Location = new System.Drawing.Point(119, 55);
this.clock_visibility_checkBox.Name = "clock_visibility_checkBox";
this.clock_visibility_checkBox.Size = new System.Drawing.Size(65, 17);
this.clock_visibility_checkBox.TabIndex = 38;
this.clock_visibility_checkBox.Text = "Enabled";
this.clock_visibility_checkBox.UseVisualStyleBackColor = true;
//
// clock_label
//
this.clock_label.AutoSize = true;
this.clock_label.Location = new System.Drawing.Point(44, 56);
this.clock_label.Name = "clock_label";
this.clock_label.Size = new System.Drawing.Size(76, 13);
this.clock_label.TabIndex = 37;
this.clock_label.Text = "Clock Visibility:";
//
// web_server_port_textbox
//
this.web_server_port_textbox.Location = new System.Drawing.Point(246, 19);
@@ -459,30 +513,11 @@
this.label12.TabIndex = 27;
this.label12.Text = "Separating character format";
//
// clock_label
//
this.clock_label.AutoSize = true;
this.clock_label.Location = new System.Drawing.Point(44, 56);
this.clock_label.Name = "clock_label";
this.clock_label.Size = new System.Drawing.Size(76, 13);
this.clock_label.TabIndex = 37;
this.clock_label.Text = "Clock Visibility:";
//
// clock_visibility_checkBox
//
this.clock_visibility_checkBox.AutoSize = true;
this.clock_visibility_checkBox.Location = new System.Drawing.Point(119, 55);
this.clock_visibility_checkBox.Name = "clock_visibility_checkBox";
this.clock_visibility_checkBox.Size = new System.Drawing.Size(65, 17);
this.clock_visibility_checkBox.TabIndex = 38;
this.clock_visibility_checkBox.Text = "Enabled";
this.clock_visibility_checkBox.UseVisualStyleBackColor = true;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(572, 729);
this.ClientSize = new System.Drawing.Size(553, 764);
this.Controls.Add(this.groupBox2);
this.Controls.Add(this.button2);
this.Controls.Add(this.ok_button_master);
@@ -496,6 +531,8 @@
this.groupBox3.PerformLayout();
this.groupBox2.ResumeLayout(false);
this.groupBox2.PerformLayout();
this.toast_box_group_box.ResumeLayout(false);
this.toast_box_group_box.PerformLayout();
this.web_server_settings_groupbox.ResumeLayout(false);
this.web_server_settings_groupbox.PerformLayout();
this.ResumeLayout(false);
@@ -548,5 +585,8 @@
private System.Windows.Forms.Label web_server_port_label;
private System.Windows.Forms.CheckBox clock_visibility_checkBox;
private System.Windows.Forms.Label clock_label;
private System.Windows.Forms.GroupBox toast_box_group_box;
private System.Windows.Forms.CheckBox toast_check_box_enable;
private System.Windows.Forms.Label toast_label;
}
}
+3 -1
View File
@@ -85,6 +85,7 @@ namespace RadioDJViewer
web_server_port_textbox.Text = (profile.WebServerPort > 0 ? profile.WebServerPort : 8080).ToString();
// Clock visibility setting
clock_visibility_checkBox.Checked = profile.ClockVisibilityEnabled;
toast_check_box_enable.Checked = profile.ToastNotificationsEnabled;
}
catch { }
}
@@ -143,7 +144,8 @@ namespace RadioDJViewer
WebServerEnabled = web_server_checkbox.Checked,
WebServerPort = int.TryParse(web_server_port_textbox.Text, out int wp) ? wp : 8080,
// Clock visibility setting
ClockVisibilityEnabled = clock_visibility_checkBox.Checked
ClockVisibilityEnabled = clock_visibility_checkBox.Checked,
ToastNotificationsEnabled = toast_check_box_enable.Checked
};
ProfileStorage.SaveProfile(profile);
// Refresh profile list