Skip to content

Commit

Permalink
Add detection for current use of computer's camera
Browse files Browse the repository at this point in the history
Spawns another thread to poll the registry for any apps that have a reg value "LastUsedTimeStop" of 0. Reports the application name and the status to HA. Ref #1
  • Loading branch information
pathartl committed Oct 12, 2022
1 parent ecd5d6b commit 8c6b872
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 5 deletions.
76 changes: 76 additions & 0 deletions CameraDetectionService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

namespace TeamsPresence
{
public class CameraStatusChangedEventArgs : EventArgs
{
public CameraStatus Status { get; set; }
public string AppName { get; set; }
}

public class CameraDetectionService
{
private const string SubKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\webcam";
private const string AppNamePattern = @"_[\d|\w]{13}$";

public event EventHandler<CameraStatusChangedEventArgs> StatusChanged;

private int PollingRate;
private string ActiveAppName = "";

public CameraDetectionService(int pollingRate)
{
PollingRate = pollingRate;
}

public void Start()
{
var appNameRegex = new Regex(AppNamePattern);

while (true)
{
var activeCameraApp = GetActiveCameraApp();

if (activeCameraApp != null)
activeCameraApp = appNameRegex.Replace(activeCameraApp, "", 1);

if (activeCameraApp != ActiveAppName)
{
ActiveAppName = activeCameraApp;

StatusChanged?.Invoke(this, new CameraStatusChangedEventArgs()
{
Status = ActiveAppName == "" ? CameraStatus.Inactive : CameraStatus.Active,
AppName = ActiveAppName
});
}

Thread.Sleep(PollingRate);
}
}

private string GetActiveCameraApp()
{
var key = Registry.CurrentUser.OpenSubKey(SubKey);

foreach (var app in key.GetSubKeyNames())
{
var lastUsedTimeStop = Registry.CurrentUser.OpenSubKey($@"{SubKey}\{app}")?.GetValue("LastUsedTimeStop");

if (lastUsedTimeStop != null && (long)lastUsedTimeStop == 0)
{
return app;
}
}

return "";
}
}
}
42 changes: 39 additions & 3 deletions Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class Program
{
private static HomeAssistantService HomeAssistantService;
private static TeamsLogService TeamsLogService;
private static CameraDetectionService CameraDetectionService;
private static TeamsPresenceConfig Config;

private static NotifyIcon NotifyIcon;
Expand Down Expand Up @@ -63,8 +64,11 @@ static void Main(string[] args)
HomeAssistantUrl = "https://yourha.duckdns.org",
HomeAssistantToken = "eyJ0eXAiOiJKV1...",
AppDataRoamingPath = "",
StatusEntity = "sensor.teams_status",
ActivityEntity = "sensor.teams_activity",
StatusEntity = "sensor.teams_presence_status",
ActivityEntity = "sensor.teams_presence_activity",
CameraAppEntity = "sensor.teams_presence_camera_app",
CameraStatusEntity = "sensor.teams_presence_camera_status",
CameraStatusPollingRate = 1000,
FriendlyStatusNames = new Dictionary<TeamsStatus, string>()
{
{ TeamsStatus.Available, "Available" },
Expand All @@ -85,12 +89,23 @@ static void Main(string[] args)
{ TeamsActivity.NotInACall, "Not in a call" },
{ TeamsActivity.Unknown, "Unknown" }
},
FriendlyCameraStatusNames = new Dictionary<CameraStatus, string>()
{
{ CameraStatus.Inactive, "Inactive" },
{ CameraStatus.Active, "Active" }
},
ActivityIcons = new Dictionary<TeamsActivity, string>()
{
{ TeamsActivity.InACall, "mdi:phone-in-talk-outline" },
{ TeamsActivity.NotInACall, "mdi:phone-off" },
{ TeamsActivity.Unknown, "mdi:phone-cancel" }
}
},
CameraStatusIcons = new Dictionary<CameraStatus, string>()
{
{ CameraStatus.Inactive, "mdi:webcam-off" },
{ CameraStatus.Active, "mdi:webcam" }
},
CameraAppIcon = "mdi:application"
};

