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 { /// /// 컴퓨터 시작 시 기동. /// 관리자 권한 프로그램은 레지스트리 Run 키로는 자동 실행되지 않으므로 /// 작업 스케줄러(로그온 시, 가장 높은 권한)를 사용함. /// 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 = $@" JJServiceManager 자동 실행 true {SecurityElement.Escape(user)} {SecurityElement.Escape(user)} InteractiveToken HighestAvailable IgnoreNew false false true false false true true false PT0S 7 {SecurityElement.Escape(exe)} {taskArgs} {SecurityElement.Escape(Path.GetDirectoryName(exe) ?? "")} "; 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); } /// 명령 프롬프트 출력 인코딩 (한국어 Windows = CP949) private static Encoding OemEncoding() { try { return Encoding.GetEncoding(System.Globalization.CultureInfo.CurrentCulture.TextInfo.OEMCodePage); } catch { return Encoding.UTF8; } } private static (int ExitCode, string Output) RunSchtasks(string args) { try { var psi = new ProcessStartInfo("schtasks.exe", args) { UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, StandardOutputEncoding = OemEncoding(), StandardErrorEncoding = OemEncoding() }; using var p = Process.Start(psi); var output = p.StandardOutput.ReadToEnd() + p.StandardError.ReadToEnd(); p.WaitForExit(15000); return (p.ExitCode, output.Trim()); } catch (Exception ex) { return (-1, ex.Message); } } } /// 전역 단축키 (어느 프로그램을 쓰고 있어도 동작) 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); } /// 단축키 등록. 다른 프로그램이 사용 중이면 false /// 마지막으로 등록에 성공한 단축키 public (ModifierKeys mods, Key key)? Active { get; private set; } /// /// 단축키 등록. 다른 프로그램이 사용 중이면 false를 돌려주고, /// 이전에 쓰던 단축키가 있으면 그것을 다시 등록해 유지함 /// 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); } /// "Ctrl + Shift + A" 형태 문자열 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() }; } } /// Windows 강제 종료 / 재시작 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); } } }