Skip to content

Commit

Permalink
Added option to lock computer when closing lid
Browse files Browse the repository at this point in the history
Moved activity monitoring from MainWindow to separate class (ActivityMonitor)
  • Loading branch information
jokedst committed Jul 8, 2018
1 parent 9b4211d commit e2333c8
Show file tree
Hide file tree
Showing 17 changed files with 1,553 additions and 167 deletions.
1 change: 1 addition & 0 deletions ATray.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/CodeAnnotations/NamespacesWithAnnotations/=ATray_002EAnnotations/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/ThisQualifier/INSTANCE_MEMBERS_QUALIFY_MEMBERS/@EntryValue">None</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FCONSTANT/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/JavaScriptNaming/UserRules/=JS_005FBLOCK_005FSCOPE_005FFUNCTION/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
Expand Down
5 changes: 5 additions & 0 deletions ATray/ATray.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,10 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Activity\ActivityMonitor.cs" />
<Compile Include="Activity\DayActivityList.cs" />
<Compile Include="Activity\HashList.cs" />
<Compile Include="Activity\IActivityMonitor.cs" />
<Compile Include="Activity\WorkPlayFilter.cs" />
<Compile Include="AutoStart.cs" />
<Compile Include="Dialogs\ActivityHistoryForm.cs">
Expand Down Expand Up @@ -209,6 +211,7 @@
<Compile Include="IconAnimator.cs" />
<Compile Include="NativeMethods.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\Annotations.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Dialogs\SettingsForm.cs">
<SubType>Form</SubType>
Expand All @@ -217,7 +220,9 @@
<DependentUpon>SettingsForm.cs</DependentUpon>
</Compile>
<Compile Include="Tools\IFactory.cs" />
<Compile Include="Tools\InternalTraceListener.cs" />
<Compile Include="Tools\OverallStatusType.cs" />
<Compile Include="Tools\PubSubHub.cs" />
<Compile Include="Tools\SimpleFactory.cs" />
<Compile Include="Tools\SimpleWebServer.cs" />
<EmbeddedResource Include="Dialogs\ActivityHistoryForm.resx">
Expand Down
157 changes: 157 additions & 0 deletions ATray/Activity/ActivityMonitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using ATray.Annotations;
using Microsoft.Win32;

