Added Main Project

This commit is contained in:
krjan02 2025-03-25 22:43:13 +01:00
parent 3272781a12
commit 724c410f36
30 changed files with 3849 additions and 0 deletions

454
.gitignore vendored Normal file
View File

@ -0,0 +1,454 @@
## 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/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# 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/
# Tye
.tye/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.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
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# 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/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
##
## Visual studio for Mac
##
# globs
Makefile.in
*.userprefs
*.usertasks
config.make
config.status
aclocal.m4
install-sh
autom4te.cache/
*.tar.gz
tarballs/
test-results/
# Mac bundle stuff
*.dmg
*.app
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
# Windows thumbnail cache files
Thumbs.db
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
# JetBrains Rider
.idea/
*.sln.iml
##
## Visual Studio Code
##
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

37
TeamsNetphoneLink.sln Normal file
View File

@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.10.35027.167
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeamsLocalLibary", "TeamsLocalAPI\TeamsLocalLibary.csproj", "{0075EE3E-C82F-4EAA-A9A3-32BED89C90EA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeamsNetphoneLinkWPF", "TeamsNetphoneLinkWPF\TeamsNetphoneLinkWPF.csproj", "{5904F1F3-755E-4D10-8906-5FC69F85F389}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsNetphoneLinkUpdater", "TeamsNetphoneLinkUpdater\TeamsNetphoneLinkUpdater.csproj", "{BB18A636-95F8-4555-9E96-31575E9CB6A2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0075EE3E-C82F-4EAA-A9A3-32BED89C90EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0075EE3E-C82F-4EAA-A9A3-32BED89C90EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0075EE3E-C82F-4EAA-A9A3-32BED89C90EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0075EE3E-C82F-4EAA-A9A3-32BED89C90EA}.Release|Any CPU.Build.0 = Release|Any CPU
{5904F1F3-755E-4D10-8906-5FC69F85F389}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5904F1F3-755E-4D10-8906-5FC69F85F389}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5904F1F3-755E-4D10-8906-5FC69F85F389}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5904F1F3-755E-4D10-8906-5FC69F85F389}.Release|Any CPU.Build.0 = Release|Any CPU
{BB18A636-95F8-4555-9E96-31575E9CB6A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BB18A636-95F8-4555-9E96-31575E9CB6A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB18A636-95F8-4555-9E96-31575E9CB6A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB18A636-95F8-4555-9E96-31575E9CB6A2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {32340761-08D0-41C6-A769-B606A5B73F01}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="TeamsNetphoneLink.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
<section name="TeamsNetphoneLink.Settings1" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
<section name="TeamsNetphoneLinkWPF.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
</configSections>
<userSettings>
<TeamsNetphoneLink.Settings>
<setting name="Token" serializeAs="String">
<value />
</setting>
<setting name="TeamsPermission" serializeAs="String">
<value>False</value>
</setting>
<setting name="TenantID" serializeAs="String">
<value />
</setting>
<setting name="AppID" serializeAs="String">
<value />
</setting>
<setting name="SaveEntraCredentials" serializeAs="String">
<value>False</value>
</setting>
<setting name="UseGraphForMeetingState" serializeAs="String">
<value>False</value>
</setting>
<setting name="CurrentVersion" serializeAs="String">
<value />
</setting>
</TeamsNetphoneLink.Settings>
<TeamsNetphoneLink.Settings1>
<setting name="Token" serializeAs="String">
<value />
</setting>
<setting name="TeamsPermission" serializeAs="String">
<value>True</value>
</setting>
<setting name="TenatID" serializeAs="String">
<value />
</setting>
<setting name="AppID" serializeAs="String">
<value />
</setting>
</TeamsNetphoneLink.Settings1>
<TeamsNetphoneLinkWPF.Settings>
<setting name="Token" serializeAs="String">
<value />
</setting>
<setting name="TeamsPermission" serializeAs="String">
<value>True</value>
</setting>
<setting name="TenatID" serializeAs="String">
<value />
</setting>
<setting name="AppID" serializeAs="String">
<value />
</setting>
</TeamsNetphoneLinkWPF.Settings>
</userSettings>
</configuration>

View File

@ -0,0 +1,9 @@
<Application x:Class="TeamsNetphoneLink.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TeamsNetphoneLink.WPF"
>
<Application.Resources>
</Application.Resources>
</Application>

View File

@ -0,0 +1,79 @@
using Azure.Identity.Broker;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.NativeInterop;
using System;
using System.Configuration;
using System.Data;
using System.Printing;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using TeamsNetphoneLink.WPF;
using TeamsNetphoneLink.WPF.MVVM;
namespace TeamsNetphoneLink
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool AllocConsole();
public App()
{
#if DEBUG
// Konsole für Debugging-Zwecke öffnen
AllocConsole();
#endif
// Initialisierung der Anwendungskomponenten
InitializeApplication();
}
private void InitializeApplication()
{
UpdateCheck.UpgradeSettingsIfRequired();
// Erstellen der benötigten Instanzen
var TeamsLocalAPI = new Teams.TeamsLocalAPI();
var Netphone = new Netphone.NetPhoneEvents();
var TeamsGraph = new Teams.TeamsGraph();
var dashboardViewModel = new DashboardViewModel();
var LogEntries = new LogViewModel();
var finishPanelViewModel = new FinishPanelViewModel();
var latestTag = UpdateCheck.GetLatestReleaseTagAsync().GetAwaiter().GetResult();
var currentTag = UpdateCheck.GetCurrentVersion();
dashboardViewModel.VersionText = currentTag;
// Erstellen der Synchronisationsinstanz
var sync = new Syncronisation(Netphone, TeamsLocalAPI, TeamsGraph, dashboardViewModel, LogEntries, finishPanelViewModel);
// Erstellen und Anzeigen des Dashboard-Fensters
var dashboard = new WPF.DashboardWindow(sync);
dashboard.Show();
if (UpdateCheck.IsVersionLower(currentTag, latestTag))
{
UpdateCheck.UpdateAvailable = true;
dashboardViewModel.VersionText = String.Format("Version {0} verfügbar!", latestTag);
dashboardViewModel.VersionBackground = new SolidColorBrush(Colors.Orange);
// Erstellen und Anzeigen des Update-Fensters
var updateWindow = new WPF.UpdateWindow(currentTag, latestTag);
updateWindow.Show();
}
// Asynchrone Initialisierung von Netphone und TeamsLocalAPI
sync.InitializeTeamsGraphAsync();
sync.InitializeNetphoneAsync();
sync.InitializeTeamsLocalAPIAsync();
}
}
}

View File

@ -0,0 +1,10 @@
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)
)]

View File

@ -0,0 +1,121 @@
using CLMGRLib;
namespace TeamsNetphoneLink.Netphone
{
public class ClientSdkEventArgs : EventArgs
{
public CLMgrMessage Msg;
public int Param;
public ClientSdkEventArgs(CLMgrMessage msg, int param)
{
Msg = msg;
Param = param;
}
}
public delegate void LineManagerMessageHandler(ClientSdkEventArgs e);
public class ClientSdkEventSink
{
private ClientLineMgrClass ConnectedLineManager;
private IClientLineMgrEventsPub_PubOnLineMgrNotificationEventHandler EventHandler;
private LineManagerMessageHandler LineManagerMessageDelegateOfForm;
public ClientSdkEventSink()
{
EventHandler = new IClientLineMgrEventsPub_PubOnLineMgrNotificationEventHandler(clmgr_EventSink);
}
public void Connect(ClientLineMgrClass lineManager, LineManagerMessageHandler lineManagerMessageDelegateOfForm)
{
ConnectedLineManager = lineManager;
LineManagerMessageDelegateOfForm = lineManagerMessageDelegateOfForm;
//add eventhandler for the PubOnlineMgrNotification Events
ConnectedLineManager.PubOnLineMgrNotification += EventHandler;
}
public void Disconnect()
{
//remove eventhandler for the PubOnlineMgrNotification Events
ConnectedLineManager.PubOnLineMgrNotification -= EventHandler;
ConnectedLineManager = null;
LineManagerMessageDelegateOfForm = null;
}
private void clmgr_EventSink(int msg, int param)
{
//this method receives the COM events from the client line manger
if ((LineManagerMessageDelegateOfForm != null))
{
LineManagerMessageDelegateOfForm(new ClientSdkEventArgs((CLMgrMessage)msg, param));
}
}
}
public enum CLMgrMessage
{
CLMgrLineStateChangedMessage = 0, //state of at least one line has changed
CLMgrLineSelectionChangedMessage = 1, //line in focus has changed
CLMgrLineDetailsChangedMessage = 2, //details of at least one line have changed
CLMgrCallDetailsMessage = 4, //details of last call are available, post mortem for logging purpose
CLMgrServerDownMessage = 5, //server goes down, keep line manager, wait for ServerUp message
CLMgrServerUpMessage = 6, //server is up again, keep interfaces to line manger
CLMgrWaveDeviceChanged = 7, //speaker / micro has been switched on / off
CLMgrGroupCallNotificationMessage = 8, //notification about group call
CLMgrNumberOfLinesChangedMessage = 10, //the number of lines has changed
CLMgrClientShutDownRequest = 11, //Client Line Manager requests client to shutdown and release all interfaces
CLMgrLineStateChangedMessageEx = 28, //state of certain line has changed, lParam: LOWORD: line index of line that changed its state (starting with 0) HIWORD: new state of this line
CLMgrSIPRegistrationStateChanged = 30, //registration state of SIP account has changed
//lParam: LOBYTE: Account index
// HIBYTE: new state
CLMgrWaveFilePlayed = 31, //wave file playback finished
//lParam: line index;
//if -1, the message is related to a LineMgr function PlaySoundFile or PlayToRtp
//if >=0 the message is related to a line function PlaySoundFile of line with this index
PubCLMgrFirstDataReceived = 32 //first RTP data received on line, might be silence
//lParam: line index;
}
public enum LineState
{
Inactive = 0, //line is inactive
HookOffInternal = 1, //off hook, internal dialtone
HookOffExternal = 2, //off hook, external dialtone
Ringing = 3, //incoming call, ringing
Dialing = 4, //outgoing call, we are dialing, no sound
Alerting = 5, //outgoing call, alerting = ringing on destination
Knocking = 6, //outgoing call, knocking = second call ringing on destination
Busy = 7, //outgoing call, destination is busy
Active = 8, //incoming / outgoing call, logical and physical connection is established
OnHold = 9, //incoming / outgoing call, logical connection is established, destination gets music on hold
ConferenceActive = 10, //incoming / outgoing conference, logical and physical connection is established
ConferenceOnHold = 11, //incoming / outgoing conference, logical connection is established, not physcically connected
Terminated = 12, //incoming / outgoing connection / call has been disconnected
Transferring = 13, //special LSOnHold, call is awaiting to be transferred, peer gets special music on hold
Disabled = 14 //special LSInactive: wrap up time
}
public enum DisconnectReason
{
Normal = 0,
Busy = 1,
Rejected = 2,
Cancelled = 3,
Transferred = 4,
JoinedConference = 5,
NoAnswer = 6,
TooLate = 7,
DirectCallImpossible = 8,
WrongNumber = 9,
Unreachable = 10,
CallDiverted = 11,
CallRoutingFailed = 12,
PermissionDenied = 13,
NetworkCongestion = 14,
NoChannelAvailable = 15,
NumberChanged = 16,
IncompatibleDestination = 17
}
}

View File

