SHA256
Add Notification When Render End
This commit is contained in:
2 files changed
+195
No files matched your search
@@ -0,0 +1,189 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user