Files
caffeine/caffeine.ps1
2026-02-25 08:27:37 +11:00

1983 lines
67 KiB
PowerShell

$ErrorActionPreference = 'Stop'
$csharp = @'
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace PortableCaffeine
{
internal enum RemoteCommandKind
{
None,
AppExit,
AppOn,
AppOff,
AppToggle,
AppToggleShowDialog,
ShowDialog,
ExitForReplace
}
internal enum ManualOverrideKind
{
None,
ForceActive,
ForceInactive
}
internal enum EffectiveMode
{
Inactive,
ActiveKeyPulse,
ActiveStesSystemOnly,
ActiveStesSystemAndDisplay
}
internal sealed class TimeRange
{
public TimeSpan Start;
public TimeSpan End;
public bool Contains(TimeSpan time)
{
if (Start == End)
{
return true;
}
if (Start < End)
{
return time >= Start && time < End;
}
return time >= Start || time < End;
}
public override string ToString()
{
return Start.ToString(@"hh\:mm") + "-" + End.ToString(@"hh\:mm");
}
}
internal sealed class Options
{
public bool ShowHelp;
public bool StartupEnabled = true;
public bool StartupModeExplicit;
public bool LockWorkstation;
public bool UseStes;
public bool AllowScreenSaver;
public bool Notify;
public bool ShowDialog;
public bool OnTaskbar;
public bool NoHIcon;
public bool ReplaceExisting;
public bool OnAcOnly;
public bool AllowLocalCompat;
public bool KeyWithShift;
public string WatchWindowContains;
public int? CpuThresholdPercent;
public double? ExitAfterMinutes;
public double? ActiveForMinutes;
public double? InactiveForMinutes;
public ushort VirtualKey = 0x7E; // F15 default
public List<TimeRange> ActivePeriods = new List<TimeRange>();
public RemoteCommandKind AppCommand = RemoteCommandKind.None;
public List<string> UnknownSwitches = new List<string>();
public List<string> RawArgs = new List<string>();
public bool HasAnyAppCommand
{
get { return AppCommand != RemoteCommandKind.None; }
}
}
internal static class CommandLine
{
public static Options Parse(string[] args)
{
Options options = new Options();
if (args == null)
{
return options;
}
for (int i = 0; i < args.Length; i++)
{
string token = args[i];
if (token == null)
{
continue;
}
options.RawArgs.Add(token);
if (!token.StartsWith("-") && !token.StartsWith("/"))
{
options.UnknownSwitches.Add(token);
continue;
}
string body = token.Substring(1);
string name = body;
string value = null;
int colon = body.IndexOf(':');
int equals = body.IndexOf('=');
int sep = -1;
if (colon >= 0 && equals >= 0)
{
sep = Math.Min(colon, equals);
}
else if (colon >= 0)
{
sep = colon;
}
else if (equals >= 0)
{
sep = equals;
}
if (sep >= 0)
{
name = body.Substring(0, sep);
value = body.Substring(sep + 1);
}
name = name.Trim().ToLowerInvariant();
if (value != null)
{
value = value.Trim();
}
switch (name)
{
case "?":
case "h":
case "help":
options.ShowHelp = true;
break;
case "on":
options.StartupEnabled = true;
options.StartupModeExplicit = true;
break;
case "off":
options.StartupEnabled = false;
options.StartupModeExplicit = true;
break;
case "lock":
options.LockWorkstation = true;
break;
case "showdlg":
options.ShowDialog = true;
break;
case "ontaskbar":
options.OnTaskbar = true;
break;
case "notify":
options.Notify = true;
break;
case "nohicon":
options.NoHIcon = true;
break;
case "replace":
options.ReplaceExisting = true;
break;
case "onac":
options.OnAcOnly = true;
break;
case "allowlocal":
options.AllowLocalCompat = true;
break;
case "stes":
options.UseStes = true;
break;
case "allowss":
options.AllowScreenSaver = true;
break;
case "watchwindow":
if (!string.IsNullOrEmpty(value))
{
options.WatchWindowContains = value;
}
break;
case "cpu":
{
int cpu;
if (TryParseInt(value, out cpu))
{
if (cpu < 0) cpu = 0;
if (cpu > 100) cpu = 100;
options.CpuThresholdPercent = cpu;
}
}
break;
case "exitafter":
options.ExitAfterMinutes = ParseMinutes(value, options.ExitAfterMinutes);
break;
case "activefor":
options.ActiveForMinutes = ParseMinutes(value, options.ActiveForMinutes);
break;
case "inactivefor":
options.InactiveForMinutes = ParseMinutes(value, options.InactiveForMinutes);
break;
case "activehours":
case "activeperiods":
if (!string.IsNullOrEmpty(value))
{
List<TimeRange> parsed = ParseTimeRanges(value);
if (parsed.Count > 0)
{
options.ActivePeriods = parsed;
}
}
break;
case "useshift":
options.VirtualKey = 0x10; // VK_SHIFT
break;
case "leftshift":
options.VirtualKey = 0xA0; // VK_LSHIFT
break;
case "key":
case "keypress":
{
ushort vk;
if (TryParseVirtualKey(value, out vk))
{
options.VirtualKey = vk;
}
}
break;
case "keyshift":
options.KeyWithShift = true;
if (!string.IsNullOrEmpty(value))
{
ushort vk;
if (TryParseVirtualKey(value, out vk))
{
options.VirtualKey = vk;
}
}
break;
case "appexit":
options.AppCommand = RemoteCommandKind.AppExit;
break;
case "appon":
options.AppCommand = RemoteCommandKind.AppOn;
break;
case "appoff":
options.AppCommand = RemoteCommandKind.AppOff;
break;
case "apptoggle":
options.AppCommand = RemoteCommandKind.AppToggle;
break;
case "apptoggleshowdlg":
options.AppCommand = RemoteCommandKind.AppToggleShowDialog;
break;
default:
options.UnknownSwitches.Add(token);
break;
}
}
return options;
}
private static double? ParseMinutes(string value, double? fallback)
{
if (string.IsNullOrEmpty(value))
{
return fallback;
}
double minutes;
if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out minutes))
{
return minutes;
}
return fallback;
}
private static bool TryParseInt(string value, out int number)
{
number = 0;
if (string.IsNullOrEmpty(value))
{
return false;
}
return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out number);
}
private static bool TryParseVirtualKey(string value, out ushort vk)
{
vk = 0;
if (string.IsNullOrEmpty(value))
{
return false;
}
string v = value.Trim();
try
{
if (v.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
vk = Convert.ToUInt16(v.Substring(2), 16);
return true;
}
vk = Convert.ToUInt16(v, CultureInfo.InvariantCulture);
return true;
}
catch
{
return false;
}
}
private static List<TimeRange> ParseTimeRanges(string value)
{
List<TimeRange> list = new List<TimeRange>();
string[] parts = value.Split(new char[] { ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < parts.Length; i++)
{
string p = parts[i].Trim();
if (p.Length == 0)
{
continue;
}
int dash = p.IndexOf('-');
if (dash <= 0 || dash >= p.Length - 1)
{
continue;
}
string left = p.Substring(0, dash).Trim();
string right = p.Substring(dash + 1).Trim();
TimeSpan start;
TimeSpan end;
if (!TryParseTime(left, out start) || !TryParseTime(right, out end))
{
continue;
}
TimeRange range = new TimeRange();
range.Start = start;
range.End = end;
list.Add(range);
}
return list;
}
private static bool TryParseTime(string text, out TimeSpan time)
{
time = TimeSpan.Zero;
if (string.IsNullOrEmpty(text))
{
return false;
}
DateTime dt;
string[] formats = new string[]
{
"H",
"HH",
"H:mm",
"HH:mm",
"H.mm",
"HH.mm",
"Hmm",
"HHmm"
};
if (DateTime.TryParseExact(text, formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out dt))
{
time = dt.TimeOfDay;
return true;
}
int hour;
if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out hour) && hour >= 0 && hour <= 23)
{
time = new TimeSpan(hour, 0, 0);
return true;
}
return false;
}
}
internal static class NativeMethods
{
public const int WM_COPYDATA = 0x004A;
public const uint ES_SYSTEM_REQUIRED = 0x00000001;
public const uint ES_DISPLAY_REQUIRED = 0x00000002;
public const uint ES_CONTINUOUS = 0x80000000;
public const uint KEYEVENTF_KEYUP = 0x0002;
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
public IntPtr lpData;
}
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_POWER_STATUS
{
public byte ACLineStatus;
public byte BatteryFlag;
public byte BatteryLifePercent;
public byte SystemStatusFlag;
public int BatteryLifeTime;
public int BatteryFullLifeTime;
}
[DllImport("kernel32.dll")]
public static extern uint SetThreadExecutionState(uint esFlags);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, ref COPYDATASTRUCT lParam);
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
public static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool LockWorkStation();
[DllImport("kernel32.dll")]
public static extern bool GetSystemPowerStatus(out SYSTEM_POWER_STATUS sps);
[DllImport("user32.dll")]
public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DestroyIcon(IntPtr hIcon);
}
internal static class TrayIconFactory
{
public static Icon CreateCaffeineTrayIcon(bool active)
{
Bitmap bmp = new Bitmap(16, 16);
try
{
using (Graphics g = Graphics.FromImage(bmp))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.Clear(Color.Transparent);
Color cupFill = active ? Color.FromArgb(68, 118, 184) : Color.FromArgb(140, 145, 152);
Color cupOutline = active ? Color.FromArgb(35, 62, 102) : Color.FromArgb(84, 88, 94);
Color coffeeFill = active ? Color.FromArgb(111, 72, 41) : Color.FromArgb(108, 96, 88);
Color steamColor = active ? Color.FromArgb(245, 248, 252) : Color.FromArgb(188, 191, 196);
Color saucerColor = active ? Color.FromArgb(54, 77, 101) : Color.FromArgb(106, 110, 116);
using (SolidBrush cupBrush = new SolidBrush(cupFill))
using (SolidBrush coffeeBrush = new SolidBrush(coffeeFill))
using (Pen outlinePen = new Pen(cupOutline, 1f))
using (Pen steamPen = new Pen(steamColor, 1.1f))
using (Pen saucerPen = new Pen(saucerColor, 1f))
{
// Mug body
g.FillRectangle(cupBrush, 3, 7, 7, 5);
g.DrawRectangle(outlinePen, 3, 7, 7, 5);
// Coffee surface
g.FillRectangle(coffeeBrush, 4, 8, 5, 2);
// Mug handle
g.DrawArc(outlinePen, 8, 7, 4, 4, 285, 255);
// Saucer
g.DrawLine(saucerPen, 2, 13, 12, 13);
// Steam
g.DrawBezier(steamPen, 4, 7, 3, 5, 5, 4, 4, 2);
g.DrawBezier(steamPen, 7, 7, 6, 5, 8, 4, 7, 2);
}
if (!active)
{
using (Pen slashPen = new Pen(Color.FromArgb(210, 186, 53, 53), 1.8f))
{
g.DrawLine(slashPen, 3, 13, 13, 3);
}
}
}
IntPtr hIcon = bmp.GetHicon();
try
{
using (Icon tmp = Icon.FromHandle(hIcon))
{
return (Icon)tmp.Clone();
}
}
finally
{
NativeMethods.DestroyIcon(hIcon);
}
}
catch
{
return null;
}
finally
{
bmp.Dispose();
}
}
}
internal static class IpcTransport
{
public const string WindowTitle = "PortableCaffeine.HiddenIpcWindow";
public static bool Send(string payload)
{
IntPtr hwnd = NativeMethods.FindWindow(null, WindowTitle);
if (hwnd == IntPtr.Zero)
{
return false;
}
IntPtr hGlobal = IntPtr.Zero;
try
{
string data = payload ?? string.Empty;
hGlobal = Marshal.StringToHGlobalUni(data);
NativeMethods.COPYDATASTRUCT cds = new NativeMethods.COPYDATASTRUCT();
cds.dwData = IntPtr.Zero;
cds.cbData = (data.Length + 1) * 2;
cds.lpData = hGlobal;
NativeMethods.SendMessage(hwnd, NativeMethods.WM_COPYDATA, IntPtr.Zero, ref cds);
return true;
}
catch
{
return false;
}
finally
{
if (hGlobal != IntPtr.Zero)
{
Marshal.FreeHGlobal(hGlobal);
}
}
}
}
internal sealed class IpcForm : Form
{
public delegate void PayloadReceivedHandler(object sender, string payload);
public event PayloadReceivedHandler PayloadReceived;
public IpcForm()
{
this.Text = IpcTransport.WindowTitle;
this.ShowInTaskbar = false;
this.FormBorderStyle = FormBorderStyle.FixedToolWindow;
this.StartPosition = FormStartPosition.Manual;
this.Size = new Size(1, 1);
this.Location = new Point(-32000, -32000);
this.Opacity = 0;
}
protected override void SetVisibleCore(bool value)
{
base.SetVisibleCore(false);
}
protected override void OnShown(EventArgs e)
{
this.Hide();
base.OnShown(e);
}
protected override void WndProc(ref Message m)
{
if (m.Msg == NativeMethods.WM_COPYDATA)
{
try
{
NativeMethods.COPYDATASTRUCT cds = (NativeMethods.COPYDATASTRUCT)Marshal.PtrToStructure(m.LParam, typeof(NativeMethods.COPYDATASTRUCT));
if (cds.lpData != IntPtr.Zero && cds.cbData > 0)
{
string payload = Marshal.PtrToStringUni(cds.lpData);
if (PayloadReceived != null)
{
PayloadReceived(this, payload);
}
}
}
catch
{
// Ignore malformed IPC messages.
}
}
base.WndProc(ref m);
}
}
internal sealed class StatusForm : Form
{
private Label _statusLabel;
private Label _detailLabel;
private Label _conditionLabel;
private Button _toggleButton;
private Button _revertButton;
private Button _closeButton;
public event EventHandler ToggleRequested;
public event EventHandler RevertRequested;
public StatusForm(bool showInTaskbar)
{
this.Text = "Portable Caffeine";
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.StartPosition = FormStartPosition.CenterScreen;
this.ClientSize = new Size(470, 190);
this.ShowInTaskbar = showInTaskbar;
_statusLabel = new Label();
_statusLabel.AutoSize = false;
_statusLabel.Location = new Point(12, 12);
_statusLabel.Size = new Size(446, 28);
_statusLabel.Font = new Font(SystemFonts.MessageBoxFont.FontFamily, 11, FontStyle.Bold);
_detailLabel = new Label();
_detailLabel.AutoSize = false;
_detailLabel.Location = new Point(12, 48);
_detailLabel.Size = new Size(446, 44);
_conditionLabel = new Label();
_conditionLabel.AutoSize = false;
_conditionLabel.Location = new Point(12, 96);
_conditionLabel.Size = new Size(446, 42);
_toggleButton = new Button();
_toggleButton.Text = "Toggle";
_toggleButton.Location = new Point(12, 148);
_toggleButton.Size = new Size(90, 28);
_toggleButton.Click += delegate(object sender, EventArgs e)
{
if (ToggleRequested != null)
{
ToggleRequested(this, EventArgs.Empty);
}
};
_revertButton = new Button();
_revertButton.Text = "Revert";
_revertButton.Location = new Point(108, 148);
_revertButton.Size = new Size(90, 28);
_revertButton.Click += delegate(object sender, EventArgs e)
{
if (RevertRequested != null)
{
RevertRequested(this, EventArgs.Empty);
}
};
_closeButton = new Button();
_closeButton.Text = "Close";
_closeButton.Location = new Point(368, 148);
_closeButton.Size = new Size(90, 28);
_closeButton.Click += delegate(object sender, EventArgs e)
{
this.Hide();
};
this.Controls.Add(_statusLabel);
this.Controls.Add(_detailLabel);
this.Controls.Add(_conditionLabel);
this.Controls.Add(_toggleButton);
this.Controls.Add(_revertButton);
this.Controls.Add(_closeButton);
}
public void UpdateView(string status, string detail, string condition)
{
_statusLabel.Text = status ?? string.Empty;
_detailLabel.Text = detail ?? string.Empty;
_conditionLabel.Text = condition ?? string.Empty;
}
}
internal sealed class CaffeineAppContext : ApplicationContext
{
private readonly Options _options;
private readonly Mutex _instanceMutex;
private readonly NotifyIcon _tray;
private readonly Icon _trayIconActive;
private readonly Icon _trayIconInactive;
private readonly ContextMenuStrip _menu;
private readonly IpcForm _ipcForm;
private readonly System.Windows.Forms.Timer _timer;
private readonly DateTime _startedLocal;
private DateTime? _exitAtLocal;
private DateTime? _startupActiveUntilLocal;
private DateTime? _startupInactiveUntilLocal;
private DateTime? _manualOverrideUntilLocal;
private ManualOverrideKind _manualOverrideKind;
private bool _lastEffectiveActive;
private bool _hasLastEffectiveActive;
private bool _lastConditionsMatch;
private string _lastReason = "Initializing";
private string _lastConditionSummary = "Initializing";
private string _lastCpuSummary = "CPU condition not configured.";
private DateTime _lastPulseUtc = DateTime.MinValue;
private DateTime _lastStesRefreshUtc = DateTime.MinValue;
private PerformanceCounter _cpuCounter;
private bool _cpuCounterFailed;
private bool _cpuCounterPrimed;
private float _lastCpuPercent;
private StatusForm _statusForm;
private bool _lockPending;
private bool _disposed;
// Menu references for state updates
private ToolStripMenuItem _miStatus;
private ToolStripMenuItem _miActiveNow;
private ToolStripMenuItem _miInactiveNow;
private ToolStripMenuItem _miRevert;
private ToolStripMenuItem _miShowHide;
private ToolStripMenuItem _miTimed5;
private ToolStripMenuItem _miTimed15;
private ToolStripMenuItem _miTimed30;
private ToolStripMenuItem _miTimed60;
private ToolStripMenuItem _miTimed120;
private ToolStripMenuItem _miTimedInactive5;
private ToolStripMenuItem _miTimedInactive15;
private ToolStripMenuItem _miTimedInactive30;
public CaffeineAppContext(Options options, Mutex instanceMutex)
{
_options = options;
_instanceMutex = instanceMutex;
_startedLocal = DateTime.Now;
_lockPending = options.LockWorkstation;
if (options.ExitAfterMinutes.HasValue)
{
_exitAtLocal = _startedLocal.AddMinutes(options.ExitAfterMinutes.Value);
}
if (options.ActiveForMinutes.HasValue)
{
_startupActiveUntilLocal = _startedLocal.AddMinutes(options.ActiveForMinutes.Value);
}
if (options.InactiveForMinutes.HasValue)
{
_startupInactiveUntilLocal = _startedLocal.AddMinutes(options.InactiveForMinutes.Value);
}
_tray = new NotifyIcon();
_trayIconActive = TrayIconFactory.CreateCaffeineTrayIcon(true);
_trayIconInactive = TrayIconFactory.CreateCaffeineTrayIcon(false);
_tray.Visible = true;
_tray.Text = "Portable Caffeine";
_tray.DoubleClick += delegate(object sender, EventArgs e)
{
ToggleManual();
};
_menu = new ContextMenuStrip();
_tray.ContextMenuStrip = _menu;
BuildMenu();
SetTrayIcon(false);
_ipcForm = new IpcForm();
_ipcForm.PayloadReceived += OnIpcPayload;
IntPtr dummyHandle = _ipcForm.Handle;
_ipcForm.Show();
_ipcForm.Hide();
if (_options.CpuThresholdPercent.HasValue)
{
TryInitCpuCounter();
}
_timer = new System.Windows.Forms.Timer();
_timer.Interval = 1000;
_timer.Tick += OnTick;
_timer.Start();
if (_options.ShowDialog)
{
EnsureStatusForm();
ShowStatusForm(true);
}
// Run one evaluation immediately so state is correct on launch.
TickCore();
}
private void BuildMenu()
{
_menu.Items.Clear();
_miStatus = new ToolStripMenuItem("Status: Initializing");
_miStatus.Enabled = false;
_miActiveNow = new ToolStripMenuItem("Active (indefinite)");
_miActiveNow.Click += delegate { SetManualOverride(ManualOverrideKind.ForceActive, null); };
_miInactiveNow = new ToolStripMenuItem("Inactive (indefinite)");
_miInactiveNow.Click += delegate { SetManualOverride(ManualOverrideKind.ForceInactive, null); };
_miTimed5 = new ToolStripMenuItem("Active for 5 minutes");
_miTimed5.Click += delegate { SetManualOverride(ManualOverrideKind.ForceActive, 5); };
_miTimed15 = new ToolStripMenuItem("Active for 15 minutes");
_miTimed15.Click += delegate { SetManualOverride(ManualOverrideKind.ForceActive, 15); };
_miTimed30 = new ToolStripMenuItem("Active for 30 minutes");
_miTimed30.Click += delegate { SetManualOverride(ManualOverrideKind.ForceActive, 30); };
_miTimed60 = new ToolStripMenuItem("Active for 60 minutes");
_miTimed60.Click += delegate { SetManualOverride(ManualOverrideKind.ForceActive, 60); };
_miTimed120 = new ToolStripMenuItem("Active for 120 minutes");
_miTimed120.Click += delegate { SetManualOverride(ManualOverrideKind.ForceActive, 120); };
_miTimedInactive5 = new ToolStripMenuItem("Inactive for 5 minutes");
_miTimedInactive5.Click += delegate { SetManualOverride(ManualOverrideKind.ForceInactive, 5); };
_miTimedInactive15 = new ToolStripMenuItem("Inactive for 15 minutes");
_miTimedInactive15.Click += delegate { SetManualOverride(ManualOverrideKind.ForceInactive, 15); };
_miTimedInactive30 = new ToolStripMenuItem("Inactive for 30 minutes");
_miTimedInactive30.Click += delegate { SetManualOverride(ManualOverrideKind.ForceInactive, 30); };
_miRevert = new ToolStripMenuItem("Revert To Parameters");
_miRevert.Click += delegate { RevertToParameters(); };
_miShowHide = new ToolStripMenuItem("Show Status");
_miShowHide.Click += delegate { ToggleStatusForm(); };
ToolStripMenuItem about = new ToolStripMenuItem("About");
about.Click += delegate { ShowAbout(); };
ToolStripMenuItem exit = new ToolStripMenuItem("Exit");
exit.Click += delegate { ExitApplication(); };
_menu.Items.Add(_miStatus);
_menu.Items.Add(new ToolStripSeparator());
_menu.Items.Add(_miActiveNow);
_menu.Items.Add(_miInactiveNow);
_menu.Items.Add(new ToolStripSeparator());
_menu.Items.Add(_miTimed5);
_menu.Items.Add(_miTimed15);
_menu.Items.Add(_miTimed30);
_menu.Items.Add(_miTimed60);
_menu.Items.Add(_miTimed120);
_menu.Items.Add(new ToolStripSeparator());
_menu.Items.Add(_miTimedInactive5);
_menu.Items.Add(_miTimedInactive15);
_menu.Items.Add(_miTimedInactive30);
_menu.Items.Add(new ToolStripSeparator());
_menu.Items.Add(_miRevert);
_menu.Items.Add(_miShowHide);
_menu.Items.Add(new ToolStripSeparator());
_menu.Items.Add(about);
_menu.Items.Add(exit);
}
private void EnsureStatusForm()
{
if (_statusForm != null && !_statusForm.IsDisposed)
{
return;
}
_statusForm = new StatusForm(_options.OnTaskbar);
_statusForm.ToggleRequested += delegate { ToggleManual(); };
_statusForm.RevertRequested += delegate { RevertToParameters(); };
_statusForm.FormClosing += delegate(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
_statusForm.Hide();
UpdateMenuState();
}
};
}
private void ToggleStatusForm()
{
EnsureStatusForm();
if (_statusForm.Visible)
{
_statusForm.Hide();
}
else
{
ShowStatusForm(true);
}
UpdateMenuState();
}
private void ShowStatusForm(bool activate)
{
EnsureStatusForm();
if (!_statusForm.Visible)
{
_statusForm.Show();
}
if (activate)
{
_statusForm.WindowState = FormWindowState.Normal;
_statusForm.BringToFront();
_statusForm.Activate();
}
UpdateMenuState();
}
private void ShowAbout()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("Portable Caffeine (PowerShell + embedded C#)");
sb.AppendLine("Zhorn Caffeine-compatible portable implementation.");
sb.AppendLine();
sb.AppendLine("Method: " + GetMethodSummary());
sb.AppendLine("Key: " + FormatVirtualKey(_options.VirtualKey) + (_options.KeyWithShift ? " (with Shift)" : string.Empty));
if (_options.AllowLocalCompat)
{
sb.AppendLine("Compatibility flag: -allowlocal recognized.");
}
if (_options.UnknownSwitches.Count > 0)
{
sb.AppendLine("Unknown switches ignored: " + string.Join(", ", _options.UnknownSwitches.ToArray()));
}
MessageBox.Show(sb.ToString(), "About Portable Caffeine", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void OnTick(object sender, EventArgs e)
{
TickCore();
}
private void TickCore()
{
if (_disposed)
{
return;
}
DateTime nowLocal = DateTime.Now;
DateTime nowUtc = DateTime.UtcNow;
if (_lockPending)
{
_lockPending = false;
try
{
NativeMethods.LockWorkStation();
}
catch
{
// Ignore lock failures.
}
}
if (_exitAtLocal.HasValue && nowLocal >= _exitAtLocal.Value)
{
ExitApplication();
return;
}
ExpireManualOverrideIfNeeded(nowLocal);
bool conditionsMatch;
string conditionSummary;
bool baseShouldBeActive = EvaluateBaseRules(nowLocal, out conditionsMatch, out conditionSummary);
bool effectiveActive;
string reason;
EvaluateEffectiveState(nowLocal, baseShouldBeActive, out effectiveActive, out reason);
_lastConditionsMatch = conditionsMatch;
_lastReason = reason;
_lastConditionSummary = conditionSummary;
EffectiveMode mode = effectiveActive ? GetActiveMode() : EffectiveMode.Inactive;
ApplyActiveMode(mode, nowUtc);
if (!_hasLastEffectiveActive || effectiveActive != _lastEffectiveActive)
{
_hasLastEffectiveActive = true;
_lastEffectiveActive = effectiveActive;
SetTrayIcon(effectiveActive);
if (_options.Notify)
{
ShowNotify(effectiveActive ? "Caffeine active" : "Caffeine inactive", reason);
}
}
UpdateMenuState();
UpdateStatusForm();
UpdateTrayTooltip(effectiveActive, reason);
}
private void ExpireManualOverrideIfNeeded(DateTime nowLocal)
{
if (_manualOverrideUntilLocal.HasValue && nowLocal >= _manualOverrideUntilLocal.Value)
{
_manualOverrideKind = ManualOverrideKind.None;
_manualOverrideUntilLocal = null;
}
}
private bool EvaluateBaseRules(DateTime nowLocal, out bool conditionsMatch, out string conditionSummary)
{
List<string> parts = new List<string>();
bool baseMode = _options.StartupEnabled;
parts.Add("Base: " + (baseMode ? "On" : "Off"));
if (_options.ActivePeriods != null && _options.ActivePeriods.Count > 0)
{
bool inPeriod = false;
TimeSpan current = nowLocal.TimeOfDay;
for (int i = 0; i < _options.ActivePeriods.Count; i++)
{
if (_options.ActivePeriods[i].Contains(current))
{
inPeriod = true;
break;
}
}
parts.Add("Period: " + (inPeriod ? "matched" : "outside"));
if (!inPeriod)
{
conditionsMatch = false;
conditionSummary = string.Join(" | ", parts.ToArray());
return false;
}
}
if (!string.IsNullOrEmpty(_options.WatchWindowContains))
{
bool match = ForegroundWindowMatches(_options.WatchWindowContains);
parts.Add("WatchWindow: " + (match ? "matched" : "not matched"));
if (!match)
{
conditionsMatch = false;
conditionSummary = string.Join(" | ", parts.ToArray());
return false;
}
}
if (_options.OnAcOnly)
{
bool onAc;
string powerSummary;
onAc = IsOnAc(out powerSummary);
parts.Add(powerSummary);
if (!onAc)
{
conditionsMatch = false;
conditionSummary = string.Join(" | ", parts.ToArray());
return false;
}
}
if (_options.CpuThresholdPercent.HasValue)
{
bool cpuOk;
string cpuSummary;
cpuOk = IsCpuAtOrAboveThreshold(_options.CpuThresholdPercent.Value, out cpuSummary);
_lastCpuSummary = cpuSummary;
parts.Add(cpuSummary);
if (!cpuOk)
{
conditionsMatch = false;
conditionSummary = string.Join(" | ", parts.ToArray());
return false;
}
}
else
{
_lastCpuSummary = "CPU condition not configured.";
}
conditionsMatch = true;
conditionSummary = string.Join(" | ", parts.ToArray());
return baseMode;
}
private void EvaluateEffectiveState(DateTime nowLocal, bool baseShouldBeActive, out bool effectiveActive, out string reason)
{
string timedSource;
ManualOverrideKind timedStartupOverride = GetStartupTimedOverride(nowLocal, out timedSource);
if (_manualOverrideKind != ManualOverrideKind.None)
{
effectiveActive = _manualOverrideKind == ManualOverrideKind.ForceActive;
if (_manualOverrideUntilLocal.HasValue)
{
reason = "Manual " + (effectiveActive ? "active" : "inactive") + " until " + _manualOverrideUntilLocal.Value.ToString("HH:mm:ss");
}
else
{
reason = "Manual " + (effectiveActive ? "active" : "inactive");
}
return;
}
if (timedStartupOverride != ManualOverrideKind.None)
{
effectiveActive = timedStartupOverride == ManualOverrideKind.ForceActive;
reason = timedSource;
return;
}
effectiveActive = baseShouldBeActive;
reason = baseShouldBeActive ? "Parameters/rules allow active" : "Parameters/rules inactive";
}
private ManualOverrideKind GetStartupTimedOverride(DateTime nowLocal, out string source)
{
source = null;
bool activeLive = _startupActiveUntilLocal.HasValue && nowLocal < _startupActiveUntilLocal.Value;
bool inactiveLive = _startupInactiveUntilLocal.HasValue && nowLocal < _startupInactiveUntilLocal.Value;
if (!activeLive && !inactiveLive)
{
return ManualOverrideKind.None;
}
// If both exist, later end time wins because it was most recently specified in typical use.
if (activeLive && inactiveLive)
{
if (_startupInactiveUntilLocal.Value >= _startupActiveUntilLocal.Value)
{
source = "Startup inactivefor until " + _startupInactiveUntilLocal.Value.ToString("HH:mm:ss");
return ManualOverrideKind.ForceInactive;
}
source = "Startup activefor until " + _startupActiveUntilLocal.Value.ToString("HH:mm:ss");
return ManualOverrideKind.ForceActive;
}
if (activeLive)
{
source = "Startup activefor until " + _startupActiveUntilLocal.Value.ToString("HH:mm:ss");
return ManualOverrideKind.ForceActive;
}
source = "Startup inactivefor until " + _startupInactiveUntilLocal.Value.ToString("HH:mm:ss");
return ManualOverrideKind.ForceInactive;
}
private EffectiveMode GetActiveMode()
{
if (_options.AllowScreenSaver)
{
return EffectiveMode.ActiveStesSystemOnly;
}
if (_options.UseStes)
{
return EffectiveMode.ActiveStesSystemAndDisplay;
}
return EffectiveMode.ActiveKeyPulse;
}
private void ApplyActiveMode(EffectiveMode mode, DateTime nowUtc)
{
switch (mode)
{
case EffectiveMode.Inactive:
ClearExecutionState();
return;
case EffectiveMode.ActiveKeyPulse:
PulseKeyIfDue(nowUtc);
return;
case EffectiveMode.ActiveStesSystemOnly:
RefreshStesIfDue(nowUtc, NativeMethods.ES_CONTINUOUS | NativeMethods.ES_SYSTEM_REQUIRED);
return;
case EffectiveMode.ActiveStesSystemAndDisplay:
RefreshStesIfDue(nowUtc, NativeMethods.ES_CONTINUOUS | NativeMethods.ES_SYSTEM_REQUIRED | NativeMethods.ES_DISPLAY_REQUIRED);
return;
}
}
private void ClearExecutionState()
{
try
{
NativeMethods.SetThreadExecutionState(NativeMethods.ES_CONTINUOUS);
}
catch
{
// Ignore.
}
}
private void RefreshStesIfDue(DateTime nowUtc, uint flags)
{
if ((nowUtc - _lastStesRefreshUtc).TotalSeconds < 15)
{
return;
}
_lastStesRefreshUtc = nowUtc;
try
{
NativeMethods.SetThreadExecutionState(flags);
}
catch
{
// Ignore.
}
}
private void PulseKeyIfDue(DateTime nowUtc)
{
if ((nowUtc - _lastPulseUtc).TotalSeconds < 59)
{
return;
}
_lastPulseUtc = nowUtc;
SendConfiguredKeyPulse();
}
private void SendConfiguredKeyPulse()
{
byte vk = (byte)_options.VirtualKey;
bool pressShiftModifier = _options.KeyWithShift;
try
{
if (pressShiftModifier && vk != 0x10 && vk != 0xA0)
{
NativeMethods.keybd_event(0x10, 0, 0, UIntPtr.Zero);
}
NativeMethods.keybd_event(vk, 0, 0, UIntPtr.Zero);
NativeMethods.keybd_event(vk, 0, NativeMethods.KEYEVENTF_KEYUP, UIntPtr.Zero);
if (pressShiftModifier && vk != 0x10 && vk != 0xA0)
{
NativeMethods.keybd_event(0x10, 0, NativeMethods.KEYEVENTF_KEYUP, UIntPtr.Zero);
}
}
catch
{
// Ignore.
}
}
private void SetManualOverride(ManualOverrideKind kind, double? minutes)
{
_manualOverrideKind = kind;
if (minutes.HasValue)
{
_manualOverrideUntilLocal = DateTime.Now.AddMinutes(minutes.Value);
}
else
{
_manualOverrideUntilLocal = null;
}
TickCore();
}
private void ToggleManual()
{
bool currentEffective = _hasLastEffectiveActive && _lastEffectiveActive;
SetManualOverride(currentEffective ? ManualOverrideKind.ForceInactive : ManualOverrideKind.ForceActive, null);
}
private void RevertToParameters()
{
_manualOverrideKind = ManualOverrideKind.None;
_manualOverrideUntilLocal = null;
TickCore();
}
private void UpdateMenuState()
{
bool manualActive = _manualOverrideKind == ManualOverrideKind.ForceActive;
bool manualInactive = _manualOverrideKind == ManualOverrideKind.ForceInactive;
bool dialogVisible = _statusForm != null && !_statusForm.IsDisposed && _statusForm.Visible;
_miActiveNow.Checked = manualActive && !_manualOverrideUntilLocal.HasValue;
_miInactiveNow.Checked = manualInactive && !_manualOverrideUntilLocal.HasValue;
_miTimed5.Checked = manualActive && IsMinutesApproximately(5);
_miTimed15.Checked = manualActive && IsMinutesApproximately(15);
_miTimed30.Checked = manualActive && IsMinutesApproximately(30);
_miTimed60.Checked = manualActive && IsMinutesApproximately(60);
_miTimed120.Checked = manualActive && IsMinutesApproximately(120);
_miTimedInactive5.Checked = manualInactive && IsMinutesApproximately(5);
_miTimedInactive15.Checked = manualInactive && IsMinutesApproximately(15);
_miTimedInactive30.Checked = manualInactive && IsMinutesApproximately(30);
_miRevert.Enabled = _manualOverrideKind != ManualOverrideKind.None;
_miShowHide.Text = dialogVisible ? "Hide Status" : "Show Status";
string stateText = (_hasLastEffectiveActive && _lastEffectiveActive) ? "Active" : "Inactive";
_miStatus.Text = "Status: " + stateText + " | " + GetMethodSummary();
}
private bool IsMinutesApproximately(int minutes)
{
if (!_manualOverrideUntilLocal.HasValue)
{
return false;
}
double diff = (_manualOverrideUntilLocal.Value - DateTime.Now).TotalMinutes;
return diff > minutes - 1.1 && diff <= minutes + 0.1;
}
private void UpdateStatusForm()
{
if (_statusForm == null || _statusForm.IsDisposed)
{
return;
}
string state = (_hasLastEffectiveActive && _lastEffectiveActive) ? "ACTIVE" : "INACTIVE";
string detail = "Reason: " + _lastReason + Environment.NewLine +
"Mode: " + GetMethodSummary() +
" | Key: " + FormatVirtualKey(_options.VirtualKey) + (_options.KeyWithShift ? " + Shift" : string.Empty);
if (_manualOverrideKind != ManualOverrideKind.None)
{
detail += Environment.NewLine + "Manual override: " + _manualOverrideKind.ToString() +
(_manualOverrideUntilLocal.HasValue ? (" until " + _manualOverrideUntilLocal.Value.ToString("HH:mm:ss")) : string.Empty);
}
string condition = _lastConditionSummary;
if (_options.CpuThresholdPercent.HasValue)
{
condition += Environment.NewLine + _lastCpuSummary;
}
if (_options.UnknownSwitches.Count > 0)
{
condition += Environment.NewLine + "Unknown switches ignored: " + string.Join(", ", _options.UnknownSwitches.ToArray());
}
if (_options.AllowLocalCompat)
{
condition += Environment.NewLine + "Compatibility flag -allowlocal is recognized (no special handling required for local IPC).";
}
_statusForm.UpdateView(state, detail, condition);
}
private void UpdateTrayTooltip(bool active, string reason)
{
string text = "Portable Caffeine: " + (active ? "Active" : "Inactive");
if (!string.IsNullOrEmpty(reason))
{
text += " | " + reason;
}
if (text.Length > 63)
{
text = text.Substring(0, 63);
}
_tray.Text = text;
}
private void SetTrayIcon(bool active)
{
if (active)
{
_tray.Icon = _trayIconActive ?? SystemIcons.Information;
}
else
{
_tray.Icon = _options.NoHIcon
? (_trayIconActive ?? SystemIcons.Information)
: (_trayIconInactive ?? SystemIcons.Warning);
}
}
private void ShowNotify(string title, string text)
{
try
{
_tray.BalloonTipTitle = title;
_tray.BalloonTipText = string.IsNullOrEmpty(text) ? string.Empty : text;
_tray.BalloonTipIcon = ToolTipIcon.Info;
_tray.ShowBalloonTip(1500);
}
catch
{
// Ignore notification failures.
}
}
private void OnIpcPayload(object sender, string payload)
{
if (string.IsNullOrEmpty(payload))
{
return;
}
string cmd = payload.Trim();
if (cmd.Length == 0)
{
return;
}
if (string.Equals(cmd, "EXIT_REPLACE", StringComparison.Ordinal))
{
ExitApplication();
return;
}
if (cmd.StartsWith("APPCMD:", StringComparison.Ordinal))
{
string name = cmd.Substring("APPCMD:".Length);
ApplyRemoteCommand(name);
return;
}
if (string.Equals(cmd, "SHOW", StringComparison.Ordinal))
{
ShowStatusForm(true);
}
}
private void ApplyRemoteCommand(string name)
{
if (string.IsNullOrEmpty(name))
{
return;
}
if (string.Equals(name, "AppExit", StringComparison.OrdinalIgnoreCase))
{
ExitApplication();
return;
}
if (string.Equals(name, "AppOn", StringComparison.OrdinalIgnoreCase))
{
SetManualOverride(ManualOverrideKind.ForceActive, null);
return;
}
if (string.Equals(name, "AppOff", StringComparison.OrdinalIgnoreCase))
{
SetManualOverride(ManualOverrideKind.ForceInactive, null);
return;
}
if (string.Equals(name, "AppToggle", StringComparison.OrdinalIgnoreCase))
{
ToggleManual();
return;
}
if (string.Equals(name, "AppToggleShowDialog", StringComparison.OrdinalIgnoreCase))
{
ToggleManual();
ShowStatusForm(true);
return;
}
if (string.Equals(name, "ShowDialog", StringComparison.OrdinalIgnoreCase))
{
ShowStatusForm(true);
}
}
private void ExitApplication()
{
if (_disposed)
{
return;
}
ExitThread();
}
protected override void ExitThreadCore()
{
if (_disposed)
{
base.ExitThreadCore();
return;
}
_disposed = true;
try
{
ClearExecutionState();
}
catch
{
}
try
{
if (_timer != null)
{
_timer.Stop();
_timer.Dispose();
}
}
catch
{
}
try
{
if (_tray != null)
{
_tray.Visible = false;
_tray.Dispose();
}
}
catch
{
}
try
{
if (_trayIconActive != null)
{
_trayIconActive.Dispose();
}
if (_trayIconInactive != null)
{
_trayIconInactive.Dispose();
}
}
catch
{
}
try
{
if (_ipcForm != null && !_ipcForm.IsDisposed)
{
_ipcForm.Close();
_ipcForm.Dispose();
}
}
catch
{
}
try
{
if (_statusForm != null && !_statusForm.IsDisposed)
{
_statusForm.Close();
_statusForm.Dispose();
}
}
catch
{
}
try
{
if (_cpuCounter != null)
{
_cpuCounter.Dispose();
}
}
catch
{
}
try
{
if (_instanceMutex != null)
{
_instanceMutex.ReleaseMutex();
_instanceMutex.Dispose();
}
}
catch
{
}
base.ExitThreadCore();
}
private void TryInitCpuCounter()
{
try
{
_cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
_cpuCounter.NextValue();
_cpuCounterPrimed = false;
_cpuCounterFailed = false;
}
catch
{
_cpuCounterFailed = true;
_cpuCounter = null;
}
}
private bool IsCpuAtOrAboveThreshold(int threshold, out string summary)
{
summary = "CPU >= " + threshold.ToString(CultureInfo.InvariantCulture) + "% required";
if (_cpuCounterFailed)
{
summary = "CPU counter unavailable (condition not met)";
return false;
}
if (_cpuCounter == null)
{
TryInitCpuCounter();
if (_cpuCounter == null)
{
summary = "CPU counter unavailable (condition not met)";
return false;
}
}
try
{
float next = _cpuCounter.NextValue();
if (!_cpuCounterPrimed)
{
_cpuCounterPrimed = true;
summary = "CPU warmup sample...";
return false;
}
_lastCpuPercent = next;
bool ok = next >= threshold;
summary = "CPU " + next.ToString("0.0", CultureInfo.InvariantCulture) + "% vs threshold " + threshold.ToString(CultureInfo.InvariantCulture) + "% (" + (ok ? "matched" : "below") + ")";
return ok;
}
catch
{
_cpuCounterFailed = true;
summary = "CPU counter failed (condition not met)";
return false;
}
}
private bool ForegroundWindowMatches(string needle)
{
try
{
IntPtr hwnd = NativeMethods.GetForegroundWindow();
if (hwnd == IntPtr.Zero)
{
return false;
}
int length = NativeMethods.GetWindowTextLength(hwnd);
if (length <= 0)
{
return false;
}
StringBuilder sb = new StringBuilder(length + 1);
NativeMethods.GetWindowText(hwnd, sb, sb.Capacity);
string title = sb.ToString();
if (string.IsNullOrEmpty(title))
{
return false;
}
return title.IndexOf(needle, StringComparison.OrdinalIgnoreCase) >= 0;
}
catch
{
return false;
}
}
private bool IsOnAc(out string summary)
{
summary = "Power: unknown";
try
{
NativeMethods.SYSTEM_POWER_STATUS sps;
if (!NativeMethods.GetSystemPowerStatus(out sps))
{
summary = "Power status unavailable";
return false;
}
if (sps.ACLineStatus == 1)
{
summary = "Power: on AC";
return true;
}
if (sps.ACLineStatus == 0)
{
summary = "Power: on battery";
return false;
}
summary = "Power: AC unknown (allowing)";
return true;
}
catch
{
summary = "Power status error";
return false;
}
}
private string GetMethodSummary()
{
EffectiveMode mode = GetActiveMode();
switch (mode)
{
case EffectiveMode.ActiveKeyPulse:
return "KeyPulse";
case EffectiveMode.ActiveStesSystemOnly:
return "STES(System)";
case EffectiveMode.ActiveStesSystemAndDisplay:
return "STES(System+Display)";
default:
return "Inactive";
}
}
private static string FormatVirtualKey(ushort vk)
{
return "0x" + vk.ToString("X2", CultureInfo.InvariantCulture);
}
}
public static class Program
{
private const string MutexName = @"Local\PortableCaffeine.ZhornCompat";
[STAThread]
public static int Main(string[] args)
{
return Run(args);
}
public static int Run(string[] args)
{
Options options = CommandLine.Parse(args);
if (options.ShowHelp)
{
Console.WriteLine(BuildUsageText());
return 0;
}
bool createdNew;
Mutex instanceMutex = null;
try
{
instanceMutex = new Mutex(true, MutexName, out createdNew);
}
catch (Exception ex)
{
Console.Error.WriteLine("Failed to acquire single-instance mutex: " + ex.Message);
return 2;
}
if (!createdNew)
{
try
{
if (options.ReplaceExisting)
{
IpcTransport.Send("EXIT_REPLACE");
instanceMutex.Dispose();
instanceMutex = null;
DateTime deadline = DateTime.UtcNow.AddSeconds(8);
while (DateTime.UtcNow < deadline)
{
Thread.Sleep(200);
try
{
instanceMutex = new Mutex(true, MutexName, out createdNew);
if (createdNew)
{
break;
}
instanceMutex.Dispose();
instanceMutex = null;
}
catch
{
// Keep retrying.
}
}
if (!createdNew)
{
Console.Error.WriteLine("Could not replace existing instance (timeout).");
return 3;
}
}
else
{
if (options.HasAnyAppCommand)
{
bool sent = IpcTransport.Send("APPCMD:" + options.AppCommand.ToString());
return sent ? 0 : 1;
}
bool showSent = IpcTransport.Send("SHOW");
return showSent ? 0 : 1;
}
}
catch (Exception ex)
{
Console.Error.WriteLine("Failed to communicate with running instance: " + ex.Message);
return 1;
}
}
if (createdNew && options.HasAnyAppCommand && !options.ReplaceExisting)
{
// App commands target an existing instance only; no-op if nothing is running.
try
{
instanceMutex.ReleaseMutex();
}
catch
{
}
instanceMutex.Dispose();
return 0;
}
try
{
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
{
Console.Error.WriteLine("This script must run in an STA PowerShell host (Windows PowerShell 5.1 console is typically STA).");
return 4;
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
CaffeineAppContext ctx = null;
try
{
ctx = new CaffeineAppContext(options, instanceMutex);
instanceMutex = null; // ownership transferred
Application.Run(ctx);
}
finally
{
if (ctx != null)
{
ctx.Dispose();
}
}
return 0;
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.ToString());
return 5;
}
finally
{
if (instanceMutex != null)
{
try
{
instanceMutex.ReleaseMutex();
}
catch
{
}
try
{
instanceMutex.Dispose();
}
catch
{
}
}
}
}
private static string BuildUsageText()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("Portable Caffeine (Zhorn-compatible, PowerShell + embedded C#)");
sb.AppendLine();
sb.AppendLine("Common switches:");
sb.AppendLine(" -on / -off Start active or inactive (default active)");
sb.AppendLine(" -activefor:N Force active for N minutes");
sb.AppendLine(" -inactivefor:N Force inactive for N minutes");
sb.AppendLine(" -exitafter:N Exit app after N minutes");
sb.AppendLine(" -showdlg Show status dialog");
sb.AppendLine(" -ontaskbar Show status dialog in taskbar");
sb.AppendLine(" -notify Balloon notifications on state changes");
sb.AppendLine(" -lock Lock workstation after startup");
sb.AppendLine(" -replace Replace a running instance");
sb.AppendLine(" -watchwindow:TEXT Only active when foreground window title contains TEXT");
sb.AppendLine(" -activeperiods:RANGES Active only within local time ranges (e.g. 08:00-12:00,13:00-17:00)");
sb.AppendLine(" -activehours:RANGES Alias of -activeperiods");
sb.AppendLine(" -onac Only active when on AC power");
sb.AppendLine(" -cpu:N Only active when total CPU >= N percent");
sb.AppendLine(" -stes Use SetThreadExecutionState instead of key pulse");
sb.AppendLine(" -allowss Allow screensaver (uses STES system-only mode)");
sb.AppendLine(" -useshift / -leftshift Use Shift / Left Shift instead of F15");
sb.AppendLine(" -key:NN or -keypress:NN Use custom virtual key (decimal or hex, e.g. 0x7E)");
sb.AppendLine(" -keyshift[:NN] Press key with Shift modifier; optional custom key");
sb.AppendLine(" -appexit Ask running instance to exit");
sb.AppendLine(" -appon / -appoff Ask running instance to force active/inactive");
sb.AppendLine(" -apptoggle Ask running instance to toggle");
sb.AppendLine(" -apptoggleshowdlg Toggle and show running instance status dialog");
sb.AppendLine(" -nohicon Use same tray icon while inactive");
sb.AppendLine(" -allowlocal Compatibility flag (recognized)");
return sb.ToString();
}
}
}
'@
if (-not ('PortableCaffeine.Program' -as [type])) {
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
Add-Type -TypeDefinition $csharp -ReferencedAssemblies @(
'System.dll',
'System.Windows.Forms.dll',
'System.Drawing.dll'
)
}
$exitCode = [PortableCaffeine.Program]::Run([string[]]$args)
exit $exitCode