File.WriteAllText(configFilePath, JsonConvert.SerializeObject(Config, new JsonSerializerSettings()
Expand All @@ -108,22 +123,32 @@ static void Main(string[] args)
}

TeamsLogService = new TeamsLogService();
CameraDetectionService = new CameraDetectionService(Config.CameraStatusPollingRate);
HomeAssistantService = new HomeAssistantService(Config.HomeAssistantUrl, Config.HomeAssistantToken);

TeamsLogService.StatusChanged += Service_StatusChanged;
TeamsLogService.ActivityChanged += Service_ActivityChanged;

CameraDetectionService.StatusChanged += Camera_StatusChanged;

Thread presenceDetectionThread = new Thread(
delegate ()
{
TeamsLogService.Start();
});

Thread cameraDetectionThread = new Thread(
delegate ()
{
CameraDetectionService.Start();
});

NotifyIcon.BalloonTipText = "Service started. Waiting for Teams updates...";

NotifyIcon.ShowBalloonTip(2000);

presenceDetectionThread.Start();
cameraDetectionThread.Start();

Application.Run();
}
Expand Down Expand Up @@ -187,6 +212,17 @@ private static void Service_ActivityChanged(object sender, TeamsActivity activit
Console.WriteLine($"Updated activity to {Config.FriendlyActivityNames[activity]} ({activity})");
}

private static void Camera_StatusChanged(object sender, CameraStatusChangedEventArgs args)
{
HomeAssistantService.UpdateEntity(Config.CameraStatusEntity, Config.FriendlyCameraStatusNames[args.Status], Config.FriendlyCameraStatusNames[args.Status], Config.CameraStatusIcons[args.Status]);

Console.WriteLine($"Updated camera status to {args.Status}");

HomeAssistantService.UpdateEntity(Config.CameraAppEntity, args.AppName, args.AppName, Config.CameraAppIcon);

Console.WriteLine($"Updated camera app to {args.AppName}");
}

private static void OpenConfigDirectory(object sender, EventArgs e)
{
OpenConfigDirectory();
Expand Down
6 changes: 6 additions & 0 deletions TeamsEnums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,10 @@ public enum TeamsActivity
NotInACall,
InACall
}

public enum CameraStatus
{
Inactive,
Active
}
}
4 changes: 2 additions & 2 deletions TeamsPresence.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
<GenerateManifests>true</GenerateManifests>
</PropertyGroup>
<PropertyGroup>
<SignManifests>true</SignManifests>
<SignManifests>false</SignManifests>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>Icon.ico</ApplicationIcon>
Expand Down Expand Up @@ -89,6 +89,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="CameraDetectionService.cs" />
<Compile Include="HomeAssistantEntityStateUpdate.cs" />
<Compile Include="HomeAssistantEntityStateUpdateAttributes.cs" />
<Compile Include="HomeAssistantService.cs" />
Expand All @@ -106,7 +107,6 @@
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
<None Include="TeamsPresence_TemporaryKey.pfx" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resource.Designer.cs">
Expand Down
8 changes: 8 additions & 0 deletions TeamsPresenceConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,18 @@ public class TeamsPresenceConfig

public string StatusEntity { get; set; }
public string ActivityEntity { get; set; }
public string CameraStatusEntity { get; set; }
public string CameraAppEntity { get; set; }

public int CameraStatusPollingRate { get; set; }

public Dictionary<TeamsStatus, string> FriendlyStatusNames { get; set; }
public Dictionary<TeamsActivity, string> FriendlyActivityNames { get; set; }
public Dictionary<CameraStatus, string> FriendlyCameraStatusNames { get; set; }

public Dictionary<TeamsActivity, string> ActivityIcons { get; set; }
public Dictionary<CameraStatus, string> CameraStatusIcons { get; set; }

public string CameraAppIcon { get; set; }
}
}

0 comments on commit 8c6b872

Please sign in to comment.