SHA256
190 lines
8.5 KiB
C#
190 lines
8.5 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using MelonLoader;
|
|
|
|
namespace ADOFAIRenderer.Util
|
|
{
|
|
public static class OSNotification
|
|
{
|
|
/// <summary>
|
|
/// Sends a native desktop notification on Windows, macOS, or Linux.
|
|
/// If a folder path is provided, it attempts to add an action button to open the folder on Windows and Linux.
|
|
/// </summary>
|
|
public static void Show(string title, string message, string? folderPath = null)
|
|
{
|
|
try
|
|
{
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
{
|
|
ShowWindowsNotification(title, message, folderPath);
|
|
}
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
|
{
|
|
ShowMacNotification(title, message);
|
|
}
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
|
{
|
|
ShowLinuxNotification(title, message, folderPath);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MelonLogger.Warning($"[OSNotification] 알림을 보내지 못했습니다: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private static void ShowWindowsNotification(string title, string message, string? folderPath)
|
|
{
|
|
// XML-escape the title and message to prevent XML syntax errors (e.g. from '&', '<', '>', '"', '\'')
|
|
string xmlTitle = System.Security.SecurityElement.Escape(title)!;
|
|
string xmlMessage = System.Security.SecurityElement.Escape(message)!;
|
|
|
|
string toastXml;
|
|
string psScript;
|
|
|
|
if (!string.IsNullOrEmpty(folderPath))
|
|
{
|
|
var nonNullFolder = folderPath;
|
|
var escapedFolder = nonNullFolder.Replace("'", "''");
|
|
|
|
// UWP Toast XML using protocol activation.
|
|
// Clicking the body (launch) or the button (arguments) will invoke the custom 'adofai-renderer://' protocol.
|
|
toastXml = $@"
|
|
<toast launch=""adofai-renderer://"" activationType=""protocol"">
|
|
<visual>
|
|
<binding template=""ToastGeneric"">
|
|
<text>{xmlTitle}</text>
|
|
<text>{xmlMessage}</text>
|
|
</binding>
|
|
</visual>
|
|
<actions>
|
|
<action content=""렌더 폴더 열기"" arguments=""adofai-renderer://"" activationType=""protocol""/>
|
|
</actions>
|
|
</toast>
|
|
".Replace("\r\n", "").Replace("\n", "").Trim();
|
|
|
|
// Build a PowerShell script that registers a custom protocol 'adofai-renderer' in HKCU (no admin rights needed),
|
|
// mapping it to invoke PowerShell and open the folder, then sends the notification and exits immediately.
|
|
psScript =
|
|
$"$protocolPath = 'HKCU:\\Software\\Classes\\adofai-renderer';\n" +
|
|
$"if (-not (Test-Path $protocolPath)) {{\n" +
|
|
$" $null = New-Item -Path $protocolPath -Force;\n" +
|
|
$" $null = New-ItemProperty -Path $protocolPath -Name 'URL Protocol' -Value '' -PropertyType String -Force;\n" +
|
|
$"}}\n" +
|
|
$"Set-Item -Path $protocolPath -Value 'URL:adofai-renderer Protocol' | Out-Null;\n" +
|
|
$"$cmdPath = \"$protocolPath\\shell\\open\\command\";\n" +
|
|
$"if (-not (Test-Path $cmdPath)) {{ $null = New-Item -Path $cmdPath -Force; }}\n" +
|
|
$"$command = 'powershell.exe -NoProfile -NonInteractive -WindowStyle Hidden -Command \"Invoke-Item ''{escapedFolder}''\"';\n" +
|
|
$"Set-Item -Path $cmdPath -Value $command | Out-Null;\n" +
|
|
$"[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null;\n" +
|
|
$"$xml = [Windows.Data.Xml.Dom.XmlDocument]::new();\n" +
|
|
$"$xml.LoadXml('{toastXml}');\n" +
|
|
$"[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('ADOFAI Renderer').Show([Windows.UI.Notifications.ToastNotification]::new($xml))";
|
|
}
|
|
else
|
|
{
|
|
// Simple UWP Toast XML without actions.
|
|
toastXml = $@"
|
|
<toast>
|
|
<visual>
|
|
<binding template=""ToastGeneric"">
|
|
<text>{xmlTitle}</text>
|
|
<text>{xmlMessage}</text>
|
|
</binding>
|
|
</visual>
|
|
</toast>
|
|
".Replace("\r\n", "").Replace("\n", "").Trim();
|
|
|
|
psScript =
|
|
$"[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null;\n" +
|
|
$"$xml = [Windows.Data.Xml.Dom.XmlDocument]::new();\n" +
|
|
$"$xml.LoadXml('{toastXml}');\n" +
|
|
$"[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('ADOFAI Renderer').Show([Windows.UI.Notifications.ToastNotification]::new($xml))";
|
|
}
|
|
|
|
// Encode the script to UTF-16LE Base64 and run it via PowerShell -EncodedCommand.
|
|
// This guarantees that any special characters (double quotes, backslashes, ampersands, etc.) in the title/message
|
|
// do not break the command-line parsing or cause syntax errors.
|
|
byte[] bytes = System.Text.Encoding.Unicode.GetBytes(psScript);
|
|
string base64 = Convert.ToBase64String(bytes);
|
|
|
|
RunProcess("powershell.exe", $"-NoProfile -NonInteractive -EncodedCommand {base64}");
|
|
}
|
|
|
|
private static void ShowMacNotification(string title, string message)
|
|
{
|
|
string escapedTitle = title.Replace("\"", "\\\"");
|
|
string escapedMessage = message.Replace("\"", "\\\"");
|
|
|
|
string args = $"-e \"display notification \\\"{escapedMessage}\\\" with title \\\"{escapedTitle}\\\"\"";
|
|
RunProcess("osascript", args);
|
|
}
|
|
|
|
private static void ShowLinuxNotification(string title, string message, string? folderPath)
|
|
{
|
|
var escapedTitle = title.Replace("\"", "\\\"");
|
|
var escapedMessage = message.Replace("\"", "\\\"");
|
|
|
|
if (!string.IsNullOrEmpty(folderPath))
|
|
{
|
|
var nonNullFolder = folderPath;
|
|
// Linux notify-send supports action buttons since v1.2.
|
|
// It prints the action name to standard output when clicked.
|
|
var args = $"-a \"ADOFAI Renderer\" --action=\"open_folder=렌더 폴더 열기\" \"{escapedTitle}\" \"{escapedMessage}\"";
|
|
|
|
System.Threading.Tasks.Task.Run(() =>
|
|
{
|
|
try
|
|
{
|
|
var startInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "notify-send",
|
|
Arguments = args,
|
|
CreateNoWindow = true,
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
WindowStyle = ProcessWindowStyle.Hidden
|
|
};
|
|
|
|
using var process = new Process();
|
|
process.StartInfo = startInfo;
|
|
process.Start();
|
|
string output = process.StandardOutput.ReadToEnd().Trim();
|
|
// If user clicked the button, open the folder using default file manager
|
|
if (output == "open_folder")
|
|
{
|
|
RunProcess("xdg-open", $"\"{nonNullFolder.Replace("\"", "\\\"")}\"");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
MelonLogger.Warning($"[OSNotification] 리눅스 알림 처리 실패: {ex.Message}");
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
var args = $"-a \"ADOFAI Renderer\" \"{escapedTitle}\" \"{escapedMessage}\"";
|
|
RunProcess("notify-send", args);
|
|
}
|
|
}
|
|
|
|
private static void RunProcess(string fileName, string arguments)
|
|
{
|
|
var startInfo = new ProcessStartInfo
|
|
{
|
|
FileName = fileName,
|
|
Arguments = arguments,
|
|
CreateNoWindow = true, // CREATE_NO_WINDOW flag
|
|
UseShellExecute = false, // Direct execution
|
|
WindowStyle = ProcessWindowStyle.Hidden
|
|
};
|
|
|
|
using var process = new Process();
|
|
process.StartInfo = startInfo;
|
|
process.Start();
|
|
}
|
|
}
|
|
}
|