Full Code of isaacrlevin/PresenceLight for AI

main 5603889a898e cached
230 files
696.5 KB
162.7k tokens
493 symbols
1 requests
Download .txt
Showing preview only (765K chars total). Download the full file or copy to clipboard to get everything.
Repository: isaacrlevin/PresenceLight
Branch: main
Commit: 5603889a898e
Files: 230
Total size: 696.5 KB

Directory structure:
gitextract_bd12mvz1/

├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── Azure_Blob_Deploy.yml
│       ├── Choco.yml
│       ├── Deploy_Desktop.yml
│       ├── Deploy_Web.yml
│       ├── Sign.yml
│       └── WinGet.yml
├── .gitignore
├── Build/
│   ├── Signing/
│   │   └── filelist.txt
│   ├── Worker/
│   │   ├── presencelight.crt
│   │   ├── presencelight.service
│   │   └── trust-cert.sh
│   └── scripts/
│       ├── push-choco.ps1
│       ├── push-winget.ps1
│       ├── update-desktop-settings.ps1
│       └── update-web-settings.ps1
├── LICENSE
├── PrivacyPolicy.md
├── README.md
├── chocolatey/
│   ├── PresenceLight.nuspec
│   └── tools/
│       ├── ChocolateyBeforeModify.ps1
│       ├── ChocolateyInstall.ps1
│       ├── ChocolateyUninstall.ps1
│       ├── LICENSE.txt
│       └── Verification.txt
├── docker-compose-example.yml
├── docs/
│   ├── CONTRIBUTING.md
│   ├── configure-custom-api.md
│   ├── configure-entra-app.md
│   ├── configure-hardware.md
│   ├── desktop-README.md
│   ├── faq.md
│   └── web-README.md
├── src/
│   ├── .editorconfig
│   ├── DesktopClient/
│   │   ├── Directory.Build.props
│   │   ├── Directory.Build.targets
│   │   ├── PresenceLight/
│   │   │   ├── App.xaml
│   │   │   ├── App.xaml.cs
│   │   │   ├── MainWindow.xaml
│   │   │   ├── MainWindow.xaml.cs
│   │   │   ├── PresenceLight.csproj
│   │   │   ├── PresenceLight.exe.gui
│   │   │   ├── Properties/
│   │   │   │   ├── AssemblyInfo.cs
│   │   │   │   ├── PublishProfiles/
│   │   │   │   │   ├── WinARM64.pubxml
│   │   │   │   │   ├── WinX64.pubxml
│   │   │   │   │   └── WinX86.pubxml
│   │   │   │   ├── Resources.Designer.cs
│   │   │   │   ├── Resources.resx
│   │   │   │   ├── Settings.Designer.cs
│   │   │   │   ├── Settings.settings
│   │   │   │   └── app.manifest
│   │   │   ├── Services/
│   │   │   │   ├── Constants.cs
│   │   │   │   ├── MessageBoxHelper.cs
│   │   │   │   ├── NotifyIcon.cs
│   │   │   │   ├── Settings/
│   │   │   │   │   ├── AppPackageSettingsService.cs
│   │   │   │   │   └── StandaloneSettingsService.cs
│   │   │   │   ├── SingleInstanceAppMutex.cs
│   │   │   │   └── Telemetry/
│   │   │   │       └── DiagnosticsClient.cs
│   │   │   ├── appsettings.json
│   │   │   └── wwwroot/
│   │   │       └── index.html
│   │   └── PresenceLight.Package/
│   │       ├── Package-Local.appxmanifest
│   │       ├── Package-Nightly.appxmanifest
│   │       ├── Package.appinstaller
│   │       ├── Package.appxmanifest
│   │       ├── Package.xml
│   │       └── PresenceLight.Package.wapproj
│   ├── DockerCompose/
│   │   ├── .dockerignore
│   │   ├── docker-compose.dcproj
│   │   ├── docker-compose.override.yml
│   │   └── docker-compose.yml
│   ├── PresenceLight.Core/
│   │   ├── Configuration/
│   │   │   ├── AAD.cs
│   │   │   ├── AppState.cs
│   │   │   ├── AvailabilityStatus.cs
│   │   │   ├── Base.cs
│   │   │   ├── BaseLight.cs
│   │   │   ├── CustomApi.cs
│   │   │   ├── CustomApiSetting.cs
│   │   │   ├── Hue.cs
│   │   │   ├── ISettingsService.cs
│   │   │   ├── LIFX.cs
│   │   │   ├── LightSettings.cs
│   │   │   ├── LocalSerialHost.cs
│   │   │   ├── LocalSerialHostSetting.cs
│   │   │   ├── Statuses.cs
│   │   │   ├── Wiz.cs
│   │   │   └── Yeelight.cs
│   │   ├── GraphServices/
│   │   │   ├── AuthorizationProvider.cs
│   │   │   ├── GetIsInitialized/
│   │   │   │   ├── GetIsInitializedCommand.cs
│   │   │   │   └── GetIsInitializedHandler.cs
│   │   │   ├── GetPhoto/
│   │   │   │   ├── GetPhotoCommand.cs
│   │   │   │   └── GetPhotoHandler.cs
│   │   │   ├── GetPresence/
│   │   │   │   ├── GetPresenceCommand.cs
│   │   │   │   └── GetPresenceHandler.cs
│   │   │   ├── GetProfile/
│   │   │   │   ├── GetProfileCommand.cs
│   │   │   │   └── GetProfileHandler.cs
│   │   │   ├── GetProfileAndPresence/
│   │   │   │   ├── GetProfileAndPresenceCommand.cs
│   │   │   │   └── GetProfileAndPresenceHandler.cs
│   │   │   ├── GraphWrapper.cs
│   │   │   ├── Initialize/
│   │   │   │   ├── InitializeCommand.cs
│   │   │   │   └── InitializeHandler.cs
│   │   │   ├── LoginService.cs
│   │   │   └── TokenCacheHelper.cs
│   │   ├── Helpers.cs
│   │   ├── Lights/
│   │   │   ├── CustomApiService/
│   │   │   │   ├── CustomApiService.cs
│   │   │   │   ├── Initialize/
│   │   │   │   │   ├── InitializeCommand.cs
│   │   │   │   │   └── InitializeHandler.cs
│   │   │   │   └── SetColor/
│   │   │   │       ├── SetColorCommand.cs
│   │   │   │       └── SetColorHandler.cs
│   │   │   ├── HueServices/
│   │   │   │   ├── FindBridge/
│   │   │   │   │   ├── FindBridgeCommand.cs
│   │   │   │   │   └── FindBridgeHandler.cs
│   │   │   │   ├── GetGroups/
│   │   │   │   │   ├── GetGroupsCommand.cs
│   │   │   │   │   └── GetGroupsHandler.cs
│   │   │   │   ├── GetLights/
│   │   │   │   │   ├── GetLightsCommand.cs
│   │   │   │   │   └── GetLightsHandler.cs
│   │   │   │   ├── HueService.cs
│   │   │   │   ├── Initialize/
│   │   │   │   │   ├── InitializeCommand.cs
│   │   │   │   │   └── InitializeHandler.cs
│   │   │   │   ├── RegisterBridge/
│   │   │   │   │   ├── RegisterBridgeCommand.cs
│   │   │   │   │   └── RegisterBridgeHandler.cs
│   │   │   │   └── SetColor/
│   │   │   │       ├── SetColorCommand.cs
│   │   │   │       └── SetColorHandler.cs
│   │   │   ├── LifxServices/
│   │   │   │   ├── GetAllGroups/
│   │   │   │   │   ├── GetAllGroupsCommand.cs
│   │   │   │   │   └── GetAllGroupsHandler.cs
│   │   │   │   ├── GetAllLights/
│   │   │   │   │   ├── GetAllLightsCommand.cs
│   │   │   │   │   └── GetAllLightsHandler.cs
│   │   │   │   ├── Initialize/
│   │   │   │   │   ├── InitializeCommand.cs
│   │   │   │   │   └── InitializeHandler.cs
│   │   │   │   ├── LIFXOAuthHelper.cs
│   │   │   │   ├── LifxService.cs
│   │   │   │   └── SetColor/
│   │   │   │       ├── SetColorCommand.cs
│   │   │   │       └── SetColorHandler.cs
│   │   │   ├── LocalSerialHostService/
│   │   │   │   ├── GetSerialHosts/
│   │   │   │   │   ├── GetSerialHostsCommand.cs
│   │   │   │   │   └── GetSerialHostsHandler.cs
│   │   │   │   ├── Initialize/
│   │   │   │   │   ├── InitializeCommand.cs
│   │   │   │   │   └── InitializeHandler.cs
│   │   │   │   ├── LocalSerialHost.cs
│   │   │   │   └── SetColor/
│   │   │   │       ├── SetColorCommand.cs
│   │   │   │       └── SetColorHandler.cs
│   │   │   ├── RemoteHueServices/
│   │   │   │   ├── GetGroups/
│   │   │   │   │   ├── GetGroupsCommand.cs
│   │   │   │   │   └── GetGroupsHandler.cs
│   │   │   │   ├── GetLights/
│   │   │   │   │   ├── GetLightsCommand.cs
│   │   │   │   │   └── GetLightsHandler.cs
│   │   │   │   ├── RegisterBridge/
│   │   │   │   │   ├── RegisterBridgeCommand.cs
│   │   │   │   │   └── RegisterBridgeHandler.cs
│   │   │   │   ├── RemoteAuthenticationClient.cs
│   │   │   │   ├── RemoteHueService.cs
│   │   │   │   └── SetColor/
│   │   │   │       ├── SetColorCommand.cs
│   │   │   │       └── SetColorHandler.cs
│   │   │   ├── ServicesExtensions.cs
│   │   │   ├── WizServices/
│   │   │   │   ├── GetLights/
│   │   │   │   │   ├── GetLightCommand.cs
│   │   │   │   │   └── GetLightHandler.cs
│   │   │   │   ├── IPAddressExtensions.cs
│   │   │   │   ├── SetColor/
│   │   │   │   │   ├── SetColorCommand.cs
│   │   │   │   │   └── SetColorHandler.cs
│   │   │   │   ├── WizLight.cs
│   │   │   │   └── WizService.cs
│   │   │   ├── WorkingHoursServices/
│   │   │   │   ├── IsInWorkingHours/
│   │   │   │   │   ├── IsInWorkingHoursCommand.cs
│   │   │   │   │   └── IsInWorkingHoursHandler.cs
│   │   │   │   ├── UseWorkingHours/
│   │   │   │   │   ├── UseWorkingHoursCommand.cs
│   │   │   │   │   └── UseWorkingHoursHandler.cs
│   │   │   │   └── WorkingHoursService.cs
│   │   │   └── YeelightServices/
│   │   │       ├── FindLights/
│   │   │       │   ├── FindLightsCommand.cs
│   │   │       │   └── FindLightsHandler.cs
│   │   │       ├── SetColor/
│   │   │       │   ├── SetColorCommand.cs
│   │   │       │   └── SetColorHandler.cs
│   │   │       └── YeelightService.cs
│   │   ├── Logging/
│   │   │   ├── ILoggerExtensions.cs
│   │   │   └── PresenceEventsLogSink.cs
│   │   └── PresenceLight.Core.csproj
│   ├── PresenceLight.Razor/
│   │   ├── Components/
│   │   │   ├── Layout/
│   │   │   │   ├── MainLayout.razor
│   │   │   │   ├── MainLayout.razor.css
│   │   │   │   ├── NavMenu.razor
│   │   │   │   └── NavMenu.razor.css
│   │   │   ├── Pages/
│   │   │   │   ├── About.razor
│   │   │   │   ├── Color.razor
│   │   │   │   ├── Color.razor.css
│   │   │   │   ├── CustomApiSetup.razor
│   │   │   │   ├── HueSetup.razor
│   │   │   │   ├── Index.razor
│   │   │   │   ├── Index.razor.css
│   │   │   │   ├── Lifx.razor
│   │   │   │   ├── LocalSerialHostSetup.razor
│   │   │   │   ├── Logs.razor
│   │   │   │   ├── Settings.razor
│   │   │   │   ├── Wiz.razor
│   │   │   │   └── Yeelight.razor
│   │   │   ├── PresenceLightClientApp.razor
│   │   │   ├── Shared/
│   │   │   │   ├── Confirm.razor
│   │   │   │   ├── LoginDisplay.razor
│   │   │   │   └── Statuses.razor
│   │   │   └── _Imports.razor
│   │   ├── PresenceLight.Razor.csproj
│   │   ├── Services/
│   │   │   ├── AppInfo.cs
│   │   │   ├── AppVersionTelemetryInitializer.cs
│   │   │   └── WebAppSettingsService.cs
│   │   └── wwwroot/
│   │       ├── css/
│   │       │   ├── open-iconic/
│   │       │   │   ├── FONT-LICENSE
│   │       │   │   ├── ICON-LICENSE
│   │       │   │   ├── README.md
│   │       │   │   └── font/
│   │       │   │       └── fonts/
│   │       │   │           └── open-iconic.otf
│   │       │   └── site.css
│   │       └── js/
│   │           └── site.js
│   ├── PresenceLight.Web/
│   │   ├── .config/
│   │   │   └── dotnet-tools.json
│   │   ├── App.razor
│   │   ├── AppOld.razor
│   │   ├── Dockerfile
│   │   ├── Dockerfile.debian-arm32
│   │   ├── Dockerfile.debian-arm64
│   │   ├── Pages/
│   │   │   ├── Error.cshtml
│   │   │   ├── Error.cshtml.cs
│   │   │   └── _Host.cshtml
│   │   ├── PresenceLight.Web.csproj
│   │   ├── PresenceLightSettings.json
│   │   ├── Program.cs
│   │   ├── Program_New.cs
│   │   ├── Program_Old.cs
│   │   ├── Properties/
│   │   │   ├── PublishProfiles/
│   │   │   │   └── FolderProfile.pubxml
│   │   │   ├── launchSettings.json
│   │   │   ├── serviceDependencies.json
│   │   │   └── serviceDependencies.local.json
│   │   ├── Routes.razor
│   │   ├── ServiceCollectionExtensions.cs
│   │   ├── Worker.cs
│   │   ├── _Imports.razor
│   │   └── appsettings.json
│   └── PresenceLight.sln
└── version.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto

###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs     diff=csharp

###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following 
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln       merge=binary
#*.csproj    merge=binary
#*.vbproj    merge=binary
#*.vcxproj   merge=binary
#*.vcproj    merge=binary
#*.dbproj    merge=binary
#*.fsproj    merge=binary
#*.lsproj    merge=binary
#*.wixproj   merge=binary
#*.modelproj merge=binary
#*.sqlproj   merge=binary
#*.wwaproj   merge=binary

###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg   binary
#*.png   binary
#*.gif   binary

###############################################################################
# diff behavior for common document formats
# 
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the 
# entries below.
###############################################################################
#*.doc   diff=astextplain
#*.DOC   diff=astextplain
#*.docx  diff=astextplain
#*.DOCX  diff=astextplain
#*.dot   diff=astextplain
#*.DOT   diff=astextplain
#*.pdf   diff=astextplain
#*.PDF   diff=astextplain
#*.rtf   diff=astextplain
#*.RTF   diff=astextplain


================================================
FILE: .github/FUNDING.yml
================================================
github: isaacrlevin



================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.


