266 lines
9.3 KiB
C#
266 lines
9.3 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);
|
|
}
|
|
|
|
/// <summary>명령 프롬프트 출력 인코딩 (한국어 Windows = CP949)</summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <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);
|
|
}
|
|
}
|
|
}
|