@ -0,0 +1,200 @@
using CLMGRLib;
using System.Diagnostics;
using TeamsLocalLibary.EventArgs;
namespace TeamsNetphoneLink.Netphone
{
public delegate void LineStateChangedEventHandler(LineState newLineState);
public delegate void LoggedInStateEventHandler(bool loggedInState);
public class NetPhoneEvents : IDisposable
{
private ClientLine SelectedLine;
private ClientLineMgrClass pCLMgr;
private ClientSdkEventSink MyEventSink;
private LineState LastLineState;
private bool lastLoggedInState;
private CancellationTokenSource aliveCheckTokenSource;
private CancellationToken aliveCheckToken;
private Dictionary<LineState, List<LineStateChangedEventHandler>> lineStateEvents = new Dictionary<LineState, List<LineStateChangedEventHandler>>();
private List<LoggedInStateEventHandler> loggedInStateEvents = new List<LoggedInStateEventHandler>();
private Timer loggedInStateTimer;
private Timer aliveTimer;
public event EventHandler<TokenReceivedEventArgs>? TokenReceived;
public NetPhoneEvents()
{
loggedInStateTimer = new Timer(CheckLoggedInState, null, Timeout.Infinite, 1000);
aliveTimer = new Timer(AliveCheck, null, Timeout.Infinite, 1000);
}
public void Dispose()
{
loggedInStateTimer?.Change(Timeout.Infinite, 0);
loggedInStateTimer?.Dispose();
RemoveAllEventHandlers();
}
public async Task<bool> Initialize(bool waitForNetphone = false, CancellationToken cancellationToken = default)
{
if (Process.GetProcessesByName("CLMgr").Length == 0 && !waitForNetphone)
return false;
while (Process.GetProcessesByName("CLMgr").Length == 0)
await Task.Delay(1000, cancellationToken);
Console.WriteLine("new interface");
pCLMgr = new ClientLineMgrClass();
MyEventSink = new ClientSdkEventSink();
MyEventSink.Connect(pCLMgr, new LineManagerMessageHandler(OnLineManagerMessage));
aliveTimer.Change(0, 1000);
loggedInStateTimer.Change(0, 1500);
return true;
}
public void AliveCheck(object state)
{
if (Process.GetProcessesByName("CLMgr").Length == 0)
{
_ = Initialize(true).GetAwaiter().GetResult();
}
}
private void CheckLoggedInState(object state)
{
bool currentLoggedInState = false;
try
{
currentLoggedInState = pCLMgr.DispIsLoggedIn != 0;
}
catch (Exception ex)
{
currentLoggedInState = false;
}
if (currentLoggedInState != lastLoggedInState)
{
lastLoggedInState = currentLoggedInState;
OnLoggedInStateChanged(currentLoggedInState);
}
}
private void OnLoggedInStateChanged(bool isLoggedIn)
{
foreach (var handler in loggedInStateEvents)
{
handler?.Invoke(isLoggedIn);
}
}
public void AddLineStateEventHandler(LineState lineState, LineStateChangedEventHandler handler)
{
lock (lineStateEvents)
{
if (!lineStateEvents.ContainsKey(lineState))
{
lineStateEvents[lineState] = new List<LineStateChangedEventHandler>();
}
lineStateEvents[lineState].Add(handler);
}
}
public void AddLoggedInStateEventHandler(LoggedInStateEventHandler handler)
{
lock (loggedInStateEvents)
{
loggedInStateEvents.Add(handler);
}
}
public void RemoveLineStateEventHandler(LineState lineState, LineStateChangedEventHandler handler)
{
lock (lineStateEvents)
{
if (lineStateEvents.ContainsKey(lineState))
{
lineStateEvents[lineState].Remove(handler);
}
}
}
public void RemoveLoggedInStateEventHandler(LoggedInStateEventHandler handler)
{
lock (loggedInStateEvents)
{
loggedInStateEvents.Remove(handler);
}
}
public void RemoveAllEventHandlers()
{
lock (lineStateEvents)
{
lineStateEvents.Clear();
}
lock (loggedInStateEvents)
{
loggedInStateEvents.Clear();
}
}
private void OnLineManagerMessage(ClientSdkEventArgs e)
{
SelectedLine = (ClientLine)pCLMgr.DispSelectedLine;
if (e.Msg == CLMgrMessage.CLMgrClientShutDownRequest)
{
//aliveCheckTokenSource.Cancel();
//MyEventSink.Disconnect();
//OnConnectionStateChanged(false,true);
}
if (e.Msg == CLMgrMessage.CLMgrLineStateChangedMessageEx)
{
int line = e.Param & 0xff;
int high = e.Param >> 8;
LineState NewLineState = (LineState)high;
if (LastLineState != NewLineState)
{
LastLineState = NewLineState;
lock (lineStateEvents)
{
if (lineStateEvents.ContainsKey(NewLineState))
{
foreach (var handler in lineStateEvents[NewLineState])
{
handler?.Invoke(NewLineState);
}
}
}
}
}
}
public void SetRichPresenceStatus(int away, int dnd, DateTime expires)
{
var clientConfig = (ClientConfig)this.pCLMgr.ClientConfig;
clientConfig.SetRichPresenceStatus(away, dnd, expires);
}
public void SetAppointmentText(string appointmentText, DateTime expires)
{
var clientConfig = (ClientConfig)this.pCLMgr.ClientConfig;
clientConfig.SetAppointmentText(appointmentText, expires);
}
}
}

View File

@ -0,0 +1,290 @@
using Azure.Core;
using Azure.Identity;
using Azure.Identity.Broker;
using Microsoft.Graph;
using Microsoft.Graph.Drives.Item.Items.Item.Workbook.Functions.Cosh;
using Microsoft.Graph.Me.Presence.SetUserPreferredPresence;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Broker;
using Microsoft.Identity.Client.Extensions.Msal;
using System.Net.Http.Headers;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows;
using TeamsLocalLibary;
using static System.Formats.Asn1.AsnWriter;
using Microsoft.Identity.Client.NativeInterop;
using TeamsNetphoneLink.WPF;
using TeamsNetphoneLink.Communication;
using System;
using System.Text.RegularExpressions;
namespace TeamsNetphoneLink.Teams
{
public delegate void PresenceStausEventHandler(Microsoft.Graph.Models.Presence presence);
public class TeamsGraph : TeamsGraphEventHandlers
{
private GraphServiceClient graphClient;
public bool Authenticated { get; private set; }
private Timer presenceStatusTimer;
private Microsoft.Graph.Models.Presence lastPresence;
public void CheckPresenceStatusTimer()
{
presenceStatusTimer = new Timer(CheckPresenceStatus, null, Timeout.Infinite, 2000);
presenceStatusTimer.Change(0, 1000);
}
public void CheckPresenceStatus(object state)
{
if (graphClient is not null && Authenticated)
{
try
{
var presence = graphClient.Me.Presence.GetAsync().GetAwaiter().GetResult();
// If lastPresence is null, initialize it with the current presence
if (lastPresence == null)
{
lastPresence = presence;
}
// Check if Availability has changed
if (lastPresence.Availability != presence.Availability)
{
OnAvailabilityChanged(presence);
}
// Check if Activity has changed
if (lastPresence.Activity != presence.Activity)
{
OnActivityChanged(presence);
}
// Update lastPresence to the current presence
lastPresence = presence;
}
catch (Exception ex)
{
Console.WriteLine($"Error checking presence state: {ex.Message}");
}
}
}
public async Task<bool> IsCachedAccounts()
{
IPublicClientApplication app = PublicClientApplicationBuilder.Create(Settings.Default.AppID)
.WithDefaultRedirectUri()
.WithAuthority(String.Format("https://login.microsoftonline.com/{0}", Settings.Default.TenantID))
.Build();
// Register MSAL cache
var storage = new StorageCreationPropertiesBuilder("teamsnetphonelink.msal.cache", MsalCacheHelper.UserRootDirectory).Build();
var cacheHelper = await MsalCacheHelper.CreateAsync(storage);
cacheHelper.RegisterCache(app.UserTokenCache);
IEnumerable<IAccount> accounts = await app.GetAccountsAsync();
return accounts.Any();
}
// Authentifizierungsmethode mit Azure Identity
public async Task<bool> AuthenticateAsync(bool clearCache = false, bool silent = true)
{
try
{
// Check if caching is enabled in settings
bool useCache = Settings.Default.SaveEntraCredentials;
IPublicClientApplication app = PublicClientApplicationBuilder.Create(Settings.Default.AppID)
.WithDefaultRedirectUri()
.WithAuthority(String.Format("https://login.microsoftonline.com/{0}", Settings.Default.TenantID))
.Build();
// Register MSAL cache only if caching is enabled
if (useCache)
{
var storage = new StorageCreationPropertiesBuilder("teamsnetphonelink.msal.cache", MsalCacheHelper.UserRootDirectory).Build();
var cacheHelper = await MsalCacheHelper.CreateAsync(storage);
cacheHelper.RegisterCache(app.UserTokenCache);
}
IEnumerable<IAccount> accounts = await app.GetAccountsAsync();
//Alle Accounts aus dem Cache entfernen wenn clearCache gesetzt ist
if (clearCache)
{
foreach(var account in accounts)
await app.RemoveAsync(account);
}
// Try to use the previously signed-in account from the cache
var existingAccount = accounts.FirstOrDefault();
AuthenticationResult authentication;
if (existingAccount is not null && useCache && !clearCache)
{
Console.WriteLine("Attempting to acquire token silently using cached account.");
try
{
authentication = await app.AcquireTokenSilent(new[] { "Presence.ReadWrite", "offline_access" }, existingAccount)
.ExecuteAsync();
await InitializeGraphClient(authentication.AccessToken);
return Authenticated;
}
catch (MsalUiRequiredException)
{
Console.WriteLine("Silent token acquisition failed. Falling back to interactive authentication.");
}
}
// If no cached account or silent authentication fails, prompt the user for authentication
Console.WriteLine("Prompting user for authentication.");
authentication = await app.AcquireTokenInteractive(new[] { "Presence.ReadWrite", "offline_access" })
.ExecuteAsync();
await InitializeGraphClient(authentication.AccessToken);
return Authenticated;
}
catch (Exception ex)
{
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Fehler bei der Authentifizierung", ex.Message, ex.StackTrace);
Console.WriteLine($"Error during authentication: {ex.Message}");
return Authenticated;
}
}
// Helper method to initialize GraphServiceClient
private async Task InitializeGraphClient(string accessToken)
{
graphClient = new GraphServiceClient(new HttpClient(new AuthHandler(accessToken, new HttpClientHandler())));
Authenticated = await TestAuthentication();
}
// Testung der Anmeldung und Zugriffsrechte
public async Task<bool> TestAuthentication()
{
try
{
// Test-Anfrage, um Authentifizierung zu validieren
await graphClient.Me.Presence.GetAsync();
return true; // Anmeldung und Zugriffsrechte korrekt
}
catch (Exception ex)
{
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Authentifizierung fehlerhaft", ex.Message, ex.StackTrace);
return false; // Anmeldung und Zugriffsrechte felerhaft
}
}
// Methode zum Setzen des Präsenzstatus
public async Task<bool> SetPresenceAsync(PresenceState presenceState)
{
if (graphClient is null || !Authenticated)
{
Console.WriteLine("Authentifizierung nicht abgeschlossen. Präsenz kann nicht gesetzt werden.");
return false;
}
// Zuordnen des Präsenzstatus zu Verfügbarkeit und Aktivität
var presenceMap = new Dictionary<PresenceState, (string Availability, string Activity)>
{
{ PresenceState.Available, ("Available", "Available") },
{ PresenceState.Busy, ("Busy", "Busy") },
{ PresenceState.DoNotDisturb, ("DoNotDisturb", "DoNotDisturb") },
{ PresenceState.BeRightBack, ("BeRightBack", "BeRightBack") },
{ PresenceState.Away, ("Away", "Away") },
{ PresenceState.Offline, ("Offline", "OffWork") }
};
if (!presenceMap.ContainsKey(presenceState))
{
Console.WriteLine("Ungültiger Präsenzstatus.");
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Ungültiger Präsenzstatus", presenceState.ToString());
return false; // Ungültiger Status
}
var (availability, activity) = presenceMap[presenceState];
var requestBody = new SetUserPreferredPresencePostRequestBody
{
Availability = availability,
Activity = activity,
ExpirationDuration = TimeSpan.FromHours(2) // Ablaufzeit der Präsenz
};
try
{
await graphClient.Me.Presence.SetUserPreferredPresence.PostAsync(requestBody);
return true; // Präsenz erfolgreich gesetzt
}
catch (Exception ex)
{
if(ex.GetType() != typeof(Microsoft.Graph.Models.ODataErrors.ODataError))
{
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Fehler beim Setzen der Präsenz", ex.Message, ex.StackTrace);
}
return false; // Fehler beim Setzen der Präsenz
}
}
// Enum für die verschiedenen Präsenzstatus
public enum PresenceState
{
Available,
Busy,
DoNotDisturb,
BeRightBack,
Away,
Offline
}
private static Regex isGuid =
new Regex(@"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$", RegexOptions.Compiled);
public static bool IsGuid(string candidate)
{
bool isValid = false;
if (candidate != null)
{
if (isGuid.IsMatch(candidate))
{
isValid = true;
}
}
return isValid;
}
}
// Custom HttpMessageHandler to inject the access token
public class AuthHandler : DelegatingHandler
{
private readonly string _accessToken;
public AuthHandler(string accessToken, HttpMessageHandler innerHandler)
: base(innerHandler)
{
_accessToken = accessToken;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Add the access token to the request headers
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
return await base.SendAsync(request, cancellationToken);
}
}
}