## Further details
- OS: [e.g. iOS]
- PresenceLight App Type (Desktop, Blazor)
- PresenceLight Build (Nightly, Store, etc)
- PresenceLight Version (The # in About)
- Did you clear settings.json? (C:\Users\username\AppData\Local\Packages\37828IsaacLevin.197278F15330A.SomeValue\LocalState\settings.json
- ASP.NET Core version


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: nuget
  directory: "/src"
  schedule:
    interval: daily
    time: "10:00"
  open-pull-requests-limit: 10
  ignore:
  - dependency-name: Blazorise.Bootstrap
    versions:
    - 0.9.3
    - 0.9.3.1
    - 0.9.3.3
    - 0.9.3.4
    - 0.9.3.5
  - dependency-name: Microsoft.Identity.Client
    versions:
    - 4.26.0
    - 4.27.0
    - 4.28.0
    - 4.28.1
    - 4.29.0
    - 4.30.0
  - dependency-name: Blazorise.Icons.FontAwesome
    versions:
    - 0.9.3
    - 0.9.3.1
    - 0.9.3.3
    - 0.9.3.4
    - 0.9.3.5
  - dependency-name: Microsoft.Identity.Web
    versions:
    - 1.6.0
    - 1.7.0
    - 1.8.1
    - 1.8.2
    - 1.9.0
  - dependency-name: Microsoft.Identity.Web.MicrosoftGraphBeta
    versions:
    - 1.6.0
    - 1.7.0
    - 1.8.1
    - 1.8.2
    - 1.9.0
  - dependency-name: Microsoft.Identity.Web.UI
    versions:
    - 1.6.0
    - 1.7.0
    - 1.8.1
    - 1.8.2
    - 1.9.0
  - dependency-name: Nerdbank.GitVersioning
    versions:
    - 3.4.190
  - dependency-name: Newtonsoft.Json
    versions:
    - 13.0.1
  - dependency-name: YeelightAPI
    versions:
    - 1.10.1
    - 1.10.2
  - dependency-name: Microsoft.AspNetCore.Authentication.OpenIdConnect
    versions:
    - 5.0.3
    - 5.0.4
  - dependency-name: Serilog.Extensions.Hosting
    versions:
    - 4.0.0
    - 4.1.0
    - 4.1.2
  - dependency-name: Microsoft.ApplicationInsights.NLogTarget
    versions:
    - 2.17.0
  - dependency-name: Microsoft.ApplicationInsights.AspNetCore
    versions:
    - 2.17.0
  - dependency-name: Microsoft.ApplicationInsights.WorkerService
    versions:
    - 2.17.0
  - dependency-name: IdentityModel
    versions:
    - 5.0.1


================================================
FILE: .github/workflows/Azure_Blob_Deploy.yml
================================================
name: Deploy Worker Apps to Azure Blob Storage

on:
  workflow_dispatch:
    inputs:
      target:
        description: 'Target of Channel Name to Sign'
        required: true
        default: ''
        type: string

  workflow_call:
    inputs:
      target:
        description: 'Target of Channel Name to Sign'
        required: true
        default: ''
        type: string

jobs:

  Deploy_Worker_Artifacts:
    name: Deploy Worker Artifacts
    environment:
        name: Deploy_Azure_Blob
    permissions:
      id-token: write # Required for requesting the JWT
    runs-on: ubuntu-latest

    steps:
    - name: Azure login
      uses: azure/login@v2
      with:
        client-id: ${{ secrets.AZURE_CLIENT_ID }}
        tenant-id: ${{ secrets.AZURE_TENANT_ID }}
        subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

    - name: Download ${{ inputs.target }} Artifacts
      uses: actions/download-artifact@v4
      with:
        name: ${{ inputs.target }}_Signed
        path: ./Signed/${{ inputs.target }}

    - name: Archive Previous Release
      uses: azure/cli@v2
      with:
        inlineScript: |
          account_name="${{ secrets.ACCOUNT_NAME }}"
          account_key="${{ secrets.ACCOUNT_KEY }}"
          source_container="${{ secrets.WORKER_CONTAINER }}"
          destination_container="${{ secrets.WORKER_ARCHIVE_CONTAINER }}"
          pattern="${{ inputs.target }}"

          # Get a list of blobs in the source container that match the pattern
          blobs=$(az storage blob list --account-name $account_name --account-key $account_key --container-name $source_container --query "[?contains(name, '$pattern')].name" -o tsv)

          # Loop through the blobs and copy each one to the destination container
          for blob in $blobs
          do
            echo "Copying blob $blob from $source_container to $destination_container"

            blob=$(echo $blob | tr -d '\r')

            az storage blob copy start \
              --account-name $account_name \
              --account-key $account_key \
              --destination-container $destination_container \
              --destination-blob $blob \
              --source-account-name $account_name \
              --source-account-key $account_key \
              --source-container $source_container \
              --source-blob $blob

            echo "Deleting blob $blob from $source_container"
            az storage blob delete \
              --account-key $account_key \
              --account-name $account_name \
              --container-name $source_container \
              --name $blob
          done
        azcliversion: latest

    - name: Upload Release to Blob Storage
      uses: azure/cli@v2
      with:
        inlineScript: |
          az storage blob upload-batch --connection-string "${{ secrets.AZUREBLOBCONNECTIONSTRING }}" \
          -d "${{ secrets.WORKER_CONTAINER }}" -s "./Signed"
        azcliversion: latest

================================================
FILE: .github/workflows/Choco.yml
================================================
on:
  workflow_call:

  workflow_dispatch:
    inputs:
      version:
        description: 'Version to deploy'
        required: true

jobs:

  Deploy_Choco:
    name: Publish App to Chocolatey
    runs-on: windows-latest

    steps:
      - uses: actions/download-artifact@v4
        name: Download Standalone Signed App Artifacts
        with:
          name: Standalone_Signed
          path: .\StandaloneSigned

      - uses: actions/download-artifact@v4
        if: ${{ github.event_name == 'push' }}
        name: Download Build Artifacts
        with:
          name: BuildArtifacts
          path: .\BuildArtifacts

      - name: Set VERSION Environment Variable (PUSH)
        if: ${{ github.event_name == 'push' }}
        run: |
          $version = Get-Content ".\BuildArtifacts\version.txt"
          echo "VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
        shell: powershell

      - name: Set VERSION Environment Variable (Workflow Dispatch)
        if: ${{ github.event_name == 'workflow_dispatch' }}
        run: |
          version = "${{ inputs.Version }}"
          echo "VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
        shell: powershell

      - uses: actions/download-artifact@v4
        name: Download Chocolatey Artifacts
        with:
          name: Chocolatey
          path: .\Chocolatey

      - name: Push to Chocolatey
        run: |
          .\BuildArtifacts\scripts\push-choco.ps1 -Version "${{ env.VERSION}}" -CHOCOAPIKEY "${{ secrets.CHOCOAPIKEY}}"
        shell: powershell

      - name: Setup tmate session
        if: ${{ failure() }}
        uses: mxschmitt/action-tmate@v3
        timeout-minutes: 15

================================================
FILE: .github/workflows/Deploy_Desktop.yml
================================================
on:
  push:
    branches: [ main ]
    paths-ignore:
    - '.github/workflows/Deploy_Web.yml'
    - 'src/PresenceLight.Web/**'
    - 'src/DockerFiles/**'
    - '*..md'
    - 'docs/*..md'
    - 'Build/**'
    - 'chocolatey/**'

  pull_request:
    branches: [ main ]
    paths-ignore:
    - '.github/workflows/Deploy_Web.yml'
    - 'src/PresenceLight.Web/**'
    - 'src/DockerFiles/**'
    - '*..md'
    - 'docs/*..md'
    - 'Build/**'
    - 'chocolatey/**'


jobs:

  Setup_Desktop:
    name: Setup App for Build
    runs-on: ubuntu-latest
    strategy:
      matrix:
        ChannelName:
        - Release
        - Nightly
        - Standalone

    env:
      DOTNET_CLI_TELEMETRY_OPTOUT: 1
      DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
      DOTNET_NOLOGO: true
      BuildConfiguration: Release
      ACTIONS_ALLOW_UNSECURE_COMMANDS: true
      Win10RID: net10.0-windows10.0.19041

    steps:

    - name: Checkout Code
      uses: actions/checkout@v4
      with:
        fetch-depth: 0

    - name: Use .NET Core SDK 8.0.x and 10.0.x
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: |
          8.0.x
          10.0.x

    - name: Get Version from Nerdbank.GitVersioning
      uses: dotnet/nbgv@v0.4.2
      with:
        setCommonVars: true

    - run: echo "BuildNumber - ${{ env.GitBuildVersionSimple }}"

    - name: Add Secrets to appsettings.json
      run: |
        ./Build/scripts/update-desktop-settings.ps1 -Release "${{ matrix.ChannelName}}" -Version "${{ env.GitBuildVersionSimple }}" -ApplicationId "${{ secrets.ApplicationId }}" `
        -ClientSecret "${{ secrets.ClientSecret }}" -InstrumentationKey "${{ secrets.InstrumentationKey }}" `
        -LIFXClientId "${{ secrets.LIFXClientId }}" -LIFXClientSecret "${{ secrets.LIFXClientSecret }}" `
        -RemoteHueClientId "${{ secrets.RemoteHueClientId }}" -RemoteHueClientSecret "${{ secrets.RemoteHueClientSecret }}" `
        -RemoteHueClientAppName "${{ secrets.RemoteHueClientAppName }}"
      shell: pwsh
      if: ${{ success() && github.event_name != 'pull_request' }}

    - name: Create Version File to Artifact
      run : |
        New-Item -Path ./Build -Name "version.txt" -ItemType "file" -Value "${{ env.GitBuildVersionSimple }}"
      shell: pwsh

    - name: Publish ${{ matrix.ChannelName }} Arifacts
      uses: actions/upload-artifact@v4
      with:
        path: ./src
        name: PreBuild-${{ matrix.ChannelName }}

    - name: Publish Build Artifacts
      uses: actions/upload-artifact@v4
      with:
        path: Build
        name: BuildArtifacts
      if:  ${{ success() && matrix.ChannelName  == 'Standalone' }}

    - name: Publish Chocolatey Artifacts
      uses: actions/upload-artifact@v4
      with:
        path: chocolatey
        name: Chocolatey
      if:  ${{ success() && matrix.ChannelName  == 'Standalone' }}

  Build_WPF:
    name: Build App
    needs: Setup_Desktop
    runs-on: windows-latest
    strategy:
      matrix:
        ChannelName: [ Release,  Nightly,  Standalone ]

    env:
      DOTNET_CLI_TELEMETRY_OPTOUT: 1
      DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
      DOTNET_NOLOGO: true
      BuildConfiguration: Release
      ACTIONS_ALLOW_UNSECURE_COMMANDS: true
      Win10RID: net10.0-windows10.0.19041

    steps:

    - name: setup-msbuild
      uses: microsoft/setup-msbuild@v1

    - name: Use .NET Core SDK 8.0.x and 10.0.x
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: |
          8.0.x
          10.0.x

    - name: Download PreBuild
      uses: actions/download-artifact@v4
      with:
        name: PreBuild-${{ matrix.ChannelName }}
        path: ./src

    - name: Download Build Artifacts
      uses: actions/download-artifact@v4
      with:
        name: BuildArtifacts
        path: ./BuildArtifacts

    - name: Set GitBuildVersionSimple Environment Variable
      run: |
        $version = Get-Content ".\BuildArtifacts\version.txt"
        echo "GitBuildVersionSimple=$version" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
      shell: pwsh

    - name: Create Directory for Channel
      run: mkdir ./${{ matrix.ChannelName }}

    - name: Update Badge Versions
      run: |
        # Update badges
        [xml]$badge = Get-Content ".\BuildArtifacts\ci_badge.svg"
        $badge.svg.g[1].text[2].InnerText = "${{ env.GitBuildVersionSimple }}"
        $badge.svg.g[1].text[3].InnerText = "${{ env.GitBuildVersionSimple }}"
        $badge.Save(".\${{ matrix.ChannelName }}\ci_badge.svg")
        [xml]$badge = Get-Content ".\BuildArtifacts\store_badge.svg"
        $badge.svg.g[1].text[2].InnerText = "${{ env.GitBuildVersionSimple }}"
        $badge.svg.g[1].text[3].InnerText = "${{ env.GitBuildVersionSimple }}"
        $badge.Save(".\${{ matrix.ChannelName }}\stable_badge.svg")
      shell: powershell

    - name: Setup Windows SDK
      uses: GuillaumeFalourd/setup-windows10-sdk-action@v2
      with:
        sdk-version: 19041
      if:  ${{ success() && matrix.ChannelName  != 'Standalone' }}

    - name: Build Standalone Presence Light x86
      run: |
        dotnet restore .\src\DesktopClient\PresenceLight\PresenceLight.csproj
        dotnet publish .\src\DesktopClient\PresenceLight\PresenceLight.csproj -c ${{ env.BuildConfiguration }} /p:Version=${{ env.GitBuildVersionSimple }} /p:PublishProfile=Properties/PublishProfiles/WinX86.pubxml --property WarningLevel=3
      if:  ${{ success() && matrix.ChannelName  == 'Standalone' }}

    - name: Build Standalone Presence Light x64
      run: |
        dotnet restore .\src\DesktopClient\PresenceLight\PresenceLight.csproj
        dotnet publish .\src\DesktopClient\PresenceLight\PresenceLight.csproj -c ${{ env.BuildConfiguration }} /p:Version=${{ env.GitBuildVersionSimple }} /p:PublishProfile=Properties/PublishProfiles/WinX64.pubxml --property WarningLevel=3
      if:  ${{ success() && matrix.ChannelName  == 'Standalone' }}

    - name: Build Standalone Presence Light ARM64
      run: |
        dotnet restore .\src\DesktopClient\PresenceLight\PresenceLight.csproj
        dotnet publish .\src\DesktopClient\PresenceLight\PresenceLight.csproj -c ${{ env.BuildConfiguration }} /p:Version=${{ env.GitBuildVersionSimple }} /p:PublishProfile=Properties/PublishProfiles/WinARM64.pubxml --property WarningLevel=3
      if:  ${{ success() && matrix.ChannelName  == 'Standalone' }}

    - name: Zip Standalone PresenceLight x86 Files
      run: |
        Compress-Archive -Path '.\src\DesktopClient\PresenceLight\bin\${{ env.BuildConfiguration }}\${{ env.Win10RID }}\win-x86\publish\*' `
        -DestinationPath ".\${{ matrix.ChannelName }}\PresenceLight.${{ env.GitBuildVersionSimple }}-x86.zip"
      shell: powershell
      if:  ${{ success() && matrix.ChannelName  == 'Standalone' }}

    - name: Zip Standalone PresenceLight x64 Files
      run: |
        Compress-Archive -Path '.\src\DesktopClient\PresenceLight\bin\${{ env.BuildConfiguration }}\${{ env.Win10RID }}\win-x64\publish\*' `
        -DestinationPath ".\${{ matrix.ChannelName }}\PresenceLight.${{ env.GitBuildVersionSimple }}-x64.zip"
      shell: powershell
      if:  ${{ success() && matrix.ChannelName  == 'Standalone' }}

    - name: Zip Standalone PresenceLight ARM Files
      run: |
        Compress-Archive -Path '.\src\DesktopClient\PresenceLight\bin\${{ env.BuildConfiguration }}\${{ env.Win10RID }}\win-arm64\publish\*' `
        -DestinationPath ".\${{ matrix.ChannelName }}\PresenceLight.${{ env.GitBuildVersionSimple }}-win-arm64.zip"
      shell: powershell
      if:  ${{ success() && matrix.ChannelName  == 'Standalone' }}

    - name: Build Appx Package
      run: |
        msbuild '.\src\DesktopClient\PresenceLight.Package\PresenceLight.Package.wapproj' /p:VersionNumber=${{ env.GitBuildVersionSimple }} `
        /p:ChannelName=${{ matrix.ChannelName }} /p:configuration='${{ env.BuildConfiguration }}' /p:IncludeSymbols=true /p:WarningLevel=3  `
        /p:AppxPackageDir="${{ github.workspace }}\${{ matrix.ChannelName }}\"
      if:  ${{ success() && matrix.ChannelName  != 'Standalone' }}

    - name: Publish ${{ matrix.ChannelName }} Arifacts
      uses: actions/upload-artifact@v4
      with:
        path: .\${{ matrix.ChannelName }}
        name: ${{ matrix.ChannelName }}

    - name: Setup Tmate session
      if: ${{ failure() }}
      uses: mxschmitt/action-tmate@v3
      timeout-minutes: 15

  Code_Signing:
    name: Code Sign
    needs: Build_WPF
    strategy:
      matrix:
        target: [ Release, Nightly, Standalone ]
    uses: isaacrlevin/presencelight/.github/workflows/Sign.yml@main
    with:
      target: ${{ matrix.target }}
    secrets: inherit

  Deploy_Azure_Blob:
    name: Deploy Nightly App to Azure Blob Storage
    needs: Code_Signing
    if: ${{ github.event_name != 'pull_request' }}
    environment:
        name: Deploy_Azure_Blob
        url: ${{ steps.deploy_staging.outputs.webapp-url }}
    permissions:
      id-token: write # Required for requesting the JWT
    runs-on: ubuntu-latest
    steps:
    - name: Azure Login
      uses: azure/login@v2
      with:
        client-id: ${{ secrets.AZURE_CLIENT_ID }}
        tenant-id: ${{ secrets.AZURE_TENANT_ID }}
        subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

    - name: Download Nightly Signed
      uses: actions/download-artifact@v4
      with:
        name: Nightly_Signed
        path: "./NightlySigned"

    - name: Upload Nightly App to Azure Blob Storage
      run: |
        Copy-Item "./NightlySigned" -Destination "./Upload" -Recurse -Verbose
        dir .\Upload\
        az storage blob upload --account-key ${{ secrets.ACCOUNT_KEY }} --account-name ${{ secrets.ACCOUNT_NAME }} -f ./Upload/ci_badge.svg -n ci_badge.svg -c nightly --content-type image/svg+xml  --debug --overwrite
        az storage blob upload --account-key ${{ secrets.ACCOUNT_KEY }} --account-name ${{ secrets.ACCOUNT_NAME }} -f ./Upload/PresenceLight.Package.appinstaller -n PresenceLight.Package.appinstaller -c nightly --content-type application/xml  --debug --overwrite
        az storage blob upload-batch --account-key ${{ secrets.ACCOUNT_KEY }} --account-name ${{ secrets.ACCOUNT_NAME }} --source ./Upload --pattern *.appxbundle -d nightly --content-type application/vns.ms-appx --debug
      shell: pwsh

  Deploy_GitHub_Release:
    name: Deploy App to GitHub Release
    needs: Deploy_Azure_Blob
    if: ${{ github.event_name != 'pull_request' }}
    environment:
        name: Deploy_GitHub_Release
        url: ${{ steps.deploy_staging.outputs.webapp-url }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Generate Changelog for Latest Commit
        id: changelog
        uses: jaywcjlove/changelog-generator@main
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          filter: ''
        env:
          commitMode: true
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Download Standalone Signed App
        uses: actions/download-artifact@v4
        with:
          name: Standalone_Signed
          path: ./StandaloneSigned

      - name: Download Release Signed App
        uses: actions/download-artifact@v4
        with:
          name: Release_Signed
          path: ./ReleaseSigned

      - name: Download Build Artifacts
        uses: actions/download-artifact@v4
        if: ${{ github.event_name == 'push' }}
        with:
          name: BuildArtifacts
          path: ./BuildArtifacts

      - name: Get Version from Artifact
        run: |
          version=$(<"./BuildArtifacts/version.txt")
          echo "VERSION=$version" >> $GITHUB_ENV

      - name: Add hashes for Standalone App
        run: |
            $zip64Hash = Get-FileHash "./StandaloneSigned/PresenceLight.${{ env.VERSION }}-x64.zip" -Algorithm SHA256
            $zip64Hash.Hash | Out-File -Encoding 'UTF8' "./StandaloneSigned/PresenceLight.${{ env.VERSION }}-x64.zip.sha256"

            $zip86Hash = Get-FileHash "./StandaloneSigned/PresenceLight.${{ env.VERSION }}-x86.zip" -Algorithm SHA256
            $zip86Hash.Hash | Out-File -Encoding 'UTF8' "./StandaloneSigned/PresenceLight.${{ env.VERSION }}-x86.zip.sha256"

            $zipARMHash = Get-FileHash "./StandaloneSigned/PresenceLight.${{ env.VERSION }}-win-arm64.zip" -Algorithm SHA256
            $zipARMHash.Hash | Out-File -Encoding 'UTF8' "./StandaloneSigned/PresenceLight.${{ env.VERSION }}-win-arm64.zip.sha256"

            $appxHash = Get-FileHash "./ReleaseSigned/PresenceLight.Package_${{ env.VERSION }}.0_Test/PresenceLight.Package_${{ env.VERSION }}.0_x64_x86_ARM64.appxbundle" -Algorithm SHA256
            $appxHash.Hash | Out-File -Encoding 'UTF8' "./ReleaseSigned/PresenceLight.Package_${{ env.VERSION }}.0_x64_x86_ARM64.appxbundle.sha256"
        shell: pwsh

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: "Desktop-v${{ env.VERSION }}"
          body: ${{ steps.changelog.outputs.changelog }}
          fail_on_unmatched_files: true
          token: ${{ secrets.GITHUB_TOKEN }}
          files: |
            StandaloneSigned/*.zip
            StandaloneSigned/*.sha256
            ReleaseSigned/*.sha256
            ReleaseSigned/**/*.appxbundle

      - name: Setup tmate session
        if: ${{ failure() }}
        uses: mxschmitt/action-tmate@v3
        timeout-minutes: 15

  Deploy_Choco:
    name: Deploy Standalone App Chocolatey
    needs: Deploy_GitHub_Release
    if: ${{ github.event_name != 'pull_request' }}
    uses: isaacrlevin/presencelight/.github/workflows/Choco.yml@main
    secrets: inherit

  Deploy_Store:
    name: Deploy App to Windows Store
    needs: Deploy_Azure_Blob
    if: ${{ github.event_name != 'pull_request' }}
    environment:
        name: Deploy_Store
        url: ${{ steps.deploy_staging.outputs.webapp-url }}
    runs-on: ubuntu-latest
    steps:

    - name: Download Release Signed
      uses: actions/download-artifact@v4
      with:
        name: Release_Signed
        path: ./ReleaseSigned

    - name: Upload Badges to Azure Blob Storage
      run: |
        az storage blob upload --account-key ${{ secrets.ACCOUNT_KEY }} --account-name ${{ secrets.ACCOUNT_NAME }} -f "./ReleaseSigned/stable_badge.svg" -n stable_badge.svg -c store --content-type image/svg+xml  --debug --overwrite
      shell: pwsh

    - name: Windows Store Publish
      uses: isaacrlevin/windows-store-action@1.0
      with:
        tenant-id: ${{ secrets.STORE_TENANT }}
        client-id: ${{ secrets.STORE_CLIENT_ID }}
        client-secret: ${{ secrets.STORE_CLIENT_SECRET }}
        app-id: ${{ secrets.APP_ID }}
        package-path: "./ReleaseSigned/"

  Deploy_Winget:
    name: Deploy App to WinGet
    needs: Deploy_GitHub_Release
    if: ${{ github.event_name != 'pull_request' }}
    uses: isaacrlevin/presencelight/.github/workflows/WinGet.yml@main
    secrets: inherit

================================================
FILE: .github/workflows/Deploy_Web.yml
================================================
on:

  push:
    branches: [ main ]
    paths-ignore:
    - '.github/workflows/Deploy_Desktop.yml'
    - 'src/DesktopClient/**'
    - 'src/**'
    - '*..md'
    - 'docs/*..md'
    - 'Build/**'
    - 'chocolatey/**'

  pull_request:
    branches: [ main ]
    paths-ignore:
    - '.github/workflows/Deploy_Desktop.yml'
    - 'src/DesktopClient/**'
    - 'src/**'
    - '*..md'
    - 'docs/*..md'
    - 'Build/**'
    - 'chocolatey/**'

jobs:
  Setup_Web:
    name: Setup Web
    runs-on: ubuntu-latest

    env:
      DOTNET_CLI_TELEMETRY_OPTOUT: 1
      DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
      DOTNET_NOLOGO: true
      BuildConfiguration: Release
      ACTIONS_ALLOW_UNSECURE_COMMANDS: true

    steps:
    - name: Checkout Code
      uses: actions/checkout@v4
      with:
        fetch-depth: 0

    - name: Use .NET Core SDK 8.0.x and 10.0.x
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: |
          8.0.x
          10.0.x

    - name: Nerdbank.GitVersioning
      uses: dotnet/nbgv@v0.4.2
      with:
        setCommonVars: true

    - run: echo "BuildNumber - ${{ env.GitBuildVersionSimple }}"

    - name: Add Secrets to appsettings.json
      run: |
        ./Build/scripts/update-web-settings.ps1 -Version "${{ env.GitBuildVersionSimple }}" -ApplicationId "${{ secrets.ApplicationId }}" `
        -ClientSecret "${{ secrets.ClientSecret }}" -InstrumentationKey "${{ secrets.InstrumentationKey }}" `
        -LIFXClientId "${{ secrets.LIFXClientId }}" -LIFXClientSecret "${{ secrets.LIFXClientSecret }}" `
        -RemoteHueClientId "${{ secrets.RemoteHueClientId }}" -RemoteHueClientSecret "${{ secrets.RemoteHueClientSecret }}" `
        -RemoteHueClientAppName "${{ secrets.RemoteHueClientAppName }}"
      shell: pwsh
      if: ${{ success() && github.event_name != 'pull_request' }}

    - name: Create Version File to Artifact
      run : |
        New-Item -Path ./Build -Name "version.txt" -ItemType "file" -Value "${{ env.GitBuildVersionSimple }}"
      shell: pwsh

    - name: Publish PreBuild Arifacts
      uses: actions/upload-artifact@v4
      with:
        path: ./src
        name: PreBuild

    - name: Publish Files for Build
      uses: actions/upload-artifact@v4
      with:
        path: Build
        name: BuildArtifacts
      if:  ${{ success() }}

  Build_Web:
    name: Build Web
    needs: Setup_Web
    runs-on: ubuntu-latest
    strategy:
      matrix:
        include:
          - ChannelName: Windows_x64_x86
            RID: win-x64
          - ChannelName: Windows_ARM
            RID: win-arm64
          - ChannelName: macOS
            RID: osx-x64
          - ChannelName: Linux_ARM
            RID: linux-arm
          - ChannelName: Linux_ARM64
            RID: linux-x64
          - ChannelName: Linux_Musl_x64
            RID: linux-musl-x64
          - ChannelName: Linux_Musl_ARM_x64
            RID: linux-musl-arm64

    env:
      DOTNET_CLI_TELEMETRY_OPTOUT: 1
      DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
      DOTNET_NOLOGO: true
      BuildConfiguration: Release
      ACTIONS_ALLOW_UNSECURE_COMMANDS: true

    steps:

    - name: Use .NET Core SDK 8.0.x and 10.0.x
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: |
          8.0.x
          10.0.x

    - name: Download PreBuild
      uses: actions/download-artifact@v4
      with:
        name: PreBuild
        path: ./src

    - name: Download Build Artifacts
      uses: actions/download-artifact@v4
      with:
        name: BuildArtifacts
        path: ./BuildArtifacts

    - name: Get Version from Artifact
      run: |
        version=$(<"${{ github.workspace }}/BuildArtifacts/version.txt")
        echo "GitBuildVersionSimple=$version" >> $GITHUB_ENV

    - name: Create Directory for ${{ matrix.ChannelName }} Channel
      run: mkdir ./${{ matrix.ChannelName }}

    - name: dotnet publish ${{ matrix.RID }}
      run: dotnet publish './src/PresenceLight.Web/PresenceLight.Web.csproj' -r ${{ matrix.RID }} -c ${{ env.BuildConfiguration }} /p:PublishSingleFile=true -o ./PresenceLight.${{ env.GitBuildVersionSimple }}_${{ matrix.ChannelName }} /p:Version=${{ env.GitBuildVersionSimple }} --property WarningLevel=0
      if: ${{ success() }}

    - name: Zip PresenceLight Web Files
      run: |
        Compress-Archive -Path './PresenceLight.${{ env.GitBuildVersionSimple }}_${{ matrix.ChannelName }}' `
        -DestinationPath ./${{ matrix.ChannelName }}/PresenceLight.${{ matrix.ChannelName }}.${{ env.GitBuildVersionSimple }}.zip
      shell: pwsh

    - name: Publish ${{ matrix.ChannelName }} Arifacts
      uses: actions/upload-artifact@v4
      with:
        path: ./${{ matrix.ChannelName }}
        name: ${{ matrix.ChannelName }}

  Code_Signing:
    name: Code Sign Worker
    needs: Build_Web
    strategy:
      matrix:
        target: [ Windows_x64_x86, Windows_ARM, macOS, Linux_ARM, Linux_ARM64, Linux_Musl_x64, Linux_Musl_ARM_x64 ]
    uses: isaacrlevin/presencelight/.github/workflows/Sign.yml@main
    with:
      target: ${{ matrix.target }}
    secrets: inherit

  Deploy_Azure_Blob:
    needs: Code_Signing
    name: Deploy Worker to Azure Blob Storage
    if: ${{ github.event_name != 'pull_request' }}
    strategy:
      matrix:
        target: [ Windows_x64_x86, Windows_ARM, macOS, Linux_ARM, Linux_ARM64, Linux_Musl_x64, Linux_Musl_ARM_x64 ]
    uses: isaacrlevin/presencelight/.github/workflows/Azure_Blob_Deploy.yml@main
    with:
      target: ${{ matrix.target }}
    secrets: inherit

  Deploy_Containers:
    name: Deploy Web Containers (DockerHub / GitHub Packages)
    needs: Build_Web
    if: ${{ github.event_name != 'pull_request' }}
    environment:
        name: Deploy_Containers
        url: ${{ steps.deploy_staging.outputs.webapp-url }}
    runs-on: ubuntu-latest

    steps:
      - name: Download PreBuild
        uses: actions/download-artifact@v4
        with:
          name: PreBuild
          path: ./src

      - name: Download Build Artifacts
        uses: actions/download-artifact@v4
        with:
          name: BuildArtifacts
          path: ./BuildArtifacts

      - name: Get Version from Artifact
        run: |
          version=$(<"./BuildArtifacts/version.txt")
          echo "VERSION=$version" >> $GITHUB_ENV

      - name: Update Docker Files
        run: |
          $dockerFileLatest = Get-Content -path "./src/PresenceLight.Web/Dockerfile" -Raw
          $dockerFileLatest = $dockerFileLatest -replace '{VERSION}', "${{ env.VERSION }} "
          $dockerFileLatest | Set-Content -Path "./src/PresenceLight.Web/Dockerfile"

          $dockerFile32 = Get-Content -path "./src/PresenceLight.Web/Dockerfile.debian-arm32" -Raw
          $dockerFile32 = $dockerFile32 -replace '{VERSION}', "${{ env.VERSION }} "
          $dockerFile32 | Set-Content -Path "./src/PresenceLight.Web/Dockerfile.debian-arm32"

          $dockerFile64 = Get-Content -path "./src/PresenceLight.Web/Dockerfile.debian-arm64" -Raw
          $dockerFile64 = $dockerFile64 -replace '{VERSION}', "${{ env.VERSION }} "
          $dockerFile64 | Set-Content -Path "./src/PresenceLight.Web/Dockerfile.debian-arm64"
        shell: pwsh
        if: ${{ success() && github.event_name != 'pull_request' }}

      - name: Push latest Container tag to GitHub Registry
        uses: opspresso/action-docker@master
        with:
          args: --docker
        env:
          USERNAME: isaacrlevin
          REGISTRY: "ghcr.io"
          PASSWORD: ${{ secrets.GH_PERSONAL_TOKEN }}
          DOCKERFILE: "./src/PresenceLight.Web/Dockerfile"
          IMAGE_NAME: "isaacrlevin/presencelight"
          TAG_NAME: "${{ env.VERSION }}"
          LATEST: "true"
          BUILD_PATH: "./src/"

      - name: Push ARM Container tag to GitHub Registry
        uses: opspresso/action-docker@master
        with:
          args: --docker
        env:
          USERNAME: isaacrlevin
          REGISTRY: "ghcr.io"
          PASSWORD: ${{ secrets.GH_PERSONAL_TOKEN }}
          DOCKERFILE: "./src/PresenceLight.Web/Dockerfile.debian-arm32"
          IMAGE_NAME: "isaacrlevin/presencelight"
          TAG_NAME: "debian-arm32"
          BUILD_PATH: "./src/"

      - name: Push ARM64 tag to GitHub Registry
        uses: opspresso/action-docker@master
        with:
          args: --docker
        env:
          USERNAME: isaacrlevin
          REGISTRY: "ghcr.io"
          PASSWORD: ${{ secrets.GH_PERSONAL_TOKEN }}
          DOCKERFILE: "./src/PresenceLight.Web/Dockerfile.debian-arm64"
          IMAGE_NAME: "isaacrlevin/presencelight"
          TAG_NAME: "debian-arm64"
          BUILD_PATH: "./src/"

      - name: Push ARM tagto DockerHub (Versioned)
        uses: opspresso/action-docker@master
        with:
          args: --docker
        env:
          USERNAME: ${{ secrets.DOCKER_USERNAME }}
          PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
          DOCKERFILE: "./src/PresenceLight.Web/Dockerfile.debian-arm32"
          IMAGE_NAME: "isaaclevin/presencelight"
          TAG_NAME: "${{ env.VERSION }}-debian-arm32"
          BUILD_PATH: "./src/"

      - name: Push ARM tag to DockerHub (Latest)
        uses: opspresso/action-docker@master
        with:
          args: --docker
        env:
          USERNAME: ${{ secrets.DOCKER_USERNAME }}
          PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
          DOCKERFILE: "./src/PresenceLight.Web/Dockerfile.debian-arm32"
          IMAGE_NAME: "isaaclevin/presencelight"
          TAG_NAME: "debian-arm32"
          BUILD_PATH: "./src/"

      - name: Push ARM64 tag to DockerHub (Versioned)
        uses: opspresso/action-docker@master
        with:
          args: --docker
        env:
          USERNAME: ${{ secrets.DOCKER_USERNAME }}
          PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
          DOCKERFILE: "./src/PresenceLight.Web/Dockerfile.debian-arm64"
          IMAGE_NAME: "isaaclevin/presencelight"
          TAG_NAME: "${{ env.VERSION }}-debian-arm64"
          BUILD_PATH: "./src/"

      - name: Push ARM64 tag to DockerHub (Latest)
        uses: opspresso/action-docker@master
        with:
          args: --docker
        env:
          USERNAME: ${{ secrets.DOCKER_USERNAME }}
          PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
          DOCKERFILE: "./src/PresenceLight.Web/Dockerfile.debian-arm64"
          IMAGE_NAME: "isaaclevin/presencelight"
          TAG_NAME: "debian-arm64"
          BUILD_PATH: "./src/"

      - name: Push latest tag to DockerHub
        uses: opspresso/action-docker@master
        with:
          args: --docker
        env:
          USERNAME: ${{ secrets.DOCKER_USERNAME }}
          PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
          DOCKERFILE: "./src/PresenceLight.Web/Dockerfile"
          IMAGE_NAME: "isaaclevin/presencelight"
          TAG_NAME: "${{ env.VERSION }}"
          LATEST: "true"
          BUILD_PATH: "./src/"

  Deploy_GitHub_Release:
    name: Deploy Web to GitHub Release
    needs: Deploy_Azure_Blob
    if: ${{ github.event_name != 'pull_request' }}
    environment:
        name: Deploy_GitHub_Release
        url: ${{ steps.deploy_staging.outputs.webapp-url }}
    runs-on: ubuntu-latest
    steps:

      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Generate Changelog
        id: changelog
        uses: jaywcjlove/changelog-generator@main
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          filter: ''
        env:
          commitMode: true
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Download Windows_x64_x86 Artifacts
        uses: actions/download-artifact@v4
        with:
          name: Windows_x64_x86
          path: ./Sign/Windows_x64_x86

      - name: Download Windows_ARM Artifacts
        uses: actions/download-artifact@v4
        with:
          name: Windows_ARM
          path: ./Sign/Windows_ARM

      - name: Download macOS Artifacts
        uses: actions/download-artifact@v4
        with:
          name: macOS
          path: ./Sign/macOS

      - name: Download Linux_ARM Artifacts
        uses: actions/download-artifact@v4
        with:
          name: Linux_ARM
          path: ./Sign/Linux_ARM

      - name: Download Linux_ARM64 Artifacts
        uses: actions/download-artifact@v4
        with:
          name: Linux_ARM64
          path: ./Sign/Linux_ARM64

      - name: Download Linux_Musl_x64 Artifacts
        uses: actions/download-artifact@v4
        with:
          name: Linux_Musl_x64
          path: ./Sign/Linux_Musl_x64

      - name: Download Linux_Musl_ARM_x64 Artifacts
        uses: actions/download-artifact@v4
        with:
          name: Linux_Musl_ARM_x64
          path: ./Sign/Linux_Musl_ARM_x64

      - name: Download Build Artifacts
        uses: actions/download-artifact@v4
        with:
          name: BuildArtifacts
          path: "./BuildArtifacts"

      - name: Get Version from Artifact
        run: |
          version=$(<"./BuildArtifacts/version.txt")
          echo "VERSION=$version" >> $GITHUB_ENV

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: "Web-v${{ env.VERSION }}"
          body: ${{ steps.changelog.outputs.changelog }}
          fail_on_unmatched_files: true
          token: ${{ secrets.GITHUB_TOKEN }}
          files: |
            ./Sign/**/*.zip

      - name: Setup Tmate session
        if: ${{ failure() }}
        uses: mxschmitt/action-tmate@v3
        timeout-minutes: 15


================================================
FILE: .github/workflows/Sign.yml
================================================
name: Code Sign App

on:
  workflow_dispatch:
    inputs:
      target:
        description: 'Target of Channel Name to Sign'
        required: true
        default: ''
        type: string

  workflow_call:
    inputs:
      target:
        description: 'Target of Channel Name to Sign'
        required: true
        default: ''
        type: string

jobs:

  Sign_Code:
    name: Sign ${{ inputs.target }} App
    permissions:
      id-token: write # Required for requesting the JWT
    runs-on: windows-latest

    steps:
    - name: Download ${{ inputs.target }} Artifacts
      uses: actions/download-artifact@v4
      with:
        name: ${{ inputs.target }}
        path: .\ToSign\${{ inputs.target }}

    - name: Download Build Artifacts
      uses: actions/download-artifact@v4
      with:
        name: BuildArtifacts
        path: .\BuildArtifacts

    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: |
          8.0.x
          10.0.x

    - name: Install Code Sign CLI tool
      # run: dotnet tool install --tool-path . sign --version 0.9.0-beta.23063.3
      run: dotnet tool install --tool-path . --prerelease sign

    - name: Azure Login
      uses: azure/login@v2
      with:
        client-id: ${{ secrets.AZURE_CLIENT_ID }}
        tenant-id: ${{ secrets.AZURE_TENANT_ID }}
        subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

    - name: Run Code Signing CLI
      shell: pwsh
      run: >
        ./sign code azure-key-vault
        '**/*.{exe,zip,appxbundle,appinstaller}'
        --timestamp-url "http://timestamp.digicert.com"
        --base-directory "${{ github.workspace }}\ToSign"
        --file-list "${{ github.workspace }}\BuildArtifacts\Signing\filelist.txt"
        --publisher-name "Isaac Levin"
        --description "PresenceLight"
        --description-url "https://github.com/isaacrlevin/presencelight"
        --azure-key-vault-managed-identity true
        --azure-key-vault-url "${{ secrets.KEY_VAULT_URL }}"
        --azure-key-vault-certificate "${{ secrets.KEY_VAULT_CERTIFICATE_ID }}"
        --verbosity Trace

    - name: Publish Signed ${{ inputs.target }} Packages
      uses: actions/upload-artifact@v4
      with:
        path: .\ToSign\${{ inputs.target }}
        name: '${{ inputs.target }}_Signed'

    - name: Setup Tmate session
      if: ${{ failure() }}
      uses: mxschmitt/action-tmate@v3
      timeout-minutes: 15

================================================
FILE: .github/workflows/WinGet.yml
================================================
name: Winget Publish
on:
  workflow_dispatch:
    inputs:
      Version:
        description: 'Release'
        required: true
        default: '5.0'
        type: string

  workflow_call:

jobs:
  publish:
    runs-on: windows-latest
    name: Publish App to Winget
    env:
      WINGETCREATE_TOKEN: ${{ secrets.WINGETCREATE_TOKEN }}
    steps:

      - name: Download Artifacts for Winget Publish
        uses: actions/download-artifact@v4
        if: ${{ github.event_name == 'push' }}
        with:
          name: BuildArtifacts
          path: .\BuildArtifacts

      - name: Set VERSION Environment Variable (PUSH)
        if: ${{ github.event_name == 'push' }}
        run: |
          $version = Get-Content ".\BuildArtifacts\version.txt"
          echo "VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
        shell: pwsh

      - name: Set VERSION Environment Variable (Workflow Dispatch)
        if: ${{ github.event_name == 'workflow_dispatch' }}
        run: |
          version = "${{ inputs.Version }}"
          echo "VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
        shell: pwsh

      - name: Publish App to Winget
        working-directory: ${{ github.workspace }}\BuildArtifacts\scripts
        run: |
          .\push-winget.ps1 -Version "${{ env.VERSION }}" -Token "${{ secrets.WINGETCREATE_TOKEN }}"
        shell: pwsh

      - name: Setup Tmate session
        if: ${{ failure() }}
        uses: mxschmitt/action-tmate@v3
        timeout-minutes: 15


================================================
FILE: .gitignore
================================================

# Created by https://www.gitignore.io/api/visualstudio,windows
# Edit at https://www.gitignore.io/?templates=visualstudio,windows

### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db

# Dump file
*.stackdump

# Folder config file
[Dd]esktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp

# Windows shortcuts
*.lnk

### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore

# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

# Mono auto generated files
mono_crash.*

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
win-arm64/
Nightly/
.Store
SignClient.exe

[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Visual Studio 2015/2017 cache/options directory
.vs/
.vscode/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

# Visual Studio 2017 auto generated files
Generated\ Files/

# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*

# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml

# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c

# Benchmark Results
BenchmarkDotNet.Artifacts/

# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/

# StyleCop
StyleCopReport.xml

# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc

# Chutzpah Test files
_Chutzpah*

# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb

# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap

# Visual Studio Trace Files
*.e2e

# TFS 2012 Local Workspace
$tf/

# Guidance Automation Toolkit
*.gpState

# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user

# JustCode is a .NET coding add-in
.JustCode

# TeamCity is a build add-in
_TeamCity*

# DotCover is a Code Coverage Tool
*.dotCover

# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json

# Visual Studio code coverage results
*.coverage
*.coveragexml

# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*

# MightyMoose
*.mm.*
AutoTest.Net/

# Web workbench (sass)
.sass-cache/

# Installshield output folder
[Ee]xpress/

# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html

# Click-Once directory
publish/

# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
#*.pubxml
*.publishproj

# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/

# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets

# Microsoft Azure Build Output
csx/
*.build.csdef

# Microsoft Azure Emulator
ecf/
rcf/

# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload

# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/

# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs

# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk

# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/

# RIA/Silverlight projects
Generated_Code/

# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak

# SQL Server files
*.mdf
*.ldf
*.ndf

# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl

# Microsoft Fakes
FakesAssemblies/

# GhostDoc plugin setting file
*.GhostDoc.xml

# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/

# Visual Studio 6 build log
*.plg

# Visual Studio 6 workspace options file
*.opt

# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw

# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions

# Paket dependency manager
.paket/paket.exe
paket-files/

# FAKE - F# Make
.fake/

# CodeRush personal settings
.cr/personal

# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc

# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config

# Tabs Studio
*.tss

# Telerik's JustMock configuration file
*.jmconfig

# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs

# OpenCover UI analysis results
OpenCover/

# Azure Stream Analytics local run output
ASALocalRun/

# MSBuild Binary and Structured Log
*.binlog

# NVidia Nsight GPU debugger configuration file
*.nvuser

# MFractors (Xamarin productivity tool) working folder
.mfractor/

# Local History for Visual Studio
.localhistory/

# BeatPulse healthcheck temp database
healthchecksdb

# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/

# End of https://www.gitignore.io/api/visualstudio,windows

/src/lib/RCWPF/2020.1.218.45.NoXaml.Trial
/sign.ps1
/src/DesktopClient/PresenceLight/appsettings.Development.json
/src/PresenceLight.Web/PresenceLightSettings.Development.json
/src/PresenceLight.Web/appsettings.Development.json
src/local-action-test-wpf.ps1
.github/workflows/test.yml
Build/StorePublish/.env.local
Build/StorePublish/node_modules
Build/StorePublish/temp.zip
/src/DesktopClient/PresenceLight/Properties/launchSettings.json
/src/PresenceLight.Web/config/
logs/
/src/config

.ionide
src/DesktopClient/PresenceLight/settings.json
settings.json


================================================
FILE: Build/Signing/filelist.txt
================================================
**/PresenceLight.*



================================================
FILE: Build/Worker/presencelight.crt
================================================
-----BEGIN CERTIFICATE-----
MIICBTCCAasCFBvttCTh9n9rqOSzRE1jFBdeRyRXMAoGCCqGSM49BAMCMIGEMQsw
CQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjEUMBIGA1UEBwwLV29vZGlu
dmlsbGUxFDASBgNVBAoMC0lzYWFjIExldmluMRYwFAYDVQQDDA1wcmVzZW5jZWxp
Z2h0MRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3QuY29tMB4XDTIwMDUyNDIxNTAz
MloXDTIxMDUyNDIxNTAzMlowgYQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNo
aW5ndG9uMRQwEgYDVQQHDAtXb29kaW52aWxsZTEUMBIGA1UECgwLSXNhYWMgTGV2
aW4xFjAUBgNVBAMMDXByZXNlbmNlbGlnaHQxHDAaBgkqhkiG9w0BCQEWDXRlc3RA
dGVzdC5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQmWh86vQxjFjlNHt/6
8PD9Jg2TJIvaceGaiq+2t/CKoG0FEeiHUYwiozztU6Ad5+dp25OSUzqz2JFy/N+J
iI+2MAoGCCqGSM49BAMCA0gAMEUCIFnvmXTnJQNmPCS6QDVYLrFIYTx3Gzi1nTVD
h5n//+/7AiEAlUxK5U45oJtK2CuHcwquxzar8eB9ZcBAydfuGA9G7o8=
-----END CERTIFICATE-----


================================================
FILE: Build/Worker/presencelight.service
================================================
[Unit]
Description=PresenceLight is a solution to broadcast your various statuses to a Philips Hue or LIFX light bulb.

[Service]
WorkingDirectory=/home/pi/PresenceLight
ExecStart=/home/pi/PresenceLight/PresenceLight
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=PresenceLight
User=pi
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target

================================================
FILE: Build/Worker/trust-cert.sh
================================================
pk12util -d sql:$HOME/.pki/nssdb -i presencelight.pfx

certutil -d sql:$HOME/.pki/nssdb -A -t "P,," -n 'presencelight' -i presencelight.crt

================================================
FILE: Build/scripts/push-choco.ps1
================================================
Param
(
    [parameter(Mandatory = $true)]    [string]
    $Version,
    [parameter(Mandatory = $true)]    [string]
    $CHOCOAPIKEY
)

function Hash-Files {
    param (
        [parameter(Mandatory = $true)]
        [string]
        $Version
    )

    # Get the latest release from GitHub
    $github = Invoke-RestMethod -uri "https://api.github.com/repos/isaacrlevin/presencelight/releases"

    $targetRelease = $github | Where-Object -Property name -match "Desktop-v$Version" | Select-Object -First 1

    mkdir .\Download
    $fileNames = (, "x86", "x64", "win-arm64")

    # Get the Hashes from the GitHub Release and save them to files
    $hashUrls = $targetRelease | Select-Object -ExpandProperty assets -First 1 | Where-Object -Property name -match '.*?.zip.sha256' | Select-Object -ExpandProperty browser_download_url

    foreach ($url in $hashUrls) {
        foreach ($fileName in $fileNames) {
            if ($url -like "*$fileName*") {
                $filePath = ".\Download\$fileName.zip.sha256"
                Invoke-WebRequest -Uri $url -OutFile $filePath
                break
            }
        }
    }

    $hash86 = get-content ".\Download\x86.zip.sha256"
    $hash64 = get-content ".\Download\x64.zip.sha256"
    $hashARM = get-content ".\Download\win-arm64.zip.sha256"

    # Update ChocolateyInstall.ps1 with Hashes
    $installFile = Get-Content -path ".\Chocolatey\tools\ChocolateyInstall.ps1" -Raw
    $installFile = $installFile -replace '{ReplaceCheckSumARM}', $hashARM
    $installFile = $installFile -replace '{ReplaceCheckSumx86}', $hash86
    $installFile = $installFile -replace '{ReplaceCheckSumx64}', $hash64

    # Update Verification.txt with Hashes
    $verificationFile = Get-Content -path ".\Chocolatey\tools\Verification.txt"
    $verificationFile = $verificationFile -replace '{HASHx64}', $hash64
    $verificationFile = $verificationFile -replace '{HASHx86}', $hash86
    $verificationFile = $verificationFile -replace '{HASHARM}', $hashARM

    # Get the Download Urls for the Zip files and update ChocolateyInstall.ps1 and Verification.txt with Urls
    $zipUrls = $targetRelease | Select-Object -ExpandProperty assets | Where-Object { $_.name -like '*.zip' } | Select-Object -ExpandProperty browser_download_url

    foreach ($url in $zipUrls) {
        if ($url -like "*x64*") {
            $installFile = $installFile -replace '{x64Link}' , $url
            $verificationFile = $verificationFile -replace '{x64Link}' , $url
        }
        if ($url -like "*x86*") {
            $installFile = $installFile -replace '{x86Link}' , $url
            $verificationFile = $verificationFile -replace '{x86Link}' , $url
        }
        if ($url -like "*arm64*") {
            $installFile = $installFile -replace '{ARMLink}' , $url
            $verificationFile = $verificationFile -replace '{ARMLink}' , $url
        }
    }

    # Save the updated files
    $verificationFile | Set-Content -Path ".\Chocolatey\tools\Verification.txt"
    $installFile | Set-Content -Path ".\Chocolatey\tools\ChocolateyInstall.ps1"
}


Hash-Files -Version $Version

# Chocolatey Pack
& choco.exe pack ".\Chocolatey\PresenceLight.nuspec" --version "${Version}" --OutputDirectory ".\Chocolatey"

$CHOCOAPIKEY = $CHOCOAPIKEY -replace "`n", "" -replace "`r", "" -replace " ", ""

& choco.exe apikey --key "${CHOCOAPIKEY}" --source https://push.chocolatey.org/

$nupkgs = gci ".\Chocolatey\PresenceLight.*.nupkg" | Select -ExpandProperty FullName
foreach ($nupkg in $nupkgs) {
    & choco.exe push $nupkg --source https://push.chocolatey.org/ --debug --verbose
}

================================================
FILE: Build/scripts/push-winget.ps1
================================================

Param
(
    [parameter(Mandatory = $true)]
    [string]
    $Version,
    [parameter(Mandatory = $false)]
    [string]
    $Token
)

$github = Invoke-RestMethod -uri "https://api.github.com/repos/isaacrlevin/presencelight/releases"
$targetRelease = $github | Where-Object -Property name -match "Desktop-v$Version" | Select-Object -First 1

$installerUrl = $targetRelease | Select-Object -ExpandProperty assets | Where-Object { $_.name -like '*.appxbundle' } | Select-Object -ExpandProperty browser_download_url
# Update package using wingetcreate
Invoke-WebRequest https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
.\wingetcreate.exe update "isaaclevin.presencelight" --version $Version --urls "$installerUrl" --submit --token $Token

================================================
FILE: Build/scripts/update-desktop-settings.ps1
================================================
Param
(
    [parameter(Mandatory = $true)]    [string]
    $Release,

    [parameter(Mandatory = $true)]    [string]
    $Version,

    [parameter(Mandatory = $true)]
    [string]
    $ApplicationId,

    [parameter(Mandatory = $true)]
    [string]
    $ClientSecret,

    [parameter(Mandatory = $true)]
    [string]
    $InstrumentationKey,

    [parameter(Mandatory = $true)]
    [string]
    $LIFXClientId,

    [parameter(Mandatory = $true)]
    [string]
    $LIFXClientSecret,

    [parameter(Mandatory = $true)]
    [string]
    $RemoteHueClientId,

    [parameter(Mandatory = $true)]
    [string]
    $RemoteHueClientSecret,

    [parameter(Mandatory = $true)]
    [string]
    $RemoteHueClientAppName
)


switch ($Release) {
    "Release" {
        Write-Host "Updating AppxManifest for Release"
        $xmlPath= Resolve-Path ".\src\DesktopClient\PresenceLight.Package\Package.appxmanifest"
        Write-Host "Updating AppxManifest for Release"
        [xml]$manifest = get-content  $xmlPath
        $manifest.Package.Identity.Version = "${Version}.0"
        $manifest.save($xmlPath)
    }
    "Nightly" {
        Write-Host "Updating AppxManifest for Nightly"
        $xmlPath= Resolve-Path ".\src\DesktopClient\PresenceLight.Package\Package-Nightly.appxmanifest"
        Write-Host "Updating AppxManifest for Nightly"
        [xml]$manifest = get-content  $xmlPath
        $manifest.Package.Identity.Version = "${Version}.0"
        $manifest.save($xmlPath)
    }
    "Standalone" {
    }
}


Write-Host "Updating AppSettings for All Channels"
$appsettings = get-content ".\src\DesktopClient\PresenceLight\appsettings.json" -raw | ConvertFrom-Json
$appsettings.aadSettings.clientId = "${ApplicationId}"
$appsettings.appVersion = "${Version}"
$appsettings.lightSettings.lifx.LIFXClientId = "${LIFXClientId}"
$appsettings.lightSettings.lifx.LIFXClientSecret = "${LIFXClientSecret}"
$appsettings.applicationInsights.instrumentationkey = "${InstrumentationKey}"
$appsettings.lightSettings.hue.RemoteHueClientId = "${RemoteHueClientId}"
$appsettings.lightSettings.hue.RemoteHueClientSecret = "${RemoteHueClientSecret}"
$appsettings.lightSettings.hue.RemoteHueClientAppName = "${RemoteHueClientAppName}"
$appsettings | ConvertTo-Json -depth 32 | set-content '.\src\DesktopClient\PresenceLight\appsettings.json'

================================================
FILE: Build/scripts/update-web-settings.ps1
================================================
Param
(
    [parameter(Mandatory = $true)]    [string]
    $Version,

    [parameter(Mandatory = $true)]
    [string]
    $ApplicationId,

    [parameter(Mandatory = $true)]
    [string]
    $ClientSecret,

    [parameter(Mandatory = $true)]
    [string]
    $InstrumentationKey,

    [parameter(Mandatory = $true)]
    [string]
    $LIFXClientId,

    [parameter(Mandatory = $true)]
    [string]
    $LIFXClientSecret,

    [parameter(Mandatory = $true)]
    [string]
    $RemoteHueClientId,

    [parameter(Mandatory = $true)]
    [string]
    $RemoteHueClientSecret,

    [parameter(Mandatory = $true)]
    [string]
    $RemoteHueClientAppName
)

# Update AppSettings.json. This must be done before build.
$appsettings = get-content ".\src\PresenceLight.Web\appsettings.json" -raw | ConvertFrom-Json

$appsettings.AADSettings.clientId = $ApplicationId
$appsettings.AADSettings.clientSecret = $ClientSecret

$appsettings.appVersion = $GitBuildVersionSimple

$appsettings.applicationInsights.instrumentationkey = $InstrumentationKey
$appsettings | ConvertTo-Json -depth 32 | set-content '.\src\PresenceLight.Web\appsettings.json'

# Update PresenceLightSettings.json. This must be done before build.
$PresenceLightSettings = get-content ".\src\PresenceLight.Web\PresenceLightSettings.json" -raw | ConvertFrom-Json

$PresenceLightSettings.lightSettings.lifx.LIFXClientId = $LIFXClientId
$PresenceLightSettings.lightSettings.lifx.LIFXClientSecret = $LIFXClientSecret

$PresenceLightSettings.lightSettings.hue.RemoteHueClientId = $RemoteHueClientId
$PresenceLightSettings.lightSettings.hue.RemoteHueClientSecret = $RemoteHueClientSecret
$PresenceLightSettings.lightSettings.hue.RemoteHueClientAppName = $RemoteHueClientAppName

$PresenceLightSettings | ConvertTo-Json -depth 32 | set-content '.\src\PresenceLight.Web\PresenceLightSettings.json'

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2023 Isaac Levin

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: PrivacyPolicy.md
================================================
# Information Collected And Transmitted By PresenceLight

First, a reminder: PresenceLight is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement.

With that out of the way, here's a breakdown of all the information we may collect via Application Insights.

### Application-Level
Includes:
* Exception information
  * Could, in rare cases, contain paths packages on your computer
* Machine name
* Host name
* Version number (e.g. 2.0.x.x)

### Operating System-Level
Includes:
* Architecture (e.g. 32-bit)
* Version (e.g. Windows 10.0.17763.0)
* Build (e.g. 17134.1.amd64fre.rs4_release.180410-1804)
* Available processors/cores (e.g. 8 cores)
* Machine Name (e.g. MyFastPC)
* .NET Core Common Language Runtime version (e.g. 4.0.30319.42000)

## Package Sources

**OS information and IP address**

When Presence Light makes calls to authenticate users, it uses the Microsoft Graph Api. The author of PresenceLight does not have access to this information, but Microsoft Graph does and logs this information. 
**3rd-party package source**

When user specifies a different package source than the default source at http://nuget.org, he/she will be subjected to the privacy policy of that website. NuGet Package Explorer does not send any such data to its author.

## Third-Party Policies

* LIFX https://www.lifx.com/pages/privacy-policy
* Philips Hue https://www2.meethue.com/en-us/support/privacy-policy
* Microsoft Store https://docs.microsoft.com/en-us/legal/windows/agreements/store-policies


================================================
FILE: README.md
================================================
![Logo](https://github.com/isaacrlevin/PresenceLight/raw/main/Icon.png)
# PresenceLight

### NOTE: Due to internal changes at Microsoft, the Web/Container Version no longer works. I am currently looking into resolving this issue, but in the meantime, you will have to create an App Registration yourself and build the code on your own. :(


![.github/workflows/Deploy_Web.yml](https://github.com/isaacrlevin/presencelight/workflows/.github/workflows/Deploy_Web.yml/badge.svg)
![.github/workflows/Deploy_Desktop.yml](https://github.com/isaacrlevin/presencelight/workflows/.github/workflows/Deploy_Desktop.yml/badge.svg)

## Get PresenceLight

### Desktop Version

| Nightly | Microsoft Store | Chocolatey | GitHub Releases |
| ------- | --------------- | ---------- | --------------- |
| [<img src="https://github.com/isaacrlevin/PresenceLight/raw/main/Icon.png" width="100">](https://presencelight.blob.core.windows.net/nightly/index.html)| [<img src="https://github.com/isaacrlevin/PresenceLight/raw/main/static/store.svg" width="100">](https://www.microsoft.com/en-us/p/presencelight/9nffkd8gznl7) | [<img src="https://chocolatey.org/assets/images/global-shared/logo.svg" width="100">](https://chocolatey.org/packages/PresenceLight/) | [<img src="https://user-images.githubusercontent.com/8878502/110871471-55fe7c00-8283-11eb-8ce4-afeeaf62458a.png" width="100">](https://github.com/isaacrlevin/presencelight/releases) |

## Web Version

|Web Download Site | Web Container from DockerHub | Web Container from GitHub Registry
| ------- | --------------- | --------------- |
[<img src="https://github.com/isaacrlevin/PresenceLight/raw/main/Icon.png" width="100">](https://presencelightapp.azurewebsites.net/) | [<img src="https://user-images.githubusercontent.com/8878502/110870857-2602a900-8282-11eb-8846-89c61a219236.png" width="100">](https://hub.docker.com/r/isaaclevin/presencelight) | [<img src="https://user-images.githubusercontent.com/8878502/110871471-55fe7c00-8283-11eb-8ce4-afeeaf62458a.png" width="100">](https://github.com/users/isaacrlevin/packages/container/package/presencelight) |

## App Versions

| Application Type |  Platforms | Readme
|--- |  ---- | ---- |
| Desktop (.NET 10) | Windows 10 (min Version 1803) / Windows 11 | [Desktop Readme](docs/desktop-README.md)
| Web (ASP.NET 10) | Windows, MacOS, Linux (Debian, AMD x64, ARM, ARM x64),  | [Web Readme](docs/web-README.md)
## What is PresenceLight?

[PresenceLight](https://isaacl.dev/presence-light) is a solution to broadcast your various statuses to various kinds of smart lights. Some statuses you can broadcast are: your availability in Microsoft Teams or color of your choosing. There are other solutions that do something similar to sending Teams Availability to a light, but they require a tethered solution (plugging a light into a computer via USB). What PresenceLight does is leverage the [Presence Api](https://docs.microsoft.com/graph/api/presence-get), which is available in [Microsoft Graph](https://docs.microsoft.com/graph/overview), allowing to retrieve your presence without having to be tethered. This could potentially allow someone to update the light bulb from a remote machine they do not use.

#### [Blog Post](https://isaacl.dev/presence-light)

#### [PresenceLight Demos](https://www.youtube.com/playlist?list=PL_IEvQa-oTVtB3fKUclJNNJ1r-Sxtjc-m)

## Supported Hardware

| Light Type  |
| ------------ |
| Philips Hue (Local and Remote)
| LIFX |
| Yeelight |
| Philips Wiz |
| [WLED](https://kno.wled.ge/) (via serial or web API) |
| Any light which can be controlled via a GET or POST call to a web API |

## Docs
- [Configure Hardware](docs/configure-hardware.md)
- [FAQ](docs/faq.mdFAQ)
- [Configure Custom Api Endpoint](docs/configure-custom-api.md)
- [Configure Microsft Entra ID App (OPTIONAL)](/docs/configure-entra-app.md)

## Please Contribute

I welcome all contributions here! Before you do, please read the [Contributors Guide](docs/CONTRIBUTING.md)

## Third Party Libraries

Presence Light would not be possible without the amazing work from the contributors to the following third party libraries!

- Lights
  - [Q42.HueApi](https://github.com/Q42/Q42.HueApi)
  - [OpenWiz](https://github.com/UselessMnemonic/OpenWiz)
  - [YeelightAPI](https://github.dev/roddone/YeelightAPI)
  - [LifxCloud](https://github.com/isaacrlevin/LifxCloudClient)
- UI Components
  - [MudBlazor](https://www.mudblazor.com/)
  - [Blazorise](https://github.com/Megabit/Blazorise)
  - [BlazorPro.Spinkit](https://github.com/EdCharbeneau/BlazorPro.Spinkit)
- Backend
  - [MediatR](https://github.com/jbogard/MediatR)
  - [Polly](https://github.com/App-vNext/Polly)
  - [Serilog](https://github.com/serilog/serilog)
  - [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json)
  - [IdentityModel.OidcClient](https://github.com/IdentityModel/IdentityModel.OidcClient)
  - [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning)


================================================
FILE: chocolatey/PresenceLight.nuspec
================================================
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
  <metadata>
    <id>PresenceLight</id>
    <version>$version$</version>
    <title>PresenceLight</title>
    <authors>Isaac Levin</authors>
    <owners>Isaac Levin</owners>
    <packageSourceUrl>https://github.com/isaacrlevin/PresenceLight</packageSourceUrl>
    <docsUrl>https://github.com/isaacrlevin/presencelight/blob/main/README.md</docsUrl>
    <bugTrackerUrl>https://github.com/isaacrlevin/PresenceLight/issues</bugTrackerUrl>
    <licenseUrl>https://github.com/isaacrlevin/PresenceLight/blob/main/LICENSE</licenseUrl>
    <projectUrl>https://github.com/isaacrlevin/PresenceLight/</projectUrl>
    <projectSourceUrl>https://github.com/isaacrlevin/PresenceLight/</projectSourceUrl>
    <iconUrl>https://rawcdn.githack.com/isaacrlevin/PresenceLight/2c388db6a155cf7bbc8b12578222e0609d147ee0/Icon.png</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>
PresenceLight is a solution to broadcast your various statuses to a Philips Hue or LIFX light bulb.
Some statuses you can broadcast are: your availability in Microsoft Teams, your current Windows 10 theme, and a theme or color of your choosing.

## Package Parameters

- InstallDir: Change installation directory of PresenceLight. Default is "$env:APPDATA\PresenceLight"
    </description>
    <summary>Broadcasts colors to various Smart Lights</summary>
    <releaseNotes>https://github.com/isaacrlevin/PresenceLight/releases</releaseNotes>
    <tags>presence lifx hue philips teams</tags>
  </metadata>
  <files>
    <file src="tools\**" target="tools" />
  </files>
</package>


================================================
FILE: chocolatey/tools/ChocolateyBeforeModify.ps1
================================================
# Make sure to kill any presencelight processes before attempting an
# uninstall or upgrade of PresenceLight
Get-Process presencelight* -ErrorAction SilentlyContinue | Stop-Process

================================================
FILE: chocolatey/tools/ChocolateyInstall.ps1
================================================
$ErrorActionPreference  = 'Stop';

# Make sure to kill any presencelight processes before attempting an
# installation. This covers the case that PresenceLight is currently
# installed outside of Chocolatey
Get-Process presencelight* -ErrorAction SilentlyContinue | Stop-Process

$WindowsVersion=[Environment]::OSVersion.Version
if ($WindowsVersion.Major -ne "10") {
  throw "This package requires Windows 10."
}

$IsCorrectBuild=[Environment]::OSVersion.Version.Build
if ($IsCorrectBuild -lt "17134") {
  throw "This package requires at least Windows 10 version build 17134.x."
}

$packageName    = "presencelight"
$toolsDir       = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
$InstallDir     = Join-Path $env:APPDATA 'PresenceLight'

$pp = Get-PackageParameters

if($pp['InstallDir']){
  $InstallDir = $pp['InstallDir']
}

$packageArgs = @{
  packageName    = $packageName
  unzipLocation  = $InstallDir
  urlARM         = "{ARMLink}"
  url86bit       = "{x86Link}"
  url64bit       = "{x64Link}"
  checksumARM    = "{ReplaceCheckSumARM}"
  checksum       = "{ReplaceCheckSumx86}"
  checksum64     = "{ReplaceCheckSumx64}"
  checksumType   = 'SHA256'
}

Install-ChocolateyZipPackage @packageArgs

$exePath = Join-Path $InstallDir 'PresenceLight.exe'


Write-Output "Adding shortcut to Start Menu"
Install-ChocolateyShortcut -ShortcutFilePath "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\PresenceLight.lnk" -TargetPath $exePath -WorkingDirectory $InstallDir

Write-Output "Adding shortcut to Startup"
Install-ChocolateyShortcut -ShortcutFilePath "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\PresenceLight.lnk" -TargetPath $exePath -WorkingDirectory $InstallDir


================================================
FILE: chocolatey/tools/ChocolateyUninstall.ps1
================================================
# This logic can be removed after a couple of package version releases, since
# this will be handled in the ChocolateyBeforeModify.ps1 going forward
$light = Get-process presencelight*

if($light){
	$light | Stop-Process -Force
}

Remove-Item $Env:AppData\PresenceLight -Recurse -Force
Remove-Item "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\PresenceLight.lnk"
Remove-Item "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\PresenceLight.lnk"

================================================
FILE: chocolatey/tools/LICENSE.txt
================================================
MIT License

Copyright (c) 2023 Isaac Levin

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: chocolatey/tools/Verification.txt
================================================
VERIFICATION
Verification is intended to assist the Chocolatey moderators and community
in verifying that this package's contents are trustworthy.

The package has been generated by our CI system and binaries/scripts signed with Authenticode.


1. Download Zipped Application with below Url

Package Zip Urls
 - xARM Link: {ARMLink}
 - x86 Link: {x86Link}
 - x64 Link: {x64Link}

2. You can use one of the following methods to obtain the checksum
  - Use powershell function 'Get-Filehash'
  - Use chocolatey utility 'checksum.exe'

  checksum type: sha256
  checksum: {HASHx86}
  checksum64: {HASHx64}
  checksumARM:   {HASHARM}

================================================
FILE: docker-compose-example.yml
================================================
version: '3.7'

services:
  presencelight:
    image: isaaclevin/presencelight:latest
    container_name: presencelight
    restart: unless-stopped
    ports:
      - 5000:80
      - 5001:443
    volumes:
      - /mnt/c/Users/isaac/.aspnet/https:/https:ro
    environment:
      ASPNETCORE_HTTPS_PORT: "5001"
      ASPNETCORE_URLS: "https://+;http://+"
      ASPNETCORE_Kestrel__Certificates__Default__Password: "presencelight"
      ASPNETCORE_Kestrel__Certificates__Default__Path: "/https/presencelight.pfx"

================================================
FILE: docs/CONTRIBUTING.md
================================================
# Contributing to PresenceLight

Thank you for your interest in contributing to the PresenceLight project! We welcome contributions from the community to help improve and enhance the project. This guide will provide you with the necessary information to get started.

## Table of Contents
- [Contributing to PresenceLight](#contributing-to-presencelight)
  - [Table of Contents](#table-of-contents)
  - [Getting Started](#getting-started)
  - [Setting Up Local Environment](#setting-up-local-environment)
    - [Obtain Microsoft Entra Client ID.](#obtain-microsoft-entra-client-id)
    - [Debugging Windows App](#debugging-windows-app)
    - [Debugging Web App](#debugging-web-app)
  - [Adding New Functionality](#adding-new-functionality)
  - [Contributing Guidelines](#contributing-guidelines)
  - [Submitting a Pull Request](#submitting-a-pull-request)
  - [Code of Conduct](#code-of-conduct)
  - [License](#license)

## Getting Started

To contribute to PresenceLight, you will need to have the following prerequisites:

- Basic knowledge of Git and GitHub.
- Knowledge of the .NET framework and C# programming language (this project uses the latest version, [.NET 10](https://dot.net)).
- IDE of choice ([Visual Studio 2022](https://visualstudio.microsoft.com/downloads/), [Visual Studio Code](https://code.visualstudio.com/Download), [JetBrains Rider](https://www.jetbrains.com/rider/download))
- [Docker Desktop](https://www.docker.com/products/docker-desktop/) if you want to test the Web project running as a container.

## Setting Up Local Environment

### Obtain Microsoft Entra Client ID.
PresenceLight WILL not work if you try to clone and run, because there is a dependency on Microsoft Entra. Because of this, if you want to contribute to the project at this time, please reach out to the maintainer, [Isaac Levin](mailto:isaac@isaaclevin.com) to obtain the Client ID and Client Secret. Once obtained, the Client ID will need to be placed in 2 locations. Firstly, create create a copy of appsettings.json in both the Desktop and Web Projects, calling this new file `appsettings.Development.json`. Place the Client Id, in the proper locations in each file
- [Desktop Version](https://github.com/isaacrlevin/presencelight/blob/main/src/DesktopClient/PresenceLight/appsettings.json#L13)
- [Web Version](https://github.com/isaacrlevin/presencelight/blob/main/src/PresenceLight.Web/appsettings.json#L6)

If you have access to your own Microsoft Entra tenant, you can also create your own Entra App and use the Client ID as well. More information on that is [here](configure-entra-app.md).

### Debugging Windows App

After this, the app should build and run without issue. You can run the Desktop version as either a standalone .NET WPF app or as a packaged app that is deployed locally to the Windows store.
- To debug standalone version, set [PresenceLight](https://github.com/isaacrlevin/presencelight/blob/main/src/DesktopClient/PresenceLight/PresenceLight.csproj) as startup project
- To debug Microsoft Store version, set to [PresenceLight.Package](https://github.com/isaacrlevin/presencelight/blob/main/src/DesktopClient/PresenceLight.Package/PresenceLight.Package.wapproj)
  - NOTE: You may need to enable additional things in Visual Studio to make this work. More info [here](https://learn.microsoft.com/en-us/visualstudio/debugger/debug-installed-app-package)

### Debugging Web App

After adding the [Client ID](https://github.com/isaacrlevin/presencelight/blob/main/src/PresenceLight.Web/appsettings.json#L6) and [Client Secret](https://github.com/isaacrlevin/presencelight/blob/de14b62d0e6b433735eef653cee48d550747b60d/src/PresenceLight.Web/appsettings.json#L10), you should be able to debug the web version by setting the [Web Project](https://github.com/isaacrlevin/presencelight/blob/main/src/PresenceLight.Web/PresenceLight.Web.csproj) as startup.

## Adding New Functionality

If you are adding new functionality to PresenceLight (adding support for a new light for instance), there are a handful of steps you will need to take to enable the functionality in all versions. To understand what you need to do, it would be helpful to understand what all projects in the solution do.

- ### [PresenceLight.Core](https://github.com/isaacrlevin/presencelight/tree/main/src/PresenceLight.Core)
    This project holds all the shared logic for PresenceLight, including interfacing with Microsoft Entra, Microsoft Graph, all lights, as well as all the models that exist for the solution. More than likely, you will be working inside the [Lights](https://github.com/isaacrlevin/presencelight/tree/main/src/PresenceLight.Core/Lights) folder in this project to add a new folder to include the code to support your feature. The project uses [MediatR](https://github.com/jbogard/MediatR) to send messages in-process across the application. Be sure to follow the existing pattern when adding Requests and Handlers

- ### [PresenceLight.Razor]((https://github.com/isaacrlevin/presencelight/tree/main/src/PresenceLight.Razor))
  This project holds all of the UI for the application, and leverages ASP.NET Core Blazor components to achieve this. If you are adding new functionality, you will either update an existing component or add a new one. If you are adding a new component, you will add a new `.razor` file in the [Pages](https://github.com/isaacrlevin/presencelight/tree/main/src/PresenceLight.Razor/Components/Pages) folder and add an entry in the `NavMenu.razor` for your new component. Please follow the existing patterns that you see in the other `.razor` files.

- ### [PresenceLight](https://github.com/isaacrlevin/presencelight/tree/main/src/DesktopClient/PresenceLight)
    This is the WPF project that runs the desktop version of the application. The application contains a single Window that runs all of the functionality (calling the Graph API, calling handlers to update lights). Once you are ready to test your functionality for the Desktop version, add code to light up the functionality in [`MainWindow.xaml.cs`](https://github.com/isaacrlevin/presencelight/blob/main/src/DesktopClient/PresenceLight/MainWindow.xaml.cs).

- ### [PresenceLight.Package](https://github.com/isaacrlevin/presencelight/tree/main/src/DesktopClient/PresenceLight.Package)
  This project wires up the WPF project to run in the Microsoft store. You should not need to update or add anything to this project.

- ### [PresenceLight.Web](https://github.com/isaacrlevin/presencelight/tree/main/src/PresenceLight.Web)
    This is the project that runs the web version of the application. The project leverages a ASP.NET Core Worker Service to run the functionality that "polls" (calling Graph API, calling handlers for lights). Once you are ready to test your functionality for the Web version, add code to light up the functionality in [`Worker.cs`](https://github.com/isaacrlevin/presencelight/blob/main/src/PresenceLight.Web/Worker.cs).

## Contributing Guidelines

Before you start contributing, please take a moment to review the following guidelines:

1. Fork the repository and create a new branch for your contribution.
2. Make sure your code follows the project's coding style and conventions.
3. Write clear and concise commit messages.
4. Test your changes thoroughly before submitting a pull request. It is important if you are adding new functionality to test both the Desktop AND Web versions.
5. Document any new features or changes in the project's documentation.

## Submitting a Pull Request

Once you have made your changes and are ready to submit a pull request, follow these steps:

1. Push your changes to your forked repository.
2. Go to the original repository and create a new pull request.
3. Provide a clear and descriptive title for your pull request.
4. Include a detailed description of the changes you have made.
5. Wait for the project maintainers to review your pull request and provide feedback.

## Code of Conduct

Please note that by contributing to the PresenceLight project, you are expected to adhere to the project's Code of Conduct. The CoC is simple, be respectful and considerate towards others in all interactions.

## License

PresenceLight is licensed under the [MIT License](https://github.com/isaacrlevin/presencelight/blob/main/LICENSE). By contributing to this project, you agree to license your contributions under the same license.

---

We appreciate your contributions to the PresenceLight project! Thank you for helping us make it better.


================================================
FILE: docs/configure-custom-api.md
================================================
# Custom API

The Custom API page lets you use any generic service which has a web API which accepts GET or POST requests.

For example, IFTTT webhooks can be used to run an action on any IFTTT-integrated service.

In this way IFTTT can act as a bridge to other light services (such as Magic Home / MagicHue) or any other service which you may want to control with your Teams presence, e.g. 'When I'm in do not disturb pause Roomba'.

To connect PresenceLight to a custom API:

Configure the web service (e.g. created the applets in IFTTT)
Enter the corresponding API method and URI against each presence state.

   ![Configured](../static/CustomAPI.png)   
  
The Custom API REST API calls also support providing a json formatted body to the endpoints (Uri) of Custom API.

You can use the following variables in your JSON body:

- {{availability}}
- {{activity}}

If you use above variables in the JSON body they will be replaced with the availability and/or activity values of your Microsoft Teams status.

## Home Assistant integration

To use PresenceLight with Home Assistant you can use the Custom API functionality as follows:

In Home Assistant you can use [Webhooks triggers](https://www.home-assistant.io/docs/automation/trigger/#webhook-trigger) to trigger an Automation Action, like turning on a light bulb.

Example Automation for turning on a light bulb based on the Teams status send using the Custom API functionality of PresenceLight.

```yaml
alias: Teams presence - IKEA Light Bulb Living Room
description: >-
  Show the Microsoft Teams status via a color of the Light Bulb in the Living
  Room
trigger:
  - platform: webhook
    allowed_methods:
      - POST
    local_only: true
    webhook_id: "<enter secret webhook id here>"
condition: []
action:
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ trigger.json.presence_status == 'Busy' }}"
        sequence:
          - service: light.turn_on
            metadata: {}
            data:
              color_name: red
            target:
              entity_id: light.ikea_bulb
      - conditions:
          - condition: template
            value_template: "{{ trigger.json.presence_status == 'Available' }}"
        sequence:
          - service: light.turn_on
            metadata: {}
            data:
              color_name: green
            target:
              entity_id: light.ikea_bulb
      - conditions:
          - condition: template
            value_template: "{{ trigger.json.presence_status == 'Away' }}"
        sequence:
          - service: light.turn_on
            metadata: {}
            data:
              color_name: yellow
            target:
              entity_id: light.ikea_bulb
      - conditions: null
        sequence:
          - service: light.turn_off
            metadata: {}
            target:
              entity_id: light.ikea_bulb
            data: {}
mode: single
```

In PresenceLight Custom API setting you need to enter the following information:

| Method | Uri            | Body |
|--------|----------------|------|
| POST   | http://homeassistant.local:8123/api/webhook/webhook_id |    {   "presence_status":"Away" }  |


================================================
FILE: docs/configure-entra-app.md
================================================

## Configure an Entra ID Application

1. Sign in to the [Microsoft Entra admin center](https://entra.microsoft.com/) using either a work or school account or a personal Microsoft account.
1. If your account gives you access to more than one tenant, select your account in the top right corner, and set your portal session to the desired Azure AD tenant
   (using **Switch Directory**).
1. In the left-hand navigation pane, select the **Entra ID** service, and then select **App registrations**.

#### Register the client app (WpfApp)

1. Navigate to the Microsoft identity platform for developers [App registrations](https://go.microsoft.com/fwlink/?linkid=2083908) page.
1. Select **New registration**.
   - In the **Name** section, enter a meaningful application name that will be displayed to users of the app, for example `Presence Light`.
   - In the **Supported account types** section, select **Accounts in this organizational directory only (YOUR_TENANT_NAME only - Single tenant)**.
   - In the **Redirect URI (optional)** section, select **Public client/native (mobile & desktop)** and enter http://localhost for the value.
    - Select **Register** to create the application.
1. On the app **Overview** page, find the **Application (client) ID** value and record it for later.
1. On the app **Overview** page, find the **Directory (tenant) ID** value and record it for later.<br>![Ids](../static/id.png)
1. In the list of pages select **API permissions**
   - Select **Add a permission**
   - Ensure that the **Microsoft APIs** tab is selected
   - In the **Commonly used Microsoft APIs** section, click on **Microsoft Graph**
   - Select **Delegated permissions**.
   - Ensure that the right permissions are checked: **Presence.Read, User.Read**. Use the search box if necessary. See the screenshot below.
   - Select **Add permissions**
   - You can consent for your entire organization by selecting **Grant admin consent for YOUR_TENANT_NAME**
     - In the **Grant admin consent confirmation** section select **Yes**

   ![Api Permissions](..//static/api-perms.png)

#### Configuring PresenceLight (Desktop)

1. Start `PresenceLight`.
1. Select **Settings**
  1. Enter your **Directory (tenant) ID** or `common` if you elected to support Multitenant account types.
  1. Enter your **Application (client) ID**
  1. Select **SAVE SETTINGS**
1. Select **Team Status**
  1. Select **SIGN IN**
  1. Complete authentication in your browser.

================================================
FILE: docs/configure-hardware.md
================================================
## Hue HW Notes

### Hue Hardware Requirements

| Item  |
| ------------ |
| [Philips Hue Bridge](https://www2.meethue.com/en-us/p/hue-bridge/046677458478)
| [Philips Hue Light Bulb](https://www2.meethue.com/en-us/p/hue-white-and-color-ambiance-1-pack-e26/046677548483) |

You will need the above Philips Hue items to broadcast your presence to, but you can still "use" PresenceLight without them. One of the requirements of the Bridge is that it needs to be hard-wired to an internet connection via ethernet, so it will need to be placed close to a router or network switch. There are steps to setup the bridge and bulb in the [Hardware and Connectivity Section](https://www2.meethue.com/en-us/support/hardware-and-connectivity) of the Philips Support Site, but you should be able to just plug the bridge, wait for the lights to light up, get the IP address for the bridge, enter it into the app, and register the device. The app will register your device, create an account to interact with the bulbs, and finally add any bulbs it finds.

Philips also provides a Remote implementation of their connectivity (requires connecting your account to Philips Cloud). PresenceLight is configured to let you choose between the two.

## LIFX HW Notes

### LIFX Hardware Requirements
 [Any LIFX Light (tested with LIFX Beam & LIFX Color)](https://www.lifx.com/pages/all-products)

LIFX Bulbs can be connected to over [LAN Protocol](https://lan.developer.lifx.com/), or [Cloud Api](https://lifx.readme.io/docs). PresenceLight uses the Cloud, which requires getting a token from the [developer portal](https://cloud.lifx.com/settings). Putting that token in PresenceLight will enable all connected lights.

## Philips Wiz

| Item  |
| ------------ |
| [Wiz Smart Bulb](https://www.wizconnected.com/en-us/products/bulbs)
|
PresenceLight uses LAN discovery to get all Philips Wiz smart bulbs on the network.

## Yeelight
| Item  |
| ------------ |
| [Yeelight Smart Bulb](https://store.yeelight.com/collections/smart-bulb)
|
PresenceLight uses LAN discovery to get all Yeelight smart bulbs on the network.

================================================
FILE: docs/desktop-README.md
================================================
![Logo](https://github.com/isaacrlevin/PresenceLight/raw/main/Icon.png)
# PresenceLight - Desktop Version

![.github/workflows/Deploy_Desktop.yml](https://github.com/isaacrlevin/presencelight/workflows/.github/workflows/Deploy_Desktop.yml/badge.svg)

## Desktop App Setup

**NOTE: These steps are for the WPF (Windows desktop client) application. If you want to get PresenceLight working on non-Windows, please take a look at the [Web Readme](web-README.md).**




### Install App

After you have followed installed the app, you will see a window like below

   ![Configured](../static/configured.png)

PresenceLight obtains your Microsoft Teams Availability using a multi-tenant Microsoft Entra ID Application, meaning you will need to "grant" access to your Presence the first time you use the app. Clicking sign-in will prompt you for a login with your Microsoft 365 credentials, and finally when authenticated, you will be shown your Graph profile image and your presence. If you are curious about what is required to do this on your own tenant, read [Configure an Entra ID Application](configure-entra-app.md)

   ![Profile Image](../static/profile.png)

The application "polls" the Presence Api at a configured value, which you can set between 1 and 5 seconds on the Settings page. This means that the light and app will update based on your Teams presence with a slight delay.

### Broadcasting to Lights

There are 2 ways to currently update your lights using PresenceLight

 - Updating with Teams Presence (status)
 - Setting a fixed color using color picker

You can only do one of these at a time, so if you for instance are syncing with Teams, choosing another option will sign you out of Teams. This will happen with the other options as well.

## Customize Icons

One of the features of PresenceLight is that you can minimize the app to the icon tray. When you open the app, you will see an icon similar to this.

   ![white Image](../static/light-icon.png)

This icon will represent your presence color. There are two "kinds" of icons: Transparent, and White. Here is the transparent icon

   ![Settings 1](../static/trans-icon.png)

You can change the icon type in the settings pane.

   ![Settings 2](../static/settings1.png)

After you change and save, the icon will update in the icon tray.

## Wire Up Philips Light

To connect PresenceLight to Philips Hue, you can do it 1 of 2 ways

 - Obtain the IP Address of your Philips Hue Bridge (if you have it)
 - Ask PresenceLight to find it for you (may no work in certain network configurations)

 ![Hue Settings](../static/hue-settings.png)

Once you have the IP of the bridge, you will need to register a developer account and get an Api Key. This is easily done by clicking the "Register Bridge" button. Clicking the button will popup a window asking you to press the sync button on the bridge, this is needed to register PresenceLight to the bridge.

 ![Sync Button](../static/sync-button.png)

When PresenceLight is configured, you will see a dropdown of Hue Bulbs connected to the bridge for you to set your presence to.

 ![Registered Bridge](../static/registered-bridge.png)

## Wire up LIFX

To connect PresenceLight to LIFX colored bulbs, you need to obtain a LIFX Developer Token. When you first arrive at the LIFX tab, you will see a message like this if you try to get Lights or Groups

 ![LIFX Unconfigured](../static/lifx-unconfigured.png)

After entering an obtained token, you will be able to get a list of either individual lights or groups of lights, selecting one of the options and saving gives you a message like this

 ![LIFX Configured](../static/lifx-configured.png)

## [Wire-up Custom API](configure-custom-api.md)


## In Conclusion

At this point PresenceLight should be setup. Feel free to file an issue if you have any problems.

================================================
FILE: docs/faq.md
================================================
### What is the best version to install?
It really depends on your workflow, for normal users, I would use the Microsoft Store as it least barrier to entry.

### How do I use my lights after installing PresenceLight?
PresenceLight "polls" Graph and Windows Theme data until you tell it not to. The easiest way to do this is either shutdown the app, or do a one-time sync to a custom color, which should stop any polling.

### Where is X light?
I would love PresenceLight to support EVERY smart light on the market, but I do not have the hardware to do that, I simply wrote a tool with the HW I have. If you want to add your own Smart Light brand, a PR is the fastest way. If you do, please follow the [Contributors Guide](CONTRIBUTING.md)

### This only runs on Windows, lame....
I am currently working on a cross-platform version using ASP.NET Core Blazor and Workers. I have been testing it on WSL2 as well as a RaspberryPi I have at home and it seems to be working well. I will release more info about that [here](web-README.md)



================================================
FILE: docs/web-README.md
================================================
![Logo](Icon.png)
# PresenceLight - Web Version
![.github/workflows/Deploy_Web.yml](https://github.com/isaacrlevin/presencelight/workflows/.github/workflows/Deploy_Web.yml/badge.svg)

The cross platform version of PresenceLight runs as a .NET 9 single file executable application that runs a Server-Side Blazor Web Application and a ASP.NET Core Worker Service. The Blazor App is used as the mechanism to log the user in and configure settings of the app, while the Worker Service is responsible for interaction with Graph Api as well as the Smart Lights. This allows users to not need to have a UI version of the app open at all time, since the worker runs as a process.
## App Setup

### Prerequisites

For PresenceLight to run out of the box, you need to setup a local SSL Cert for the app to run under. Here are two ways to do this

- dotnet dev-certs
  - dotnet dev-certs https -ep C:\Users\youruserid\.aspnet\https\presencelight.pfx -p presencelight
  - dotnet dev-certs https --trust
- openssl (Linux)
  - [Go here make your life easier](https://www.digicert.com/easy-csr/openssl.htm)
  - openssl x509 -signkey my_web_domain.key -in my_web_domain.csr -req -days 365 -out my_web_domain.crt
  - openssl pkcs12 -inkey my_web_domain.key -in my_web_domain.crt -export -out %PATHTOYOURCERT%/presencelight.pfx

### Install

There is no installer for PresenceLight, so all that needs to be done is to download the zip folder from the [install site](http://presencelightapp.azurewebsites.net/), unzip, and run the .exe. At this point, a terminal window will open showing

 ![Terminal](../static/blazor-terminal.png)

Here you will the Url for the Kestrel hosted Web Application, which will be `https://localhost:5001`. Going to that Url will take you through the login process for Azure Active Directory (for the Graph call). After login, you will see a similar look and feel to the client app.

 ![Index](../static/blazor-index.png)

 From here you can use PresenceLight in a similar way to the client app. You can enable and operate lights, push custom lights and configure polling. When done, you can close the browser and PresenceLight will continue to run in the background.

 To make the process even cleaner, you can configure a startup task to run the exe at startup, and PresenceLight will be available at the url listed the first time you ran it.

## Running PresenceLight as a container

PresenceLight can be configured to run in a Docker container, and I have images on my [DockerHub](https://hub.docker.com/repository/docker/isaaclevin/presencelight) for the primary Linux distros.

- x64 Linux (latest tag)
- ARM64 (debian-arm64 tag)
- ARM32 (debian-arm32 tag) **This is the 4GB Raspberry Pi one**

### How are you handling SSL?

In order for PresenceLight to work, you need to have a redirect url to AAD
that is https. In order to make it easy for folks, I provided a self-signed cert that will allow PresenceLight to do Https redirection out of the box. Isaac is this secure? Weeeeeeelllll not the best, but since PresenceLight runs locally you should be fine. If you want to expose PresenceLight over the internet, it more than likely won't work as I have to register EACH redirect uri with Azure AD.

For my particular use-case I do not need SSL. WHAT?!?! Actually it is pretty cool. My personal setup is that PresenceLight runs in a docker container on a Raspberry Pi. I have Traefik, which is a well-known
reverse proxy that allows me to forward applications through my domain, so I can access the application from anywhere by going to

presencelight.mydomain.com

The best part about this is that [Traefik](https://traefik.io/) can be configured to pull LetsEncrypt Certificates and integration with CloudFlare SSL. There is a [great blog post on this](https://www.smarthomebeginner.com/traefik-2-docker-tutorial/), that I highly reccomend if you are interested.

### SSL for Docker Containers

To get PresenceLight to work in a Docker container, you will need to obtain (or generate like above) a certificate and mount it as a volume to your container.

**[Doc on subject](https://docs.microsoft.com/dotnet/core/additional-tools/self-signed-certificates-guide)**

Once you have a valid .pfx file, you will need to wire up the app to use that cert, the way you do that depends on how you host your app. If you app is just running locally on the machine,
you can just set environment variables for your app.

- ASPNETCORE_Kestrel__Certificates__Default__Path
- ASPNETCORE_Kestrel__Certificates__Default__Password

Or if you are running in docker, you will need to mount a volume that has your cert in it.

Here are some examples for this

**docker run example**

```bash
docker run --rm -it -p 5000:80 -p 5001:443 -e ASPNETCORE_URLS="https://+;http://+" -e ASPNETCORE_HTTPS_PORT=5001 -e ASPNETCORE_Kestrel__Certificates__Default__Password="presencelight" -e ASPNETCORE_Kestrel__Certificates__Default__Path=/https/presencelight.pfx -v $env:USERPROFILE\.aspnet\https:/https/ isaaclevin/presencelight
```


**docker-compose example**

```bash
ports:
  - 5000:80
  - 5001:443
volumes:
  C:\Users\isaac\.aspnet\https:/https/ #Windows Way
  /mnt/c/Users/isaac/.aspnet/https:/https #Linux Way
environment:
  ASPNETCORE_HTTPS_PORT: "5001"
  ASPNETCORE_URLS: "https://+;http://+"
  ASPNETCORE_Kestrel__Certificates__Default__Password: "presencelight"
  ASPNETCORE_Kestrel__Certificates__Default__Path: "/https/presencelight.pfx"
```

### Mounting settings path to host

If you want to configure PresenceLight to use your own settings (maybe your own AAD, your own smart light registered app), you can do that by editing the appsettings.json

To do this in docker, just run the container once, and than stop and rerun by mounting the appsettings via a local volume.**

Log data and Configuration file will need to be written to a directory that has read/write enabled.   This is accomplished using
volumes.
```dotnetcli
volumes:
    /somedirectory:/app/config
```

`/app/config/appsettings.json` contains settings for AAD and `/app/config/PresenceLightSettings.json` contains settings for Lights, in case you wanted to configure lights outside of the UI. If you need to customize your configuration. Add/edit one or more of the nec`essary configuration files in this attached directory. This will get you host access to the appsettings.json and PresenceLightSettings.json

When running under a container, logs will save to  `/app/config/logs` as well.

================================================
FILE: src/.editorconfig
================================================
# EditorConfig is awesome:http://EditorConfig.org
# From https://raw.githubusercontent.com/dotnet/roslyn/master/.editorconfig

# top-most EditorConfig file
root = true

# Don't use tabs for indentation.
[*]
indent_style = space
trim_trailing_whitespace = true
# (Please don't specify an indent_size here; that has too many unintended consequences.)

# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
insert_final_newline = true
charset = utf-8-bom

# Xml project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2

# Xml config files
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2

# Yml/Yaml files
[*.{yaml,yml}]
indent_size = 2

# Powershell files
[*.ps1]
indent_size = 2

# JSON files
[*.json]
indent_size = 2

# Shell scripts
[*.sh]
end_of_line = lf

[*.{cmd,bat}]
end_of_line = crlf

# Dotnet code style settings:
[*.{cs,vb}]
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
# Put a blank line between System.* and Microsoft.*
dotnet_separate_import_directive_groups = true

# Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion

# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion

# Prefer read-only on fields
dotnet_style_readonly_field = false:warning

# Suggest more modern language features when available
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_conditional_expression_over_return = false
dotnet_style_prefer_conditional_expression_over_assignment = false
dotnet_style_prefer_auto_properties = true:suggestion

# Parentheses
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent

# Accessibility modifiers
dotnet_style_require_accessibility_modifiers = always:suggestion


# Naming Rules

# Interfaces start with an I and are PascalCased
dotnet_naming_rule.interfaces_must_be_pascal_cased_and_prefixed_with_I.symbols                        = interface_symbols
dotnet_naming_rule.interfaces_must_be_pascal_cased_and_prefixed_with_I.style                          = pascal_case_and_prefix_with_I_style
dotnet_naming_rule.interfaces_must_be_pascal_cased_and_prefixed_with_I.severity                       = warning

# External members are PascalCased
dotnet_naming_rule.externally_visible_members_must_be_pascal_cased.symbols                            = externally_visible_symbols
dotnet_naming_rule.externally_visible_members_must_be_pascal_cased.style                              = pascal_case_style
dotnet_naming_rule.externally_visible_members_must_be_pascal_cased.severity                           = warning

# Parameters are camelCased
dotnet_naming_rule.parameters_must_be_camel_cased.symbols                                             = parameter_symbols
dotnet_naming_rule.parameters_must_be_camel_cased.style                                               = camel_case_style
dotnet_naming_rule.parameters_must_be_camel_cased.severity                                            = warning

# Constants are PascalCased
dotnet_naming_rule.constants_must_be_pascal_cased.symbols                                             = constant_symbols
dotnet_naming_rule.constants_must_be_pascal_cased.style                                               = pascal_case_style
dotnet_naming_rule.constants_must_be_pascal_cased.severity                                            = warning

# Uncomment this group and comment out the next group if you prefer s_ prefixes for static fields

# Private static fields are prefixed with s_ and are camelCased like s_myStatic
#dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.symbols   = private_static_field_symbols
#dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.style     = camel_case_and_prefix_with_s_underscore_style
#dotnet_naming_rule.private_static_fields_must_be_camel_cased_and_prefixed_with_s_underscore.severity  = warning

# Static readonly fields are PascalCased
dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.symbols                               = private_static_readonly_field_symbols
dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.style                                 = pascal_case_style
dotnet_naming_rule.static_readonly_fields_should_be_pascal_case.severity                              = warning

# Comment this group and uncomment out the next group if you don't want _ prefixed fields.

# Private instance fields are camelCased with an _ like _myField
dotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.symbols   = private_field_symbols
dotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.style     = camel_case_and_prefix_with_underscore_style
dotnet_naming_rule.private_instance_fields_must_be_camel_cased_and_prefixed_with_underscore.severity  = warning

# Private instance fields are camelCased
#dotnet_naming_rule.private_instance_fields_must_be_camel_cased.symbols                                = private_field_symbols
#dotnet_naming_rule.private_instance_fields_must_be_camel_cased.style                                  = camel_case_style
#dotnet_naming_rule.private_instance_fields_must_be_camel_cased.severity                               = warning

# Symbols
dotnet_naming_symbols.externally_visible_symbols.applicable_kinds                                     = class,struct,interface,enum,property,method,field,event,delegate
dotnet_naming_symbols.externally_visible_symbols.applicable_accessibilities                           = public,internal,friend,protected,protected_internal,protected_friend,private_protected

dotnet_naming_symbols.interface_symbols.applicable_kinds                                              = interface
dotnet_naming_symbols.interface_symbols.applicable_accessibilities                                    = *

dotnet_naming_symbols.parameter_symbols.applicable_kinds                                              = parameter
dotnet_naming_symbols.parameter_symbols.applicable_accessibilities                                    = *

dotnet_naming_symbols.constant_symbols.applicable_kinds                                               = field
dotnet_naming_symbols.constant_symbols.required_modifiers                                             = const
dotnet_naming_symbols.constant_symbols.applicable_accessibilities                                     = *

dotnet_naming_symbols.private_static_field_symbols.applicable_kinds                                   = field
dotnet_naming_symbols.private_static_field_symbols.required_modifiers                                 = static,shared
dotnet_naming_symbols.private_static_field_symbols.applicable_accessibilities                         = private

dotnet_naming_symbols.private_static_readonly_field_symbols.applicable_kinds                          = field
dotnet_naming_symbols.private_static_readonly_field_symbols.required_modifiers                        = static,shared,readonly
dotnet_naming_symbols.private_static_readonly_field_symbols.applicable_accessibilities                = private

dotnet_naming_symbols.private_field_symbols.applicable_kinds                                          = field
dotnet_naming_symbols.private_field_symbols.applicable_accessibilities                                = private

# Styles
dotnet_naming_style.camel_case_style.capitalization                                                   = camel_case

dotnet_naming_style.pascal_case_style.capitalization                                                  = pascal_case

dotnet_naming_style.camel_case_and_prefix_with_s_underscore_style.required_prefix                     = s_
dotnet_naming_style.camel_case_and_prefix_with_s_underscore_style.capitalization                      = camel_case

dotnet_naming_style.camel_case_and_prefix_with_underscore_style.required_prefix                       = _
dotnet_naming_style.camel_case_and_prefix_with_underscore_style.capitalization                        = camel_case

dotnet_naming_style.pascal_case_and_prefix_with_I_style.required_prefix                               = I
dotnet_naming_style.pascal_case_and_prefix_with_I_style.capitalization                                = pascal_case


# CSharp code style settings:
[*.cs]
# Modifier order
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion

# Code block
csharp_prefer_braces = false:none

# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = flush_left

# Prefer "var" everywhere
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion

# Code style defaults
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true

# Prefer method-like constructs to have a block body
csharp_style_expression_bodied_methods = false:none
csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_operators = false:none

# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:none
csharp_style_expression_bodied_indexers = true:none
csharp_style_expression_bodied_accessors = true:none

# Expression 
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion

# Pattern matching
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion

# Null checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion

# Newline settings
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true

# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false

# CA1303: Do not pass literals as localized parameters
dotnet_diagnostic.CA1303.severity = none

# CA1051: Do not declare visible instance fields
dotnet_diagnostic.CA1051.severity = none

# CA1031: Do not catch general exception types
dotnet_diagnostic.CA1031.severity = none

# CA1812: Avoid uninstantiated internal classes
dotnet_diagnostic.CA1812.severity = silent

# CA1816: Dispose methods should call SuppressFinalize
dotnet_diagnostic.CA1816.severity = silent

# CA1054: Uri parameters should not be strings
dotnet_diagnostic.CA1054.severity = none
dotnet_diagnostic.CA1056.severity = none

# Default severity for analyzer diagnostics with category 'Style'
dotnet_analyzer_diagnostic.category-Style.severity = silent


================================================
FILE: src/DesktopClient/Directory.Build.props
================================================
<Project>

  <PropertyGroup>
    <Authors>Isaac Levin</Authors>
    <Copyright>© 2023 Isaac Levin</Copyright>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <DefaultLanguage>en-US</DefaultLanguage>
    <NoWarn>1701;1702;1705;1591;NU1701</NoWarn>

    <IsLegacyProject>$(MSBuildProjectName.Equals('PresenceLight'))</IsLegacyProject>
    <IsPackageProject>$(MSBuildProjectName.Contains('.Package'))</IsPackageProject>
    <DebugType>embedded</DebugType>

    <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
    <PublishRepositoryUrl>true</PublishRepositoryUrl>
    <EmbedUntrackedSources>true</EmbedUntrackedSources>
    <UseWpf>true</UseWpf>
    <UseWindowsForms>true</UseWindowsForms>
    <MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);NETSDK1107</MSBuildWarningsAsMessages>

    <LangVersion>preview</LangVersion>
    <NullableContextOptions>enable</NullableContextOptions>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <PropertyGroup>
    <NuGetDependencyVersion>5.8.0</NuGetDependencyVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
  </ItemGroup>

  <PropertyGroup>
    <ReleaseChannel Condition="'$(ChannelName)' == '' ">Debug</ReleaseChannel>
  </PropertyGroup>

  <PropertyGroup>
    <DefineConstants Condition="'$(ChannelName)' == 'Nightly' ">$(DefineConstants);NIGHTLY</DefineConstants>
    <DefineConstants Condition="'$(ChannelName)' == 'Release' ">$(DefineConstants);RELEASE</DefineConstants>
    <DefineConstants Condition="'$(ChannelName)' == 'Standalone' ">$(DefineConstants);STANDALONE</DefineConstants>
  </PropertyGroup>

</Project>


================================================
FILE: src/DesktopClient/Directory.Build.targets
================================================
<Project>

  <Target Name="AddCommitHashToAssemblyAttributes" BeforeTargets="GetAssemblyAttributes">
    <ItemGroup>
      <AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute" Condition=" '$(SourceRevisionId)' != '' ">
        <_Parameter1>CommitHash</_Parameter1>
        <_Parameter2>$(SourceRevisionId)</_Parameter2>
      </AssemblyAttribute>

      <AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute" Condition=" '$(PublicRelease)' == 'true' ">
        <_Parameter1>CloudBuildNumber</_Parameter1>
        <_Parameter2>$(BuildVersionSimple)</_Parameter2>
      </AssemblyAttribute>
      <AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute" Condition=" '$(PublicRelease)' == 'false' ">
        <_Parameter1>CloudBuildNumber</_Parameter1>
        <_Parameter2>$(BuildVersionSimple)$(SemVerBuildSuffix)</_Parameter2>
      </AssemblyAttribute>
    </ItemGroup>

  </Target>
</Project>

================================================
FILE: src/DesktopClient/PresenceLight/App.xaml
================================================
<Application x:Class="PresenceLight.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:PresenceLight"
             Startup="OnStartup">
    <Application.Resources>
         
    </Application.Resources>
</Application>


================================================
FILE: src/DesktopClient/PresenceLight/App.xaml.cs
================================================
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Windows;

using Blazorise;
using Blazorise.Bootstrap;
using Blazorise.Icons.FontAwesome;

using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

using MudBlazor.Services;

using PresenceLight.Core;
using PresenceLight.Razor;
using PresenceLight.Razor.Services;
using PresenceLight.Services;
using PresenceLight.Telemetry;

using Serilog;

using Windows.Storage;

namespace PresenceLight
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : System.Windows.Application
    {
        public IServiceProvider? ServiceProvider { get; private set; }

        public IConfiguration? Configuration { get; private set; }

        public App()
        {


        }

        private void OnStartup(object sender, StartupEventArgs e)
        {
            if (SingleInstanceAppMutex.TakeExclusivity())
            {
                Exit += (_, __) => SingleInstanceAppMutex.ReleaseExclusivity();

                try
                {
                    ContinueStartup();
                }
                catch (Exception ex) when (IsCriticalFontLoadFailure(ex))
                {
                    Trace.WriteLine($"## Warning Notify ##: {ex}");
                    Log.Error(ex, "Stopped program because of exception");
                }
            }
            else
            {
                Log.CloseAndFlush();
                Shutdown();
            }
        }

        Dictionary<string, string> InMemorySettings = new();

        private void ContinueStartup()
        {
            IServiceCollection services = new ServiceCollection();

            // Configuration Section
            var builder = new Microsoft.Extensions.Configuration.ConfigurationBuilder()
                 .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                 .AddJsonFile($"appsettings.Development.json", optional: true, reloadOnChange: true);

            string userAppSettings;

            //Override the save file location for logs if this is a packaged app... 
            if (new DesktopBridge.Helpers().IsRunningAsUwp())
            {
                var _logFilePath = System.IO.Path.Combine(ApplicationData.Current.LocalFolder.Path, "PresenceLight\\logs\\DesktopClient\\log-.json");
                InMemorySettings.Add("Serilog:WriteTo:1:Args:Path", _logFilePath);
                builder.AddInMemoryCollection(InMemorySettings);

                userAppSettings = AppPackageSettingsService.BuildSettingsFileLocation();
            }
            else
            {
                userAppSettings = StandaloneSettingsService.BuildSettingsFileLocation();
            }
            
            builder.AddJsonFile(userAppSettings, optional: true, reloadOnChange: true);

            Configuration = builder.Build();

            services.Configure<BaseConfig>(Configuration);
            services.AddSingleton(Configuration);
            services.AddOptions();
            services.Configure<AADSettings>(Configuration.GetSection("AADSettings"));

            //Logging
            var telemetryConfiguration = TelemetryConfiguration.CreateDefault();
            telemetryConfiguration.InstrumentationKey = Configuration["ApplicationInsights:InstrumentationKey"];



            var loggerConfig =
            new LoggerConfiguration()
                          .ReadFrom.Configuration(Configuration)
                          .WriteTo.PresenceEventsLogSink()
                          .Enrich.FromLogContext();


            Log.Logger = loggerConfig.CreateLogger();
            Log.Debug("Starting PresenceLight");

            services.AddLogging(logging =>
            {
                logging.AddSerilog();
            });

#if DEBUG
            services.AddBlazorWebViewDeveloperTools();
#endif


            services.Configure<TelemetryConfiguration>((o) =>
            {
                o.InstrumentationKey = Configuration["ApplicationInsights:InstrumentationKey"];
                o.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer());
            });
            services.AddApplicationInsightsTelemetryWorkerService(options =>
            {
                options.EnablePerformanceCounterCollectionModule = false;
                options.EnableDependencyTrackingTelemetryModule = false;
            });



            //Blazor

            services.AddMudServices();

            services.AddHttpClient();
            services.AddHttpContextAccessor();
            services.AddWpfBlazorWebView();

            services.AddBlazorise(options =>
                 {
                     options.Immediate = true;
                 })
    .AddBootstrapProviders()
    .AddFontAwesomeIcons();

            services.AddMediatR(cfg =>
            {
                cfg.RegisterServicesFromAssembly(typeof(App).Assembly);
                cfg.RegisterServicesFromAssembly(typeof(BaseConfig).Assembly);
            });

            //Singleton Services
            services.AddSingleton<AppState>();
            services.AddSingleton<AppInfo, AppInfo>();

            services.AddSingleton<LoginService, LoginService>();
            services.AddSingleton<AuthorizationProvider, AuthorizationProvider>();



            services.AddPresenceServices();

            services.AddSingleton<LIFXOAuthHelper, LIFXOAuthHelper>();
            services.AddSingleton<MainWindow>();
            services.AddTransient<DiagnosticsClient, DiagnosticsClient>();

            if (new DesktopBridge.Helpers().IsRunningAsUwp())
            {
                services.AddSingleton<ISettingsService, AppPackageSettingsService>();
            }
            else
            {
                services.AddSingleton<ISettingsService, StandaloneSettingsService>();
            }

            services.AddSingleton<ITelemetryInitializer, AppVersionTelemetryInitializer>();

            //Inject Services Into MainWindow
            ServiceProvider = services.BuildServiceProvider();

            var configuration = ServiceProvider.GetService<TelemetryConfiguration>();

            if (configuration != null)
            {
                var b = configuration.DefaultTelemetrySink.TelemetryProcessorChainBuilder;
                double fixedSamplingPercentage = 10;
                b.UseSampling(fixedSamplingPercentage);
                b.Build();
            }
            var mainWindow = ServiceProvider.GetRequiredService<MainWindow>();
            mainWindow.Show();
        }

        private static bool IsCriticalFontLoadFailure(Exception ex)
        {
            return ex.StackTrace.Contains("MS.Internal.Text.TextInterface.FontFamily.GetFirstMatchingFont", StringComparison.OrdinalIgnoreCase) ||
                   ex.StackTrace.Contains("MS.Internal.Text.Line.Format", StringComparison.OrdinalIgnoreCase);
        }
    }
}


================================================
FILE: src/DesktopClient/PresenceLight/MainWindow.xaml
================================================
<Window x:Class="PresenceLight.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:l="clr-namespace:PresenceLight"
        xmlns:gif="http://wpfanimatedgif.codeplex.com"
        xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
        xmlns:local="clr-namespace:PresenceLight.Razor.Components;assembly=PresenceLight.Razor"
        xmlns:blazor="clr-namespace:Microsoft.AspNetCore.Components.WebView.Wpf;assembly=Microsoft.AspNetCore.Components.WebView.Wpf"
        mc:Ignorable="d" Title="PresenceLight" Icon="Icons/Icon.ico" Height="1200" Width="1200" MinWidth="600px">
    <Grid>
        <blazor:BlazorWebView x:Name="blazorWebView1" HostPage="wwwroot\index.html" Services="{StaticResource services}" Loaded="MainWindow_Loaded">
            <blazor:BlazorWebView.RootComponents>
                <blazor:RootComponent Selector="#app" ComponentType="{x:Type local:PresenceLightClientApp}" />
            </blazor:BlazorWebView.RootComponents>
        </blazor:BlazorWebView>
        <l:NotifyIcon x:Name="notificationIcon" MouseDoubleClick="OnNotifyIconDoubleClick">
            <l:NotifyIcon.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Open" Click="OnOpenClick" />
                    <MenuItem Header="Turn Off Sync" x:Name="turnOffButton" Click="OnTurnOffSyncClick" />
                    <MenuItem Header="Turn On Sync" x:Name="turnOnButton" Click="OnTurnOnSyncClick" />
                    <MenuItem Header="Exit" Click="OnExitClick" />
                </ContextMenu>
            </l:NotifyIcon.ContextMenu>
        </l:NotifyIcon>
    </Grid>
</Window>


================================================
FILE: src/DesktopClient/PresenceLight/MainWindow.xaml.cs
================================================
using System;
using System.IO;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Graph;
using Microsoft.Graph.Models;
using Microsoft.Identity.Client;

using PresenceLight.Core;

using PresenceLight.Telemetry;

namespace PresenceLight
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly BaseConfig _options;


        // private Presence presence { get; set; }
        private DateTime settingsLastSaved = DateTime.MinValue;

        private MediatR.IMediator _mediator;

        private readonly LoginService _loginService;
        private DiagnosticsClient _diagClient;
        private ISettingsService _settingsService;
        private WindowState lastWindowState;
        private bool isInteractRunning;
        private readonly ILogger<MainWindow> _logger;
        private readonly AppState _appState = new AppState();

        #region Init
        public MainWindow(LoginService loginService,
                          MediatR.IMediator mediator,
                          IOptionsMonitor<BaseConfig> optionsAccessor,
                          DiagnosticsClient diagClient,
                          ILogger<MainWindow> logger,
                          ISettingsService settingsService,
                          AppState appState)
        {
            var currentApp = (App)System.Windows.Application.Current;
            Resources.Add("services", currentApp.ServiceProvider);
            InitializeComponent();
            _appState = appState;

            _logger = logger;
            System.Windows.Application.Current.SessionEnding += new SessionEndingCancelEventHandler(Current_SessionEnding);

            _loginService = loginService;


            _mediator = mediator;
            _options = optionsAccessor != null ? optionsAccessor.CurrentValue : throw new NullReferenceException("Options Accessor is null");
            _diagClient = diagClient;
            _settingsService = settingsService;

            LoadSettings().ContinueWith(
                async t =>
                {
                    if (t.IsFaulted)
                    {
                        var foo = "";
                    }

                    await Task.Run(async () =>
                    {
                        this.Dispatcher.Invoke(() =>
                        {
                            appState.SignedIn = false;
                            LoadApp();

                            var tbContext = notificationIcon.DataContext;
                            DataContext = _appState.Config;
                            notificationIcon.DataContext = tbContext;

                            if (_appState.Config.StartMinimized)
                            {
                                this.Hide();
                            }

                        });

                        while (true)
                        {

                            await Task.Run(async () =>
                            {
                                Thread.Sleep(100);
                                if (_appState.SignInRequested)
                                {
                                    _appState.SignInRequested = false;

                                    await this.Dispatcher.BeginInvoke(async () =>
                                     {
                                         await SignIn();
                                     });
                                }

                                if (_appState.SignOutRequested)
                                {
                                    _appState.SignOutRequested = false;
                                    await this.Dispatcher.BeginInvoke(async () =>
                                     {
                                         await SignOut();
                                     });
                                }

                                if (_appState.RebuildRequested)
                                {
                                    _appState.RebuildRequested = false;
                                    await this.Dispatcher.BeginInvoke(async () =>
                                     {
                                         await RebuildClient();
                                     });
                                }
                            });
                        }
                    });
                }, TaskScheduler.Current);
        }

        private async Task LoadSettings()
        {
            try
            {
                _logger.LogInformation("Load Settings Initialized");
                if (!(await _settingsService.IsFilePresent()))
                {
                    await _settingsService.SaveSettings(_options);
                }

                _appState.SetConfig(await _settingsService.LoadSettings() ?? throw new NullReferenceException("Settings Load Service Returned null"));

                bool useWorkingHours = await _mediator.Send(new Core.WorkingHoursServices.UseWorkingHoursCommand());
                bool IsInWorkingHours = await _mediator.Send(new Core.WorkingHoursServices.IsInWorkingHoursCommand());
                _logger.LogInformation("Load Settings Successfull");
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Error occurred Loading Settings");
                _diagClient.TrackException(e);
            }
        }
        private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            await blazorWebView1.WebView.EnsureCoreWebView2Async();
            blazorWebView1.WebView.CoreWebView2.Settings.IsZoomControlEnabled = false;
        }
        private void LoadApp()
        {
            try
            {
                notificationIcon.Text = $"PresenceLight Status - {PresenceConstants.Inactive}";
                notificationIcon.Icon = new BitmapImage(new Uri(IconConstants.GetIcon(string.Empty, string.Empty)));

                _appState.Config.LightSettings.WorkingHoursStartTimeAsDate = string.IsNullOrEmpty(_appState.Config.LightSettings.WorkingHoursStartTime) ? null : DateTime.Parse(_appState.Config.LightSettings.WorkingHoursStartTime, null);
                _appState.Config.LightSettings.WorkingHoursEndTimeAsDate = string.IsNullOrEmpty(_appState.Config.LightSettings.WorkingHoursEndTime) ? null : DateTime.Parse(_appState.Config.LightSettings.WorkingHoursEndTime, null);

                CallGraph();
            }
            catch (Exception e)
            {
                _logger.LogError(e, $"Error occurred - {e.Message}");
            }
        }

        #endregion

        #region Profile Panel

        private async Task SignIn()
        {
            await CallGraph();
        }

        private async Task CallGraph()
        {
            _appState.SetLightMode("Graph");
            _logger.LogInformation("Light Mode Set: Graph");
            if (!await _mediator.Send(new Core.GraphServices.GetIsInitializedCommand()))
            {
                await _mediator.Send(new Core.GraphServices.InitializeCommand()
                {
                });

                if (_loginService.IsInitialized)
                {
                    _appState.SignedIn = true;
                }
            }

            try
            {
                await _settingsService.SaveSettings(_appState.Config);

                if (!isInteractRunning)
                {
                    await InteractWithLights();
                }
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Error occurred calling Graph");
            }
        }

        public async Task SetColor(string color, string activity = "")
        {
            try
            {
                if (_appState.Config.LightSettings.Hue.IsEnabled)
                {
                    if (Helpers.AreStringsNotEmpty(new string[] {
                                                    _appState.Config.LightSettings.Hue.HueApiKey,
                                                    _appState.Config.LightSettings.Hue.SelectedItemId}))
                    {
                        if (_appState.Config.LightSettings.Hue.UseRemoteApi)
                        {
                            if (!string.IsNullOrEmpty(_appState.Config.LightSettings.Hue.RemoteBridgeId))
                            {
                                await _mediator.Send(new Core.RemoteHueServices.SetColorCommand
                                {
                                    Availability = color,
                                    Activity = activity,
                                    LightId = _appState.Config.LightSettings.Hue.SelectedItemId,
                                    BridgeId = _appState.Config.LightSettings.Hue.RemoteBridgeId
                                });
                            }
                        }
                        if (!string.IsNullOrEmpty(_appState.Config.LightSettings.Hue.HueIpAddress))
                        {
                            await _mediator.Send(new Core.HueServices.SetColorCommand()
                            {
                                Activity = activity,
                                Availability = color,
                                LightID = _appState.Config.LightSettings.Hue.SelectedItemId
                            });
                        }
                    }
                }

                if (_appState.Config.LightSettings.LIFX.IsEnabled && !string.IsNullOrEmpty(_appState.Config.LightSettings.LIFX.LIFXApiKey))
                {
                    await _mediator.Send(new PresenceLight.Core.LifxServices.SetColorCommand { Activity = activity, Availability = color, LightId = _appState.Config.LightSettings.LIFX.SelectedItemId });

                }

                if (_appState.Config.LightSettings.Wiz.IsEnabled)
                {
                    await _mediator.Send(new PresenceLight.Core.WizServices.SetColorCommand { Activity = activity, Availability = color, LightID = _appState.Config.LightSettings.Wiz.SelectedItemId });

                }

                if (_appState.Config.LightSettings.Yeelight.IsEnabled && !string.IsNullOrEmpty(_appState.Config.LightSettings.Yeelight.SelectedItemId))
                {
                    await _mediator.Send(new PresenceLight.Core.YeelightServices.SetColorCommand { Activity = activity, Availability = color, LightId = _appState.Config.LightSettings.Yeelight.SelectedItemId });

                }

                if (_appState.Config.LightSettings.CustomApi.IsEnabled)
                {
                    string response = await _mediator.Send(new Core.CustomApiServices.SetColorCommand() { Activity = activity, Availability = color });
                }

                if (_appState.Config.LightSettings.LocalSerialHost.IsEnabled)
                {
                    string response = await _mediator.Send(new Core.LocalSerialHostServices.SetColorCommand() { Activity = activity, Availability = color });
                }
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Error occurred Setting Color");
            }
        }

        private async Task SignOut()
        {
            _logger.LogInformation("Signing out of Graph PresenceLight Sync");

            _appState.SetLightMode("Graph");
            try
            {
                await _loginService.SignOut();

                _appState.SignedIn = false;
                _appState.SetUserInfo(null, null, null);
                notificationIcon.Text = $"PresenceLight Status - {PresenceConstants.Inactive}";
                notificationIcon.Icon = new BitmapImage(new Uri(IconConstants.GetIcon(string.Empty, string.Empty)));

                await SetColor("Off");
            }
            catch (MsalException)
            {
            }

            await _settingsService.SaveSettings(_appState.Config);
        }

        private async Task RebuildClient()
        {
            _appState.RebuildRequested = false;
            await SignOut();
            _loginService.RebuildClient();
            //this was called by signout before, but reusing it here to trigger NotifyStateChanged
            _appState.SetUserInfo(null, null, null);
        }

#endregion

#region UI Helpers
        private BitmapImage? LoadImage(byte[] imageData)
        {
            try
            {
                if (imageData == null || imageData.Length == 0) return null;
                var image = new BitmapImage();
                using (var mem = new MemoryStream(imageData))
                {
                    mem.Position = 0;
                    image.BeginInit();
                    image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
                    image.CacheOption = BitmapCacheOption.OnLoad;
                    image.UriSource = null;
                    image.StreamSource = mem;
                    image.EndInit();
                }
                image.Freeze();
                return image;
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Error occurred in LoadImager");
                throw;
            }
        }

        public void MapUI(Presence presence)
        {
            try
            {
                SolidColorBrush mySolidColorBrush = new SolidColorBrush();
                if (presence != null)
                {
                    notificationIcon.Text = $"PresenceLight Status - {Helpers.HumanifyText(presence.Availability)}";
                    notificationIcon.Icon = new BitmapImage(new Uri(IconConstants.GetIcon(_appState.Config.IconType, presence.Availability)));
                }
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Error Occurred Mapping UI");
                throw;
            }
        }
        #endregion

        #region Graph Calls
        public async Task<Presence> GetPresence()
        {
            try
            {
                return await _mediator.Send(new Core.GraphServices.GetPresenceCommand());
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Error occurred Getting Presence");
                throw;
            }
        }

        public async Task<byte[]?> GetPhoto()
        {
            try
            {
                var photo = await _mediator.Send(new Core.GraphServices.GetPhotoCommand());

                if (photo == null)
                {
                    return null;
                }
                else
                {
                    return StreamToByteArray(photo);
                }
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Error occurred Getting Photo");
                return null;
            }
        }

        public static byte[] StreamToByteArray(Stream input)
        {
            byte[] buffer = new byte[16 * 1024];
            using (MemoryStream ms = new MemoryStream())
            {
                int read;
                while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
                {
                    ms.Write(buffer, 0, read);
                }
                return ms.ToArray();
            }
        }

        #endregion

        #region Tray Methods

        protected override async void OnClosing(System.ComponentModel.CancelEventArgs e)
        {
            e.Cancel = true;
            await _settingsService.SaveSettings(_appState.Config);
            this.Hide();
        }

        private void OnNotifyIconDoubleClick(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton == MouseButton.Left)
            {
                this.Show();
                this.WindowState = this.lastWindowState;
            }
        }

        private void OnOpenClick(object sender, RoutedEventArgs e)
        {
            this.Show();
            this.WindowState = this.lastWindowState;
        }

        private void OnTurnOnSyncClick(object sender, RoutedEventArgs e)
        {
            _appState.SetLightMode("Graph");

            this.WindowState = this.lastWindowState;
            _logger.LogInformation("Turning On PresenceLight Sync");
        }

        private async void OnTurnOffSyncClick(object sender, RoutedEventArgs e)
        {
            try
            {
                _appState.SetLightMode("Custom");
                await SetColor("Off", "Off");

                notificationIcon.Text = PresenceConstants.Inactive;
                notificationIcon.Icon = new BitmapImage(new Uri(IconConstants.GetIcon(string.Empty, string.Empty)));

                this.WindowState = this.lastWindowState;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occurred turning Off Sync");
            }
            _logger.LogInformation("Turning Off PresenceLight Sync");
        }

        private async void OnExitClick(object sender, RoutedEventArgs e)
        {
            try
            {
                await SetColor("Off", "Off");

                await _settingsService.SaveSettings(_appState.Config);
                System.Windows.Application.Current.Shutdown();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occurred Exiting");
            }
            _logger.LogInformation("PresenceLight Exiting");
        }

        private async void Current_SessionEnding(object sender, SessionEndingCancelEventArgs e)
        {
            try
            {
                await SetColor("Off", "Off");

                await _settingsService.SaveSettings(_appState.Config);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error occurred Ending Session");
            }

            _logger.LogInformation("PresenceLight Session Ending");
        }
#endregion

        private async Task InteractWithLights()
        {
            bool previousWorkingHours = false;
            string previousLightMode = string.Empty;
            while (true)
            {
                isInteractRunning = true;
                try
                {
                    if (_appState.SignedIn)
                    {
                        if (_appState.User == null || string.IsNullOrEmpty(_appState.User.DisplayName))
                        {
                            try
                            {
                                var (profile, presence) = await _mediator.Send(new Core.GraphServices.GetProfileAndPresenceCommand());
                                

                                var photo = await GetPhoto();

                                _appState.SetLightMode("Graph");

                                if (photo == null)
                                {
                                    MapUI(presence);
                                    _appState.SetUserInfo(profile, presence);
                                }
                                else
                                {
                                    MapUI(presence);
                                    _appState.SetUserInfo(profile, presence, $"data:image/gif;base64,{Convert.ToBase64String(photo)}");
                                }
                            }
                            catch (ServiceException ex)
                            {
                                if (ex.ResponseStatusCode == (int) System.Net.HttpStatusCode.Unauthorized ||
                                    ex.ResponseStatusCode == (int) System.Net.HttpStatusCode.Forbidden)
                                {
                                    _logger.LogWarning("Error getting profile and presence info. Something is likely corrupt. Requesting sign out.");
                                    _appState.SignOutRequested = true;
                                }
                            }
                        }
                        await Task.Delay(Convert.ToInt32(_appState.Config.LightSettings.PollingInterval * 1000));

                        bool touchLight = false;
                        string newColor = "";

                        if (_appState.Config.LightSettings.SyncLights)
                        {
                            if (!await _mediator.Send(new Core.WorkingHoursServices.UseWorkingHoursCommand()))
                            {
                                if (_appState.LightMode == "Graph")
                                {
                                    touchLight = true;
                                }
                            }
                            else
                            {
                                var isInWorkingHours = await _mediator.Send(new Core.WorkingHoursServices.IsInWorkingHoursCommand());
                                if (isInWorkingHours)
                                {
                                    previousWorkingHours = isInWorkingHours;
                                    if (_appState.LightMode == "Graph")
                                    {
                                        touchLight = true;
                                    }
                                }
                                else
                                {
                                    // check to see if working hours have passed
                                    if (previousWorkingHours)
                                    {
                                        previousWorkingHours = false;
                                        previousLightMode = _appState.LightMode;
                                        switch (_appState.Config.LightSettings.HoursPassedStatus)
                                        {

                                            case "White":
                                                newColor = "Offline";
                                                _appState.SetLightMode("Manual");
                                                break;
                                            case "Off":
                                                newColor = "Off";
                                                _appState.SetLightMode("Manual");
                                                break;
                                            case "Keep":
                                            default:
                                                break;
                                        }

                                        touchLight = true;
                                    }
                                }
                            }
                        }

                        if (touchLight && _appState.SignedIn)
                        {
                            switch (_appState.LightMode)
                            {
                                case "Manual":
                                    // No need to check presence... if it's after hours, we just want to action upon it... 
                                    await SetColor(newColor, _appState.Presence.Activity);
                                    //Reset the light mode so that we don't potentially mess something up.
                                    _appState.SetLightMode(previousLightMode);
                                    break;
                                case "Graph":
                                    _logger.LogInformation("PresenceLight Running in Teams Mode");

                                    _appState.SetPresence(await System.Threading.Tasks.Task.Run(() => GetPresence()));

                                    if (newColor == string.Empty)
                                    {
                                        await SetColor(_appState.Presence.Availability, _appState.Presence.Activity);
                                    }
                                    else
                                    {
                                        await SetColor(newColor, _appState.Presence.Activity);
                                    }
                                    if (DateTime.Now.AddMinutes(-5) > settingsLastSaved)
                                    {
                                        await _settingsService.SaveSettings(_appState.Config);
                                        settingsLastSaved = DateTime.Now;
                                    }

                                    MapUI(_appState.Presence);
                                    break;
                                default:
                                    break;
                            }
                        }
                    }
                    else
                    {
                        isInteractRunning = false;
                        break;
                    }
                }
                catch (Exception e)
                {
                    _logger.LogError(e, "Error occurred interacting with lights");
                }
            }
        }
    }
}


================================================
FILE: src/DesktopClient/PresenceLight/PresenceLight.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk.Razor">

  <PropertyGroup>
    <TargetFramework>net10.0-windows10.0.19041</TargetFramework>
    <OutputType>WinExe</OutputType>
    <AssemblyName>PresenceLight</AssemblyName>
    <Title>PresenceLight</Title>
    <Description>PresenceLight is a solution to broadcast your Microsoft Teams presence to a Philips Hue or LIFX light bulb. There are other solutions that do something similar, but they require a tethered solution (plugging a light into a computer via USB). What PresenceLight does is leverage the Presence Api, which is available in Microsoft Graph, allowing to retrieve your presence without having to be tethered. This could potentially allow someone to update the light bulb from a remote machine they do not use.</Description>
    <RootNamespace>PresenceLight</RootNamespace>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    <TieredCompilationQuickJitForLoops>true</TieredCompilationQuickJitForLoops>
    <ApplicationManifest>Properties\app.manifest</ApplicationManifest>
    <EnableWindowsTargeting>true</EnableWindowsTargeting>
    <ApplicationIcon>Icons\Icon.ico</ApplicationIcon>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="DesktopBridge.Helpers" Version="1.2.2" />
    <PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.23.0" />
    <PackageReference Include="Microsoft.AspNetCore.Components.WebView.Wpf" Version="10.0.10" />
    <PackageReference Update="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.2" />
    <PackageReference Update="Microsoft.SourceLink.GitHub" Version="8.0.0" />
    <PackageReference Update="Nerdbank.GitVersioning" Version="3.6.133" />
    <PackageReference Include="Serilog.Sinks.ApplicationInsights" Version="4.1.0" />
    <ProjectReference Include="..\..\PresenceLight.Core\PresenceLight.Core.csproj" />
    <ProjectReference Include="..\..\PresenceLight.Razor\PresenceLight.Razor.csproj" />
  </ItemGroup>
  <ItemGroup>
    <Resource Include="Icons\Available.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\BusyIdle.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\AvailableIdle.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\Away.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\BeRightBack.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\Busy.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\DoNotDisturb.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\Icon.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\Icon.png">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\Offline.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\Inactive.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\loading.gif">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\OutOfOffice.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\t_Available.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\t_BusyIdle.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\t_AvailableIdle.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\t_Away.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\t_BeRightBack.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\t_Busy.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\t_DoNotDisturb.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
    <Resource Include="Icons\t_OutOfOffice.ico">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Resource>
  </ItemGroup>

  <ItemGroup>
    <None Update="appsettings.Development.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
    <None Update="appsettings.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>

    <ItemGroup>
    <Content Update="wwwroot\**">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
  </ItemGroup>

</Project>


================================================
FILE: src/DesktopClient/PresenceLight/PresenceLight.exe.gui
================================================


================================================
FILE: src/DesktopClient/PresenceLight/Properties/AssemblyInfo.cs
================================================
using System.Windows;

[assembly:ThemeInfo(
    ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
                                     //(used if a resource is not found in the page,
                                     // or application resource dictionaries)
    ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
                                              //(used if a resource is not found in the page,
                                              // app, or any theme specific resource dictionaries)
)]


================================================
FILE: src/DesktopClient/PresenceLight/Properties/PublishProfiles/WinARM64.pubxml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <PublishProtocol>FileSystem</PublishProtocol>
    <Platform>ARM64</Platform>
    <TargetFramework>net10.0-windows10.0.19041</TargetFramework>
    <RuntimeIdentifier>win-arm64</RuntimeIdentifier>
    <PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
    <SelfContained>true</SelfContained>
    <PublishSingleFile>False</PublishSingleFile>
    <PublishReadyToRun>False</PublishReadyToRun>
    <PublishTrimmed>False</PublishTrimmed>
  </PropertyGroup>
</Project>


================================================
FILE: src/DesktopClient/PresenceLight/Properties/PublishProfiles/WinX64.pubxml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <PublishProtocol>FileSystem</PublishProtocol>
    <Platform>x64</Platform>
    <TargetFramework>net10.0-windows10.0.19041</TargetFramework>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
    <PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
    <SelfContained>true</SelfContained>
    <PublishSingleFile>False</PublishSingleFile>
    <PublishReadyToRun>False</PublishReadyToRun>
    <PublishTrimmed>False</PublishTrimmed>
  </PropertyGroup>
</Project>


================================================
FILE: src/DesktopClient/PresenceLight/Properties/PublishProfiles/WinX86.pubxml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <PublishProtocol>FileSystem</PublishProtocol>
    <Platform>x86</Platform>
    <TargetFramework>net10.0-windows10.0.19041</TargetFramework>
    <RuntimeIdentifier>win-x86</RuntimeIdentifier>
    <PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
    <SelfContained>true</SelfContained>
    <PublishSingleFile>False</PublishSingleFile>
    <PublishReadyToRun>False</PublishReadyToRun>
    <PublishTrimmed>False</PublishTrimmed>
  </PropertyGroup>
</Project>


================================================
FILE: src/DesktopClient/PresenceLight/Properties/Resources.Designer.cs
================================================
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.42000
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace PresenceLight.Properties {
    using System;
    
    
    /// <summary>
    ///   A strongly-typed resource class, for looking up localized strings, etc.
    /// </summary>
    // This class was auto-generated by the StronglyTypedResourceBuilder
    // class via a tool like ResGen or Visual Studio.
    // To add or remove a member, edit your .ResX file then rerun ResGen
    // with the /str option, or rebuild your VS project.
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    internal class Resources {
        
        private static global::System.Resources.ResourceManager resourceMan;
        
        private static global::System.Globalization.CultureInfo resourceCulture;
        
        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        internal Resources() {
        }
        
        /// <summary>
        ///   Returns the cached ResourceManager instance used by this class.
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
        internal static global::System.Resources.ResourceManager ResourceManager {
            get {
                if (object.ReferenceEquals(resourceMan, null)) {
                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PresenceLight.Properties.Resources", typeof(Resources).Assembly);
                    resourceMan = temp;
                }
                return resourceMan;
            }
        }
        
        /// <summary>
        ///   Overrides the current thread's CurrentUICulture property for all
        ///   resource lookups using this strongly typed resource class.
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
        internal static global::System.Globalization.CultureInfo Culture {
            get {
                return resourceCulture;
            }
            set {
                resourceCulture = value;
            }
        }
        
        /// <summary>
        ///   Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
        /// </summary>
        internal static System.Drawing.Icon Available {
            get {
                object obj = ResourceManager.GetObject("Available", resourceCulture);
                return ((System.Drawing.Icon)(obj));
            }
        }
        
        /// <summary>
        ///   Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
        /// </summary>
        internal static System.Drawing.Icon Away {
            get {
                object obj = ResourceManager.GetObject("Away", resourceCulture);
                return ((System.Drawing.Icon)(obj));
            }
        }
        
        /// <summary>
        ///   Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
        /// </summary>
        internal static System.Drawing.Icon BeRightBack {
            get {
                object obj = ResourceManager.GetObject("BeRightBack", resourceCulture);
                return ((System.Drawing.Icon)(obj));
            }
        }
        
        /// <summary>
        ///   Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
        /// </summary>
        internal static System.Drawing.Icon Busy {
            get {
                object obj = ResourceManager.GetObject("Busy", resourceCulture);
                return ((System.Drawing.Icon)(obj));
            }
        }
        
        /// <summary>
        ///   Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
        /// </summary>
        internal static System.Drawing.Icon DoNotDisturb {
            get {
                object obj = ResourceManager.GetObject("DoNotDisturb", resourceCulture);
                return ((System.Drawing.Icon)(obj));
            }
        }
        
        /// <summary>
        ///   Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
        /// </summary>
        internal static System.Drawing.Icon Icon {
            get {
                object obj = ResourceManager.GetObject("Icon", resourceCulture);
                return ((System.Drawing.Icon)(obj));
            }
        }
        
        /// <summary>
        ///   Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
        /// </summary>
        internal static System.Drawing.Icon Inactive {
            get {
                object obj = ResourceManager.GetObject("Inactive", resourceCulture);
                return ((System.Drawing.Icon)(obj));
            }
        }
    }
}


================================================
FILE: src/DesktopClient/PresenceLight/Properties/Resources.resx
================================================
<?xml version="1.0" encoding="utf-8"?>
<root>
  <!-- 
    Microsoft ResX Schema 
    
    Version 2.0
    
    The primary goals of this format is to allow a simple XML format 
    that is mostly human readable. The generation and parsing of the 
    various data types are done through the TypeConverter classes 
    associated with the data types.
    
    Example:
    
    ... ado.net/XML headers & schema ...
    <resheader name="resmimetype">text/microsoft-resx</resheader>
    <resheader name="version">2.0</resheader>
    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
        <value>[base64 mime encoded serialized .NET Framework object]</value>
    </data>
    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
        <comment>This is a comment</comment>
    </data>
                
    There are any number of "resheader" rows that contain simple 
    name/value pairs.
    
    Each data row contains a name, and value. The row also contains a 
    type or mimetype. Type corresponds to a .NET class that support 
    text/value conversion through the TypeConverter architecture. 
    Classes that don't support this are serialized and stored with the 
    mimetype set.
    
    The mimetype is used for serialized objects, and tells the 
    ResXResourceReader how to depersist the object. This is currently not 
    extensible. For a given mimetype the value must be set accordingly:
    
    Note - application/x-microsoft.net.object.binary.base64 is the format 
    that the ResXResourceWriter will generate, however the reader can 
    read any of the formats listed below.
    
    mimetype: application/x-microsoft.net.object.binary.base64
    value   : The object must be serialized with 
            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
            : and then encoded with base64 encoding.
    
    mimetype: application/x-microsoft.net.object.soap.base64
    value   : The object must be serialized with 
            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
            : and then encoded with base64 encoding.

    mimetype: application/x-microsoft.net.object.bytearray.base64
    value   : The object must be serialized into a byte array 
            : using a System.ComponentModel.TypeConverter
            : and then encoded with base64 encoding.
    -->
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  <data name="Available" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>..\Icons\Available.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
  </data>
  <data name="Away" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>..\Icons\Away.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
  </data>
  <data name="BeRightBack" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>..\Icons\BeRightBack.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
  </data>
  <data name="Busy" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>..\Icons\Busy.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
  </data>
  <data name="DoNotDisturb" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>..\Icons\DoNotDisturb.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
  </data>
  <data name="Icon" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>..\Icons\Icon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
  </data>
  <data name="Inactive" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>..\Icons\Inactive.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
  </data>
</root>

================================================
FILE: src/DesktopClient/PresenceLight/Properties/Settings.Designer.cs
================================================
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.42000
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace PresenceLight.Properties {
    
    
    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.7.0.0")]
    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
        
        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
        
        public static Settings Default {
            get {
                return defaultInstance;
            }
        }
    }
}


================================================
FILE: src/DesktopClient/PresenceLight/Properties/Settings.settings
================================================
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
  <Profiles>
    <Profile Name="(Default)" />
  </Profiles>
  <Settings />
</SettingsFile>

================================================
FILE: src/DesktopClient/PresenceLight/Properties/app.manifest
================================================
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <assemblyIdentity version="1.0.0.0" name="MyApplication.app" />
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
        <!-- UAC Manifest Options
            If you want to change the Windows User Account Control level replace the 
            requestedExecutionLevel node with one of the following.
        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
        <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
        <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />
            Specifying requestedExecutionLevel node will disable file and registry virtualization.
            If you want to utilize File and Registry Virtualization for backward 
            compatibility then delete the requestedExecutionLevel node.
        -->
        <requestedExecutionLevel level="asInvoker" uiAccess="false" />
      </requestedPrivileges>
      <applicationRequestMinimum>
        <defaultAssemblyRequest permissionSetReference="Custom" />
        <PermissionSet class="System.Security.PermissionSet" version="1" ID="Custom" SameSite="site" Unrestricted="true" />
      </applicationRequestMinimum>
    </security>
  </trustInfo>
  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <!-- A list of all Windows versions that this application is designed to work with. Windows will automatically select the most compatible environment.-->
      <!-- If your application is designed to work with Windows 7, uncomment the following supportedOS node-->
      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" /> <!-- Windows 8 -->
      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" /> <!-- Windows 8.1 -->
      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /> <!-- Windows 10 -->
    </application>
  </compatibility>
  <!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
  <dependency>
    <dependentAssembly>
      <assemblyIdentity
          type="win32"
          name="Microsoft.Windows.Common-Controls"
          version="6.0.0.0"
          processorArchitecture="*"
          publicKeyToken="6595b64144ccf1df"
          language="*"
        />
    </dependentAssembly>
  </dependency>

  <!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
       DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need 
       to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should 
       also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->

  <application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <!-- Per Monitor V1 [OS >= Windows 8.1] 
         Values: False, True, Per-monitor, True/PM -->
      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
        true/PM
      </dpiAware>
      <!-- Per Monitor V1 [OS >= Windows 10 Anniversary Update (1607, 10.0.14393, Redstone 1)]
         Values: Unaware, System, PerMonitor -->
      <!-- Per Monitor V2 [OS >= Windows 10 Creators Update (1703, 10.0.15063, Redstone 2)]
         Value: PerMonitorV2 -->
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
        PerMonitorV2, PerMonitor
      </dpiAwareness>
    </windowsSettings>
  </application>
</asmv1:assembly>

================================================
FILE: src/DesktopClient/PresenceLight/Services/Constants.cs
================================================

using System;
using System.Reflection;

namespace PresenceLight
{
    public static class PresenceConstants
    {
        public const string Inactive = "Not Logged In";
    }

    public class PresenceColors
    {
        public const string Available = "#009933";
        public const string AvailableIdle = "#FFFF00";
        public const string Busy = "#FF3300";
        public const string BusyIdle = "#FFFF00";
        public const string BeRightBack = "#FFFF00";
        public const string Away = "#FFFF00";
        public const string DoNotDisturb = "#B03CDE";
        public const string OutOfOffice = "#800080";
        public const string Offline = "#FFFFFF";
        public const string Inactive = "#FFFFFF";

        public static string GetColor(string status)
        {
            var pc = new PresenceColors();
            Type type =pc.GetType();
            PropertyInfo[] props = type.GetProperties();

            foreach (var prop in props)
            {
                if (prop.Name == status)
                {
                    return prop.GetValue(pc).ToString();
                }
            }
            return PresenceColors.Inactive;
        }
    }

    public static class IconConstants
    {
        private static string Base = "pack://application:,,,/PresenceLight;component/icons/";

        public static string GetIcon(string iconType, string status)
        {
            if (string.IsNullOrEmpty(status))
            {
                status = "Inactive";
            }
            if (iconType == "Transparent")
            {
                return $"{Base}t_{status}.ico";
            }
            else
            {
                return $"{Base}{status}.ico";
            }
        }
    }
}


================================================
FILE: src/DesktopClient/PresenceLight/Services/MessageBoxHelper.cs
================================================
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
namespace PresenceLight
{
    internal static class MessageBoxHelper
    {
        internal static void PrepToCenterMessageBoxOnForm(Window form)
        {
            MessageBoxCenterHelper helper = new MessageBoxCenterHelper();
            helper.Prep(form);
        }

        private class MessageBoxCenterHelper
        {
            private int messageHook;
            private IntPtr parentFormHandle;

            public void Prep(Window form)
            {
                NativeMethods.CenterMessageCallBackDelegate callBackDelegate = new NativeMethods.CenterMessageCallBackDelegate(CenterMessageCallBack);
                GCHandle.Alloc(callBackDelegate);

                parentFormHandle = new WindowInteropHelper(form).Handle;
                messageHook = NativeMethods.SetWindowsHookEx(5, callBackDelegate, new IntPtr(NativeMethods.GetWindowLong(parentFormHandle, -6)), NativeMethods.GetCurrentThreadId()).ToInt32();
            }

            private int CenterMessageCallBack(int message, int wParam, int lParam)
            {
                NativeMethods.RECT formRect;
                NativeMethods.RECT messageBoxRect;
                int xPos;
                int yPos;

                if (message == 5)
                {
                    NativeMethods.GetWindowRect(parentFormHandle, out formRect);
                    NativeMethods.GetWindowRect(new IntPtr(wParam), out messageBoxRect);

                    xPos = (int)((formRect.Left + (formRect.Right - formRect.Left) / 2) - ((messageBoxRect.Right - messageBoxRect.Left) / 2));
                    yPos = (int)((formRect.Top + (formRect.Bottom - formRect.Top) / 2) - ((messageBoxRect.Bottom - messageBoxRect.Top) / 2));

                    NativeMethods.SetWindowPos(wParam, 0, xPos, yPos, 0, 0, 0x1 | 0x4 | 0x10);
                    NativeMethods.UnhookWindowsHookEx(messageHook);
                }

                return 0;
            }
        }

        private static class NativeMethods
        {
            internal struct RECT
            {
                public int Left;
                public int Top;
                public int Right;
                public int Bottom;
            }

            internal delegate int CenterMessageCallBackDelegate(int message, int wParam, int lParam);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool UnhookWindowsHookEx(int hhk);

            [DllImport("user32.dll", SetLastError = true)]
            internal static extern int GetWindowLong(IntPtr hWnd, int nIndex);

            [DllImport("kernel32.dll")]
            internal static extern int GetCurrentThreadId();

            [DllImport("user32.dll", SetLastError = true)]
            internal static extern IntPtr SetWindowsHookEx(int hook, CenterMessageCallBackDelegate callback, IntPtr hMod, int dwThreadId);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool SetWindowPos(int hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);

            [DllImport("user32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
        }
    }
}


================================================
FILE: src/DesktopClient/PresenceLight/Services/NotifyIcon.cs
================================================
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using Drawing = System.Drawing;
using Forms = System.Windows.Forms;

namespace PresenceLight
{
	[ContentProperty("Text")]
	[DefaultEvent("MouseDoubleClick")]
	public partial class NotifyIcon : FrameworkElement, IAddChild
	{
		public static readonly RoutedEvent MouseClickEvent = EventManager.RegisterRoutedEvent(
			"MouseClick",
			RoutingStrategy.Bubble,
			typeof(MouseButtonEventHandler),
			typeof(NotifyIcon));

		public static readonly RoutedEvent MouseDoubleClickEvent = EventManager.RegisterRoutedEvent(
			"MouseDoubleClick",
			RoutingStrategy.Bubble,
			typeof(MouseButtonEventHandler),
			typeof(NotifyIcon));

		public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
			"Icon",
			typeof(ImageSource),
			typeof(NotifyIcon),
			new FrameworkPropertyMetadata(OnIconChanged));

		public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
			"Text",
			typeof(string),
			typeof(NotifyIcon),
			new PropertyMetadata(OnTextChanged));

		private Forms.NotifyIcon notifyIcon;

		static NotifyIcon()
		{
			VisibilityProperty.OverrideMetadata(typeof(NotifyIcon), new PropertyMetadata(OnVisibilityChanged));
		}

		public event MouseButtonEventHandler MouseClick
		{
			add { this.AddHandler(MouseClickEvent, value); }
			remove { this.RemoveHandler(MouseClickEvent, value); }
		}

		public event MouseButtonEventHandler MouseDoubleClick
		{
			add { this.AddHandler(MouseDoubleClickEvent, value); }
			remove { this.RemoveHandler(MouseDoubleClickEvent, value); }
		}

		public ImageSource Icon
		{
			get { return (ImageSource)this.GetValue(IconProperty); }
			set { this.SetValue(IconProperty, value); }
		}

		public string Text
		{
			get { return (string)this.GetValue(TextProperty); }
			set { this.SetValue(TextProperty, value); }
		}

		public override void BeginInit()
		{
			base.BeginInit();
			this.InitializeNotifyIcon();
		}

		#region IAddChild Members

		void IAddChild.AddChild(object value)
		{
			throw new InvalidOperationException();
		}

		void IAddChild.AddText(string text)
		{
			if (text == null)
			{
				throw new ArgumentNullException(nameof(text));
			}

			this.Text = text;
		}

		#endregion

		protected override void OnVisualParentChanged(DependencyObject oldParent)
		{
			base.OnVisualParentChanged(oldParent);
			this.AttachToWindowClose();
		}

		private static MouseButtonEventArgs CreateMouseButtonEventArgs(
			RoutedEvent handler,
			Forms.MouseButtons button)
		{
			return new MouseButtonEventArgs(InputManager.Current.PrimaryMouseDevice, 0, ToMouseButton(button))
			{
				RoutedEvent = handler
			};
		}

		private static Drawing.Icon? FromImageSource(ImageSource icon)
		{
			if (icon == null)
			{
				return null;
			}

			Uri iconUri = new Uri(icon.ToString());
			return new Drawing.Icon(Application.GetResourceStream(iconUri).Stream);
		}

		private static void OnIconChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
		{
			if (!DesignerProperties.GetIsInDesignMode(target))
			{
				NotifyIcon control = (NotifyIcon)target;
				control.notifyIcon.Icon = FromImageSource(control.Icon);
			}
		}

		private static void OnTextChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
		{
			NotifyIcon control = (NotifyIcon)target;
			control.notifyIcon.Text = control.Text;
		}

		private static void OnVisibilityChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
		{
			NotifyIcon control = (NotifyIcon)target;
			control.notifyIcon.Visible = control.Visibility == Visibility.Visible;
		}

		private static MouseButton ToMouseButton(Forms.MouseButtons button)
		{
			switch (button)
			{
				case Forms.MouseButtons.Left:
					return MouseButton.Left;
				case Forms.MouseButtons.Right:
					return MouseButton.Right;
				case Forms.MouseButtons.Middle:
					return MouseButton.Middle;
				case Forms.MouseButtons.XButton1:
					return MouseButton.XButton1;
				case Forms.MouseButtons.XButton2:
					return MouseButton.XButton2;
			}

			throw new InvalidOperationException();
		}

		private void AttachToWindowClose()
		{
			var window = Window.GetWindow(this);
			if (window != null)
			{
				window.Closed += (s, a) => this.notifyIcon.Dispose();
			}
		}

		private void InitializeNotifyIcon()
		{
			this.notifyIcon = new Forms.NotifyIcon();
			this.notifyIcon.Text = this.Text;
			this.notifyIcon.Icon = FromImageSource(this.Icon);
			this.notifyIcon.Visible = this.Visibility == Visibility.Visible;

			this.notifyIcon.MouseDown += this.OnMouseDown;
			this.notifyIcon.MouseUp += this.OnMouseUp;
			this.notifyIcon.MouseClick += this.OnMouseClick;
			this.notifyIcon.MouseDoubleClick += this.OnMouseDoubleClick;

			this.InitializeNativeHooks();
		}

		private void OnMouseDown(object sender, Forms.MouseEventArgs e)
		{
			this.RaiseEvent(CreateMouseButtonEventArgs(MouseDownEvent, e.Button));
		}

		private void OnMouseDoubleClick(object sender, Forms.MouseEventArgs e)
		{
			this.RaiseEvent(CreateMouseButtonEventArgs(MouseDoubleClickEvent, e.Button));
		}

		private void OnMouseClick(object sender, Forms.MouseEventArgs e)
		{
			this.RaiseEvent(CreateMouseButtonEventArgs(MouseClickEvent, e.Button));
		}

		private void OnMouseUp(object sender, Forms.MouseEventArgs e)
		{
			if (e.Button == Forms.MouseButtons.Right)
			{
				this.ShowContextMenu();
			}

			this.RaiseEvent(CreateMouseButtonEventArgs(MouseUpEvent, e.Button));
		}

		private void ShowContextMenu()
		{
			if (this.ContextMenu != null)
			{
				this.AttachContextMenu();
				this.ContextMenu.IsOpen = true;
			}
		}

		partial void AttachContextMenu();

		partial void InitializeNativeHooks();


	}
}


================================================
FILE: src/DesktopClient/PresenceLight/Services/Settings/AppPackageSettingsService.cs
================================================
using System;
using Newtonsoft.Json;
using PresenceLight.Core;
using PresenceLight.Telemetry;
using System.Threading.Tasks;
using Windows.Storage;
using Microsoft.Extensions.Logging;
using System.IO;
using PresenceLight.Razor;

namespace PresenceLight.Services
{
    public class AppPackageSettingsService : ISettingsService
    {
        private const string SETTINGS_FILENAME = "settings.json";
        private static readonly StorageFolder _settingsFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
        private DiagnosticsClient _diagClient;
        private readonly ILogger<AppPackageSettingsService> _logger;
        private readonly AppState _appState;

        public AppPackageSettingsService(DiagnosticsClient diagClient, ILogger<AppPackageSettingsService> logger, AppState appState)
        {
            _appState = appState;
            _logger = logger;
            _diagClient = diagClient;
        }

        public async Task<BaseConfig?> LoadSettings()
        {
            try
            {
                StorageFile sf = await _settingsFolder.GetFileAsync(SETTINGS_FILENAME);
                if (sf == null) return null;

                string content = await FileIO.ReadTextAsync(sf, Windows.Storage.Streams.UnicodeEncoding.Utf8);
                var config = JsonConvert.DeserializeObject<BaseConfig>(content);
                _appState.SetConfig(config);
                return config;
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Error saving Settings");
                _diagClient.TrackException(e);
                return null;
            }
        }

        public async Task<bool> SaveSettings(BaseConfig data)
        {
            try
            {
                string content = JsonConvert.SerializeObject(data, Newtonsoft.Json.Formatting.Indented, new JsonSerializerSettings { });
                StorageFile f;
                if (await IsFilePresent())
                {
                    f = await _settingsFolder.GetFileAsync(SETTINGS_FILENAME);
                }
                else
                {
                    f = await _settingsFolder.CreateFileAsync(SETTINGS_FILENAME, CreationCollisionOption.ReplaceExisting);
                }
                bool fileWritten = false;

                while (!fileWritten)
                {
                    try
                    {
                        await FileIO.WriteTextAsync(f, content, Windows.Storage.Streams.UnicodeEncoding.Utf8);
                        fileWritten = true;
                    }
                    catch
                    {                     
                    }
                }
                _appState.SetConfig(data);
                return true;
            }
            catch (Exception e)
            {
                _logger.LogError(e, "Error Savin
Download .txt
gitextract_bd12mvz1/

├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── Azure_Blob_Deploy.yml
│       ├── Choco.yml
│       ├── Deploy_Desktop.yml
│       ├── Deploy_Web.yml
│       ├── Sign.yml
│       └── WinGet.yml
├── .gitignore
├── Build/
│   ├── Signing/
│   │   └── filelist.txt
│   ├── Worker/
│   │   ├── presencelight.crt
│   │   ├── presencelight.service
│   │   └── trust-cert.sh
│   └── scripts/
│       ├── push-choco.ps1
│       ├── push-winget.ps1
│       ├── update-desktop-settings.ps1
│       └── update-web-settings.ps1
├── LICENSE
├── PrivacyPolicy.md
├── README.md
├── chocolatey/
│   ├── PresenceLight.nuspec
│   └── tools/
│       ├── ChocolateyBeforeModify.ps1
│       ├── ChocolateyInstall.ps1
│       ├── ChocolateyUninstall.ps1
│       ├── LICENSE.txt
│       └── Verification.txt
├── docker-compose-example.yml
├── docs/
│   ├── CONTRIBUTING.md
│   ├── configure-custom-api.md
│   ├── configure-entra-app.md
│   ├── configure-hardware.md
│   ├── desktop-README.md
│   ├── faq.md
│   └── web-README.md
├── src/
│   ├── .editorconfig
│   ├── DesktopClient/
│   │   ├── Directory.Build.props
│   │   ├── Directory.Build.targets
│   │   ├── PresenceLight/
│   │   │   ├── App.xaml
│   │   │   ├── App.xaml.cs
│   │   │   ├── MainWindow.xaml
│   │   │   ├── MainWindow.xaml.cs
│   │   │   ├── PresenceLight.csproj
│   │   │   ├── PresenceLight.exe.gui
│   │   │   ├── Properties/
│   │   │   │   ├── AssemblyInfo.cs
│   │   │   │   ├── PublishProfiles/
│   │   │   │   │   ├── WinARM64.pubxml
│   │   │   │   │   ├── WinX64.pubxml
│   │   │   │   │   └── WinX86.pubxml
│   │   │   │   ├── Resources.Designer.cs
│   │   │   │   ├── Resources.resx
│   │   │   │   ├── Settings.Designer.cs
│   │   │   │   ├── Settings.settings
│   │   │   │   └── app.manifest
│   │   │   ├── Services/
│   │   │   │   ├── Constants.cs
│   │   │   │   ├── MessageBoxHelper.cs
│   │   │   │   ├── NotifyIcon.cs
│   │   │   │   ├── Settings/
│   │   │   │   │   ├── AppPackageSettingsService.cs
│   │   │   │   │   └── StandaloneSettingsService.cs
│   │   │   │   ├── SingleInstanceAppMutex.cs
│   │   │   │   └── Telemetry/
│   │   │   │       └── DiagnosticsClient.cs
│   │   │   ├── appsettings.json
│   │   │   └── wwwroot/
│   │   │       └── index.html
│   │   └── PresenceLight.Package/
│   │       ├── Package-Local.appxmanifest
│   │       ├── Package-Nightly.appxmanifest
│   │       ├── Package.appinstaller
│   │       ├── Package.appxmanifest
│   │       ├── Package.xml
│   │       └── PresenceLight.Package.wapproj
│   ├── DockerCompose/
│   │   ├── .dockerignore
│   │   ├── docker-compose.dcproj
│   │   ├── docker-compose.override.yml
│   │   └── docker-compose.yml
│   ├── PresenceLight.Core/
│   │   ├── Configuration/
│   │   │   ├── AAD.cs
│   │   │   ├── AppState.cs
│   │   │   ├── AvailabilityStatus.cs
│   │   │   ├── Base.cs
│   │   │   ├── BaseLight.cs
│   │   │   ├── CustomApi.cs
│   │   │   ├── CustomApiSetting.cs
│   │   │   ├── Hue.cs
│   │   │   ├── ISettingsService.cs
│   │   │   ├── LIFX.cs
│   │   │   ├── LightSettings.cs
│   │   │   ├── LocalSerialHost.cs
│   │   │   ├── LocalSerialHostSetting.cs
│   │   │   ├── Statuses.cs
│   │   │   ├── Wiz.cs
│   │   │   └── Yeelight.cs
│   │   ├── GraphServices/
│   │   │   ├── AuthorizationProvider.cs
│   │   │   ├── GetIsInitialized/
│   │   │   │   ├── GetIsInitializedCommand.cs
│   │   │   │   └── GetIsInitializedHandler.cs
│   │   │   ├── GetPhoto/
│   │   │   │   ├── GetPhotoCommand.cs
│   │   │   │   └── GetPhotoHandler.cs
│   │   │   ├── GetPresence/
│   │   │   │   ├── GetPresenceCommand.cs
│   │   │   │   └── GetPresenceHandler.cs
│   │   │   ├── GetProfile/
│   │   │   │   ├── GetProfileCommand.cs
│   │   │   │   └── GetProfileHandler.cs
│   │   │   ├── GetProfileAndPresence/
│   │   │   │   ├── GetProfileAndPresenceCommand.cs
│   │   │   │   └── GetProfileAndPresenceHandler.cs
│   │   │   ├── GraphWrapper.cs
│   │   │   ├── Initialize/
│   │   │   │   ├── InitializeCommand.cs
│   │   │   │   └── InitializeHandler.cs
│   │   │   ├── LoginService.cs
│   │   │   └── TokenCacheHelper.cs
│   │   ├── Helpers.cs
│   │   ├── Lights/
│   │   │   ├── CustomApiService/
│   │   │   │   ├── CustomApiService.cs
│   │   │   │   ├── Initialize/
│   │   │   │   │   ├── InitializeCommand.cs
│   │   │   │   │   └── InitializeHandler.cs
│   │   │   │   └── SetColor/
│   │   │   │       ├── SetColorCommand.cs
│   │   │   │       └── SetColorHandler.cs
│   │   │   ├── HueServices/
│   │   │   │   ├── FindBridge/
│   │   │   │   │   ├── FindBridgeCommand.cs
│   │   │   │   │   └── FindBridgeHandler.cs
│   │   │   │   ├── GetGroups/
│   │   │   │   │   ├── GetGroupsCommand.cs
│   │   │   │   │   └── GetGroupsHandler.cs
│   │   │   │   ├── GetLights/
│   │   │   │   │   ├── GetLightsCommand.cs
│   │   │   │   │   └── GetLightsHandler.cs
│   │   │   │   ├── HueService.cs
│   │   │   │   ├── Initialize/
│   │   │   │   │   ├── InitializeCommand.cs
│   │   │   │   │   └── InitializeHandler.cs
│   │   │   │   ├── RegisterBridge/
│   │   │   │   │   ├── RegisterBridgeCommand.cs
│   │   │   │   │   └── RegisterBridgeHandler.cs
│   │   │   │   └── SetColor/
│   │   │   │       ├── SetColorCommand.cs
│   │   │   │       └── SetColorHandler.cs
│   │   │   ├── LifxServices/
│   │   │   │   ├── GetAllGroups/
│   │   │   │   │   ├── GetAllGroupsCommand.cs
│   │   │   │   │   └── GetAllGroupsHandler.cs
│   │   │   │   ├── GetAllLights/
│   │   │   │   │   ├── GetAllLightsCommand.cs
│   │   │   │   │   └── GetAllLightsHandler.cs
│   │   │   │   ├── Initialize/
│   │   │   │   │   ├── InitializeCommand.cs
│   │   │   │   │   └── InitializeHandler.cs
│   │   │   │   ├── LIFXOAuthHelper.cs
│   │   │   │   ├── LifxService.cs
│   │   │   │   └── SetColor/
│   │   │   │       ├── SetColorCommand.cs
│   │   │   │       └── SetColorHandler.cs
│   │   │   ├── LocalSerialHostService/
│   │   │   │   ├── GetSerialHosts/
│   │   │   │   │   ├── GetSerialHostsCommand.cs
│   │   │   │   │   └── GetSerialHostsHandler.cs
│   │   │   │   ├── Initialize/
│   │   │   │   │   ├── InitializeCommand.cs
│   │   │   │   │   └── InitializeHandler.cs
│   │   │   │   ├── LocalSerialHost.cs
│   │   │   │   └── SetColor/
│   │   │   │       ├── SetColorCommand.cs
│   │   │   │       └── SetColorHandler.cs
│   │   │   ├── RemoteHueServices/
│   │   │   │   ├── GetGroups/
│   │   │   │   │   ├── GetGroupsCommand.cs
│   │   │   │   │   └── GetGroupsHandler.cs
│   │   │   │   ├── GetLights/
│   │   │   │   │   ├── GetLightsCommand.cs
│   │   │   │   │   └── GetLightsHandler.cs
│   │   │   │   ├── RegisterBridge/
│   │   │   │   │   ├── RegisterBridgeCommand.cs
│   │   │   │   │   └── RegisterBridgeHandler.cs
│   │   │   │   ├── RemoteAuthenticationClient.cs
│   │   │   │   ├── RemoteHueService.cs
│   │   │   │   └── SetColor/
│   │   │   │       ├── SetColorCommand.cs
│   │   │   │       └── SetColorHandler.cs
│   │   │   ├── ServicesExtensions.cs
│   │   │   ├── WizServices/
│   │   │   │   ├── GetLights/
│   │   │   │   │   ├── GetLightCommand.cs
│   │   │   │   │   └── GetLightHandler.cs
│   │   │   │   ├── IPAddressExtensions.cs
│   │   │   │   ├── SetColor/
│   │   │   │   │   ├── SetColorCommand.cs
│   │   │   │   │   └── SetColorHandler.cs
│   │   │   │   ├── WizLight.cs
│   │   │   │   └── WizService.cs
│   │   │   ├── WorkingHoursServices/
│   │   │   │   ├── IsInWorkingHours/
│   │   │   │   │   ├── IsInWorkingHoursCommand.cs
│   │   │   │   │   └── IsInWorkingHoursHandler.cs
│   │   │   │   ├── UseWorkingHours/
│   │   │   │   │   ├── UseWorkingHoursCommand.cs
│   │   │   │   │   └── UseWorkingHoursHandler.cs
│   │   │   │   └── WorkingHoursService.cs
│   │   │   └── YeelightServices/
│   │   │       ├── FindLights/
│   │   │       │   ├── FindLightsCommand.cs
│   │   │       │   └── FindLightsHandler.cs
│   │   │       ├── SetColor/
│   │   │       │   ├── SetColorCommand.cs
│   │   │       │   └── SetColorHandler.cs
│   │   │       └── YeelightService.cs
│   │   ├── Logging/
│   │   │   ├── ILoggerExtensions.cs
│   │   │   └── PresenceEventsLogSink.cs
│   │   └── PresenceLight.Core.csproj
│   ├── PresenceLight.Razor/
│   │   ├── Components/
│   │   │   ├── Layout/
│   │   │   │   ├── MainLayout.razor
│   │   │   │   ├── MainLayout.razor.css
│   │   │   │   ├── NavMenu.razor
│   │   │   │   └── NavMenu.razor.css
│   │   │   ├── Pages/
│   │   │   │   ├── About.razor
│   │   │   │   ├── Color.razor
│   │   │   │   ├── Color.razor.css
│   │   │   │   ├── CustomApiSetup.razor
│   │   │   │   ├── HueSetup.razor
│   │   │   │   ├── Index.razor
│   │   │   │   ├── Index.razor.css
│   │   │   │   ├── Lifx.razor
│   │   │   │   ├── LocalSerialHostSetup.razor
│   │   │   │   ├── Logs.razor
│   │   │   │   ├── Settings.razor
│   │   │   │   ├── Wiz.razor
│   │   │   │   └── Yeelight.razor
│   │   │   ├── PresenceLightClientApp.razor
│   │   │   ├── Shared/
│   │   │   │   ├── Confirm.razor
│   │   │   │   ├── LoginDisplay.razor
│   │   │   │   └── Statuses.razor
│   │   │   └── _Imports.razor
│   │   ├── PresenceLight.Razor.csproj
│   │   ├── Services/
│   │   │   ├── AppInfo.cs
│   │   │   ├── AppVersionTelemetryInitializer.cs
│   │   │   └── WebAppSettingsService.cs
│   │   └── wwwroot/
│   │       ├── css/
│   │       │   ├── open-iconic/
│   │       │   │   ├── FONT-LICENSE
│   │       │   │   ├── ICON-LICENSE
│   │       │   │   ├── README.md
│   │       │   │   └── font/
│   │       │   │       └── fonts/
│   │       │   │           └── open-iconic.otf
│   │       │   └── site.css
│   │       └── js/
│   │           └── site.js
│   ├── PresenceLight.Web/
│   │   ├── .config/
│   │   │   └── dotnet-tools.json
│   │   ├── App.razor
│   │   ├── AppOld.razor
│   │   ├── Dockerfile
│   │   ├── Dockerfile.debian-arm32
│   │   ├── Dockerfile.debian-arm64
│   │   ├── Pages/
│   │   │   ├── Error.cshtml
│   │   │   ├── Error.cshtml.cs
│   │   │   └── _Host.cshtml
│   │   ├── PresenceLight.Web.csproj
│   │   ├── PresenceLightSettings.json
│   │   ├── Program.cs
│   │   ├── Program_New.cs
│   │   ├── Program_Old.cs
│   │   ├── Properties/
│   │   │   ├── PublishProfiles/
│   │   │   │   └── FolderProfile.pubxml
│   │   │   ├── launchSettings.json
│   │   │   ├── serviceDependencies.json
│   │   │   └── serviceDependencies.local.json
│   │   ├── Routes.razor
│   │   ├── ServiceCollectionExtensions.cs
│   │   ├── Worker.cs
│   │   ├── _Imports.razor
│   │   └── appsettings.json
│   └── PresenceLight.sln
└── version.json
Download .txt
SYMBOL INDEX (493 symbols across 118 files)

FILE: src/DesktopClient/PresenceLight/App.xaml.cs
  class App (line 33) | public partial class App : System.Windows.Application
    method App (line 39) | public App()
    method OnStartup (line 45) | private void OnStartup(object sender, StartupEventArgs e)
    method ContinueStartup (line 70) | private void ContinueStartup()
    method IsCriticalFontLoadFailure (line 206) | private static bool IsCriticalFontLoadFailure(Exception ex)

FILE: src/DesktopClient/PresenceLight/MainWindow.xaml.cs
  class MainWindow (line 26) | public partial class MainWindow : Window
    method MainWindow (line 45) | public MainWindow(LoginService loginService,
    method LoadSettings (line 134) | private async Task LoadSettings()
    method MainWindow_Loaded (line 156) | private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
    method LoadApp (line 161) | private void LoadApp()
    method SignIn (line 183) | private async Task SignIn()
    method CallGraph (line 188) | private async Task CallGraph()
    method SetColor (line 219) | public async Task SetColor(string color, string activity = "")
    method SignOut (line 288) | private async Task SignOut()
    method RebuildClient (line 311) | private async Task RebuildClient()
    method LoadImage (line 323) | private BitmapImage? LoadImage(byte[] imageData)
    method MapUI (line 349) | public void MapUI(Presence presence)
    method GetPresence (line 369) | public async Task<Presence> GetPresence()
    method GetPhoto (line 382) | public async Task<byte[]?> GetPhoto()
    method StreamToByteArray (line 404) | public static byte[] StreamToByteArray(Stream input)
    method OnClosing (line 422) | protected override async void OnClosing(System.ComponentModel.CancelEv...
    method OnNotifyIconDoubleClick (line 429) | private void OnNotifyIconDoubleClick(object sender, MouseButtonEventAr...
    method OnOpenClick (line 438) | private void OnOpenClick(object sender, RoutedEventArgs e)
    method OnTurnOnSyncClick (line 444) | private void OnTurnOnSyncClick(object sender, RoutedEventArgs e)
    method OnTurnOffSyncClick (line 452) | private async void OnTurnOffSyncClick(object sender, RoutedEventArgs e)
    method OnExitClick (line 471) | private async void OnExitClick(object sender, RoutedEventArgs e)
    method Current_SessionEnding (line 487) | private async void Current_SessionEnding(object sender, SessionEndingC...
    method InteractWithLights (line 504) | private async Task InteractWithLights()

FILE: src/DesktopClient/PresenceLight/Properties/Resources.Designer.cs
  class Resources (line 22) | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resource...
    method Resources (line 31) | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Mic...

FILE: src/DesktopClient/PresenceLight/Properties/Settings.Designer.cs
  class Settings (line 14) | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]

FILE: src/DesktopClient/PresenceLight/Services/Constants.cs
  class PresenceConstants (line 7) | public static class PresenceConstants
  class PresenceColors (line 12) | public class PresenceColors
    method GetColor (line 25) | public static string GetColor(string status)
  class IconConstants (line 42) | public static class IconConstants
    method GetIcon (line 46) | public static string GetIcon(string iconType, string status)

FILE: src/DesktopClient/PresenceLight/Services/MessageBoxHelper.cs
  class MessageBoxHelper (line 7) | internal static class MessageBoxHelper
    method PrepToCenterMessageBoxOnForm (line 9) | internal static void PrepToCenterMessageBoxOnForm(Window form)
    class MessageBoxCenterHelper (line 15) | private class MessageBoxCenterHelper
      method Prep (line 20) | public void Prep(Window form)
      method CenterMessageCallBack (line 29) | private int CenterMessageCallBack(int message, int wParam, int lParam)
    class NativeMethods (line 52) | private static class NativeMethods
      type RECT (line 54) | internal struct RECT
      method UnhookWindowsHookEx (line 64) | [DllImport("user32.dll")]
      method GetWindowLong (line 68) | [DllImport("user32.dll", SetLastError = true)]
      method GetCurrentThreadId (line 71) | [DllImport("kernel32.dll")]
      method SetWindowsHookEx (line 74) | [DllImport("user32.dll", SetLastError = true)]
      method SetWindowPos (line 77) | [DllImport("user32.dll")]
      method GetWindowRect (line 81) | [DllImport("user32.dll")]

FILE: src/DesktopClient/PresenceLight/Services/NotifyIcon.cs
  class NotifyIcon (line 12) | [ContentProperty("Text")]
    method NotifyIcon (line 42) | static NotifyIcon()
    method BeginInit (line 71) | public override void BeginInit()
    method AddChild (line 79) | void IAddChild.AddChild(object value)
    method AddText (line 84) | void IAddChild.AddText(string text)
    method OnVisualParentChanged (line 96) | protected override void OnVisualParentChanged(DependencyObject oldParent)
    method CreateMouseButtonEventArgs (line 102) | private static MouseButtonEventArgs CreateMouseButtonEventArgs(
    method FromImageSource (line 112) | private static Drawing.Icon? FromImageSource(ImageSource icon)
    method OnIconChanged (line 123) | private static void OnIconChanged(DependencyObject target, DependencyP...
    method OnTextChanged (line 132) | private static void OnTextChanged(DependencyObject target, DependencyP...
    method OnVisibilityChanged (line 138) | private static void OnVisibilityChanged(DependencyObject target, Depen...
    method ToMouseButton (line 144) | private static MouseButton ToMouseButton(Forms.MouseButtons button)
    method AttachToWindowClose (line 163) | private void AttachToWindowClose()
    method InitializeNotifyIcon (line 172) | private void InitializeNotifyIcon()
    method OnMouseDown (line 187) | private void OnMouseDown(object sender, Forms.MouseEventArgs e)
    method OnMouseDoubleClick (line 192) | private void OnMouseDoubleClick(object sender, Forms.MouseEventArgs e)
    method OnMouseClick (line 197) | private void OnMouseClick(object sender, Forms.MouseEventArgs e)
    method OnMouseUp (line 202) | private void OnMouseUp(object sender, Forms.MouseEventArgs e)
    method ShowContextMenu (line 212) | private void ShowContextMenu()
    method AttachContextMenu (line 221) | partial void AttachContextMenu();
    method InitializeNativeHooks (line 223) | partial void InitializeNativeHooks();

FILE: src/DesktopClient/PresenceLight/Services/Settings/AppPackageSettingsService.cs
  class AppPackageSettingsService (line 13) | public class AppPackageSettingsService : ISettingsService
    method AppPackageSettingsService (line 21) | public AppPackageSettingsService(DiagnosticsClient diagClient, ILogger...
    method LoadSettings (line 28) | public async Task<BaseConfig?> LoadSettings()
    method SaveSettings (line 48) | public async Task<bool> SaveSettings(BaseConfig data)
    method DeleteSettings (line 86) | public async Task<bool> DeleteSettings()
    method IsFilePresent (line 102) | public async Task<bool> IsFilePresent()
    method GetSettingsFileLocation (line 131) | public string GetSettingsFileLocation()
    method BuildSettingsFileLocation (line 136) | public static string BuildSettingsFileLocation()

FILE: src/DesktopClient/PresenceLight/Services/Settings/StandaloneSettingsService.cs
  class StandaloneSettingsService (line 15) | public class StandaloneSettingsService : ISettingsService
    method StandaloneSettingsService (line 23) | public StandaloneSettingsService(DiagnosticsClient diagClient, ILogger...
    method DeleteSettings (line 30) | public Task<bool> DeleteSettings()
    method IsFilePresent (line 39) | public async Task<bool> IsFilePresent()
    method LoadSettings (line 66) | public async Task<BaseConfig?> LoadSettings()
    method SaveSettings (line 83) | public async Task<bool> SaveSettings(BaseConfig data)
    method GetSettingsFileLocation (line 100) | public string GetSettingsFileLocation()
    method BuildSettingsFileLocation (line 105) | public static string BuildSettingsFileLocation()

FILE: src/DesktopClient/PresenceLight/Services/SingleInstanceAppMutex.cs
  class SingleInstanceAppMutex (line 12) | class SingleInstanceAppMutex
    method TakeExclusivity (line 16) | public static bool TakeExclusivity()
    method ReleaseExclusivity (line 31) | public static void ReleaseExclusivity()

FILE: src/DesktopClient/PresenceLight/Services/Telemetry/DiagnosticsClient.cs
  class DiagnosticsClient (line 9) | public class DiagnosticsClient
    method DiagnosticsClient (line 14) | public DiagnosticsClient(TelemetryClient tc)
    method DispatcherUnhandledException (line 25) | private void DispatcherUnhandledException(object sender, System.Window...
    method Application_Exit (line 31) | private void Application_Exit(object sender, System.Windows.ExitEventA...
    method Application_Startup (line 39) | private void Application_Startup(object sender, System.Windows.Startup...
    method TrackEvent (line 44) | public void TrackEvent(string eventName, IDictionary<string, string>? ...
    method TrackTrace (line 49) | public void TrackTrace(string evt)
    method TrackException (line 54) | public void TrackException(Exception exception, IDictionary<string, st...
    method TrackPageView (line 59) | public void TrackPageView(string pageName)

FILE: src/PresenceLight.Core/Configuration/AAD.cs
  class AADSettings (line 14) | public class AADSettings

FILE: src/PresenceLight.Core/Configuration/AppState.cs
  class AppState (line 16) | public class AppState
    method SetConfig (line 128) | public void SetConfig(BaseConfig config)
    method SetUserInfo (line 139) | public void SetUserInfo(User? user, Presence? presence, string? photo ...
    method SetPresence (line 151) | public void SetPresence(Presence presence)
    method SetCustomColor (line 161) | public void SetCustomColor(string color)
    method SetLightMode (line 171) | public void SetLightMode(string lightMode)
    method SetHueLights (line 181) | public void SetHueLights(IEnumerable<object> lights)
    method SetHueLight (line 191) | public void SetHueLight(string lightId)
    method SetYeelightLights (line 201) | public void SetYeelightLights(List<Device> lights)
    method SetYeelightLight (line 211) | public void SetYeelightLight(string lightId)
    method SetLIFXLights (line 221) | public void SetLIFXLights(IEnumerable<object> lights)
    method SetLIFXLight (line 231) | public void SetLIFXLight(string lightId)
    method SetLocalSerialHosts (line 242) | public void SetLocalSerialHosts(IEnumerable<string> lights)
    method SetLocalSerialHost (line 252) | public void SetLocalSerialHost(string port)
    method SetWizLight (line 262) | public void SetWizLight(WizLight light)
    method NotifyStateChanged (line 268) | private void NotifyStateChanged() => OnChange?.Invoke();

FILE: src/PresenceLight.Core/Configuration/AvailabilityStatus.cs
  class AvailabilityStatus (line 6) | public class AvailabilityStatus

FILE: src/PresenceLight.Core/Configuration/Base.cs
  class BaseConfig (line 6) | public class BaseConfig

FILE: src/PresenceLight.Core/Configuration/BaseLight.cs
  class BaseLight (line 6) | public class BaseLight

FILE: src/PresenceLight.Core/Configuration/CustomApi.cs
  class CustomApi (line 6) | public class CustomApi : BaseLight

FILE: src/PresenceLight.Core/Configuration/CustomApiSetting.cs
  class CustomApiSetting (line 6) | public class CustomApiSetting

FILE: src/PresenceLight.Core/Configuration/Hue.cs
  class Hue (line 10) | public class Hue : BaseLight

FILE: src/PresenceLight.Core/Configuration/ISettingsService.cs
  type ISettingsService (line 14) | public interface ISettingsService
    method LoadSettings (line 20) | public Task<BaseConfig?> LoadSettings();
    method SaveSettings (line 27) | public Task<bool> SaveSettings(BaseConfig data);
    method DeleteSettings (line 33) | public Task<bool> DeleteSettings();
    method IsFilePresent (line 39) | public Task<bool> IsFilePresent();
    method GetSettingsFileLocation (line 45) | public string GetSettingsFileLocation();

FILE: src/PresenceLight.Core/Configuration/LIFX.cs
  class LIFX (line 6) | public class LIFX : BaseLight

FILE: src/PresenceLight.Core/Configuration/LightSettings.cs
  class LightSettings (line 10) | public class LightSettings

FILE: src/PresenceLight.Core/Configuration/LocalSerialHost.cs
  class LocalSerialHost (line 6) | public class LocalSerialHost : BaseLight

FILE: src/PresenceLight.Core/Configuration/LocalSerialHostSetting.cs
  class LocalSerialHostSetting (line 6) | public class LocalSerialHostSetting

FILE: src/PresenceLight.Core/Configuration/Statuses.cs
  class PresenceLightStatuses (line 12) | public class PresenceLightStatuses

FILE: src/PresenceLight.Core/Configuration/Wiz.cs
  class Wiz (line 5) | public class Wiz : BaseLight

FILE: src/PresenceLight.Core/Configuration/Yeelight.cs
  class Yeelight (line 7) | public class Yeelight : BaseLight

FILE: src/PresenceLight.Core/GraphServices/AuthorizationProvider.cs
  class AuthorizationProvider (line 19) | public class AuthorizationProvider : IAuthenticationProvider
    method AuthorizationProvider (line 32) | public AuthorizationProvider(IOptionsMonitor<BaseConfig> configMonitor...
    method RebuildMsalClients (line 38) | public bool RebuildMsalClients()
    method Invalidate (line 96) | public void Invalidate()
    method AcquireToken (line 107) | public async Task<string> AcquireToken()
    method CreateCacheHelperAsync (line 192) | private static MsalCacheHelper CreateCacheHelperAsync(string clientId)
    method AuthenticateRequestAsync (line 228) | async Task IAuthenticationProvider.AuthenticateRequestAsync(RequestInf...
    method AadChanged (line 238) | public static bool AadChanged(BaseConfig config, BaseConfig newConfig)

FILE: src/PresenceLight.Core/GraphServices/GetIsInitialized/GetIsInitializedCommand.cs
  class GetIsInitializedCommand (line 6) | public class GetIsInitializedCommand : IRequest<bool>

FILE: src/PresenceLight.Core/GraphServices/GetIsInitialized/GetIsInitializedHandler.cs
  class GetIsInitializedHandler (line 8) | internal class GetIsInitializedHandler : IRequestHandler<GetIsInitialize...
    method GetIsInitializedHandler (line 12) | public GetIsInitializedHandler(GraphWrapper graph)
    method Handle (line 18) | public async Task<bool> Handle(GetIsInitializedCommand command, Cancel...

FILE: src/PresenceLight.Core/GraphServices/GetPhoto/GetPhotoCommand.cs
  class GetPhotoCommand (line 6) | public class GetPhotoCommand : IRequest<Stream>

FILE: src/PresenceLight.Core/GraphServices/GetPhoto/GetPhotoHandler.cs
  class GetPhotoHandler (line 10) | internal class GetPhotoHandler : IRequestHandler<GetPhotoCommand, Stream>
    method GetPhotoHandler (line 14) | public GetPhotoHandler(GraphWrapper graph)
    method Handle (line 19) | public async Task<Stream> Handle(GetPhotoCommand command, Cancellation...

FILE: src/PresenceLight.Core/GraphServices/GetPresence/GetPresenceCommand.cs
  class GetPresenceCommand (line 6) | public class GetPresenceCommand : IRequest<Presence>

FILE: src/PresenceLight.Core/GraphServices/GetPresence/GetPresenceHandler.cs
  class GetPresenceHandler (line 11) | internal class GetPresenceHandler : IRequestHandler<GetPresenceCommand, ...
    method GetPresenceHandler (line 15) | public GetPresenceHandler(GraphWrapper graph)
    method Handle (line 20) | public async Task<Presence> Handle(GetPresenceCommand command, Cancell...

FILE: src/PresenceLight.Core/GraphServices/GetProfile/GetProfileCommand.cs
  class GetProfileCommand (line 7) | public class GetProfileCommand : IRequest<User>

FILE: src/PresenceLight.Core/GraphServices/GetProfile/GetProfileHandler.cs
  class GetProfileHandler (line 11) | internal class GetProfileHandler : IRequestHandler<GetProfileCommand, User>
    method GetProfileHandler (line 16) | public GetProfileHandler(GraphWrapper graph)
    method Handle (line 22) | public async Task<User> Handle(GetProfileCommand command, Cancellation...

FILE: src/PresenceLight.Core/GraphServices/GetProfileAndPresence/GetProfileAndPresenceCommand.cs
  class GetProfileAndPresenceCommand (line 9) | public class GetProfileAndPresenceCommand : IRequest<(User User, Presenc...

FILE: src/PresenceLight.Core/GraphServices/GetProfileAndPresence/GetProfileAndPresenceHandler.cs
  class GetProfileAndPresenceHandler (line 13) | internal class GetProfileAndPresenceHandler : IRequestHandler<GetProfile...
    method GetProfileAndPresenceHandler (line 17) | public GetProfileAndPresenceHandler(GraphWrapper graph)
    method Handle (line 22) | public async Task<(User User, Presence Presence)> Handle(GetProfileAnd...

FILE: src/PresenceLight.Core/GraphServices/GraphWrapper.cs
  class GraphWrapper (line 17) | public class GraphWrapper
    method GraphWrapper (line 36) | public GraphWrapper(ILogger<GraphWrapper> logger, LoginService _loginS...
    method Initialize (line 51) | public async Task Initialize()
    method GetPresence (line 57) | public async Task<Presence> GetPresence(CancellationToken token)
    method GetPhoto (line 71) | public async Task<System.IO.Stream> GetPhoto(CancellationToken token)
    method GetProfile (line 91) | public async Task<User> GetProfile(CancellationToken token)
    method GetProfileAndPresence (line 104) | public async Task<(User User, Presence Presence)> GetProfileAndPresenc...
    method GetBatchContent (line 109) | private async Task<(User User, Presence Presence)> GetBatchContent(Can...

FILE: src/PresenceLight.Core/GraphServices/Initialize/InitializeCommand.cs
  class InitializeCommand (line 9) | public class InitializeCommand : IRequest

FILE: src/PresenceLight.Core/GraphServices/Initialize/InitializeHandler.cs
  class InitializeHandler (line 8) | internal class InitializeHandler : IRequestHandler<InitializeCommand>
    method InitializeHandler (line 12) | public InitializeHandler(GraphWrapper graph)
    method Handle (line 17) | async Task IRequestHandler<InitializeCommand>.Handle(InitializeCommand...

FILE: src/PresenceLight.Core/GraphServices/LoginService.cs
  class LoginService (line 11) | public class LoginService
    method LoginService (line 24) | public LoginService(IOptionsMonitor<BaseConfig> configMonitor, Authori...
    method GetAuthenticatedGraphClient (line 56) | public async Task GetAuthenticatedGraphClient()
    method IsUserAuthenticated (line 66) | public async Task<bool> IsUserAuthenticated()
    method AddUserToTokenCache (line 103) | public async Task<string> AddUserToTokenCache(string authorizationCode)
    method SignOut (line 115) | public async Task SignOut()
    method RebuildClient (line 137) | public void RebuildClient()

FILE: src/PresenceLight.Core/GraphServices/TokenCacheHelper.cs
  class TokenCacheHelper (line 8) | static class TokenCacheHelper
    method EnableSerialization (line 10) | public static void EnableSerialization(ITokenCache tokenCache)
    method BeforeAccessNotification (line 25) | private static void BeforeAccessNotification(TokenCacheNotificationArg...
    method AfterAccessNotification (line 37) | private static void AfterAccessNotification(TokenCacheNotificationArgs...

FILE: src/PresenceLight.Core/Helpers.cs
  type HoursPassedStatus (line 20) | public enum HoursPassedStatus
  class Helpers (line 30) | public static class Helpers
    method AreStringsNotEmpty (line 33) | public static bool AreStringsNotEmpty(string[] strings)
    method OpenBrowser (line 49) | public static void OpenBrowser(string url)
    method HumanifyText (line 84) | public static string HumanifyText(string text)
    method HoursPassedStatusString (line 99) | public static string HoursPassedStatusString(HoursPassedStatus status) =>
    method ReplaceVariables (line 115) | public static string ReplaceVariables(string body, string? availabilit...

FILE: src/PresenceLight.Core/Lights/CustomApiService/CustomApiService.cs
  type ICustomApiService (line 13) | public interface ICustomApiService
    method SetColor (line 15) | Task<string> SetColor(string availability, string? activity, Cancellat...
    method Initialize (line 16) | void Initialize(AppState _appState);
  class CustomApiService (line 20) | public class CustomApiService : ICustomApiService
    method CustomApiService (line 31) | public CustomApiService(AppState appState, ILogger<CustomApiService> l...
    method Initialize (line 45) | public void Initialize(AppState appState)
    method SetColor (line 50) | public async Task<string> SetColor(string availability, string? activi...
    method CallCustomApiForActivityChanged (line 57) | private async Task<string> CallCustomApiForActivityChanged(object send...
    method CallCustomApiForAvailabilityChanged (line 133) | private async Task<string> CallCustomApiForAvailabilityChanged(object ...
    method SetAvailability (line 189) | private async Task<string> SetAvailability(string availability, Cancel...
    method SetActivity (line 212) | private async Task<string> SetActivity(string activity, CancellationTo...
    method PerformWebRequest (line 235) | private async Task<string> PerformWebRequest(string method, string uri...

FILE: src/PresenceLight.Core/Lights/CustomApiService/Initialize/InitializeCommand.cs
  class InitializeCommand (line 6) | public class InitializeCommand : IRequest

FILE: src/PresenceLight.Core/Lights/CustomApiService/Initialize/InitializeHandler.cs
  class InitializeHandler (line 13) | internal class InitializeHandler : IRequestHandler<InitializeCommand>
    method InitializeHandler (line 16) | public InitializeHandler(ICustomApiService service)
    method Handle (line 21) | Task IRequestHandler<InitializeCommand>.Handle(InitializeCommand comma...

FILE: src/PresenceLight.Core/Lights/CustomApiService/SetColor/SetColorCommand.cs
  class SetColorCommand (line 6) | public class SetColorCommand : IRequest<string>

FILE: src/PresenceLight.Core/Lights/CustomApiService/SetColor/SetColorHandler.cs
  class SetColorHandler (line 9) | internal class SetColorHandler : IRequestHandler<SetColorCommand, string>
    method SetColorHandler (line 12) | public SetColorHandler(ICustomApiService service)
    method Handle (line 17) | public async Task<string> Handle(SetColorCommand command, Cancellation...

FILE: src/PresenceLight.Core/Lights/HueServices/FindBridge/FindBridgeCommand.cs
  class FindBridgeCommand (line 6) | public class FindBridgeCommand : IRequest<string>

FILE: src/PresenceLight.Core/Lights/HueServices/FindBridge/FindBridgeHandler.cs
  class FindBridgeHandler (line 8) | public class FindBridgeHandler : IRequestHandler<FindBridgeCommand, string>
    method FindBridgeHandler (line 11) | public FindBridgeHandler(IHueService hueService)
    method Handle (line 16) | public async Task<string> Handle(FindBridgeCommand command, Cancellati...

FILE: src/PresenceLight.Core/Lights/HueServices/GetGroups/GetGroupsCommand.cs
  class GetGroupsCommand (line 8) | public class GetGroupsCommand : IRequest<IEnumerable<GroupedLight>>

FILE: src/PresenceLight.Core/Lights/HueServices/GetGroups/GetGroupsHandler.cs
  class GetGroupsHandler (line 12) | public class GetGroupsHandler : IRequestHandler<GetGroupsCommand, IEnume...
    method GetGroupsHandler (line 15) | public GetGroupsHandler(IHueService hueService)
    method Handle (line 20) | public async Task<IEnumerable<GroupedLight>> Handle(GetGroupsCommand c...

FILE: src/PresenceLight.Core/Lights/HueServices/GetLights/GetLightsCommand.cs
  class GetLightsCommand (line 7) | public class GetLightsCommand : IRequest<IEnumerable<Light>>

FILE: src/PresenceLight.Core/Lights/HueServices/GetLights/GetLightsHandler.cs
  class GetLightsHandler (line 9) | public class GetLightsHandler : IRequestHandler<GetLightsCommand, IEnume...
    method GetLightsHandler (line 12) | public GetLightsHandler(IHueService hueService)
    method Handle (line 17) | public async Task<IEnumerable<Light>> Handle(GetLightsCommand command,...

FILE: src/PresenceLight.Core/Lights/HueServices/HueService.cs
  type IHueService (line 17) | public interface IHueService
    method SetColor (line 19) | Task SetColor(string availability, string activity, string lightId);
    method RegisterBridge (line 20) | Task<string> RegisterBridge();
    method GetLights (line 21) | Task<IEnumerable<Light>> GetLights();
    method GetGroups (line 23) | Task<IEnumerable<GroupedLight>> GetGroups();
    method FindBridge (line 24) | Task<string> FindBridge();
    method Initialize (line 25) | void Initialize(AppState appState);
  class HueService (line 27) | public class HueService : IHueService
    method HueService (line 33) | public HueService(AppState appState, ILogger<HueService> logger)
    method Initialize (line 39) | public void Initialize(AppState appState)
    method SetColor (line 44) | public async Task SetColor(string availability, string activity, strin...
    method RegisterBridge (line 173) | public async Task<string> RegisterBridge()
    method FindBridge (line 192) | public async Task<string> FindBridge()
    method GetLights (line 211) | public async Task<IEnumerable<Light>> GetLights()
    method GetGroups (line 229) | public async Task<IEnumerable<GroupedLight>> GetGroups()
    method Handle (line 247) | private async Task<(string color, UpdateLight command, bool returnFunc...

FILE: src/PresenceLight.Core/Lights/HueServices/Initialize/InitializeCommand.cs
  class InitializeCommand (line 7) | public class InitializeCommand : IRequest

FILE: src/PresenceLight.Core/Lights/HueServices/Initialize/InitializeHandler.cs
  class InitializeHandler (line 11) | internal class InitializeHandler : IRequestHandler<InitializeCommand>
    method InitializeHandler (line 14) | public InitializeHandler(IHueService hueService)
    method Handle (line 20) | async Task IRequestHandler<InitializeCommand>.Handle(InitializeCommand...

FILE: src/PresenceLight.Core/Lights/HueServices/RegisterBridge/RegisterBridgeCommand.cs
  class RegisterBridgeCommand (line 6) | public class RegisterBridgeCommand : IRequest<string>

FILE: src/PresenceLight.Core/Lights/HueServices/RegisterBridge/RegisterBridgeHandler.cs
  class RegisterBridgeHandler (line 8) | public class RegisterBridgeHandler : IRequestHandler<RegisterBridgeComma...
    method RegisterBridgeHandler (line 11) | public RegisterBridgeHandler(IHueService hueService)
    method Handle (line 16) | public async Task<string> Handle(RegisterBridgeCommand command, Cancel...

FILE: src/PresenceLight.Core/Lights/HueServices/SetColor/SetColorCommand.cs
  class SetColorCommand (line 7) | public class SetColorCommand : IRequest<Unit>

FILE: src/PresenceLight.Core/Lights/HueServices/SetColor/SetColorHandler.cs
  class SetColorHandler (line 8) | public class SetColorHandler : IRequestHandler<SetColorCommand, Unit>
    method SetColorHandler (line 11) | public SetColorHandler(IHueService hueService)
    method Handle (line 16) | public  async Task<Unit> Handle(SetColorCommand command, CancellationT...

FILE: src/PresenceLight.Core/Lights/LifxServices/GetAllGroups/GetAllGroupsCommand.cs
  class GetAllGroupsCommand (line 9) | public class GetAllGroupsCommand : IRequest<List<Group>>

FILE: src/PresenceLight.Core/Lights/LifxServices/GetAllGroups/GetAllGroupsHandler.cs
  class GetAllGroupsHandler (line 12) | internal class GetAllGroupsHandler : IRequestHandler<GetAllGroupsCommand...
    method GetAllGroupsHandler (line 15) | public GetAllGroupsHandler(LIFXService service)
    method Handle (line 20) | public async Task<List<Group>> Handle(GetAllGroupsCommand command, Can...

FILE: src/PresenceLight.Core/Lights/LifxServices/GetAllLights/GetAllLightsCommand.cs
  class GetAllLightsCommand (line 9) | public class GetAllLightsCommand : IRequest<List<Light>>

FILE: src/PresenceLight.Core/Lights/LifxServices/GetAllLights/GetAllLightsHandler.cs
  class GetAllLightsHandler (line 12) | internal class GetAllLightsHandler : IRequestHandler<GetAllLightsCommand...
    method GetAllLightsHandler (line 15) | public GetAllLightsHandler(LIFXService service)
    method Handle (line 20) | public async Task<List<Light>> Handle(GetAllLightsCommand command, Can...

FILE: src/PresenceLight.Core/Lights/LifxServices/Initialize/InitializeCommand.cs
  class InitializeCommand (line 6) | public class InitializeCommand : IRequest

FILE: src/PresenceLight.Core/Lights/LifxServices/Initialize/InitializeHandler.cs
  class InitializeHandler (line 13) | internal class InitializeHandler : IRequestHandler<InitializeCommand>
    method InitializeHandler (line 16) | public InitializeHandler(LIFXService service)
    method Handle (line 21) | async Task IRequestHandler<InitializeCommand>.Handle(InitializeCommand...

FILE: src/PresenceLight.Core/Lights/LifxServices/LIFXOAuthHelper.cs
  class LIFXOAuthHelper (line 22) | public class LIFXOAuthHelper
    method LIFXOAuthHelper (line 29) | public LIFXOAuthHelper(AppState appState)
    method InitiateTokenRetrieval (line 35) | public async Task<string> InitiateTokenRetrieval()
    method RandomDataBase64Url (line 141) | private static string RandomDataBase64Url(uint length)
    method Base64UrlEncodeNoPadding (line 154) | private static string Base64UrlEncodeNoPadding(byte[] buffer)

FILE: src/PresenceLight.Core/Lights/LifxServices/LifxService.cs
  class LIFXService (line 11) | public class LIFXService
    method LIFXService (line 18) | public LIFXService(AppState appState, MediatR.IMediator mediator, ILog...
    method Initialize (line 25) | public void Initialize(AppState appState)
    method GetAllLights (line 30) | public async Task<List<Light>> GetAllLights(string apiKey = null)
    method GetAllGroups (line 54) | public async Task<List<Group>> GetAllGroups(string apiKey = null)
    method SetColor (line 77) | public async Task SetColor(string availability, string activity, strin...
    method Handle (line 180) | private async Task<(string color, SetStateRequest command, bool return...

FILE: src/PresenceLight.Core/Lights/LifxServices/SetColor/SetColorCommand.cs
  class SetColorCommand (line 7) | public class SetColorCommand : IRequest<Unit>

FILE: src/PresenceLight.Core/Lights/LifxServices/SetColor/SetColorHandler.cs
  class SetColorHandler (line 14) | internal class SetColorHandler : IRequestHandler<SetColorCommand, Unit>
    method SetColorHandler (line 17) | public SetColorHandler(LIFXService service)
    method Handle (line 22) | public async Task<Unit> Handle(SetColorCommand command, CancellationTo...

FILE: src/PresenceLight.Core/Lights/LocalSerialHostService/GetSerialHosts/GetSerialHostsCommand.cs
  class GetPortCommand (line 6) | public class GetPortCommand : IRequest<IEnumerable<string>>

FILE: src/PresenceLight.Core/Lights/LocalSerialHostService/GetSerialHosts/GetSerialHostsHandler.cs
  class GetAvailablePortsHandler (line 10) | internal class GetAvailablePortsHandler : IRequestHandler<GetPortCommand...
    method GetAvailablePortsHandler (line 14) | public GetAvailablePortsHandler(ILocalSerialHostService service)
    method Handle (line 19) | public async Task<IEnumerable<string>> Handle(GetPortCommand command, ...

FILE: src/PresenceLight.Core/Lights/LocalSerialHostService/Initialize/InitializeCommand.cs
  class InitializeCommand (line 6) | public class InitializeCommand : IRequest

FILE: src/PresenceLight.Core/Lights/LocalSerialHostService/Initialize/InitializeHandler.cs
  class InitializeHandler (line 13) | internal class InitializeHandler : IRequestHandler<InitializeCommand>
    method InitializeHandler (line 16) | public InitializeHandler(ILocalSerialHostService service)
    method Handle (line 22) | Task IRequestHandler<InitializeCommand>.Handle(InitializeCommand comma...

FILE: src/PresenceLight.Core/Lights/LocalSerialHostService/LocalSerialHost.cs
  type ILocalSerialHostService (line 14) | public interface ILocalSerialHostService
    method SetColor (line 16) | Task<string> SetColor(string availability, string? activity, Cancellat...
    method GetPorts (line 17) | Task<IEnumerable<string>> GetPorts();
    method Initialize (line 18) | void Initialize(AppState _appState);
  class LocalSerialHostService (line 22) | public class LocalSerialHostService : ILocalSerialHostService
    method LocalSerialHostService (line 34) | public LocalSerialHostService(AppState appState, ILogger<LocalSerialHo...
    method Initialize (line 71) | public void Initialize(AppState appState)
    method SetColor (line 104) | public async Task<string> SetColor(string availability, string? activi...
    method CallLocalSerialHostForActivityChanged (line 111) | private async Task<string> CallLocalSerialHostForActivityChanged(objec...
    method CallLocalSerialHostForAvailabilityChanged (line 161) | private async Task<string> CallLocalSerialHostForAvailabilityChanged(o...
    method SetAvailability (line 199) | private async Task<string> SetAvailability(string availability, Cancel...
    method SetActivity (line 222) | private async Task<string> SetActivity(string activity, CancellationTo...
    method PerformSerialMessage (line 245) | private async Task<string> PerformSerialMessage(string serialMessage, ...
    method GetPorts (line 306) | public async Task<IEnumerable<string>> GetPorts()
    method SetupSerialPort (line 320) | private void SetupSerialPort(string serialPort)

FILE: src/PresenceLight.Core/Lights/LocalSerialHostService/SetColor/SetColorCommand.cs
  class SetColorCommand (line 6) | public class SetColorCommand : IRequest<string>

FILE: src/PresenceLight.Core/Lights/LocalSerialHostService/SetColor/SetColorHandler.cs
  class SetColorHandler (line 9) | internal class SetColorHandler : IRequestHandler<SetColorCommand, string>
    method SetColorHandler (line 12) | public SetColorHandler(ILocalSerialHostService service)
    method Handle (line 17) | public async Task<string> Handle(SetColorCommand command, Cancellation...

FILE: src/PresenceLight.Core/Lights/RemoteHueServices/GetGroups/GetGroupsCommand.cs
  class GetGroupsCommand (line 7) | public class GetGroupsCommand : IRequest<IEnumerable<GroupedLight>>

FILE: src/PresenceLight.Core/Lights/RemoteHueServices/GetGroups/GetGroupsHandler.cs
  class GetGroupsHandler (line 9) | internal class GetGroupsHandler : IRequestHandler<GetGroupsCommand, IEnu...
    method GetGroupsHandler (line 12) | public GetGroupsHandler(IRemoteHueService service)
    method Handle (line 17) | public async Task<IEnumerable<GroupedLight>> Handle(GetGroupsCommand c...

FILE: src/PresenceLight.Core/Lights/RemoteHueServices/GetLights/GetLightsCommand.cs
  class GetLightsCommand (line 7) | public class GetLightsCommand : IRequest<IEnumerable<Light>>

FILE: src/PresenceLight.Core/Lights/RemoteHueServices/GetLights/GetLightsHandler.cs
  class GetLightsHandler (line 9) | internal class GetLightsHandler : IRequestHandler<GetLightsCommand, IEnu...
    method GetLightsHandler (line 12) | public GetLightsHandler(IRemoteHueService service)
    method Handle (line 17) | public async Task<IEnumerable<Light>> Handle(GetLightsCommand command,...

FILE: src/PresenceLight.Core/Lights/RemoteHueServices/RegisterBridge/RegisterBridgeCommand.cs
  class RegisterBridgeCommand (line 6) | public class RegisterBridgeCommand : IRequest<(string bridgeId, string a...

FILE: src/PresenceLight.Core/Lights/RemoteHueServices/RegisterBridge/RegisterBridgeHandler.cs
  class RegisterBridgeHandler (line 8) | internal class RegisterBridgeHandler : IRequestHandler<RegisterBridgeCom...
    method RegisterBridgeHandler (line 11) | public RegisterBridgeHandler(IRemoteHueService service)
    method Handle (line 16) | public async Task<(string bridgeId, string apiKey, string bridgeIp)> H...

FILE: src/PresenceLight.Core/Lights/RemoteHueServices/RemoteAuthenticationClient.cs
  class AccessTokenResponseV2 (line 15) | public class AccessTokenResponseV2
    method AccessTokenResponseV2 (line 25) | public AccessTokenResponseV2()
    method AccessTokenExpireTime (line 29) | public DateTimeOffset AccessTokenExpireTime()
  type IRemoteAuthenticationClient (line 38) | public interface IRemoteAuthenticationClient
    method BuildAuthorizeUri (line 40) | Uri BuildAuthorizeUri(string state, string deviceId, string? deviceNam...
    method ProcessAuthorizeResponse (line 42) | RemoteAuthorizeResponse ProcessAuthorizeResponse(string responseData);
    method Initialize (line 48) | void Initialize(AccessTokenResponseV2 storedAccessToken);
    method GetToken (line 50) | Task<AccessTokenResponseV2?> GetToken(string code);
    method RefreshToken (line 52) | Task<AccessTokenResponseV2?> RefreshToken(string refreshToken);
    method GetValidToken (line 58) | Task<string?> GetValidToken();
  class RemoteAuthenticationClientV2 (line 67) | public class RemoteAuthenticationClientV2 : IRemoteAuthenticationClient
    method RemoteAuthenticationClientV2 (line 84) | public RemoteAuthenticationClientV2(string clientId, string clientSecr...
    method Initialize (line 99) | public void Initialize(AccessTokenResponseV2 accessTokenResponse)
    method BuildAuthorizeUri (line 113) | public Uri BuildAuthorizeUri(string state, string deviceId, string? de...
    method ProcessAuthorizeResponse (line 123) | public RemoteAuthorizeResponse ProcessAuthorizeResponse(string respons...
    method GetToken (line 150) | public async Task<AccessTokenResponseV2?> GetToken(string code)
    method GetNonce (line 197) | private static string GetNonce(string r)
    method RefreshToken (line 207) | public async Task<AccessTokenResponseV2?> RefreshToken(string refreshT...
    method CalculateHash (line 260) | private static string CalculateHash(string clientId, string clientSecr...
    method GetValidToken (line 273) | public async Task<string?> GetValidToken()
    method CheckInitialized (line 296) | private void CheckInitialized()

FILE: src/PresenceLight.Core/Lights/RemoteHueServices/RemoteHueService.cs
  type IRemoteHueService (line 22) | public interface IRemoteHueService
    method SetColor (line 24) | Task SetColor(string availability, string activity, string lightId, st...
    method RegisterBridge (line 25) | Task<(string bridgeId, string apiKey, string bridgeIp)> RegisterBridge();
    method GetLights (line 26) | Task<IEnumerable<HueApi.Models.Light>> GetLights();
    method GetGroups (line 27) | Task<IEnumerable<HueApi.Models.GroupedLight>> GetGroups();
  class RemoteHueService (line 30) | public class RemoteHueService : IRemoteHueService
    method RemoteHueService (line 39) | public RemoteHueService(AppState appState, ILogger<RemoteHueService> l...
    method CreateAuthClient (line 47) | private void CreateAuthClient()
    method InitializeClient (line 64) | private async Task InitializeClient()
    method RegisterBridge (line 105) | public async Task<(string bridgeId, string apiKey, string bridgeIp)> R...
    method SetColor (line 150) | public async Task SetColor(string availability, string activity, strin...
    method GetLights (line 259) | public async Task<IEnumerable<HueApi.Models.Light>> GetLights()
    method GetGroups (line 303) | public async Task<IEnumerable<HueApi.Models.GroupedLight>> GetGroups()
    method FindBridge (line 339) | public Task<string> FindBridge()
    method Handle (line 344) | private async Task<(string color, LightCommand command, bool returnFun...
    method GetAccessToken (line 402) | private async Task GetAccessToken()
    method TryBindListenerOnFreePort (line 455) | private static bool TryBindListenerOnFreePort(out HttpListener httpLis...

FILE: src/PresenceLight.Core/Lights/RemoteHueServices/SetColor/SetColorCommand.cs
  class SetColorCommand (line 6) | public class SetColorCommand : IRequest<Unit>

FILE: src/PresenceLight.Core/Lights/RemoteHueServices/SetColor/SetColorHandler.cs
  class SetColorHandler (line 8) | internal class SetColorHandler : IRequestHandler<SetColorCommand, Unit>
    method SetColorHandler (line 11) | public SetColorHandler(IRemoteHueService service)
    method Handle (line 16) | public async Task<Unit> Handle(SetColorCommand command, CancellationTo...

FILE: src/PresenceLight.Core/Lights/ServicesExtensions.cs
  class ServicesExtensions (line 5) | public static class ServicesExtensions
    method AddPresenceServices (line 7) | public static void AddPresenceServices(this IServiceCollection services)

FILE: src/PresenceLight.Core/Lights/WizServices/GetLights/GetLightCommand.cs
  class GetLightCommand (line 9) | public class GetLightCommand : IRequest<WizLight>

FILE: src/PresenceLight.Core/Lights/WizServices/GetLights/GetLightHandler.cs
  class GetLightHandler (line 11) | public class GetLightHandler : IRequestHandler<GetLightCommand, WizLight>
    method GetLightHandler (line 14) | public GetLightHandler(IWizService service)
    method Handle (line 19) | public async Task<WizLight> Handle(GetLightCommand command, Cancellati...

FILE: src/PresenceLight.Core/Lights/WizServices/IPAddressExtensions.cs
  class IPAddressExtensions (line 12) | internal static class IPAddressExtensions
    method IsIPv4LinkLocal (line 20) | public static bool IsIPv4LinkLocal(this IPAddress ip)
    method IsLoopback (line 37) | public static bool IsLoopback(this IPAddress ip)
    method IsIPv4Private (line 48) | public static bool IsIPv4Private(this IPAddress ip)
    method GetNetworkLoopbackAddress (line 85) | public static IPAddress GetNetworkLoopbackAddress(this IPAddress ip, I...
  class NetworkInterfaceExtensions (line 137) | internal static class NetworkInterfaceExtensions
    method GetAllUpNetworkInterfacesFirstPrivateIPv4 (line 143) | public static UnicastIPAddressInformation GetAllUpNetworkInterfacesFir...

FILE: src/PresenceLight.Core/Lights/WizServices/SetColor/SetColorCommand.cs
  class SetColorCommand (line 7) | public class SetColorCommand : IRequest<Unit>

FILE: src/PresenceLight.Core/Lights/WizServices/SetColor/SetColorHandler.cs
  class SetColorHandler (line 8) | public class SetColorHandler : IRequestHandler<SetColorCommand, Unit>
    method SetColorHandler (line 11) | public SetColorHandler(IWizService hueService)
    method Handle (line 16) | public  async Task<Unit> Handle(SetColorCommand command, CancellationT...

FILE: src/PresenceLight.Core/Lights/WizServices/WizLight.cs
  class WizLight (line 9) | public class WizLight

FILE: src/PresenceLight.Core/Lights/WizServices/WizService.cs
  type IWizService (line 16) | public interface IWizService
    method GetLight (line 18) | Task<WizLight> GetLight();
    method SetColor (line 20) | Task SetColor(string availability, string activity, string lightId);
  class WizService (line 22) | public class WizService : IWizService
    method WizService (line 27) | public WizService(AppState appState, ILogger<WizService> logger)
    method WizService (line 33) | public WizService(AppState appState)
    method GetLight (line 38) | public async Task<WizLight> GetLight()
    method SetColor (line 78) | public async Task SetColor(string availability, string activity, strin...
    method Handle (line 166) | private async Task<(string color, WizParams command, bool returnFunc)>...
    method UpdateLight (line 218) | private async Task<WizResult> UpdateLight(WizParams wizParams, string ...

FILE: src/PresenceLight.Core/Lights/WorkingHoursServices/IsInWorkingHours/IsInWorkingHoursCommand.cs
  class IsInWorkingHoursCommand (line 6) | public class IsInWorkingHoursCommand : IRequest<bool>

FILE: src/PresenceLight.Core/Lights/WorkingHoursServices/IsInWorkingHours/IsInWorkingHoursHandler.cs
  class IsInWorkingHoursHandler (line 8) | internal class IsInWorkingHoursHandler : IRequestHandler<IsInWorkingHour...
    method IsInWorkingHoursHandler (line 11) | public IsInWorkingHoursHandler(IWorkingHoursService service)
    method Handle (line 16) | public async Task<bool> Handle(IsInWorkingHoursCommand command, Cancel...

FILE: src/PresenceLight.Core/Lights/WorkingHoursServices/UseWorkingHours/UseWorkingHoursCommand.cs
  class UseWorkingHoursCommand (line 6) | public class UseWorkingHoursCommand : IRequest<bool>

FILE: src/PresenceLight.Core/Lights/WorkingHoursServices/UseWorkingHours/UseWorkingHoursHandler.cs
  class UseWorkingHoursHandler (line 8) | internal class UseWorkingHoursHandler : IRequestHandler<UseWorkingHoursC...
    method UseWorkingHoursHandler (line 11) | public UseWorkingHoursHandler(IWorkingHoursService service)
    method Handle (line 16) | public async Task<bool> Handle(UseWorkingHoursCommand command, Cancell...

FILE: src/PresenceLight.Core/Lights/WorkingHoursServices/WorkingHoursService.cs
  type IWorkingHoursService (line 6) | public interface IWorkingHoursService
    method UseWorkingHours (line 8) | public bool UseWorkingHours();
    method IsInWorkingHours (line 10) | public bool IsInWorkingHours();
  class WorkingHoursService (line 13) | public class WorkingHoursService : IWorkingHoursService
    method WorkingHoursService (line 18) | public WorkingHoursService(AppState appState)
    method UseWorkingHours (line 27) | public bool UseWorkingHours()
    method IsInWorkingHours (line 33) | public bool IsInWorkingHours()

FILE: src/PresenceLight.Core/Lights/YeelightServices/FindLights/FindLightsCommand.cs
  class GetLightCommand (line 6) | public class GetLightCommand : IRequest<DeviceGroup>

FILE: src/PresenceLight.Core/Lights/YeelightServices/FindLights/FindLightsHandler.cs
  class GetLightsHandler (line 10) | internal class GetLightsHandler : IRequestHandler<GetLightCommand, Devic...
    method GetLightsHandler (line 13) | public GetLightsHandler(IYeelightService service)
    method Handle (line 18) | public async Task<DeviceGroup> Handle(GetLightCommand command, Cancell...

FILE: src/PresenceLight.Core/Lights/YeelightServices/SetColor/SetColorCommand.cs
  class SetColorCommand (line 7) | public class SetColorCommand : IRequest<Unit>

FILE: src/PresenceLight.Core/Lights/YeelightServices/SetColor/SetColorHandler.cs
  class SetColorHandler (line 8) | internal class SetColorHandler : IRequestHandler<SetColorCommand, Unit>
    method SetColorHandler (line 11) | public SetColorHandler(IYeelightService service)
    method Handle (line 16) | public async Task<Unit> Handle(SetColorCommand command, CancellationTo...

FILE: src/PresenceLight.Core/Lights/YeelightServices/YeelightService.cs
  type IYeelightService (line 11) | public interface IYeelightService
    method SetColor (line 13) | Task SetColor(string availability, string activity, string lightId);
    method FindLights (line 14) | Task<DeviceGroup> FindLights();
  class YeelightService (line 16) | public class YeelightService : IYeelightService
    method YeelightService (line 24) | public YeelightService(AppState appState, ILogger<YeelightService> log...
    method Initialize (line 31) | public void Initialize(AppState appState)
    method SetColor (line 36) | public async Task SetColor(string availability, string activity, strin...
    method Device_OnError (line 131) | private void Device_OnError(object sender, UnhandledExceptionEventArgs e)
    method Device_OnNotificationReceived (line 136) | private void Device_OnNotificationReceived(object sender, Notification...
    method FindLights (line 141) | public async Task<DeviceGroup> FindLights()
    method Handle (line 156) | private async Task<(string color, Device device, bool returnFunc)> Han...

FILE: src/PresenceLight.Core/Logging/ILoggerExtensions.cs
  class ILoggerExtensions (line 14) | public static class ILoggerExtensions
    method Log (line 29) | public static void Log(this ILogger logger,
    method Log (line 62) | public static void Log(this ILogger logger,
    method Log (line 92) | public static void Log(this ILogger logger,
    method Log (line 120) | public static void Log(this ILogger logger,
    method LogCritical (line 149) | public static void LogCritical(this ILogger logger,
    method LogCritical (line 178) | public static void LogCritical(this ILogger logger,
    method LogCritical (line 206) | public static void LogCritical(this ILogger logger,
    method LogCritical (line 234) | public static void LogCritical(this ILogger logger,
    method LogDebug (line 262) | public static void LogDebug(this ILogger logger,
    method LogDebug (line 291) | public static void LogDebug(this ILogger logger,
    method LogDebug (line 319) | public static void LogDebug(this ILogger logger,
    method LogDebug (line 346) | public static void LogDebug(this ILogger logger,
    method LogError (line 372) | public static void LogError(this ILogger logger,
    method LogError (line 417) | public static void LogError(this ILogger logger,
    method LogError (line 445) | public static void LogError(this ILogger logger,
    method LogError (line 474) | public static void LogError(this ILogger logger,
    method LogInformation (line 504) | public static void LogInformation(this ILogger logger,
    method LogInformation (line 533) | public static void LogInformation(this ILogger logger,
    method LogInformation (line 561) | public static void LogInformation(this ILogger logger,
    method LogInformation (line 588) | public static void LogInformation(this ILogger logger,
    method LogTrace (line 617) | public static void LogTrace(this ILogger logger,
    method LogTrace (line 646) | public static void LogTrace(this ILogger logger,
    method LogTrace (line 674) | public static void LogTrace(this ILogger logger,
    method LogTrace (line 701) | public static void LogTrace(this ILogger logger,
    method LogWarning (line 730) | public static void LogWarning(this ILogger logger,
    method LogWarning (line 759) | public static void LogWarning(this ILogger logger,
    method LogWarning (line 786) | public static void LogWarning(this ILogger logger,
    method LogWarning (line 813) | public static void LogWarning(this ILogger logger,

FILE: src/PresenceLight.Core/Logging/PresenceEventsLogSink.cs
  class PresenceEventsLogSink (line 17) | public class PresenceEventsLogSink : ILogEventSink
    method PresenceEventsLogSink (line 25) | public PresenceEventsLogSink(IFormatProvider formatProvider)
    method Emit (line 34) | public void Emit(LogEvent logEvent)
  class PresenceEventsLogSinkExtensions (line 49) | public static class PresenceEventsLogSinkExtensions
    method PresenceEventsLogSink (line 58) | public static LoggerConfiguration PresenceEventsLogSink(

FILE: src/PresenceLight.Razor/Services/AppInfo.cs
  class AppInfo (line 10) | public class AppInfo
    method AppInfo (line 13) | public AppInfo(IConfiguration Configuration)
    method GetInstallLocation (line 18) | public static string GetInstallLocation()
    method GetInstallationDate (line 23) | public static string GetInstallationDate()
    method GetApplicationVersion (line 29) | public string GetApplicationVersion()
    method GetDotNetRuntimeInfo (line 34) | public static string GetDotNetRuntimeInfo()
    method GetAppInstallType (line 39) | public string GetAppInstallType()

FILE: src/PresenceLight.Razor/Services/AppVersionTelemetryInitializer.cs
  class AppVersionTelemetryInitializer (line 8) | public class AppVersionTelemetryInitializer : ITelemetryInitializer
    method AppVersionTelemetryInitializer (line 12) | public AppVersionTelemetryInitializer(AppInfo appInfo)
    method Initialize (line 17) | public void Initialize(ITelemetry telemetry)

FILE: src/PresenceLight.Razor/Services/WebAppSettingsService.cs
  class WebAppSettingsService (line 15) | public class WebAppSettingsService : ISettingsService
    method WebAppSettingsService (line 19) | public WebAppSettingsService(IConfiguration configuration, AppState ap...
    method DeleteSettings (line 25) | public Task<bool> DeleteSettings()
    method IsFilePresent (line 34) | public async Task<bool> IsFilePresent()
    method LoadSettings (line 52) | public async Task<BaseConfig?> LoadSettings()
    method SaveSettings (line 60) | public async Task<bool> SaveSettings(BaseConfig data)
    method GetSettingsFileLocation (line 68) | public string GetSettingsFileLocation()

FILE: src/PresenceLight.Razor/wwwroot/js/site.js
  function saveAsFile (line 1) | function saveAsFile(filename, bytesBase64) {

FILE: src/PresenceLight.Web/Pages/Error.cshtml.cs
  class ErrorModel (line 12) | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoSt...
    method ErrorModel (line 22) | public ErrorModel(ILogger<ErrorModel> logger)
    method OnGet (line 27) | public void OnGet()

FILE: src/PresenceLight.Web/Program_New.cs
  class ProgramNew (line 23) | public static class ProgramNew
    method GetWebApplication (line 25) | public static WebApplication GetWebApplication(string[] args)

FILE: src/PresenceLight.Web/Program_Old.cs
  class ProgramOld (line 25) | public static class ProgramOld
    method GetWebApplication (line 27) | public static WebApplication GetWebApplication(string[] args) {

FILE: src/PresenceLight.Web/ServiceCollectionExtensions.cs
  class ServiceCollectionExtensions (line 19) | public static class ServiceCollectionExtensions
    method AddPresenceLight (line 21) | public static IServiceCollection AddPresenceLight(this IServiceCollect...

FILE: src/PresenceLight.Web/Worker.cs
  class Worker (line 11) | public class Worker : BackgroundService
    method Worker (line 19) | public Worker(ILogger<Worker> logger,
    method ExecuteAsync (line 32) | protected override async Task ExecuteAsync(CancellationToken stoppingT...
    method Run (line 57) | private async Task Run()
    method InteractWithLights (line 94) | private async Task InteractWithLights()
    method GetUserAndPresence (line 182) | private async Task<(User, Presence)> GetUserAndPresence()
    method GetPhotoAsBase64Async (line 198) | private async Task<string> GetPhotoAsBase64Async()
    method GetPresence (line 218) | public async Task<Presence> GetPresence()
    method SetColor (line 231) | private async Task SetColor(string color, string activity = null)
Condensed preview — 230 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (764K chars).
[
  {
    "path": ".gitattributes",
    "chars": 2518,
    "preview": "###############################################################################\n# Set default behavior to automatically "
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 21,
    "preview": "github: isaacrlevin\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 792,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 595,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 1674,
    "preview": "version: 2\nupdates:\n- package-ecosystem: nuget\n  directory: \"/src\"\n  schedule:\n    interval: daily\n    time: \"10:00\"\n  o"
  },
  {
    "path": ".github/workflows/Azure_Blob_Deploy.yml",
    "chars": 2956,
    "preview": "name: Deploy Worker Apps to Azure Blob Storage\n\non:\n  workflow_dispatch:\n    inputs:\n      target:\n        description: "
  },
  {
    "path": ".github/workflows/Choco.yml",
    "chars": 1715,
    "preview": "on:\n  workflow_call:\n\n  workflow_dispatch:\n    inputs:\n      version:\n        description: 'Version to deploy'\n        r"
  },
  {
    "path": ".github/workflows/Deploy_Desktop.yml",
    "chars": 14942,
    "preview": "on:\n  push:\n    branches: [ main ]\n    paths-ignore:\n    - '.github/workflows/Deploy_Web.yml'\n    - 'src/PresenceLight.W"
  },
  {
    "path": ".github/workflows/Deploy_Web.yml",
    "chars": 13475,
    "preview": "on:\n\n  push:\n    branches: [ main ]\n    paths-ignore:\n    - '.github/workflows/Deploy_Desktop.yml'\n    - 'src/DesktopCli"
  },
  {
    "path": ".github/workflows/Sign.yml",
    "chars": 2433,
    "preview": "name: Code Sign App\n\non:\n  workflow_dispatch:\n    inputs:\n      target:\n        description: 'Target of Channel Name to "
  },
  {
    "path": ".github/workflows/WinGet.yml",
    "chars": 1543,
    "preview": "name: Winget Publish\non:\n  workflow_dispatch:\n    inputs:\n      Version:\n        description: 'Release'\n        required"
  },
  {
    "path": ".gitignore",
    "chars": 7095,
    "preview": "\n# Created by https://www.gitignore.io/api/visualstudio,windows\n# Edit at https://www.gitignore.io/?templates=visualstud"
  },
  {
    "path": "Build/Signing/filelist.txt",
    "chars": 20,
    "preview": "**/PresenceLight.*\n\n"
  },
  {
    "path": "Build/Worker/presencelight.crt",
    "chars": 761,
    "preview": "-----BEGIN CERTIFICATE-----\nMIICBTCCAasCFBvttCTh9n9rqOSzRE1jFBdeRyRXMAoGCCqGSM49BAMCMIGEMQsw\nCQYDVQQGEwJVUzETMBEGA1UECAw"
  },
  {
    "path": "Build/Worker/presencelight.service",
    "chars": 501,
    "preview": "[Unit]\nDescription=PresenceLight is a solution to broadcast your various statuses to a Philips Hue or LIFX light bulb.\n\n"
  },
  {
    "path": "Build/Worker/trust-cert.sh",
    "chars": 139,
    "preview": "pk12util -d sql:$HOME/.pki/nssdb -i presencelight.pfx\n\ncertutil -d sql:$HOME/.pki/nssdb -A -t \"P,,\" -n 'presencelight' -"
  },
  {
    "path": "Build/scripts/push-choco.ps1",
    "chars": 3594,
    "preview": "Param\n(\n    [parameter(Mandatory = $true)]    [string]\n    $Version,\n    [parameter(Mandatory = $true)]    [string]\n    "
  },
  {
    "path": "Build/scripts/push-winget.ps1",
    "chars": 745,
    "preview": "\nParam\n(\n    [parameter(Mandatory = $true)]\n    [string]\n    $Version,\n    [parameter(Mandatory = $false)]\n    [string]\n"
  },
  {
    "path": "Build/scripts/update-desktop-settings.ps1",
    "chars": 2317,
    "preview": "Param\n(\n    [parameter(Mandatory = $true)]    [string]\n    $Release,\n\n    [parameter(Mandatory = $true)]    [string]\n   "
  },
  {
    "path": "Build/scripts/update-web-settings.ps1",
    "chars": 1842,
    "preview": "Param\n(\n    [parameter(Mandatory = $true)]    [string]\n    $Version,\n\n    [parameter(Mandatory = $true)]\n    [string]\n  "
  },
  {
    "path": "LICENSE",
    "chars": 1068,
    "preview": "MIT License\n\nCopyright (c) 2023 Isaac Levin\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
  },
  {
    "path": "PrivacyPolicy.md",
    "chars": 1628,
    "preview": "# Information Collected And Transmitted By PresenceLight\n\nFirst, a reminder: PresenceLight is provided \"as is\", without "
  },
  {
    "path": "README.md",
    "chars": 4939,
    "preview": "![Logo](https://github.com/isaacrlevin/PresenceLight/raw/main/Icon.png)\n# PresenceLight\n\n### NOTE: Due to internal chang"
  },
  {
    "path": "chocolatey/PresenceLight.nuspec",
    "chars": 1691,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<package xmlns=\"http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd\">\n  <me"
  },
  {
    "path": "chocolatey/tools/ChocolateyBeforeModify.ps1",
    "chars": 180,
    "preview": "# Make sure to kill any presencelight processes before attempting an\n# uninstall or upgrade of PresenceLight\nGet-Process"
  },
  {
    "path": "chocolatey/tools/ChocolateyInstall.ps1",
    "chars": 1703,
    "preview": "$ErrorActionPreference  = 'Stop';\n\n# Make sure to kill any presencelight processes before attempting an\n# installation. "
  },
  {
    "path": "chocolatey/tools/ChocolateyUninstall.ps1",
    "chars": 463,
    "preview": "# This logic can be removed after a couple of package version releases, since\n# this will be handled in the ChocolateyBe"
  },
  {
    "path": "chocolatey/tools/LICENSE.txt",
    "chars": 1068,
    "preview": "MIT License\n\nCopyright (c) 2023 Isaac Levin\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
  },
  {
    "path": "chocolatey/tools/Verification.txt",
    "chars": 629,
    "preview": "VERIFICATION\nVerification is intended to assist the Chocolatey moderators and community\nin verifying that this package's"
  },
  {
    "path": "docker-compose-example.yml",
    "chars": 509,
    "preview": "version: '3.7'\n\nservices:\n  presencelight:\n    image: isaaclevin/presencelight:latest\n    container_name: presencelight\n"
  },
  {
    "path": "docs/CONTRIBUTING.md",
    "chars": 8496,
    "preview": "# Contributing to PresenceLight\n\nThank you for your interest in contributing to the PresenceLight project! We welcome co"
  },
  {
    "path": "docs/configure-custom-api.md",
    "chars": 3197,
    "preview": "# Custom API\n\nThe Custom API page lets you use any generic service which has a web API which accepts GET or POST request"
  },
  {
    "path": "docs/configure-entra-app.md",
    "chars": 2450,
    "preview": "\n## Configure an Entra ID Application\n\n1. Sign in to the [Microsoft Entra admin center](https://entra.microsoft.com/) us"
  },
  {
    "path": "docs/configure-hardware.md",
    "chars": 2092,
    "preview": "## Hue HW Notes\n\n### Hue Hardware Requirements\n\n| Item  |\n| ------------ |\n| [Philips Hue Bridge](https://www2.meethue.c"
  },
  {
    "path": "docs/desktop-README.md",
    "chars": 3825,
    "preview": "![Logo](https://github.com/isaacrlevin/PresenceLight/raw/main/Icon.png)\n# PresenceLight - Desktop Version\n\n![.github/wor"
  },
  {
    "path": "docs/faq.md",
    "chars": 1033,
    "preview": "### What is the best version to install?\nIt really depends on your workflow, for normal users, I would use the Microsoft"
  },
  {
    "path": "docs/web-README.md",
    "chars": 6459,
    "preview": "![Logo](Icon.png)\n# PresenceLight - Web Version\n![.github/workflows/Deploy_Web.yml](https://github.com/isaacrlevin/prese"
  },
  {
    "path": "src/.editorconfig",
    "chars": 13362,
    "preview": "# EditorConfig is awesome:http://EditorConfig.org\n# From https://raw.githubusercontent.com/dotnet/roslyn/master/.editorc"
  },
  {
    "path": "src/DesktopClient/Directory.Build.props",
    "chars": 1794,
    "preview": "<Project>\n\n  <PropertyGroup>\n    <Authors>Isaac Levin</Authors>\n    <Copyright>© 2023 Isaac Levin</Copyright>\n    <Gener"
  },
  {
    "path": "src/DesktopClient/Directory.Build.targets",
    "chars": 947,
    "preview": "<Project>\n\n  <Target Name=\"AddCommitHashToAssemblyAttributes\" BeforeTargets=\"GetAssemblyAttributes\">\n    <ItemGroup>\n   "
  },
  {
    "path": "src/DesktopClient/PresenceLight/App.xaml",
    "chars": 360,
    "preview": "<Application x:Class=\"PresenceLight.App\"\n             xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\""
  },
  {
    "path": "src/DesktopClient/PresenceLight/App.xaml.cs",
    "chars": 7045,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Globalization;\nusing System.IO;\n"
  },
  {
    "path": "src/DesktopClient/PresenceLight/MainWindow.xaml",
    "chars": 1862,
    "preview": "<Window x:Class=\"PresenceLight.MainWindow\"\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n  "
  },
  {
    "path": "src/DesktopClient/PresenceLight/MainWindow.xaml.cs",
    "chars": 25460,
    "preview": "using System;\nusing System.IO;\nusing System.Linq.Expressions;\nusing System.Threading;\nusing System.Threading.Tasks;\nusi"
  },
  {
    "path": "src/DesktopClient/PresenceLight/PresenceLight.csproj",
    "chars": 4933,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk.Razor\">\n\n  <PropertyGroup>\n    <TargetFramework>net10.0-windows10.0.19041</TargetFramew"
  },
  {
    "path": "src/DesktopClient/PresenceLight/PresenceLight.exe.gui",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/AssemblyInfo.cs",
    "chars": 594,
    "preview": "using System.Windows;\n\n[assembly:ThemeInfo(\n    ResourceDictionaryLocation.None, //where theme specific resource diction"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/PublishProfiles/WinARM64.pubxml",
    "chars": 713,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\nhttps://go.microsoft.com/fwlink/?LinkID=208121.\n-->\n<Project ToolsVersion=\""
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/PublishProfiles/WinX64.pubxml",
    "chars": 709,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\nhttps://go.microsoft.com/fwlink/?LinkID=208121.\n-->\n<Project ToolsVersion=\""
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/PublishProfiles/WinX86.pubxml",
    "chars": 708,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\nhttps://go.microsoft.com/fwlink/?LinkID=208121.\n-->\n<Project ToolsVersion=\"4"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/Resources.Designer.cs",
    "chars": 5442,
    "preview": "//------------------------------------------------------------------------------\n// <auto-generated>\n//     This code w"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/Resources.resx",
    "chars": 7486,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The prim"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/Settings.Designer.cs",
    "chars": 1068,
    "preview": "//------------------------------------------------------------------------------\n// <auto-generated>\n//     This code w"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/Settings.settings",
    "chars": 193,
    "preview": "<?xml version='1.0' encoding='utf-8'?>\n<SettingsFile xmlns=\"uri:settings\" CurrentProfile=\"(Default)\">\n  <Profiles>\n    "
  },
  {
    "path": "src/DesktopClient/PresenceLight/Properties/app.manifest",
    "chars": 3870,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<asmv1:assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\" x"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Services/Constants.cs",
    "chars": 1746,
    "preview": "\nusing System;\nusing System.Reflection;\n\nnamespace PresenceLight\n{\n    public static class PresenceConstants\n    {\n    "
  },
  {
    "path": "src/DesktopClient/PresenceLight/Services/MessageBoxHelper.cs",
    "chars": 3405,
    "preview": "using System;\nusing System.Runtime.InteropServices;\nusing System.Windows;\nusing System.Windows.Interop;\nnamespace Prese"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Services/NotifyIcon.cs",
    "chars": 5808,
    "preview": "using System;\nusing System.ComponentModel;\nusing System.Windows;\nusing System.Windows.Input;\nusing System.Windows.Marku"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Services/Settings/AppPackageSettingsService.cs",
    "chars": 4550,
    "preview": "using System;\nusing Newtonsoft.Json;\nusing PresenceLight.Core;\nusing PresenceLight.Telemetry;\nusing System.Threading.Ta"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Services/Settings/StandaloneSettingsService.cs",
    "chars": 3397,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusi"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Services/SingleInstanceAppMutex.cs",
    "chars": 1007,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Reflection;\nu"
  },
  {
    "path": "src/DesktopClient/PresenceLight/Services/Telemetry/DiagnosticsClient.cs",
    "chars": 1911,
    "preview": "using System;\nusing System.Collections.Generic;\n\nusing Microsoft.ApplicationInsights;\nusing Microsoft.ApplicationInsigh"
  },
  {
    "path": "src/DesktopClient/PresenceLight/appsettings.json",
    "chars": 17789,
    "preview": "{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft\": \"Information\"\n    }\n  },\n  \"StartM"
  },
  {
    "path": "src/DesktopClient/PresenceLight/wwwroot/index.html",
    "chars": 2529,
    "preview": "<!DOCTYPE html>\n<html>\n\n<head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, init"
  },
  {
    "path": "src/DesktopClient/PresenceLight.Package/Package-Local.appxmanifest",
    "chars": 2509,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<Package\n  xmlns=\"http://schemas.microsoft.com/appx/manifest/foundation/windows"
  },
  {
    "path": "src/DesktopClient/PresenceLight.Package/Package-Nightly.appxmanifest",
    "chars": 2508,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<Package\n  xmlns=\"http://schemas.microsoft.com/appx/manifest/foundation/windows"
  },
  {
    "path": "src/DesktopClient/PresenceLight.Package/Package.appinstaller",
    "chars": 460,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<AppInstaller xmlns=\"http://schemas.microsoft.com/appx/appinstaller/2017/2\"\n     "
  },
  {
    "path": "src/DesktopClient/PresenceLight.Package/Package.appxmanifest",
    "chars": 2721,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<Package\n  xmlns=\"http://schemas.microsoft.com/appx/manifest/foundation/windows"
  },
  {
    "path": "src/DesktopClient/PresenceLight.Package/Package.xml",
    "chars": 22153,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<StoreAssociation xmlns=\"http://schemas.microsoft.com/appx/2010/storeassociation"
  },
  {
    "path": "src/DesktopClient/PresenceLight.Package/PresenceLight.Package.wapproj",
    "chars": 5756,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" DefaultTargets=\"Build\" xmlns=\"http://schemas.microso"
  },
  {
    "path": "src/DockerCompose/.dockerignore",
    "chars": 316,
    "preview": "**/.classpath\n**/.dockerignore\n**/.env\n**/.git\n**/.gitignore\n**/.project\n**/.settings\n**/.toolstarget\n**/.vs\n**/.vscode\n"
  },
  {
    "path": "src/DockerCompose/docker-compose.dcproj",
    "chars": 791,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" Sdk=\"Microsoft.Docker.Sdk\">\n  <PropertyGroup Label=\""
  },
  {
    "path": "src/DockerCompose/docker-compose.override.yml",
    "chars": 346,
    "preview": "version: '3.4'\n\nservices:\n  presencelight.web:\n    environment:\n      - ASPNETCORE_ENVIRONMENT=Development\n      - ASPNE"
  },
  {
    "path": "src/DockerCompose/docker-compose.yml",
    "chars": 170,
    "preview": "version: '3.4'\n\nservices:\n  presencelight.web:\n    image: ${DOCKER_REGISTRY-}presencelightweb\n    build:\n      context: "
  },
  {
    "path": "src/PresenceLight.Core/Configuration/AAD.cs",
    "chars": 1583,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.ComponentModel.DataAnnotations;\nusing System.Text.Json.Ser"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/AppState.cs",
    "chars": 8016,
    "preview": "using System;\nusing System.Collections.Generic;\n\nusing Microsoft.Graph;\nusing Microsoft.Graph.Models;\n\nusing PresenceLi"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/AvailabilityStatus.cs",
    "chars": 519,
    "preview": "namespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the availability status configuration.\n    /// </su"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/Base.cs",
    "chars": 969,
    "preview": "namespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the base configuration for the application.\n    ///"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/BaseLight.cs",
    "chars": 963,
    "preview": "namespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents a base light configuration.\n    /// </summary>\n    "
  },
  {
    "path": "src/PresenceLight.Core/Configuration/CustomApi.cs",
    "chars": 5489,
    "preview": "namespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the configuration settings for a custom API.\n    //"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/CustomApiSetting.cs",
    "chars": 581,
    "preview": "namespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the settings for a custom API.\n    /// </summary>\n "
  },
  {
    "path": "src/PresenceLight.Core/Configuration/Hue.cs",
    "chars": 1538,
    "preview": "using System.ComponentModel.DataAnnotations;\n\nusing Newtonsoft.Json;\n\nnamespace PresenceLight.Core\n{\n    /// <summary>\n"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/ISettingsService.cs",
    "chars": 1564,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nus"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/LIFX.cs",
    "chars": 597,
    "preview": "namespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the configuration for LIFX lights.\n    /// </summar"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/LightSettings.cs",
    "chars": 3393,
    "preview": "using System;\n\nusing Newtonsoft.Json;\n\nnamespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the settings"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/LocalSerialHost.cs",
    "chars": 4170,
    "preview": "namespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the configuration settings for a local serial host."
  },
  {
    "path": "src/PresenceLight.Core/Configuration/LocalSerialHostSetting.cs",
    "chars": 843,
    "preview": "namespace PresenceLight.Core\n{\n    /// <summary>\n    /// Represents the settings for a local serial host.\n    /// </sum"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/Statuses.cs",
    "chars": 5114,
    "preview": "using System;\nusing System.ComponentModel.DataAnnotations;\nusing System.Text.Json.Serialization;\n\nusing Newtonsoft.Json"
  },
  {
    "path": "src/PresenceLight.Core/Configuration/Wiz.cs",
    "chars": 303,
    "preview": "using System.ComponentModel.DataAnnotations;\n\nnamespace PresenceLight.Core\n{\n    public class Wiz : BaseLight\n    {\n   "
  },
  {
    "path": "src/PresenceLight.Core/Configuration/Yeelight.cs",
    "chars": 138,
    "preview": "using System;\nusing System.Text.Json.Serialization;\n\nnamespace PresenceLight.Core\n{\n\n    public class Yeelight : BaseLi"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/AuthorizationProvider.cs",
    "chars": 10886,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Net.Http;\nusing System.Net.Http.Headers"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetIsInitialized/GetIsInitializedCommand.cs",
    "chars": 148,
    "preview": "using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    public class GetIsInitializedCommand : I"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetIsInitialized/GetIsInitializedHandler.cs",
    "chars": 568,
    "preview": "using MediatR;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.GraphS"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetPhoto/GetPhotoCommand.cs",
    "chars": 145,
    "preview": "using MediatR;\nusing System.IO;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    public class GetPhotoCommand : IReque"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetPhoto/GetPhotoHandler.cs",
    "chars": 581,
    "preview": "using MediatR;\nusing Microsoft.Graph;\nusing Polly.Retry;\nusing System.IO;\nusing System.Threading;\nusing System.Threadin"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetPresence/GetPresenceCommand.cs",
    "chars": 163,
    "preview": "using MediatR;\nusing Microsoft.Graph.Models;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    public class GetPresence"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetPresence/GetPresenceHandler.cs",
    "chars": 592,
    "preview": "using MediatR;\n\nusing Microsoft.Graph.Models;\n\nusing Polly.Retry;\nusing System.Threading;\nusing System.Threading.Tasks;"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetProfile/GetProfileCommand.cs",
    "chars": 159,
    "preview": "using MediatR;\n\nusing Microsoft.Graph.Models;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    public class GetProfile"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetProfile/GetProfileHandler.cs",
    "chars": 581,
    "preview": "using MediatR;\n\nusing Microsoft.Graph.Models;\n\nusing Polly.Retry;\nusing System.Threading;\nusing System.Threading.Tasks;"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetProfileAndPresence/GetProfileAndPresenceCommand.cs",
    "chars": 211,
    "preview": "using MediatR;\n\nusing Microsoft.Graph.Models;\n\nusing System;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    public c"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GetProfileAndPresence/GetProfileAndPresenceHandler.cs",
    "chars": 713,
    "preview": "using MediatR;\n\nusing Microsoft.Graph.Models;\n\nusing Polly;\nusing Polly.Retry;\nusing System;\nusing System.Threading;\nus"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/GraphWrapper.cs",
    "chars": 4621,
    "preview": "using System;\nusing System.IO;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nusing Microsoft.Extensions.Loggin"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/Initialize/InitializeCommand.cs",
    "chars": 220,
    "preview": "using MediatR;\n\nusing Microsoft.Graph;\n\nusing System;\n\nnamespace PresenceLight.Core.GraphServices\n{\n    public class In"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/Initialize/InitializeHandler.cs",
    "chars": 559,
    "preview": "using System.Threading;\nusing System.Threading.Tasks;\n\nusing MediatR;\n\nnamespace PresenceLight.Core.GraphServices\n{\n   "
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/LoginService.cs",
    "chars": 4873,
    "preview": "using System;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nusing Microsoft.Extensions.Logging;\nusing Microsoft.Ext"
  },
  {
    "path": "src/PresenceLight.Core/GraphServices/TokenCacheHelper.cs",
    "chars": 2325,
    "preview": "using System;\nusing System.IO;\nusing System.Security.Cryptography;\nusing Microsoft.Identity.Client;\n\nnamespace Presence"
  },
  {
    "path": "src/PresenceLight.Core/Helpers.cs",
    "chars": 4512,
    "preview": "using System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/CustomApiService/CustomApiService.cs",
    "chars": 16305,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Net.Http;\nusing System.Net.Http.Headers;\nusing System.Text"
  },
  {
    "path": "src/PresenceLight.Core/Lights/CustomApiService/Initialize/InitializeCommand.cs",
    "chars": 182,
    "preview": "using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.Initialize\n{\n    public class InitializeCommand : IRequest\n "
  },
  {
    "path": "src/PresenceLight.Core/Lights/CustomApiService/Initialize/InitializeHandler.cs",
    "chars": 648,
    "preview": "using MediatR;\n\nusing PresenceLight.Core;\n\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nusing Y"
  },
  {
    "path": "src/PresenceLight.Core/Lights/CustomApiService/SetColor/SetColorCommand.cs",
    "chars": 240,
    "preview": "using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.CustomApiServices\n{\n    public class SetColorCommand : IRequ"
  },
  {
    "path": "src/PresenceLight.Core/Lights/CustomApiService/SetColor/SetColorHandler.cs",
    "chars": 634,
    "preview": "using MediatR;\nusing PresenceLight.Core;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/FindBridge/FindBridgeCommand.cs",
    "chars": 142,
    "preview": "using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.HueServices\n{\n    public class FindBridgeCommand : IRequest<"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/FindBridge/FindBridgeHandler.cs",
    "chars": 538,
    "preview": "using MediatR;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.HueSer"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/GetGroups/GetGroupsCommand.cs",
    "chars": 202,
    "preview": "using HueApi.Models;\n\nusing MediatR;\nusing System.Collections.Generic;\n\nnamespace PresenceLight.Core.HueServices\n{\n    "
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/GetGroups/GetGroupsHandler.cs",
    "chars": 615,
    "preview": "using System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nusing HueApi.Models;\n\nusing Me"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/GetLights/GetLightsCommand.cs",
    "chars": 194,
    "preview": "using MediatR;\nusing HueApi.Models;\nusing System.Collections.Generic;\n\nnamespace PresenceLight.Core.HueServices\n{\n    p"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/GetLights/GetLightsHandler.cs",
    "chars": 598,
    "preview": "using MediatR;\nusing HueApi.Models;\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Ta"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/HueService.cs",
    "chars": 11252,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\n\nusing HueApi;\nusing H"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/Initialize/InitializeCommand.cs",
    "chars": 182,
    "preview": "using MediatR;\n\nusing System;\n\nnamespace PresenceLight.Core.HueServices\n{\n    public class InitializeCommand : IRequest"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/Initialize/InitializeHandler.cs",
    "chars": 622,
    "preview": "using MediatR;\n\nusing PresenceLight.Core;\n\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespa"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/RegisterBridge/RegisterBridgeCommand.cs",
    "chars": 146,
    "preview": "using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.HueServices\n{\n    public class RegisterBridgeCommand : IRequ"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/RegisterBridge/RegisterBridgeHandler.cs",
    "chars": 558,
    "preview": "using MediatR;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.HueSer"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/SetColor/SetColorCommand.cs",
    "chars": 293,
    "preview": "using MediatR;\n\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.HueServices\n{\n    public class SetColorComm"
  },
  {
    "path": "src/PresenceLight.Core/Lights/HueServices/SetColor/SetColorHandler.cs",
    "chars": 599,
    "preview": "using MediatR;\n\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.HueServices.HueServ"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/GetAllGroups/GetAllGroupsCommand.cs",
    "chars": 243,
    "preview": "using LifxCloud.NET.Models;\n\nusing MediatR;\n\nusing System.Collections.Generic;\n\nnamespace PresenceLight.Core.LifxServic"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/GetAllGroups/GetAllGroupsHandler.cs",
    "chars": 700,
    "preview": "using LifxCloud.NET;\nusing LifxCloud.NET.Models;\nusing MediatR;\nusing Microsoft.Extensions.Logging;\nusing PresenceLight"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/GetAllLights/GetAllLightsCommand.cs",
    "chars": 243,
    "preview": "using LifxCloud.NET.Models;\n\nusing MediatR;\n\nusing System.Collections.Generic;\n\nnamespace PresenceLight.Core.LifxServic"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/GetAllLights/GetAllLightsHandler.cs",
    "chars": 700,
    "preview": "using LifxCloud.NET;\nusing LifxCloud.NET.Models;\nusing MediatR;\nusing Microsoft.Extensions.Logging;\nusing PresenceLight"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/Initialize/InitializeCommand.cs",
    "chars": 182,
    "preview": "using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.LifxServices\n{\n    public class InitializeCommand : IRequest"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/Initialize/InitializeHandler.cs",
    "chars": 643,
    "preview": "using MediatR;\n\nusing PresenceLight.Core;\n\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nusing Y"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/LIFXOAuthHelper.cs",
    "chars": 5904,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Net.Http;\nusing System.Security.Cryptogra"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/LifxService.cs",
    "chars": 8613,
    "preview": "using System;\nusing LifxCloud.NET;\nusing LifxCloud.NET.Models;\nusing System.Collections.Generic;\nusing System.Threading"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/SetColor/SetColorCommand.cs",
    "chars": 338,
    "preview": "using MediatR;\n\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.LifxServices\n{\n    public class SetColorCom"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LifxServices/SetColor/SetColorHandler.cs",
    "chars": 686,
    "preview": "using LifxCloud.NET;\n\nusing MediatR;\n\nusing Microsoft.Extensions.Logging;\n\nusing PresenceLight.Core;\n\nusing System.Thre"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LocalSerialHostService/GetSerialHosts/GetSerialHostsCommand.cs",
    "chars": 183,
    "preview": "using MediatR;\nusing System.Collections.Generic;\n\nnamespace PresenceLight.Core.LocalSerialHostServices\n{\n    public clas"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LocalSerialHostService/GetSerialHosts/GetSerialHostsHandler.cs",
    "chars": 623,
    "preview": "using MediatR;\n\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace Prese"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LocalSerialHostService/Initialize/InitializeCommand.cs",
    "chars": 195,
    "preview": "using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.LocalSerialHostServices\n{\n    public class InitializeCommand"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LocalSerialHostService/Initialize/InitializeHandler.cs",
    "chars": 686,
    "preview": "using MediatR;\n\nusing PresenceLight.Core;\n\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nusing Y"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LocalSerialHostService/LocalSerialHost.cs",
    "chars": 13412,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.IO.Ports;\nusing System.Net.Http;\nusing System.Net.Http.Hea"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LocalSerialHostService/SetColor/SetColorCommand.cs",
    "chars": 246,
    "preview": "using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.LocalSerialHostServices\n{\n    public class SetColorCommand :"
  },
  {
    "path": "src/PresenceLight.Core/Lights/LocalSerialHostService/SetColor/SetColorHandler.cs",
    "chars": 652,
    "preview": "using MediatR;\nusing PresenceLight.Core;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/GetGroups/GetGroupsCommand.cs",
    "chars": 207,
    "preview": "using MediatR;\nusing HueApi.Models;\nusing System.Collections.Generic;\n\nnamespace PresenceLight.Core.RemoteHueServices\n{"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/GetGroups/GetGroupsHandler.cs",
    "chars": 635,
    "preview": "using MediatR;\nusing HueApi.Models;\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Ta"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/GetLights/GetLightsCommand.cs",
    "chars": 200,
    "preview": "using MediatR;\nusing HueApi.Models;\nusing System.Collections.Generic;\n\nnamespace PresenceLight.Core.RemoteHueServices\n{"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/GetLights/GetLightsHandler.cs",
    "chars": 621,
    "preview": "using MediatR;\nusing HueApi.Models;\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Ta"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/RegisterBridge/RegisterBridgeCommand.cs",
    "chars": 195,
    "preview": "using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.RemoteHueServices\n{\n    public class RegisterBridgeCommand :"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/RegisterBridge/RegisterBridgeHandler.cs",
    "chars": 667,
    "preview": "using MediatR;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.Remote"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/RemoteAuthenticationClient.cs",
    "chars": 12202,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Net.Http;\nusing System.Threading.Tasks;\n\nusing JeffWilcox."
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/RemoteHueService.cs",
    "chars": 17706,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Linq;\nusing Sys"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/SetColor/SetColorCommand.cs",
    "chars": 343,
    "preview": "using MediatR;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.RemoteHueServices\n{\n    public class SetColo"
  },
  {
    "path": "src/PresenceLight.Core/Lights/RemoteHueServices/SetColor/SetColorHandler.cs",
    "chars": 628,
    "preview": "using MediatR;\n\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.RemoteHueServices\n{"
  },
  {
    "path": "src/PresenceLight.Core/Lights/ServicesExtensions.cs",
    "chars": 921,
    "preview": "using Microsoft.Extensions.DependencyInjection;\n\nnamespace PresenceLight.Core\n{\n    public static class ServicesExtensi"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WizServices/GetLights/GetLightCommand.cs",
    "chars": 179,
    "preview": "using System.Collections.Generic;\n\nusing MediatR;\n\nusing OpenWiz;\n\nnamespace PresenceLight.Core.WizServices\n{\n    publi"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WizServices/GetLights/GetLightHandler.cs",
    "chars": 563,
    "preview": "using MediatR;\n\nusing OpenWiz;\n\nusing System.Collections.Generic;\nusing System.Threading;\nusing System.Threading.Tasks;"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WizServices/IPAddressExtensions.cs",
    "chars": 5822,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Net;\nusing System.Net.NetworkInformatio"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WizServices/SetColor/SetColorCommand.cs",
    "chars": 293,
    "preview": "using MediatR;\n\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.WizServices\n{\n    public class SetColorComm"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WizServices/SetColor/SetColorHandler.cs",
    "chars": 599,
    "preview": "using MediatR;\n\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.WizServices.WizServ"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WizServices/WizLight.cs",
    "chars": 294,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nna"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WizServices/WizService.cs",
    "chars": 8173,
    "preview": "using System;\nusing System.Linq;\nusing System.Net;\nusing System.Threading.Tasks;\n\nusing Microsoft.Extensions.Logging;\n\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WorkingHoursServices/IsInWorkingHours/IsInWorkingHoursCommand.cs",
    "chars": 155,
    "preview": "using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.WorkingHoursServices\n{\n    public class IsInWorkingHoursComm"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WorkingHoursServices/IsInWorkingHours/IsInWorkingHoursHandler.cs",
    "chars": 618,
    "preview": "using MediatR;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.Workin"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WorkingHoursServices/UseWorkingHours/UseWorkingHoursCommand.cs",
    "chars": 154,
    "preview": "using MediatR;\nusing System;\n\nnamespace PresenceLight.Core.WorkingHoursServices\n{\n    public class UseWorkingHoursComma"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WorkingHoursServices/UseWorkingHours/UseWorkingHoursHandler.cs",
    "chars": 613,
    "preview": "using MediatR;\nusing System;\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.Workin"
  },
  {
    "path": "src/PresenceLight.Core/Lights/WorkingHoursServices/WorkingHoursService.cs",
    "chars": 2284,
    "preview": "using System;\nusing System.Globalization;\n\nnamespace PresenceLight.Core\n{\n    public interface IWorkingHoursService\n   "
  },
  {
    "path": "src/PresenceLight.Core/Lights/YeelightServices/FindLights/FindLightsCommand.cs",
    "chars": 155,
    "preview": "using MediatR;\nusing YeelightAPI;\n\nnamespace PresenceLight.Core.YeelightServices\n{\n    public class GetLightCommand : I"
  },
  {
    "path": "src/PresenceLight.Core/Lights/YeelightServices/FindLights/FindLightsHandler.cs",
    "chars": 560,
    "preview": "using MediatR;\n\nusing System.Threading;\nusing System.Threading.Tasks;\n\nusing YeelightAPI;\n\nnamespace PresenceLight.Core"
  },
  {
    "path": "src/PresenceLight.Core/Lights/YeelightServices/SetColor/SetColorCommand.cs",
    "chars": 298,
    "preview": "using MediatR;\n\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.YeelightServices\n{\n    public class SetColo"
  },
  {
    "path": "src/PresenceLight.Core/Lights/YeelightServices/SetColor/SetColorHandler.cs",
    "chars": 598,
    "preview": "using MediatR;\n\nusing System.Threading;\nusing System.Threading.Tasks;\n\nnamespace PresenceLight.Core.YeelightServices\n{\n"
  },
  {
    "path": "src/PresenceLight.Core/Lights/YeelightServices/YeelightService.cs",
    "chars": 7352,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing YeelightAPI;\nusi"
  },
  {
    "path": "src/PresenceLight.Core/Logging/ILoggerExtensions.cs",
    "chars": 48008,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Runtime.CompilerServices;\nusing System."
  },
  {
    "path": "src/PresenceLight.Core/Logging/PresenceEventsLogSink.cs",
    "chars": 2273,
    "preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nus"
  },
  {
    "path": "src/PresenceLight.Core/PresenceLight.Core.csproj",
    "chars": 2900,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n    <LangVersion>la"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Layout/MainLayout.razor",
    "chars": 740,
    "preview": "@inherits LayoutComponentBase\n\n<MudThemeProvider />\n<MudPopoverProvider />\n<MudDialogProvider />\n<MudSnackbarProvider /"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Layout/MainLayout.razor.css",
    "chars": 1187,
    "preview": ".page {\n    position: relative;\n    display: flex;\n    flex-direction: column;\n}\n\nmain {\n    flex: 1;\n}\n\n.sidebar {\n    "
  },
  {
    "path": "src/PresenceLight.Razor/Components/Layout/NavMenu.razor",
    "chars": 2011,
    "preview": "<MudNavMenu>\n    <MudNavLink Href=\"\" Match=\"NavLinkMatch.All\" Icon=\"@Icons.Material.Filled.Person\">Teams Status</MudNav"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Layout/NavMenu.razor.css",
    "chars": 1033,
    "preview": ".navbar-toggler {\n    background-color: rgba(255, 255, 255, 0.1);\n}\n\n.top-row {\n    height: 3.5rem;\n    background-color"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/About.razor",
    "chars": 3719,
    "preview": "@page \"/about\"\n@inject IJSRuntime js\n@inject AppInfo _appInfo\n\n\n<MudPaper Height=\"500px\" Width=\"100%\" Elevation=\"0\">\n  "
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Color.razor",
    "chars": 5821,
    "preview": "@page \"/color\"\n\n@using LifxCloud.NET.Models\n@inject ILogger<Color> _logger;\n\n<MudPaper Height=\"500px\" Width=\"100%\" Elev"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Color.razor.css",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/CustomApiSetup.razor",
    "chars": 9102,
    "preview": "@page \"/custom\"\n\n@using PresenceLight.Core.Initialize;\n@inject ILogger<CustomApiSetup> _logger;\n\n<MudPaper Width=\"100%\""
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/HueSetup.razor",
    "chars": 17564,
    "preview": "@page \"/hue\"\n\n@inject ILogger<HueSetup> _logger;\n@inject IDialogService DialogService;\n\n<MudPaper Width=\"100%\" Elevatio"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Index.razor",
    "chars": 3954,
    "preview": "@page \"/\"\n\n@using Microsoft.Identity.Web\n@using Microsoft.Graph\n@inject ILogger<Index> _logger;\n@inject LoginService _l"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Index.razor.css",
    "chars": 662,
    "preview": ".Available {\n    background-color: green;\n}\n\n.AvailableIdle {\n    background-color: yellow;\n}\n\n.Away {\n    background-co"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Lifx.razor",
    "chars": 11115,
    "preview": "@page \"/lifx\"\n\n@inject ILogger<Lifx> _logger;\n@inject LIFXOAuthHelper _lIFXOAuthHelper;\n\n<MudPaper Width=\"100%\" Elevati"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/LocalSerialHostSetup.razor",
    "chars": 11200,
    "preview": "@page \"/serial\"\n\n@using PresenceLight.Core.LocalSerialHostServices;\n@inject ILogger<LocalSerialHostSetup> _logger;\n\n<Mu"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Logs.razor",
    "chars": 6999,
    "preview": "@page \"/logs\"\n@using System.IO\n\n@inject ILogger<Logs> _logger\n@inject IJSRuntime js\n@inject Microsoft.Extensions.Config"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Settings.razor",
    "chars": 15171,
    "preview": "@page \"/settings\"\n\n@inject IJSRuntime js\n@inject ILogger<Settings> _logger;\n\n<MudPaper Width=\"100%\" Elevation=\"0\">\n    "
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Wiz.razor",
    "chars": 6495,
    "preview": "@page \"/wiz\"\n\n\n@inject ILogger<Wiz> _logger;\n\n<MudPaper Width=\"100%\" Elevation=\"0\">\n    <MudContainer MaxWidth=\"MaxWidt"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Pages/Yeelight.razor",
    "chars": 5697,
    "preview": "@page \"/yeelight\"\n\n@inject ILogger<CustomApiSetup> _logger;\n\n<MudPaper Width=\"100%\" Elevation=\"0\">\n    <MudContainer Ma"
  },
  {
    "path": "src/PresenceLight.Razor/Components/PresenceLightClientApp.razor",
    "chars": 185,
    "preview": "<Router AppAssembly=\"@typeof(AppInfo).Assembly\">\n    <Found Context=\"routeData\">\n        <RouteView RouteData=\"@routeDa"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Shared/Confirm.razor",
    "chars": 301,
    "preview": "<MudDialog>\n    <DialogActions>\n        <MudButton Color=\"MudBlazor.Color.Primary\" OnClick=\"Submit\">Ok</MudButton>\n    "
  },
  {
    "path": "src/PresenceLight.Razor/Components/Shared/LoginDisplay.razor",
    "chars": 2825,
    "preview": "@inject AppState appState\n@inject NavigationManager NavManager\n\n@if (appState.Config.AppType == \"Desktop\")\n{\n    @if (a"
  },
  {
    "path": "src/PresenceLight.Razor/Components/Shared/Statuses.razor",
    "chars": 2309,
    "preview": "<br />\n<br />\n<MudExpansionPanels>\n    <MudExpansionPanel>\n        <TitleContent>\n            <MudText Typo=\"Typo.h6\" C"
  },
  {
    "path": "src/PresenceLight.Razor/Components/_Imports.razor",
    "chars": 1021,
    "preview": "@using System.Net.Http\n@using Microsoft.AspNetCore.Authorization\n@using Microsoft.AspNetCore.Components.Authorization\n@"
  },
  {
    "path": "src/PresenceLight.Razor/PresenceLight.Razor.csproj",
    "chars": 2166,
    "preview": "<Project Sdk=\"Microsoft.NET.Sdk.Razor\">\n\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n  </PropertyG"
  },
  {
    "path": "src/PresenceLight.Razor/Services/AppInfo.cs",
    "chars": 1570,
    "preview": "using System;\nusing System.Diagnostics;\nusing System.Globalization;\nusing System.Reflection;\n\nusing Microsoft.Extension"
  },
  {
    "path": "src/PresenceLight.Razor/Services/AppVersionTelemetryInitializer.cs",
    "chars": 769,
    "preview": "using System.Diagnostics;\n\nusing Microsoft.ApplicationInsights.Channel;\nusing Microsoft.ApplicationInsights.Extensibili"
  },
  {
    "path": "src/PresenceLight.Razor/Services/WebAppSettingsService.cs",
    "chars": 2613,
    "preview": "using System;\nusing System.Diagnostics;\nusing System.IO;\nusing System.Text;\nusing System.Threading.Tasks;\n\nusing Micros"
  },
  {
    "path": "src/PresenceLight.Razor/wwwroot/css/open-iconic/FONT-LICENSE",
    "chars": 4017,
    "preview": "SIL OPEN FONT LICENSE Version 1.1\n\nCopyright (c) 2014 Waybury\n\nPREAMBLE\nThe goals of the Open Font License (OFL) are to "
  }
]

// ... and 30 more files (download for full content)

About this extraction

This page contains the full source code of the isaacrlevin/PresenceLight GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 230 files (696.5 KB), approximately 162.7k tokens, and a symbol index with 493 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!