JJServiceManagerEnt/Core/SystemIntegration.cs

238 lines
8.2 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;
using System.Text;
using System.Windows.Input;
using System.Windows.Interop;
namespace JJServiceManager
{
/// <summary>
/// 컴퓨터 시작 시 기동.
/// 관리자 권한 프로그램은 레지스트리 Run 키로는 자동 실행되지 않으므로
/// 작업 스케줄러(로그온 시, 가장 높은 권한)를 사용함.
/// </summary>
public static class StartupManager
{
// [테스트용 중복 실행] 인스턴스마다 작업 이름을 따로 (정식 릴리즈 시 Suffix 제거)
private static string TaskName => "JJServiceManager" + AppInstance.Suffix;
public static bool IsEnabled()
{
var r = RunSchtasks($"/query /tn \"{TaskName}\"");
return r.ExitCode == 0;
}
public static (bool ok, string message) Enable()
{
var exe = Environment.ProcessPath ?? "";
var user = WindowsIdentity.GetCurrent().Name;
var taskArgs = AppInstance.IsTest ? "--tray --instance " + AppInstance.Number : "--tray";
var xml =
$@"<?xml version=""1.0"" encoding=""UTF-16""?>
<Task version=""1.2"" xmlns=""http://schemas.microsoft.com/windows/2004/02/mit/task"">
<RegistrationInfo>
<Description>JJServiceManager 자동 실행</Description>
</RegistrationInfo>
<Triggers>
<LogonTrigger>
<Enabled>true</Enabled>
<UserId>{SecurityElement.Escape(user)}</UserId>
</LogonTrigger>
</Triggers>
<Principals>
<Principal id=""Author"">
<UserId>{SecurityElement.Escape(user)}</UserId>
<LogonType>InteractiveToken</LogonType>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context=""Author"">
<Exec>
<Command>{SecurityElement.Escape(exe)}</Command>
<Arguments>{taskArgs}</Arguments>
<WorkingDirectory>{SecurityElement.Escape(Path.GetDirectoryName(exe) ?? "")}</WorkingDirectory>
</Exec>
</Actions>
</Task>";
var tmp = Path.Combine(Path.GetTempPath(), "JJServiceManager_task.xml");
try
{
File.WriteAllText(tmp, xml, Encoding.Unicode);
var r = RunSchtasks($"/create /tn \"{TaskName}\" /xml \"{tmp}\" /f");
return (r.ExitCode == 0, r.Output);
}
catch (Exception ex)
{
return (false, ex.Message);
}
finally
{
try { File.Delete(tmp); } catch { }
}
}
public static (bool ok, string message) Disable()
{
if (!IsEnabled()) return (true, "");
var r = RunSchtasks($"/delete /tn \"{TaskName}\" /f");
return (r.ExitCode == 0, r.Output);
}
private static (int ExitCode, string Output) RunSchtasks(string args)
=> ConsoleRunner.Run("schtasks.exe", args);
}
/// <summary>전역 단축키 (어느 프로그램을 쓰고 있어도 동작)</summary>
public sealed class HotkeyManager : IDisposable
{
private const int WM_HOTKEY = 0x0312;
private const int HotkeyId = 0x4A4A; // "JJ"
private const uint MOD_NOREPEAT = 0x4000;
[DllImport("user32.dll", SetLastError = true)]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
private readonly HwndSource _source;
private bool _registered;
public event Action Pressed;
public HotkeyManager(IntPtr hwnd)
{
_source = HwndSource.FromHwnd(hwnd);
_source?.AddHook(WndProc);
}
/// <summary>단축키 등록. 다른 프로그램이 사용 중이면 false</summary>
/// <summary>마지막으로 등록에 성공한 단축키</summary>
public (ModifierKeys mods, Key key)? Active { get; private set; }
/// <summary>
/// 단축키 등록. 다른 프로그램이 사용 중이면 false를 돌려주고,
/// 이전에 쓰던 단축키가 있으면 그것을 다시 등록해 유지함
/// </summary>
public bool Register(ModifierKeys modifiers, Key key)
{
var previous = Active;
Unregister();
if (_source == null || key == Key.None) return false;
if (TryRegister(modifiers, key))
{
Active = (modifiers, key);
return true;
}
if (previous.HasValue && TryRegister(previous.Value.mods, previous.Value.key))
Active = previous;
else
Active = null;
return false;
}
private bool TryRegister(ModifierKeys modifiers, Key key)
{
// ModifierKeys 값(Alt=1, Ctrl=2, Shift=4, Win=8)은 RegisterHotKey의 MOD_* 값과 같음
uint mods = (uint)modifiers | MOD_NOREPEAT;
uint vk = (uint)KeyInterop.VirtualKeyFromKey(key);
_registered = RegisterHotKey(_source.Handle, HotkeyId, mods, vk);
return _registered;
}
public void Unregister()
{
if (_registered && _source != null)
{
UnregisterHotKey(_source.Handle, HotkeyId);
_registered = false;
}
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_HOTKEY && wParam.ToInt32() == HotkeyId)
{
Pressed?.Invoke();
handled = true;
}
return IntPtr.Zero;
}
public void Dispose()
{
Unregister();
_source?.RemoveHook(WndProc);
}
/// <summary>"Ctrl + Shift + A" 형태 문자열</summary>
public static string Format(ModifierKeys mods, Key key)
{
var sb = new StringBuilder();
if (mods.HasFlag(ModifierKeys.Control)) sb.Append("Ctrl + ");
if (mods.HasFlag(ModifierKeys.Alt)) sb.Append("Alt + ");
if (mods.HasFlag(ModifierKeys.Shift)) sb.Append("Shift + ");
if (mods.HasFlag(ModifierKeys.Windows)) sb.Append("Win + ");
sb.Append(KeyName(key));
return sb.ToString();
}
public static string KeyName(Key key)
{
if (key >= Key.D0 && key <= Key.D9) return ((int)(key - Key.D0)).ToString();
if (key >= Key.NumPad0 && key <= Key.NumPad9) return "Num " + (int)(key - Key.NumPad0);
return key switch
{
Key.None => "",
Key.OemPlus => "=",
Key.OemMinus => "-",
Key.OemComma => ",",
Key.OemPeriod => ".",
Key.Oem1 => ";",
Key.Oem2 => "/",
Key.Oem3 => "`",
Key.Oem4 => "[",
Key.Oem5 => "\\",
Key.Oem6 => "]",
Key.Oem7 => "'",
_ => key.ToString()
};
}
}
/// <summary>Windows 강제 종료 / 재시작</summary>
public static class PowerOps
{
public static void ForceShutdown() => Run("/s /f /t 0");
public static void ForceRestart() => Run("/r /f /t 0");
private static void Run(string args)
{
var psi = new ProcessStartInfo("shutdown.exe", args)
{
UseShellExecute = false,
CreateNoWindow = true
};
Process.Start(psi);
}
}
}