156 lines
5.1 KiB
C#
156 lines
5.1 KiB
C#
using System;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Windows.Interop;
|
|
using System.Windows.Threading;
|
|
|
|
namespace Bedtimer
|
|
{
|
|
public partial class OverlayWindow : Window
|
|
{
|
|
private readonly DispatcherTimer _ui;
|
|
private readonly DateTime _procStart = DateTime.Now;
|
|
|
|
private string _cycleId = "";
|
|
private DateTime WarnT, FlashT, BedT, LockT;
|
|
|
|
public OverlayWindow()
|
|
{
|
|
InitializeComponent();
|
|
|
|
Width = 360; Height = 96;
|
|
Background = System.Windows.Media.Brushes.Transparent;
|
|
WindowStyle = WindowStyle.None;
|
|
AllowsTransparency = true;
|
|
ShowInTaskbar = false;
|
|
Topmost = true;
|
|
|
|
Loaded += (_, __) =>
|
|
{
|
|
PositionTopRight(12);
|
|
MakeClickThrough(true);
|
|
};
|
|
|
|
_ui = new DispatcherTimer(TimeSpan.FromSeconds(1), DispatcherPriority.Render, (_, __) => Tick(), Dispatcher);
|
|
_ui.Start();
|
|
}
|
|
|
|
// unified signature: includes cycleId
|
|
public void SetSchedule(string cycleId, DateTime warnT, DateTime flashT, DateTime bedT, DateTime lockT)
|
|
{
|
|
_cycleId = cycleId;
|
|
WarnT = warnT; FlashT = flashT; BedT = bedT; LockT = lockT;
|
|
}
|
|
|
|
private void Tick()
|
|
{
|
|
var now = DateTime.Now;
|
|
|
|
// Prevent instant re-lock right after login/app start
|
|
bool startupGrace = (now - _procStart) < TimeSpan.FromSeconds(90);
|
|
|
|
// If we already locked for this cycle, stay hidden until next schedule is computed
|
|
if (!string.IsNullOrEmpty(_cycleId) && State.IsLockedForCycle(_cycleId))
|
|
{
|
|
Visibility = Visibility.Collapsed;
|
|
return;
|
|
}
|
|
|
|
if (now < WarnT)
|
|
{
|
|
Visibility = Visibility.Collapsed;
|
|
return;
|
|
}
|
|
|
|
if (now >= WarnT && now < FlashT)
|
|
{
|
|
Visibility = Visibility.Visible;
|
|
MakeClickThrough(true);
|
|
ShowClock(now, withCountdown: false);
|
|
return;
|
|
}
|
|
|
|
if (now >= FlashT && now < BedT)
|
|
{
|
|
Visibility = Visibility.Visible;
|
|
MakeClickThrough(true);
|
|
FlashBriefly(now);
|
|
ShowClock(now, withCountdown: true);
|
|
return;
|
|
}
|
|
|
|
if (now >= BedT && now < LockT)
|
|
{
|
|
Visibility = Visibility.Visible;
|
|
MakeClickThrough(true);
|
|
ShowBedtime();
|
|
return;
|
|
}
|
|
|
|
if (now >= LockT)
|
|
{
|
|
if (!startupGrace && !string.IsNullOrEmpty(_cycleId) && !State.IsLockedForCycle(_cycleId))
|
|
{
|
|
TryLock();
|
|
State.MarkLocked(_cycleId);
|
|
}
|
|
Visibility = Visibility.Collapsed;
|
|
}
|
|
}
|
|
|
|
private void ShowClock(DateTime now, bool withCountdown)
|
|
{
|
|
BedLabel.Visibility = Visibility.Collapsed;
|
|
HudPanel.Visibility = Visibility.Visible;
|
|
var t = now.ToString("h:mm tt");
|
|
HudText.Text = withCountdown ? $"{t} • Bed in {Pretty(BedT - now)}" : t;
|
|
}
|
|
|
|
private void ShowBedtime()
|
|
{
|
|
HudPanel.Visibility = Visibility.Collapsed;
|
|
BedLabel.Visibility = Visibility.Visible;
|
|
}
|
|
|
|
private static string Pretty(TimeSpan ts)
|
|
{
|
|
if (ts.TotalMinutes < 1) return $"{ts.Seconds}s";
|
|
if (ts.TotalHours < 1) return $"{(int)ts.TotalMinutes}m {ts.Seconds}s";
|
|
return $"{(int)ts.TotalHours}h {ts.Minutes}m";
|
|
}
|
|
|
|
private void FlashBriefly(DateTime now)
|
|
{
|
|
if (now.Second == 0)
|
|
{
|
|
var orig = Opacity; Opacity = 0.2;
|
|
_ = Task.Delay(1000).ContinueWith(_ => Dispatcher.Invoke(() => Opacity = orig));
|
|
}
|
|
}
|
|
|
|
[DllImport("user32.dll")] private static extern bool LockWorkStation();
|
|
private static void TryLock() { try { LockWorkStation(); } catch { } }
|
|
|
|
private void PositionTopRight(int margin)
|
|
{
|
|
var r = System.Windows.SystemParameters.WorkArea;
|
|
Left = r.Right - Width - margin;
|
|
Top = r.Top + margin;
|
|
}
|
|
|
|
private const int GWL_EXSTYLE = -20, WS_EX_TRANSPARENT = 0x20, WS_EX_TOOLWINDOW = 0x80, WS_EX_NOACTIVATE = 0x08000000;
|
|
[DllImport("user32.dll")] private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
|
[DllImport("user32.dll")] private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
|
|
|
private void MakeClickThrough(bool enable)
|
|
{
|
|
var hwnd = new WindowInteropHelper(this).Handle;
|
|
int ex = GetWindowLong(hwnd, GWL_EXSTYLE);
|
|
ex |= WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE;
|
|
ex = enable ? (ex | WS_EX_TRANSPARENT) : (ex & ~WS_EX_TRANSPARENT);
|
|
SetWindowLong(hwnd, GWL_EXSTYLE, ex);
|
|
}
|
|
}
|
|
}
|