namespace ATray.Activity
{
/// <summary>
/// Monitors if the user is active or not, and for how long
/// </summary>
public class ActivityMonitor : INotifyPropertyChanged, IActivityMonitor
{
private uint _workingtime;
private string _workingTime;
private string _currentlyActiveWindow;
private string _idleTime;
private DateTime _lastSave = DateTime.MinValue;
private DateTime _lastTimerEvent = DateTime.MinValue;
private DateTime _startTime = DateTime.Now;
private readonly Timer _mainTimer = new Timer {Interval = 1000};

public ActivityMonitor()
{
_mainTimer.Tick += OnMainTimerTick;
_mainTimer.Start();

SystemEvents.SessionSwitch += SystemEventsOnSessionSwitch;
}

public bool InWarnState { get; set; }

public bool HasWorkedTooLong { get; private set; }
public bool HasTakenBreak { get; private set; }

public event Action<object, EventArgs> UserWorkedTooLong;
public event Action<object, EventArgs> UserHasTakenBreak;
public event Action<object, EventArgs> UserIsBackFromAbsense;
public event PropertyChangedEventHandler PropertyChanged;

public string IdleTime
{
get => _idleTime;
private set => SetProperty(ref _idleTime, value);
}

public string WorkingTime
{
get => _workingTime;
private set => SetProperty(ref _workingTime, value);
}

public string CurrentlyActiveWindow
{
get => _currentlyActiveWindow;
private set => SetProperty(ref _currentlyActiveWindow, value);
}

[NotifyPropertyChangedInvocator]
private void SetProperty<T>(ref T underlyingField, T newValue, [CallerMemberName] string propertyName = null)
{
if (newValue.Equals(underlyingField)) return;
underlyingField = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

private MonitorState LastMonitorState = MonitorState.On;

public void HandleWindowsMessage( Message m)
{
// Detect closing/opening of lid
if (m.Msg == NativeMethods.WM_POWERBROADCAST && m.WParam.ToInt32() == NativeMethods.PBT_POWERSETTINGCHANGE)
{
var pData = (IntPtr) (m.LParam.ToInt32() + Marshal.SizeOf<NativeMethods.POWERBROADCAST_SETTING>());
var iData = (int) Marshal.PtrToStructure(pData, typeof(int));
var monitorState = (MonitorState) iData;

if (monitorState != LastMonitorState)
{
// Technically this could happen when only turning off ONE screen in a multiscreen setup, but meh
Trace.TraceInformation("Monitor changed to " + monitorState);
if (monitorState == MonitorState.Off && Program.Configuration.LockOnMonitorOff)
NativeMethods.LockWorkStation();
LastMonitorState = monitorState;
}
}
}

private void SystemEventsOnSessionSwitch(object sender, SessionSwitchEventArgs sessionSwitchEventArgs)
{
// When logging in or unlocking we want to update immediatly
if (sessionSwitchEventArgs.Reason == SessionSwitchReason.SessionLogon ||
sessionSwitchEventArgs.Reason == SessionSwitchReason.SessionUnlock)
//_repositoryCollection.TriggerUpdate(r => r.UpdateSchedule != Schedule.Never);
UserIsBackFromAbsense?.Invoke(this, EventArgs.Empty);

Trace.TraceInformation("Session changed ({0})", sessionSwitchEventArgs.Reason);
}

private void OnMainTimerTick(object sender, EventArgs e)
{
var idleMilliseconds = NativeMethods.GetIdleMilliseconds();
// Only call "Now" once to avoid annoying bugs
var now = DateTime.Now;

var unpoweredMilliseconds = (uint) Math.Min(now.Subtract(_lastTimerEvent).TotalMilliseconds, uint.MaxValue);
if (unpoweredMilliseconds > 100_000)
{
Trace.TraceInformation("No timer events for {0} ms - unpowered?", unpoweredMilliseconds);
idleMilliseconds = Math.Max(idleMilliseconds, unpoweredMilliseconds - 2000);
}

//lblSmall.Text =Helpers.MillisecondsToString(idle);
IdleTime = Helpers.MillisecondsToString(idleMilliseconds);

if (idleMilliseconds > Program.Configuration.MinimumBrakeLength * 1000)
{
_workingtime = 0;
_startTime = now;
if (InWarnState)
{
UserHasTakenBreak?.Invoke(this, EventArgs.Empty);
InWarnState = false;
}
}
else
{
_workingtime += (uint) now.Subtract(_startTime).TotalMilliseconds;
_startTime = now;

if (_workingtime > Program.Configuration.MaximumWorkTime * 1000 && !InWarnState)
{
InWarnState = true;
UserWorkedTooLong?.Invoke(this, EventArgs.Empty);
}
}

NativeMethods.GetForegroundProcessInfo(out var foregroundApp, out var foregroundTitle);

if (now.Subtract(_lastSave).TotalSeconds > Program.Configuration.SaveInterval)
{
// Time to save
var wasActive = idleMilliseconds < Program.Configuration.SaveInterval * 1000;
ActivityManager.SaveActivity(now, (uint) Program.Configuration.SaveInterval, wasActive, foregroundApp,
foregroundTitle);
_lastSave = now;
}

WorkingTime = Helpers.MillisecondsToString(_workingtime);
CurrentlyActiveWindow = foregroundApp + " : " + foregroundTitle;
_lastTimerEvent = now;
}
}
}
22 changes: 22 additions & 0 deletions ATray/Activity/IActivityMonitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace ATray.Activity
{
public interface IActivityMonitor
{
string CurrentlyActiveWindow { get; }
bool HasTakenBreak { get; }
bool HasWorkedTooLong { get; }
string IdleTime { get; }
string WorkingTime { get; }

event PropertyChangedEventHandler PropertyChanged;
event Action<object, EventArgs> UserHasTakenBreak;
event Action<object, EventArgs> UserIsBackFromAbsense;
event Action<object, EventArgs> UserWorkedTooLong;

void HandleWindowsMessage(Message m);
}
}
4 changes: 3 additions & 1 deletion ATray/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public class Configuration
public string SharedActivityStorage { get; set; }
[Description("Start application automatically at login"), Category("General"), DefaultValue(false)]
public bool StartAtLogin { get; set; }
[Description("Locks computer when the monitor is turned off (=laptop lid close)"), Category("General"), DefaultValue(false)]
public bool LockOnMonitorOff { get; set; }

public Configuration(string filename = null)
{
Expand Down Expand Up @@ -111,7 +113,7 @@ public void ReadFromIniFile(string filename = null)
}

/// <summary>
/// Saves all settings to an ini-file, under "General" section
/// Saves all settings to an ini-file
/// </summary>
/// <param name="filename">File to write to (default uses same file as loaded from)</param>
public void SaveToIniFile(string filename = null)
Expand Down
15 changes: 15 additions & 0 deletions ATray/Helpers.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace ATray
Expand Down Expand Up @@ -86,6 +87,20 @@ public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TVal
}

public static HashSet<T> ToHashSet<T>(this IEnumerable<T> values) => new HashSet<T>(values);

public static string MillisecondsToString(uint ms)
{
var totsec = (uint)Math.Round(ms / 1000d);
var totmin = totsec / 60;
var tothour = totmin / 60;
var sec = totsec % 60;
var min = totmin % 60;
var sb = new StringBuilder();
if (tothour > 0) sb.AppendFormat("{0}h", tothour);
if (totmin > 0) sb.AppendFormat("{0}m", min);
sb.AppendFormat("{0}s", sec);
return sb.ToString();
}
}

internal static class ControlExtensions
Expand Down
36 changes: 14 additions & 22 deletions ATray/MainWindow.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit e2333c8

Please sign in to comment.