View File

@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TeamsNetphoneLink.Communication
{
public class TeamsGraphEventHandlers
{
private readonly object _lock = new object();
private readonly List<EventHandler<PresenceChangedEventArgs>> _activityChangedHandlers = new List<EventHandler<PresenceChangedEventArgs>>();
private readonly List<EventHandler<PresenceChangedEventArgs>> _availabilityChangedHandlers = new List<EventHandler<PresenceChangedEventArgs>>();
public event EventHandler<PresenceChangedEventArgs> ActivityChanged
{
add
{
lock (_lock)
{
_activityChangedHandlers.Add(value);
}
}
remove
{
lock (_lock)
{
_activityChangedHandlers.Remove(value);
}
}
}
public event EventHandler<PresenceChangedEventArgs> AvailabilityChanged
{
add
{
lock (_lock)
{
_availabilityChangedHandlers.Add(value);
}
}
remove
{
lock (_lock)
{
_availabilityChangedHandlers.Remove(value);
}
}
}
protected void OnActivityChanged(Microsoft.Graph.Models.Presence presence)
{
lock (_lock)
{
var args = new PresenceChangedEventArgs(presence);
foreach (var handler in _activityChangedHandlers)
{
handler?.Invoke(this, args);
}
}
}
protected void OnAvailabilityChanged(Microsoft.Graph.Models.Presence presence)
{
lock (_lock)
{
var args = new PresenceChangedEventArgs(presence);
foreach (var handler in _availabilityChangedHandlers)
{
handler?.Invoke(this, args);
}
}
}
public void RemoveAllEventHandlers()
{
foreach (var handler in _activityChangedHandlers)
{
_availabilityChangedHandlers.Remove(handler);
}
foreach (var handler in _availabilityChangedHandlers)
{
_availabilityChangedHandlers.Remove(handler);
}
}
}
public class PresenceChangedEventArgs : EventArgs
{
public Microsoft.Graph.Models.Presence Presence { get; }
public PresenceChangedEventArgs(Microsoft.Graph.Models.Presence presence)
{
Presence = presence;
}
}
}

View File

@ -0,0 +1,118 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net.WebSockets;
using System.Reflection;
using TeamsLocalLibary;
using TeamsLocalLibary.EventArgs;
namespace TeamsNetphoneLink.Teams
{
public class TeamsLocalAPI
{
public event EventHandler<ConnectionStateChangedEventArgs>? ConnectionState;
public event EventHandler<TokenReceivedEventArgs>? TokenReceived;
private Client teamsClient;
private string token = Settings.Default.Token != string.Empty ? Settings.Default.Token : null;
private Dictionary<string, List<PropertyChangedEventHandler>> propertyChangedHandlers = new Dictionary<string, List<PropertyChangedEventHandler>>();
private CancellationTokenSource cts = new();
// Destructor
~TeamsLocalAPI()
{
RemoveAllEventHandlers();
}
public async Task<bool> Initialize()
{
// Initialize the TeamsClient with token and no auto-connect
teamsClient = new Client(autoConnect: false, token: token) ?? throw new Exception("Could not create client");
// Event-handler for token reception
teamsClient.TokenReceived += (_, args) =>
{
Settings.Default.Token = args.Token;
Settings.Default.Save();
TokenReceived?.Invoke(_, args);
};
WebSocketState lastConnectionState = WebSocketState.None;
teamsClient.ConnectionState += (sender, args) =>
{
if (args.WebSocketState != lastConnectionState)
{
lastConnectionState = args.WebSocketState;
// Raise the ConnectionState event in TeamsLocalAPI
ConnectionState?.Invoke(sender, args);
}
};
teamsClient.PropertyChanged += HandlePropertyChanged;
teamsClient.ErrorReceived += (_, args) => Console.WriteLine("Event: ErrorReceived: {0}", args.ErrorMessage);
return await teamsClient.Connect(true, cts.Token);
}
public void AddTokenRecievedHandler(EventHandler<TokenReceivedEventArgs> handler)
{
TokenReceived += handler;
}
public void RemoveTokenRecievedHandler(EventHandler<TokenReceivedEventArgs> handler)
{
TokenReceived -= handler;
}
public void AddConnectionStateHandler(EventHandler<ConnectionStateChangedEventArgs> handler)
{
ConnectionState += handler;
}
public void RemoveConnectionStateHandler(EventHandler<ConnectionStateChangedEventArgs> handler)
{
ConnectionState -= handler;
}
public async void SendDummyCommand()
{
var dummy = teamsClient.IsMuted = true;
}
public void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName is not null && propertyChangedHandlers.ContainsKey(e.PropertyName))
{
foreach (var handler in propertyChangedHandlers[e.PropertyName])
{
handler?.Invoke(sender, e);
}
}
}
public void AddEventHandler(string propertyName, PropertyChangedEventHandler handler)
{
if (!propertyChangedHandlers.ContainsKey(propertyName))
{
propertyChangedHandlers[propertyName] = new List<PropertyChangedEventHandler>();
}
propertyChangedHandlers[propertyName].Add(handler);
}
public void RemoveEventHandler(string propertyName, PropertyChangedEventHandler handler)
{
if (propertyChangedHandlers.ContainsKey(propertyName))
{
propertyChangedHandlers[propertyName].Remove(handler);
}
}
public void RemoveAllEventHandlers()
{
propertyChangedHandlers.Clear();
}
}
}

110
TeamsNetphoneLinkWPF/Settings.Designer.cs generated Normal file
View File

@ -0,0 +1,110 @@
//------------------------------------------------------------------------------
// <auto-generated>
// Dieser Code wurde von einem Tool generiert.
// Laufzeitversion:4.0.30319.42000
//
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
// der Code erneut generiert wird.
// </auto-generated>
//------------------------------------------------------------------------------
namespace TeamsNetphoneLink {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.10.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;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string Token {
get {
return ((string)(this["Token"]));
}
set {
this["Token"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool TeamsPermission {
get {
return ((bool)(this["TeamsPermission"]));
}
set {
this["TeamsPermission"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string TenantID {
get {
return ((string)(this["TenantID"]));
}
set {
this["TenantID"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string AppID {
get {
return ((string)(this["AppID"]));
}
set {
this["AppID"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool SaveEntraCredentials {
get {
return ((bool)(this["SaveEntraCredentials"]));
}
set {
this["SaveEntraCredentials"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool UseGraphForMeetingState {
get {
return ((bool)(this["UseGraphForMeetingState"]));
}
set {
this["UseGraphForMeetingState"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string CurrentVersion {
get {
return ((string)(this["CurrentVersion"]));
}
set {
this["CurrentVersion"] = value;
}
}
}
}

View File

@ -0,0 +1,27 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="TeamsNetphoneLink" GeneratedClassName="Settings">
<Profiles />
<Settings>
<Setting Name="Token" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="TeamsPermission" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="TenantID" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="AppID" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
<Setting Name="SaveEntraCredentials" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="UseGraphForMeetingState" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="CurrentVersion" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
</Settings>
</SettingsFile>

View File

@ -0,0 +1,273 @@
using System.ComponentModel;
using System.Windows.Media;
using TeamsLocalLibary.EventArgs;
using TeamsNetphoneLink.Teams;
using TeamsNetphoneLink.Netphone;
using TeamsNetphoneLink.WPF.MVVM;
using TeamsNetphoneLink.Communication;
using System.Reflection;
using TeamsNetphoneLink.WPF;
namespace TeamsNetphoneLink
{
public class Syncronisation
{
public NetPhoneEvents NetphoneEvents { get; set; }
public TeamsGraph TeamsGraph { get; set; }
public TeamsLocalAPI TeamsEvents { get; set; }
//MVVM View Models
public DashboardViewModel _dashboardViewModel { get; }
public LogViewModel LogEntries { get; }
public FinishPanelViewModel FinishPanelViewModel { get; }
public readonly SemaphoreSlim TeamsGraphAuthenticationSemaphore = new(1);
private bool _isCallActive = false;
private bool _isMeetingActive = false;
private bool NetphoneConnected = false;
public Syncronisation(NetPhoneEvents netPhoneEvents, TeamsLocalAPI teamsEvents, TeamsGraph teamsGraph, DashboardViewModel dashboardViewModel, LogViewModel logEntries, FinishPanelViewModel finishPanelViewModel)
{
_dashboardViewModel = dashboardViewModel ?? throw new ArgumentNullException(nameof(dashboardViewModel));
LogEntries = logEntries ?? throw new ArgumentNullException(nameof(logEntries));
FinishPanelViewModel = finishPanelViewModel ?? throw new ArgumentNullException(nameof(finishPanelViewModel));
NetphoneEvents = netPhoneEvents ?? throw new ArgumentNullException(nameof(netPhoneEvents));
TeamsEvents = teamsEvents ?? throw new ArgumentNullException(nameof(teamsEvents));
TeamsGraph = teamsGraph ?? throw new ArgumentNullException(nameof(teamsGraph));
// Event-Handler für Netphone-Events registrieren
NetphoneEvents.AddLineStateEventHandler(LineState.Dialing, NetphoneEventCall);
NetphoneEvents.AddLineStateEventHandler(LineState.Ringing, NetphoneEventCall);
NetphoneEvents.AddLineStateEventHandler(LineState.Active, NetphoneEventCall);
NetphoneEvents.AddLineStateEventHandler(LineState.Inactive, NetphoneEventTerminated);
NetphoneEvents.AddLineStateEventHandler(LineState.Terminated, NetphoneEventTerminated);
NetphoneEvents.AddLoggedInStateEventHandler(NetphoneOnLoggedInStateChanged);
if (!Settings.Default.UseGraphForMeetingState)
{
// Event-Handler für Teams-Events registrieren
TeamsEvents.AddEventHandler("IsInMeeting", TeamsIsInMeetingHandler);
TeamsEvents.AddConnectionStateHandler(TeamsOnConnectionStateChanged);
TeamsEvents.AddTokenRecievedHandler(TeamsTokenRecievedHandler);
}
else
{
TeamsGraph.ActivityChanged += TeamsGraphOnActivityChanged;
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsLocalAPIStatus, Colors.Orange, "Deaktiviert");
}
}
// Event-Handler für Änderungen des Verbindungsstatus in Teams
private void TeamsOnConnectionStateChanged(object? sender, ConnectionStateChangedEventArgs args)
{
var status = args.WebSocketState switch
{
System.Net.WebSockets.WebSocketState.Open when Settings.Default.TeamsPermission => (Colors.Green, "Verbunden"),
System.Net.WebSockets.WebSocketState.Open => (Colors.Orange, "Authorisieren (Klicken)"),
System.Net.WebSockets.WebSocketState.Connecting => (Colors.Blue, "Verbinden..."),
System.Net.WebSockets.WebSocketState.Closed => (Colors.Red, "Nicht verbunden"),
_ => (Colors.Gray, "Unbekannt")
};
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsLocalAPIStatus, status.Item1, status.Item2);
if(Settings.Default.TeamsPermission)
LogEntries.AddLogEntry(args.WebSocketState == System.Net.WebSockets.WebSocketState.Open ? "Info" : "Warn", "TeamsLocalAPI", status.Item2);
}
private void NetphoneOnLoggedInStateChanged(bool isLoggedIn)
{
NetphoneConnected = isLoggedIn;
var state = isLoggedIn ? ( Colors.Green, "Verbunden" ) : ( Colors.Red, "Nicht verbunden" );
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.NetphoneCLMGRStatus, state.Item1, state.Item2);
LogEntries.AddLogEntry(isLoggedIn ? "Info" : "Warn", "Netphone", state.Item2);
}
// Event-Handler für den Empfang eines Teams-Tokens
private void TeamsTokenRecievedHandler(object sender, TokenReceivedEventArgs e)
{
if (!Settings.Default.TeamsPermission)
{
Settings.Default.TeamsPermission = true;
Settings.Default.Save();
FinishPanelViewModel.FinishPanelEffect = null;
FinishPanelViewModel.FinishPanelEnabled = true;
FinishPanelViewModel.FinishPanelFinishTextText = "Verbindung erfolgreich";
FinishPanelViewModel.SetFinishPanelFinishTextColor(Colors.Green);
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsLocalAPIStatus, Colors.Green, "Verbunden");
LogEntries.AddLogEntry("TeamsLocalAPI", "Token erfolgreich abgerufen");
}
}
// Event-Handler für eingehende Anrufe
private async void NetphoneEventCall(LineState newLineState)
{
if (_isCallActive) return;
_isCallActive = true;
await TeamsGraph.SetPresenceAsync(TeamsGraph.PresenceState.Busy);
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.NetphoneCallStatus, Colors.DarkGreen, "Ongoing");
LogEntries.AddLogEntry("Syncronisation", "Netphone Anruf gestartet");
}
// Event-Handler für beendete Anrufe
private async void NetphoneEventTerminated(LineState newLineState)
{
if (!_isCallActive) return;
_isCallActive = false;
await TeamsGraph.SetPresenceAsync(TeamsGraph.PresenceState.Available);
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.NetphoneCallStatus, Colors.DarkOrange, "Not active");
LogEntries.AddLogEntry("Syncronisation", "Netphone Anruf gestoppt");
}
private void TeamsGraphOnActivityChanged(object sender, PresenceChangedEventArgs args)
{
// Define activities that indicate the user is in a meeting
var meetingActivities = new HashSet<string>
{
"InACall",
"InAConferenceCall",
"InAMeeting",
"Presenting"
};
if(args.Presence.Activity is null)
return;
// Check if the current activity is in the meetingActivities set
bool isInMeeting = meetingActivities.Contains(args.Presence.Activity);
if (!isInMeeting && _isMeetingActive)
{
_isMeetingActive = false;
SetNetphoneStatus(isInMeeting);
return;
}
if (isInMeeting && !_isMeetingActive)
{
_isMeetingActive = true;
SetNetphoneStatus(isInMeeting);
}
}
// Event-Handler für Änderungen des "IsInMeeting"-Status in Teams
private void TeamsIsInMeetingHandler(object sender, PropertyChangedEventArgs e)
{
var isInMeeting = (bool)(sender.GetType()?.GetProperty(e.PropertyName)?.GetValue(sender) ?? false);
SetNetphoneStatus(isInMeeting);
}
private void SetNetphoneStatus(bool meeting)
{
if (meeting)
{
if (NetphoneConnected)
{
NetphoneEvents.SetRichPresenceStatus(0, 1, DateTime.MaxValue);
NetphoneEvents.SetAppointmentText("In einem Teams Meeting", DateTime.MaxValue);
}
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsMeetingStatus, Colors.DarkGreen, "Ongoing");
LogEntries.AddLogEntry("Syncronisation", "Teams Meeting gestartet");
}
else
{
if (NetphoneConnected)
{
NetphoneEvents.SetRichPresenceStatus(0, 0, DateTime.MaxValue);
NetphoneEvents.SetAppointmentText("", DateTime.MaxValue);
}
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsMeetingStatus, Colors.DarkOrange, "Not active");
LogEntries.AddLogEntry("Syncronisation", "Teams Meeting gestoppt");
}
}
// Methode zur Authentifizierung bei der Graph API
public async Task<bool> GraphAuthenticate(bool clearCache = false)
{
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsGraphAPIStatus, Colors.Blue, "Anmeldung läuft...");
LogEntries.AddLogEntry("TeamsGraphAPI", String.Format("Anmeldung läuft"));
var authResult = await TeamsGraph.AuthenticateAsync(clearCache);
var status = authResult ? (Colors.Green, "Angemeldet") : (Colors.Red, "Fehler - Anmelden (Klicken)");
_dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsGraphAPIStatus, status.Item1, status.Item2);
LogEntries.AddLogEntry("TeamsGraphAPI", status.Item2);
return authResult;
}
public async void InitializeNetphoneAsync()
{
try
{
await this.NetphoneEvents.Initialize(waitForNetphone: true).ConfigureAwait(false);
}
catch (Exception ex)
{
// Fehlerbehandlung für die Netphone-Initialisierung
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Fehler bei der Initialisierung von der NetphoneAPI", ex.Message, ex.StackTrace);
}
}
public async void InitializeTeamsGraphAsync()
{
if (!Teams.TeamsGraph.IsGuid(Settings.Default.AppID) || !Teams.TeamsGraph.IsGuid(Settings.Default.TenantID))
{
this._dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsGraphAPIStatus, Colors.Red, "Konfiguration fehlt");
return;
}
try
{
await this.TeamsGraphAuthenticationSemaphore.WaitAsync().ConfigureAwait(false);
if (await this.TeamsGraph.IsCachedAccounts() && Settings.Default.SaveEntraCredentials)
{
await this.GraphAuthenticate().ConfigureAwait(false); ;
}
else
{
this._dashboardViewModel.UpdateStatusPanel(StatusPanelNames.TeamsGraphAPIStatus, Colors.Orange, "Anmelden");
}
this.TeamsGraphAuthenticationSemaphore.Release();
if (Settings.Default.UseGraphForMeetingState)
{
this.TeamsGraph.CheckPresenceStatusTimer();
}
}
catch (Exception ex)
{
// Fehlerbehandlung für die TeamsGraph-Initialisierung
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Fehler bei der Initialisierung von TeamsGraph", ex.Message, ex.StackTrace);
}
}
public async void InitializeTeamsLocalAPIAsync()
{
if (!Settings.Default.UseGraphForMeetingState)
{
try
{
await this.TeamsEvents.Initialize().ConfigureAwait(false);
}
catch (Exception ex)
{
// Fehlerbehandlung für die TeamsLocalAPI-Initialisierung
ExceptionWindowHelper.Show(this.GetType().Name, MethodBase.GetCurrentMethod().Name, "Fehler bei der Initialisierung von der TeamsLocalAPI", ex.Message, ex.StackTrace);
}
}
}
}
}

View File

@ -0,0 +1,62 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<RootNamespace>TeamsNetphoneLink</RootNamespace>
<ApplicationIcon>TeamsNetphoneSyncIcon.ico</ApplicationIcon>
<BaseOutputPath>..\build\</BaseOutputPath>
<SignAssembly>False</SignAssembly>
<AssemblyOriginatorKeyFile></AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<COMReference Include="CLMGRLib">
<Guid>{F8E552F7-4C00-11D3-80BC-00105A653379}</Guid>
<VersionMajor>2</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>False</EmbedInteropTypes>
</COMReference>
</ItemGroup>
<ItemGroup>
<Content Include="TeamsNetphoneSyncIcon.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.13.2" />
<PackageReference Include="Azure.Identity.Broker" Version="1.2.0" />
<PackageReference Include="Emoji.Wpf" Version="0.3.4" />
<PackageReference Include="Microsoft.Graph" Version="5.74.0" />
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.67.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TeamsLocalAPI\TeamsLocalLibary.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
<Compile Update="WPF\UpdateWindow.xaml.cs">
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows.Threading;
using TeamsNetphoneLink.WPF;
namespace TeamsNetphoneLink
{
public class UpdateCheck
{
public static bool UpdateAvailable;
public static async Task<string> GetLatestReleaseTagAsync()
{
using (var client = new HttpClient())
{
var url = "https://git.jan.sx/api/v1/repos/krjan02/TeamsNetphoneLink/releases/latest";
try
{
var response = await client.GetStringAsync(url);
var json = JsonDocument.Parse(response);
if (json.RootElement.TryGetProperty("tag_name", out var tagName))
{
return tagName.GetString();
}
else
{
ExceptionWindowHelper.Show("UpdateCheck", MethodBase.GetCurrentMethod().Name, "Update Check nicht erfolgreich", "tag_name is not existing");
return "";
}
}
catch (Exception ex)
{
ExceptionWindowHelper.Show("UpdateCheck", MethodBase.GetCurrentMethod().Name, "Update Check nicht erfolgreich", ex.Message, ex.StackTrace);
return "";
}
}
}
public static bool IsVersionLower(string currentVersion, string releaseVersion)
{
if(releaseVersion.Length == 0) { return false; }
Version current = Version.Parse(currentVersion);
Version release = Version.Parse(releaseVersion);
return current.CompareTo(release) < 0;
}
public static string GetCurrentVersion()
{
var assembly = Assembly.GetEntryAssembly();
var versionAttribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
var productVersion = versionAttribute?.InformationalVersion;
return productVersion.Split("+").FirstOrDefault(); ;
}
public static void UpgradeSettingsIfRequired()
{
var version = GetCurrentVersion();
if (Settings.Default.CurrentVersion != version)
{
Settings.Default.Upgrade();
Settings.Default.CurrentVersion = version;
Settings.Default.Save();
}
}
}
}

View File

@ -0,0 +1,263 @@
<Window x:Class="TeamsNetphoneLink.WPF.DashboardWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TeamsNetphoneLink.WPF.MVVM"
Title="TeamsNetphoneLink Dashboard" Height="554" Width="800"
Background="#FF1E1E1E" FontFamily="Segoe UI" ResizeMode="CanMinimize"
WindowStartupLocation="CenterScreen">
<Window.DataContext>
<local:DashboardViewModel />
</Window.DataContext>
<Window.Resources>
<!-- Button Style -->
<Style TargetType="Button">
<Setter Property="Background" Value="#FF0078D4"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Padding" Value="10,5"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="4"
Padding="{TemplateBinding Padding}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#0063B1"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- Status Border Style -->
<Style TargetType="Border" x:Key="StatusBorder">
<Setter Property="CornerRadius" Value="12"/>
<Setter Property="Padding" Value="10,5"/>
<Setter Property="Background" Value="#FF252526"/>
<Setter Property="MinWidth" Value="100"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
<!-- Status Text Style -->
<Style TargetType="TextBlock" x:Key="StatusText">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
<!-- ListView Styling -->
<Style TargetType="ListView">
<Setter Property="Background" Value="#FF252526"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="#FF3E3E40"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
</Style>
<!-- GridViewColumnHeader Style -->
<Style TargetType="GridViewColumnHeader">
<Setter Property="Background" Value="#FF252526"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="#FF3E3E40"/>
<Setter Property="Padding" Value="5"/>
<Setter Property="Height" Value="32"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<!-- Header -->
<RowDefinition Height="Auto"/>
<!-- Status Panels -->
<RowDefinition Height="*"/>
<!-- Log Section -->
<RowDefinition Height="Auto"/>
<!-- Footer -->
</Grid.RowDefinitions>
<!-- Header -->
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,15">
<TextBlock FontSize="24" FontWeight="Bold" Text="{Binding AppTitle2}" Foreground="White"/>
<Border Visibility="{Binding VersionVisibility}" Background="{Binding VersionBackground}" CornerRadius="12" Padding="12,4" Margin="15,0">
<TextBlock Text="{Binding VersionText}" Foreground="White" FontSize="16"/>
</Border>
</StackPanel>
<!-- Status Indicators -->
<Grid Grid.Row="1" Margin="15,20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Left Status Column -->
<StackPanel Grid.Column="0">
<!-- Teams Local API Status -->
<StackPanel Orientation="Horizontal" Margin="0,8">
<TextBlock Text="Teams Local API Status" Width="180" FontWeight="Bold" Foreground="White"/>
<Border Style="{StaticResource StatusBorder}"
Background="{Binding TeamsLocalAPIStatusBackground}"
MouseDown="TeamsLocalAPIBorder_MouseDown">
<TextBlock Style="{StaticResource StatusText}" Text="{Binding TeamsLocalAPIStatusText}"/>
</Border>
</StackPanel>
<!-- Teams Graph API Status -->
<StackPanel Orientation="Horizontal" Margin="0,8">
<TextBlock Text="Teams Graph API Status" Width="180" FontWeight="Bold" Foreground="White"/>
<Border Style="{StaticResource StatusBorder}"
Background="{Binding TeamsGraphAPIStatusBackground}"
MouseDown="TeamsGraphAPIBorder_MouseDown">
<TextBlock Style="{StaticResource StatusText}" Text="{Binding TeamsGraphAPIStatusText}"/>
</Border>
</StackPanel>
<!-- NetPhone CLMGR Status -->
<StackPanel Orientation="Horizontal" Margin="0,8">
<TextBlock Text="NetPhone CLMGR Status" Width="180" FontWeight="Bold" Foreground="White"/>
<Border Style="{StaticResource StatusBorder}"
Background="{Binding NetphoneCLMGRStatusBackground}">
<TextBlock Style="{StaticResource StatusText}" Text="{Binding NetphoneCLMGRStatusText}"/>
</Border>
</StackPanel>
</StackPanel>
<!-- Right Status Column -->
<StackPanel Grid.Column="1">
<!-- Teams Meeting Status -->
<StackPanel Orientation="Horizontal" Margin="0,8">
<TextBlock Text="Teams Meeting" Width="180" FontWeight="Bold" Foreground="White"/>
<Border Style="{StaticResource StatusBorder}"
Background="{Binding TeamsMeetingStatusBackground}">
<TextBlock Style="{StaticResource StatusText}" Text="{Binding TeamsMeetingStatusText}"/>
</Border>
</StackPanel>
<!-- NetPhone Call Status -->
<StackPanel Orientation="Horizontal" Margin="0,8">
<TextBlock Text="NetPhone Call" Width="180" FontWeight="Bold" Foreground="White"/>
<Border Style="{StaticResource StatusBorder}"
Background="{Binding NetphoneCallStatusBackground}">
<TextBlock Style="{StaticResource StatusText}" Text="{Binding NetphoneCallStatusText}"/>
</Border>
</StackPanel>
</StackPanel>
</Grid>
<!-- Log Section -->
<Border Grid.Row="2" Margin="15,0,15,10" Background="#FF252526" CornerRadius="8">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="Activity Log"
FontSize="18"
FontWeight="Bold"
Foreground="White"
Margin="15,10"/>
<ListView x:Name="Log" Grid.Row="1"
ItemsSource="{Binding LogEntries}"
Margin="10,0,10,10"
VirtualizingPanel.IsVirtualizing="True"
IsHitTestVisible="False">
<ListView.View>
<GridView>
<GridViewColumn Width="80" Header="Time">
<GridViewColumn.DisplayMemberBinding>
<Binding Path="Time" StringFormat="{}{0:HH:mm:ss}"/>
</GridViewColumn.DisplayMemberBinding>
</GridViewColumn>
<GridViewColumn Width="60" Header="Type">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Type}" Foreground="White">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#FF0078D4"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="Warn">
<Setter Property="Foreground" Value="Orange"/>
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="Error">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="120" Header="Subsystem">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Subsystem}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Subsystem}" Value="Netphone">
<Setter Property="Foreground" Value="#E20074"/>
</DataTrigger>
<DataTrigger Binding="{Binding Subsystem}" Value="TeamsGraphAPI">
<Setter Property="Foreground" Value="#4E5FBF"/>
</DataTrigger>
<DataTrigger Binding="{Binding Subsystem}" Value="TeamsLocalAPI">
<Setter Property="Foreground" Value="#4E5FBF"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="Auto" Header="Message"
DisplayMemberBinding="{Binding Message}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Border>
<!-- Footer -->
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="15,0,0,8">
<Button Click="SettingsButton_Click"
Padding="8"
FontSize="18"
VerticalAlignment="Center"
ToolTip="Settings">
<Button.Content>
<Path Data="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.68 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"
Fill="White" Stretch="Uniform"/>
</Button.Content>
</Button>
<TextBlock Text="Entwickelt von Jan Krampitz"
VerticalAlignment="Center"
Foreground="White"
FontSize="10"
Margin="15,0"/>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,95 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using TeamsNetphoneLink.WPF.MVVM;
namespace TeamsNetphoneLink.WPF
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
///
public partial class DashboardWindow : Window
{
private bool isDarkMode = true;
private Syncronisation sync;
private Task<bool> graphAuthenticationTask;
public DashboardViewModel _dashboardViewModel { get; }
public DashboardWindow(Syncronisation _syncronisation)
{
InitializeComponent();
sync = _syncronisation;
_dashboardViewModel = sync._dashboardViewModel;
DataContext = _dashboardViewModel;
// Set the ViewModel instance & Set the DataContext to the ViewModel
//Set the DataContext to the ViewModel
Log.DataContext = sync.LogEntries;
}
private void ToggleDarkMode(object sender, RoutedEventArgs e)
{
isDarkMode = !isDarkMode;
if (isDarkMode)
{
this.Style = (Style)FindResource("DarkModeStyle");
}
else
{
this.Style = (Style)FindResource("LightModeStyle");
}
}
private void SettingsButton_Click(object sender, RoutedEventArgs e)
{
// Einstellungsfenster öffnen
SettingsWindow settingsWindow = new SettingsWindow(sync);
settingsWindow.ShowDialog();
}
private async void TeamsGraphAPIBorder_MouseDown(object sender, MouseButtonEventArgs e)
{
if (!Teams.TeamsGraph.IsGuid(Settings.Default.AppID) || !Teams.TeamsGraph.IsGuid(Settings.Default.TenantID))
{
SettingsWindow settingsWindow = new SettingsWindow(sync);
settingsWindow.ShowDialog();
return;
};
if (sync.TeamsGraphAuthenticationSemaphore.CurrentCount == 0)
return;
bool saveCredentials = Settings.Default.SaveEntraCredentials;
if (!sync.TeamsGraph.Authenticated)
{
await sync.TeamsGraphAuthenticationSemaphore.WaitAsync();
_ = await sync.GraphAuthenticate(saveCredentials);
sync.TeamsGraphAuthenticationSemaphore.Release();
}
}
private void TeamsLocalAPIBorder_MouseDown(object sender, MouseButtonEventArgs e)
{
if (!Settings.Default.TeamsPermission)
{
var setupLocalTeams = new WPF.SetupLocalTeamsAPI(sync);
setupLocalTeams.ShowDialog();
}
}
private void LogListView_UpdateColumnsWidth(object sender, SizeChangedEventArgs e)
{
ListView _ListView = sender as ListView;
GridView _GridView = _ListView.View as GridView;
var _ActualWidth = _ListView.ActualWidth - SystemParameters.VerticalScrollBarWidth;
for (Int32 i = 1; i < _GridView.Columns.Count; i++)
{
_ActualWidth = _ActualWidth - _GridView.Columns[i].ActualWidth;
}
_GridView.Columns[0].Width = _ActualWidth;
}
}
}

View File

@ -0,0 +1,123 @@
<Window x:Class="TeamsNetphoneLink.WPF.ExceptionWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Fehlerbericht" Height="400" Width="600"
Background="#FF1E1E1E" FontFamily="Segoe UI"
WindowStyle="ToolWindow" ResizeMode="NoResize"
WindowStartupLocation="CenterScreen" Topmost="True">
<!-- Dark Mode Resources -->
<Window.Resources>
<!-- Button Style -->
<Style TargetType="Button">
<Setter Property="Background" Value="#FF0078D4"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Padding" Value="10,5"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="4"
Padding="{TemplateBinding Padding}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="OverridesDefaultStyle" Value="True"/>
</Style>
<!-- TextBox Style -->
<Style TargetType="TextBox">
<Setter Property="Background" Value="#FF252526"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="#FF3E3E40"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="3,0,0,0"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4">
<ScrollViewer x:Name="PART_ContentHost" Margin="{TemplateBinding Padding}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- TextBlock Style -->
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</Window.Resources>
<Grid Margin="10">
<!-- Fehlertext -->
<TextBlock x:Name="ErrorText"
Text="Es ist ein Fehler aufgetreten!"
FontSize="18"
FontWeight="Bold"
Margin="10,10,10,20"
VerticalAlignment="Top"
Foreground="White"/>
<!-- Modul Feld -->
<StackPanel Orientation="Horizontal" Margin="10,50,10,0">
<TextBlock Text="Modul:" VerticalAlignment="Top" Width="60" Foreground="White" Margin="0,15,0,0"/>
<TextBox x:Name="ModuleText"
FontSize="14"
VerticalAlignment="Top"
Width="480"
Margin="5,5,5,0"
IsEnabled="False"
Background="#FF252526"
Foreground="White" Text="tetet" Height="37"/>
</StackPanel>
<!-- Scrollbare Fehlerdetails -->
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
Margin="10,100,10,50">
<TextBox x:Name="TraceText"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
IsReadOnly="True"
FontSize="14"
Padding="3,3,0,0"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Top"
Height="214"
Background="#FF252526"
Foreground="White" Text="tttt">
<!-- Beispiel für Fehlerdetails -->
</TextBox>
</ScrollViewer>
<!-- Button zum Schließen -->
<Button Content="Schließen"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Width="100"
Margin="10,0,0,10"
Click="CloseButton_Click"/>
<!-- Button zum Issue melden -->
<Button Content="Issue melden"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="120"
Margin="0,0,10,10"
Click="ReportButton_Click"/>
</Grid>
</Window>

View File

@ -0,0 +1,67 @@
using System.Diagnostics;
using System.Media;
using System.Windows;
namespace TeamsNetphoneLink.WPF
{
/// <summary>
/// Interaktionslogik für ExceptionWindow.xaml
/// </summary>
public partial class ExceptionWindow : Window
{
public ExceptionWindow(string module, string trace)
{
InitializeComponent();
ModuleText.Text = module;
TraceText.Text = trace;
SystemSounds.Hand.Play();
}
// Button zum Schließen
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
// Button zum Issue melden
private void ReportButton_Click(object sender, RoutedEventArgs e)
{
// Fehlerdetails aus der TextBox holen
string moduleDetails = ModuleText.Text;
string errorDetails = TraceText.Text;
// URL für das GitHub Issue, Fehlerdetails werden als Parameter angehängt
string url = $"https://git.jan.sx/krjan02/TeamsNetphoneLink/issues/new?body={Uri.EscapeDataString(String.Format("Modul: {0}\n\nTrace:\n{1}", moduleDetails, errorDetails))}";
// GitHub im Standardbrowser öffnen
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
}
}
public static class ExceptionWindowHelper
{
public static void Show(string classname, string method, string message, string trace)
{
var modulestr = String.Format("{0} - {1}", classname, method);
var errorstr = String.Format("{0}\n{1}", message, trace);
// Use the Dispatcher to show the window on the UI thread
Application.Current.Dispatcher.InvokeAsync(() =>
{
new ExceptionWindow(modulestr, errorstr).Show();
});
}
public static void Show(string classname, string method, string message, string tracemessage, string trace)
{
var modulestr = String.Format("{0} - {1}", classname, method);
var errorstr = String.Format("{0}\n{1}\n{2}", message, tracemessage, trace);
// Use the Dispatcher to show the window on the UI thread
Application.Current.Dispatcher.InvokeAsync(() =>
{
new ExceptionWindow(modulestr, errorstr).Show();
});
}
}
}

View File

@ -0,0 +1,250 @@
using System.ComponentModel;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Media;
namespace TeamsNetphoneLink.WPF.MVVM
{
public static class StatusPanelNames
{
public const string TeamsLocalAPIStatus = nameof(TeamsLocalAPIStatus);
public const string TeamsGraphAPIStatus = nameof(TeamsGraphAPIStatus);
public const string NetphoneCLMGRStatus = nameof(NetphoneCLMGRStatus);
public const string TeamsMeetingStatus = nameof(TeamsMeetingStatus);
public const string NetphoneCallStatus = nameof(NetphoneCallStatus);
}
public class DashboardViewModel : INotifyPropertyChanged
{
// Parameterloser Konstruktor
public DashboardViewModel()
{
// Initialisieren Sie hier Standardwerte, falls erforderlich
TeamsLocalAPIStatusText = "Nicht verbunden";
TeamsLocalAPIStatusBackground = new SolidColorBrush(Colors.Red);
TeamsGraphAPIStatusText = "Konfiguration fehlt";
TeamsGraphAPIStatusBackground = new SolidColorBrush(Colors.Red);
NetphoneCLMGRStatusText = "Nicht verbunden";
NetphoneCLMGRStatusBackground = new SolidColorBrush(Colors.Red);
TeamsMeetingStatusText = "Not Active";
TeamsMeetingStatusBackground = new SolidColorBrush(Colors.DarkOrange);
NetphoneCallStatusText = "Not Active";
NetphoneCallStatusBackground = new SolidColorBrush(Colors.DarkOrange);
AppTitle2 = "Teams Netphone Link";
VersionText = "Aktuell";
VersionVisibility = Visibility.Visible;
VersionBackground = new SolidColorBrush(Colors.Green);
}
private string _appTitle;
private string _versionText;
private Visibility _versionVisibility;
private Brush _versionBackground;
private string _teamsLocalAPIStatusText;
private Brush _teamsLocalAPIStatusBackground;
private string _teamsGraphAPIStatusText;
private Brush _teamsGraphAPIStatusBackground;
private string _netphoneCLMGRStatusText;
private Brush _netphoneCLMGRStatusBackground;
private string _teamsMeetingStatusText;
private Brush _teamsMeetingStatusBackground;
private string _netphoneCallStatusText;
private Brush _netphoneCallStatusBackground;
public string TeamsLocalAPIStatusText
{
get => _teamsLocalAPIStatusText;
set
{
_teamsLocalAPIStatusText = value;
OnPropertyChanged();
}
}
public Brush TeamsLocalAPIStatusBackground
{
get => _teamsLocalAPIStatusBackground;
set
{
_teamsLocalAPIStatusBackground = value;
OnPropertyChanged();
}
}
public string TeamsGraphAPIStatusText
{
get => _teamsGraphAPIStatusText;
set
{
_teamsGraphAPIStatusText = value;
OnPropertyChanged();
}
}
public Brush TeamsGraphAPIStatusBackground
{
get => _teamsGraphAPIStatusBackground;
set
{
_teamsGraphAPIStatusBackground = value;
OnPropertyChanged();
}
}
public string NetphoneCLMGRStatusText
{
get => _netphoneCLMGRStatusText;
set
{
_netphoneCLMGRStatusText = value;
OnPropertyChanged();
}
}
public Brush NetphoneCLMGRStatusBackground
{
get => _netphoneCLMGRStatusBackground;
set
{
_netphoneCLMGRStatusBackground = value;
OnPropertyChanged();
}
}
public string TeamsMeetingStatusText
{
get => _teamsMeetingStatusText;
set
{
_teamsMeetingStatusText = value;
OnPropertyChanged();
}
}
public Brush TeamsMeetingStatusBackground
{
get => _teamsMeetingStatusBackground;
set
{
_teamsMeetingStatusBackground = value;
OnPropertyChanged();
}
}
public string NetphoneCallStatusText
{
get => _netphoneCallStatusText;
set
{
_netphoneCallStatusText = value;
OnPropertyChanged();
}
}
public Brush NetphoneCallStatusBackground
{
get => _netphoneCallStatusBackground;
set
{
_netphoneCallStatusBackground = value;
OnPropertyChanged();
}
}
public string AppTitle2
{
get => _appTitle;
set
{
Application.Current.Dispatcher.InvokeAsync(() =>
{
_appTitle = value;
OnPropertyChanged();
});
}
}
public string VersionText
{
get => _versionText;
set
{
Application.Current.Dispatcher.InvokeAsync(() =>
{
_versionText = value;
OnPropertyChanged();
});
}
}
public Visibility VersionVisibility
{
get => _versionVisibility;
set
{
Application.Current.Dispatcher.InvokeAsync(() =>
{
_versionVisibility = value;
OnPropertyChanged();
});
}
}
public Brush VersionBackground
{
get => _versionBackground;
set
{
Application.Current.Dispatcher.InvokeAsync(() =>
{
_versionBackground = value;
OnPropertyChanged();
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// Method to update status panel
public void UpdateStatusPanel(string statusPanelName, Color color, string text)
{
Application.Current.Dispatcher.InvokeAsync(() =>
{
// Get the type of the ViewModel
var type = GetType();
// Construct property names for Text and Background
string textPropertyName = $"{statusPanelName}Text";
string backgroundPropertyName = $"{statusPanelName}Background";
// Get the properties using reflection
PropertyInfo textProperty = type.GetProperty(textPropertyName); //CS8600
PropertyInfo backgroundProperty = type.GetProperty(backgroundPropertyName); //CS8600
if (textProperty != null && backgroundProperty != null)
{
// Update the Text property
textProperty.SetValue(this, text);
// Update the Background property
backgroundProperty.SetValue(this, new SolidColorBrush(color));
}
else
{
throw new ArgumentException($"Invalid status panel name: {statusPanelName}");
}
});
}
}
}

View File

@ -0,0 +1,94 @@
using Emoji.Wpf;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Threading;
namespace TeamsNetphoneLink.WPF.MVVM
{
public class FinishPanelViewModel : INotifyPropertyChanged
{
// Parameterloser Konstruktor
public FinishPanelViewModel()
{
// Initialisieren Sie hier Standardwerte, falls erforderlich
FinishPanelEnabled = false;
FinishPanelFinishTextColor = new SolidColorBrush(Colors.DarkOrange);
FinishPanelFinishTextText = "Authentifizierung ausstehend...";
FinishPanelEffect = new TintEffect { Tint = Colors.DarkGray };
}
private bool _finishPanelEnabled;
private Brush _finishPanelFinishTextColor;
private string _finishPanelFinishTextText;
private Effect _finishPanelEffect;
public bool FinishPanelEnabled
{
get => _finishPanelEnabled;
set
{
System.Windows.Application.Current.Dispatcher.Invoke(delegate
{
_finishPanelEnabled = value;
OnPropertyChanged();
});
}
}
public Brush FinishPanelFinishTextColor
{
get => _finishPanelFinishTextColor;
set
{
System.Windows.Application.Current.Dispatcher.Invoke(delegate
{
_finishPanelFinishTextColor = value;
OnPropertyChanged();
});
}
}
public string FinishPanelFinishTextText
{
get => _finishPanelFinishTextText;
set
{
System.Windows.Application.Current.Dispatcher.Invoke(delegate
{
_finishPanelFinishTextText = value;
OnPropertyChanged();
});
}
}
public Effect FinishPanelEffect
{
get => _finishPanelEffect;
set
{
System.Windows.Application.Current.Dispatcher.Invoke(delegate
{
_finishPanelEffect = value;
OnPropertyChanged();
});
}
}
public void SetFinishPanelFinishTextColor(Color color)
{
System.Windows.Application.Current.Dispatcher.Invoke(delegate
{
FinishPanelFinishTextColor = new SolidColorBrush((Color)color);
});
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@ -0,0 +1,75 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace TeamsNetphoneLink.WPF.MVVM
{
public class LogEntry
{
public DateTime Time { get; set; }
public string Subsystem { get; set; }
public string Type { get; set; }
public string Message { get; set; }
}
public class LogViewModel : INotifyPropertyChanged
{
// Collection to hold log entries
public ObservableCollection<LogEntry> LogEntries { get; } = new ObservableCollection<LogEntry>();
// Add a log entry
public void AddLogEntry(string entry)
{
Application.Current.Dispatcher.InvokeAsync(() =>
{
LogEntries.Add(new LogEntry { Time = DateTime.Now, Type = "Info", Subsystem = "NONE", Message = entry });
});
}
// Add a log entry
public void AddLogEntry(string subsystem, string entry)
{
Application.Current.Dispatcher.InvokeAsync(() =>
{
LogEntries.Add(new LogEntry { Time = DateTime.Now, Type = "Info", Subsystem = subsystem, Message = entry });
});
}
// Add a log entry
public void AddLogEntry(string type, string subsystem, string entry)
{
Application.Current.Dispatcher.InvokeAsync(() =>
{
LogEntries.Add(new LogEntry { Time = DateTime.Now, Type = type, Subsystem = subsystem, Message = entry });
});
}
public void AddLogEntry(DateTime time, string subsystem, string message)
{
Application.Current.Dispatcher.InvokeAsync(() =>
{
LogEntries.Add(new LogEntry { Time = time, Type = "Info", Subsystem = subsystem, Message = message });
});
}
// Clear all log entries
public void ClearLogEntries()
{
Application.Current.Dispatcher.InvokeAsync(() =>
{
LogEntries.Clear();
});
}
// Implement INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@ -0,0 +1,143 @@
<Window x:Class="TeamsNetphoneLink.WPF.SettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Einstellungen" Height="373" Width="500"
WindowStyle="ToolWindow" ResizeMode="NoResize"
Background="#FF1E1E1E" FontFamily="Segoe UI"
WindowStartupLocation="CenterScreen">
<!-- Dark Mode Resources -->
<Window.Resources>
<!-- Button Style -->
<Style TargetType="Button">
<Setter Property="Background" Value="#FF0078D4"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Padding" Value="10,5"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="border"
Background="{TemplateBinding Background}"
CornerRadius="4"
Padding="{TemplateBinding Padding}"
Opacity="1">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<!-- Hover Effect -->
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="Background" Value="#0066B3"/>
</Trigger>
<!-- Pressed Effect -->
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="border" Property="Background" Value="#004D86"/>
<Setter TargetName="border" Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="0.98" ScaleY="0.98"/>
</Setter.Value>
</Setter>
</Trigger>
<!-- Disabled State -->
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="border" Property="Background" Value="#3A3A3A"/>
<Setter TargetName="border" Property="Opacity" Value="0.7"/>
<Setter Property="Foreground" Value="#A0A0A0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="OverridesDefaultStyle" Value="True"/>
</Style>
<!-- TextBox Style -->
<Style TargetType="TextBox">
<Setter Property="Background" Value="#FF252526"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="#FF3E3E40"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Padding" Value="3,0,0,0"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4">
<ScrollViewer x:Name="PART_ContentHost" Margin="0,0,0,0"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- CheckBox Style -->
<Style TargetType="CheckBox">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<!-- TextBlock Style -->
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</Window.Resources>
<Grid Margin="20">
<StackPanel Orientation="Vertical">
<!-- Title -->
<TextBlock Text="Einstellungen" FontSize="24" FontWeight="SemiBold" Margin="0,0,0,20"/>
<!-- Microsoft 365 Tenant ID -->
<StackPanel Orientation="Horizontal" Margin="0,10">
<TextBlock Text="Tenant ID" VerticalAlignment="Center" Width="70" FontSize="14"/>
<TextBox x:Name="TenantIDTextBox" Width="372" Height="30" FontSize="14" TextChanged="TextBox_TextChanged" Text="dddddd"/>
</StackPanel>
<!-- Microsoft 365 App ID -->
<StackPanel Orientation="Horizontal" Margin="0,10">
<TextBlock Text="App ID" VerticalAlignment="Center" Width="70" FontSize="14"/>
<TextBox x:Name="AppIDTextBox" Width="372" Height="30" TextChanged="TextBox_TextChanged"/>
</StackPanel>
<!-- Toggle SaveEntraCredentials -->
<StackPanel Orientation="Horizontal" Margin="0,10">
<CheckBox x:Name="SaveEntraCredentialsCheck" VerticalAlignment="Center"/>
<TextBlock Text="Entra Login speichern" Width="137" FontSize="14" Margin="10,0,0,0"/>
</StackPanel>
<!-- Toggle UseGraphForMeetingState -->
<StackPanel Orientation="Horizontal" Margin="0,10" ToolTip="Nutzt statt der lokalen Teams API die Graph API um festzustellen das ein Meeting läuft">
<CheckBox x:Name="UseGraphForMeetingStateCheck"/>
<TextBlock Text="Graph für den Meeting Status verwenden" VerticalAlignment="Center" Width="259" FontSize="14" Margin="10,0,0,0"/>
</StackPanel>
<!-- Save and Reset Buttons -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,30,0,0">
<Button x:Name="ResetButton" Background="DarkRed" Content="Alles zurücksetzen" Width="140" Height="35" Margin="0,0,10,0" Click="ResetButton_Click"/>
<Button x:Name="SaveButton" Content="Speichern" Width="100" Height="35" Click="SaveButton_Click" IsEnabled="False"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,132 @@
using Microsoft.Identity.Client.NativeInterop;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using TeamsNetphoneLink;
namespace TeamsNetphoneLink.WPF
{
/// <summary>
/// Interaktionslogik für SettingsWindow.xaml
/// </summary>
public partial class SettingsWindow : Window
{
Syncronisation _sync;
public SettingsWindow(Syncronisation sync)
{
_sync = sync;
InitializeComponent();
TenantIDTextBox.Text = Settings.Default.TenantID;
AppIDTextBox.Text = Settings.Default.AppID;
SaveEntraCredentialsCheck.IsChecked = Settings.Default.SaveEntraCredentials;
UseGraphForMeetingStateCheck.IsChecked = Settings.Default.UseGraphForMeetingState;
}
private void RestartApplication()
{
string exePath = Process.GetCurrentProcess().MainModule.FileName;
var startInfo = new ProcessStartInfo
{
FileName = exePath,
UseShellExecute = true
};
Process.Start(startInfo);
Application.Current.Shutdown();
}
// Speichern der Einstellungen
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
string tenantId = TenantIDTextBox.Text.Replace(" ", "");
string appId = AppIDTextBox.Text.Replace(" ", "");
bool SaveEntraCredentials = SaveEntraCredentialsCheck.IsChecked.HasValue ? SaveEntraCredentialsCheck.IsChecked.Value : false;
bool UseGraphForMeetingState = UseGraphForMeetingStateCheck.IsChecked.HasValue ? UseGraphForMeetingStateCheck.IsChecked.Value : false;
if (SaveEntraCredentials && !ShowSecurityWarningMessage())
return;
Settings.Default.TenantID = tenantId;
Settings.Default.AppID = appId;
Settings.Default.SaveEntraCredentials = SaveEntraCredentials;
Settings.Default.UseGraphForMeetingState = UseGraphForMeetingState;
Settings.Default.Save();
RestartApplication();
}
// Reset der Einstellungen
private void ResetButton_Click(object sender, RoutedEventArgs e)
{
Settings.Default.Reset();
RestartApplication();
}
private void SaveEntraCredentialsCheck_Checked(object sender, RoutedEventArgs e)
{
//ShowSecurityWarningMessage();
}
public bool ShowSecurityWarningMessage()
{
string message =
"Die Anmeldedaten werden mit dem MSAL Cache unter Verwendung von DPAPI (Data Protection API) gespeichert. " +
"DPAPI bietet eine sichere Verschlüsselung der Daten auf Benutzerebene, sodass die Anmeldedaten nur für das Benutzerkonto zugänglich sind, " +
"unter dem sie gespeichert wurden.\n\n" +
"Allerdings ist zu beachten, dass theoretisch jede Anwendung mit derselben App-ID auf den MSAL Cache zugreifen könnte. " +
"Das liegt daran, dass der Cache anhand der App-ID identifiziert wird. Wenn eine andere Anwendung dieselbe App-ID verwendet, " +
"könnte sie versuchen, auf die gespeicherten Anmeldedaten zuzugreifen.\n\n" +
"Bewertung des Sicherheitsrisikos:\n" +
"- In einer vertrauenswürdigen Umgebung ist das Risiko gering.\n" +
"- Die App-ID sollte geheim gehalten werden, um unbefugten Zugriff zu verhindern.\n" +
"- DPAPI bietet eine starke Verschlüsselung, solange das Benutzerkonto nicht kompromittiert ist.\n\n" +
"Unter normalen Umständen stellt die Verwendung des MSAL Caches kein signifikantes Sicherheitsrisiko dar. " +
"Es ist jedoch wichtig, die App-ID geheim zu halten und sicherzustellen, dass nur vertrauenswürdige Anwendungen dieselbe App-ID verwenden.\n\n" +
"Sollen die Einstellungen gespeichert werden?.\n\n";
var result = MessageBox.Show(
message,
"Sicherheitshinweis zur Speicherung von Anmeldedaten",
MessageBoxButton.YesNo,
MessageBoxImage.Warning
);
if (result == MessageBoxResult.Yes)
return true;
return false;
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
ValidateInput();
}
private void ValidateInput()
{
if (this.IsInitialized)
{
bool isAppIDValid = Teams.TeamsGraph.IsGuid(AppIDTextBox.Text);
bool isTenantIDValid = Teams.TeamsGraph.IsGuid(TenantIDTextBox.Text);
// Set border color based on validation
AppIDTextBox.BorderBrush = isAppIDValid ? System.Windows.Media.Brushes.Green : System.Windows.Media.Brushes.Red;
TenantIDTextBox.BorderBrush = isTenantIDValid ? System.Windows.Media.Brushes.Green : System.Windows.Media.Brushes.Red;
// Enable Save button only if both fields are valid
SaveButton.IsEnabled = isAppIDValid && isTenantIDValid;
}
}
}
}

View File

@ -0,0 +1,83 @@
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Wpf="clr-namespace:Emoji.Wpf;assembly=Emoji.Wpf" xmlns:local="clr-namespace:TeamsNetphoneLink.WPF.MVVM" x:Class="TeamsNetphoneLink.WPF.SetupLocalTeamsAPI"
WindowStyle="ToolWindow" ResizeMode="NoResize"
Background="#FF1E1E1E" FontFamily="Segoe UI"
Title="Teams API Integration" Height="497" Width="525">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Background" Value="#FF0078D4"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Padding" Value="10,5"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="4"
Padding="{TemplateBinding Padding}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="OverridesDefaultStyle" Value="True"/>
</Style>
<!-- TextBlock Style -->
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<TextBlock Text="Bitte folgende Schritte durchführen, um die Teams API zu aktivieren und damit die Anwendung zu genehmigen." Margin="10" Height="45" TextWrapping="Wrap" TextAlignment="Center" FontWeight="Bold" FontSize="16"/>
<StackPanel x:Name="Step1Panel" Margin="10">
<TextBlock Text="1. Aktivieren der Teams Drittanbieter API:"/>
<!-- Hyperlink in einem TextBlock unterbringen -->
<TextBlock>
<Hyperlink x:Name="Step1Link" NavigateUri="https://support.microsoft.com/de-de/office/herstellen-einer-verbindung-mit-drittanbieterger%C3%A4ten-in-microsoft-teams-aabca9f2-47bb-407f-9f9b-81a104a883d6" RequestNavigate="Step1Link_RequestNavigate">
<Run Text="Anleitung für die Aktivierung der Drittanbieter API"/>
</Hyperlink>
</TextBlock>
<Button x:Name="Step1Button" Content="Drittanbieter-API aktiviert" Width="200" Margin="10" Click="Step1Button_Click"/>
</StackPanel>
<StackPanel x:Name="Step2Panel" Margin="10" Height="61" IsEnabled="False" >
<StackPanel.Effect >
<Wpf:TintEffect Tint="#FFA4A0A0"/>
</StackPanel.Effect>
<TextBlock Text="2. Starten Sie ein Teams Meeting mit sich selbst:"/>
<Button x:Name="Step2Button" Content="Teams Meeting ist gestartet" Width="200" Margin="10" Click="Step2Button_Click"/>
<TextBlock x:Name="Step2Status" Margin="10"/>
</StackPanel>
<!-- Schritt 3: Authentifizieren -->
<StackPanel x:Name="Step3Panel" Margin="10" IsEnabled="False" >
<StackPanel.Effect >
<Wpf:TintEffect Tint="#FFA4A0A0"/>
</StackPanel.Effect>
<TextBlock Text="3. Zulassen der Verbindungsanforderung:"/>
<TextBlock Margin="10,0,10,0" Height="34" TextWrapping="Wrap" TextAlignment="Center" FontSize="12"><Run Text="Nach dem Klick auf dem Button wird in Teams eine Anforderung sichtbar, diese bitte zulassen"/></TextBlock>
<Button x:Name="Step3Button" Content="Verbindungsanforderung senden" Width="227" Margin="10" Click="Step3Button_Click"/>
</StackPanel>
<!-- Schritt 3: Authentifizieren -->
<StackPanel x:Name="FinishPanel" Margin="10" IsEnabled="{Binding FinishPanelEnabled}" Effect="{Binding FinishPanelEffect}" >
<TextBlock Foreground="{Binding FinishPanelFinishTextColor}" Text="{Binding FinishPanelFinishTextText}" FontWeight="Bold" HorizontalAlignment="Center"/>
<Button x:Name="FinishButton" Content="Abschließen" Width="200" Margin="10" Click="FinishButton_Click"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,83 @@
using Emoji.Wpf;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Policy;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using TeamsNetphoneLink.WPF.MVVM;
namespace TeamsNetphoneLink.WPF
{
/// <summary>
/// Interaktionslogik für SetupLocalTeamsAPI.xaml
/// </summary>
public partial class SetupLocalTeamsAPI : Window
{
private Teams.TeamsLocalAPI teamsLocalAPI;
public FinishPanelViewModel finishPanelViewModel { get; }
public SetupLocalTeamsAPI(Syncronisation sync)
{
teamsLocalAPI = sync.TeamsEvents;
finishPanelViewModel = sync.FinishPanelViewModel;
DataContext = finishPanelViewModel;
InitializeComponent();
}
private void Step1Link_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
// Öffnet den Link, um die Teams API zu aktivieren
Process.Start(new ProcessStartInfo(e.Uri.ToString().Replace("&", "^&")) { UseShellExecute = true });
e.Handled = true;
}
private void Step1Button_Click(object sender, RoutedEventArgs e)
{
// Schritt 1 abgeschlossen - Schalte auf den nächsten Schritt
Step1Panel.IsEnabled = false;
Step2Panel.IsEnabled = true;
Step1Panel.Effect = new TintEffect { Tint = Colors.DarkGray };
Step2Panel.Effect = null;
}
// Schritt 2: Teams Meeting starten
private void Step2Button_Click(object sender, RoutedEventArgs e)
{
// Öffne die URL, um ein Meeting zu starten (Hier muss der genaue Link oder die API-Integration erfolgen)
Step2Panel.IsEnabled = false;
Step3Panel.IsEnabled = true;
Step2Panel.Effect = new TintEffect { Tint = Colors.DarkGray };
Step3Panel.Effect = null;
}
// Schritt 3: Authentifizierung durchführen
private async void Step3Button_Click(object sender, RoutedEventArgs e)
{
// Simuliere den Authentifizierungsprozess
Step3Button.Content = "Authentifizierung gesendet...";
Step3Button.IsEnabled = false;
this.finishPanelViewModel.FinishPanelFinishTextText = "Authentifizierung läuft...";
this.finishPanelViewModel.FinishPanelFinishTextColor = new SolidColorBrush(Colors.Orange);
teamsLocalAPI.SendDummyCommand();
}
private void FinishButton_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
}
}

View File

@ -0,0 +1,183 @@
<Window x:Class="TeamsNetphoneLink.WPF.UpdateWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Update verfügbar" Height="514" Width="833"
Background="#FF1E1E1E" FontFamily="Segoe UI"
WindowStyle="ToolWindow" ResizeMode="NoResize"
WindowStartupLocation="CenterScreen" Closing="Window_Closing">
<!-- Dark Mode Resources -->
<Window.Resources>
<!-- Button Style -->
<Style TargetType="Button">
<Setter Property="Background" Value="#FF0078D4"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Padding" Value="10,5"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="4"
Padding="{TemplateBinding Padding}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="OverridesDefaultStyle" Value="True"/>
</Style>
<!-- Special Style for Markdown TextBox -->
<Style x:Key="MarkdownTextBoxStyle" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="Foreground" Value="#FFD4D4D4"/>
<Setter Property="Background" Value="#FF1E1E1E"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="HorizontalScrollBarVisibility" Value="Disabled"/>
</Style>
<!-- TextBox Style -->
<Style TargetType="TextBox">
<Setter Property="Background" Value="#FF252526"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderBrush" Value="#FF3E3E40"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="5"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4">
<ScrollViewer x:Name="PART_ContentHost" Margin="{TemplateBinding Padding}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ProgressBar Style -->
<Style TargetType="ProgressBar">
<Setter Property="Background" Value="#FF252526"/>
<Setter Property="BorderBrush" Value="#FF3E3E40"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Foreground" Value="#FF0078D4"/>
<Setter Property="Height" Value="10"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar">
<Grid x:Name="Root">
<Border x:Name="PART_Track"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="5"/>
<Border x:Name="PART_Indicator"
CornerRadius="5"
HorizontalAlignment="Left">
<Border.Background>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="#FF0078D4" Offset="0"/>
<GradientStop Color="#FF00B4FF" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- TextBlock Style -->
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<!-- Title TextBlock Style -->
<Style x:Key="TitleTextStyle" TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="FontSize" Value="18"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Margin" Value="10,10,10,20"/>
</Style>
<!-- Status TextBlock Style -->
<Style x:Key="StatusTextStyle" TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="Margin" Value="10,0,10,5"/>
<Setter Property="Foreground" Value="#FFD4D4D4"/>
</Style>
</Window.Resources>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Title -->
<TextBlock x:Name="TitleText"
Text="Es ist ein neues Update verfügbar"
Style="{StaticResource TitleTextStyle}"
Grid.Row="0"/>
<!-- Markdown Release Notes -->
<Border Grid.Row="1"
Margin="10,0,10,10"
Background="#FF252526"
CornerRadius="4"
BorderBrush="#FF3E3E40"
BorderThickness="1">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Padding="5">
<TextBox x:Name="ReleaseNotesTextBox"
Style="{StaticResource MarkdownTextBoxStyle}"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Top"
Text="Lade Release Notes..."/>
</ScrollViewer>
</Border>
<!-- Progress Status -->
<TextBlock x:Name="ProgressStatusText"
Text=""
Style="{StaticResource StatusTextStyle}"
Grid.Row="2"/>
<!-- Progress Bar -->
<ProgressBar x:Name="UpdateProgress"
Margin="10,0,10,10"
Grid.Row="3"/>
<!-- Buttons -->
<StackPanel Grid.Row="4"
HorizontalAlignment="Right"
Orientation="Horizontal"
Margin="0,10,0,0">
<Button x:Name="CloseButton"
Content="Schließen"
Width="100"
Margin="0,0,10,0"
Click="CloseButton_Click"/>
<Button x:Name="UpdateButton"
Content="Update durchführen"
Width="150"
Background="Green"
Click="UpdateButton_Click"/>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,229 @@
using System.IO;
using System.IO.Compression;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Windows;
namespace TeamsNetphoneLink.WPF
{
/// <summary>
/// Interaktionslogik für ExceptionWindow.xaml
/// </summary>
public partial class UpdateWindow : Window
{
private bool _updateRunning = false;
private string _currentVersion;
private string _latestVersion;
public UpdateWindow(string currentVersion, string latestVersion)
{
InitializeComponent();
// Step 1: Check for updates
_currentVersion = currentVersion;
_latestVersion = latestVersion;
TitleText.Text = $"Es ist ein Update von {currentVersion} auf {latestVersion} verfügbar";
LoadReleaseNotesAsync();
}
private async void LoadReleaseNotesAsync()
{
try
{
var notes = await GetReleaseNotes();
Dispatcher.Invoke(() =>
{
ReleaseNotesTextBox.Text = notes;
});
}
catch (Exception ex)
{
Dispatcher.Invoke(() =>
{
ReleaseNotesTextBox.Text = $"Failed to load release notes: {ex.Message}";
});
}
}
// Button zum Schließen
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
// Button zum Updaten
private void UpdateButton_Click(object sender, RoutedEventArgs e)
{
this._updateRunning = true;
this.UpdateButton.IsEnabled = false;
this.CloseButton.IsEnabled = false;
_ = UpdateApplicationAsync();
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (this._updateRunning)
{
e.Cancel = true;
}
}
private void StartUpdater(string tempExtractPath)
{
string appPath = AppDomain.CurrentDomain.BaseDirectory;
string updaterPathEXE = Path.Combine(appPath, "TeamsNetphoneLinkUpdater.exe");
string updaterPathDLL = Path.Combine(appPath, "TeamsNetphoneLinkUpdater.dll");
string newUpdaterPathEXE = Path.Combine(tempExtractPath, "TeamsNetphoneLinkUpdater.exe");
string newUpdaterPathDLL = Path.Combine(tempExtractPath, "TeamsNetphoneLinkUpdater.dll");
//Replace the updater if it exists in the new release
if (File.Exists(newUpdaterPathEXE))
{
File.Copy(newUpdaterPathEXE, updaterPathEXE, true);
}
if (File.Exists(newUpdaterPathDLL))
{
File.Copy(newUpdaterPathDLL, updaterPathDLL, true);
}
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = updaterPathEXE;
p.StartInfo.ArgumentList.Add(appPath);
p.StartInfo.ArgumentList.Add(tempExtractPath);
p.StartInfo.UseShellExecute = true; // This will start the process in a new shell
p.Start();
// Exit the application
Application.Current.Shutdown();
}
private static void UnzipRelease(string zipPath, string extractPath)
{
ZipFile.ExtractToDirectory(zipPath, extractPath);
}
private async Task UpdateApplicationAsync()
{
try
{
if (_latestVersion != _currentVersion)
{
UpdateProgress.Value = 0;
// Step 2: Download the new release
string downloadUrl = $"https://git.jan.sx/krjan02/TeamsNetphoneLink/releases/download/latest/NetphoneTeamsLink{_latestVersion}.zip";
var tempZipPath = Path.GetTempFileName();
tempZipPath = tempZipPath + ".TEAMSNETPHONEUPDATE";
Console.WriteLine(tempZipPath);
await DownloadReleaseAsync(downloadUrl, tempZipPath);
// Step 3: Unzip the release
var tempExtractPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + "TEAMSNETPHONEUPDATE");
Directory.CreateDirectory(tempExtractPath);
UnzipRelease(tempZipPath, tempExtractPath);
File.Delete(tempZipPath);
// Step 4: Replace Updater.exe and run the updater
StartUpdater(tempExtractPath);
}
}
catch (Exception ex)
{
Dispatcher.Invoke(() => ProgressStatusText.Text = $"Error: {ex.Message}");
}
}
private async Task<string> GetReleaseNotes()
{
string apiUrl = $"https://git.jan.sx/api/v1/repos/krjan02/TeamsNetphoneLink/releases/tags/{_latestVersion}";
using (HttpClient client = new HttpClient())
{
// Send a GET request to the Gitea API
HttpResponseMessage response = await client.GetAsync(apiUrl);
response.EnsureSuccessStatusCode();
// Parse the JSON response
string jsonResponse = await response.Content.ReadAsStringAsync();
using (JsonDocument doc = JsonDocument.Parse(jsonResponse))
{
JsonElement root = doc.RootElement;
JsonElement body = root.GetProperty("body");
return body.GetString();
}
}
}
private async Task<long> GetFileSize()
{
string apiUrl = $"https://git.jan.sx/api/v1/repos/krjan02/TeamsNetphoneLink/releases/tags/{_latestVersion}";
using (HttpClient client = new HttpClient())
{
// Send a GET request to the Gitea API
HttpResponseMessage response = await client.GetAsync(apiUrl);
response.EnsureSuccessStatusCode();
// Parse the JSON response
string jsonResponse = await response.Content.ReadAsStringAsync();
using (JsonDocument doc = JsonDocument.Parse(jsonResponse))
{
JsonElement root = doc.RootElement;
JsonElement assets = root.GetProperty("assets");
// Iterate through the assets to find the file
foreach (JsonElement asset in assets.EnumerateArray())
{
string name = asset.GetProperty("name").GetString();
if (name == $"NetphoneTeamsLink{_latestVersion}.zip")
{
long size = asset.GetProperty("size").GetInt64();
return size;
}
}
}
}
return -1;
}
private async Task DownloadReleaseAsync(string downloadUrl, string destinationPath)
{
using (var client = new HttpClient())
{
var response = await client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
var totalBytes = await GetFileSize();
var downloadedBytes = 0L;
var buffer = new byte[8192];
using (var fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None))
using (var stream = await response.Content.ReadAsStreamAsync())
{
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
fileStream.Write(buffer, 0, bytesRead);
downloadedBytes += bytesRead;
// Update progress in the UI
Dispatcher.Invoke(() =>
{
ProgressStatusText.Text = $"Update wird heruntergeladen... ({downloadedBytes/1000}kB/{totalBytes/1000}kB)";
UpdateProgress.Value = (double)downloadedBytes / totalBytes * 100;
//StatusText.Text = $"Downloading... {DownloadProgress.Value:F2}%";
});
}
}
}
}
}
}