diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8afdcb6
--- /dev/null
+++ b/.gitignore
@@ -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
diff --git a/TeamsNetphoneLink.sln b/TeamsNetphoneLink.sln
new file mode 100644
index 0000000..19024a3
--- /dev/null
+++ b/TeamsNetphoneLink.sln
@@ -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
diff --git a/TeamsNetphoneLinkWPF/App.config b/TeamsNetphoneLinkWPF/App.config
new file mode 100644
index 0000000..ed75900
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/App.config
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+ False
+
+
+ False
+
+
+
+
+
+
+
+
+
+
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TeamsNetphoneLinkWPF/App.xaml b/TeamsNetphoneLinkWPF/App.xaml
new file mode 100644
index 0000000..53f520d
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/TeamsNetphoneLinkWPF/App.xaml.cs b/TeamsNetphoneLinkWPF/App.xaml.cs
new file mode 100644
index 0000000..2179704
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/App.xaml.cs
@@ -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
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ 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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/TeamsNetphoneLinkWPF/AssemblyInfo.cs b/TeamsNetphoneLinkWPF/AssemblyInfo.cs
new file mode 100644
index 0000000..b0ec827
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/AssemblyInfo.cs
@@ -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)
+)]
diff --git a/TeamsNetphoneLinkWPF/Communication/ClientSdkEvents.cs b/TeamsNetphoneLinkWPF/Communication/ClientSdkEvents.cs
new file mode 100644
index 0000000..8b6a2b8
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/Communication/ClientSdkEvents.cs
@@ -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
+ }
+}
diff --git a/TeamsNetphoneLinkWPF/Communication/NetPhone.cs b/TeamsNetphoneLinkWPF/Communication/NetPhone.cs
new file mode 100644
index 0000000..b772753
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/Communication/NetPhone.cs
@@ -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> lineStateEvents = new Dictionary>();
+ private List loggedInStateEvents = new List();
+
+
+ private Timer loggedInStateTimer;
+ private Timer aliveTimer;
+
+ public event EventHandler? 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 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();
+ }
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/TeamsNetphoneLinkWPF/Communication/TeamsGraph.cs b/TeamsNetphoneLinkWPF/Communication/TeamsGraph.cs
new file mode 100644
index 0000000..f4dd19e
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/Communication/TeamsGraph.cs
@@ -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 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 accounts = await app.GetAccountsAsync();
+
+ return accounts.Any();
+ }
+
+ // Authentifizierungsmethode mit Azure Identity
+ public async Task 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 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 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 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.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 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/TeamsNetphoneLinkWPF/Communication/TeamsGraphEventHandlers.cs b/TeamsNetphoneLinkWPF/Communication/TeamsGraphEventHandlers.cs
new file mode 100644
index 0000000..bae250c
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/Communication/TeamsGraphEventHandlers.cs
@@ -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> _activityChangedHandlers = new List>();
+ private readonly List> _availabilityChangedHandlers = new List>();
+
+ public event EventHandler ActivityChanged
+ {
+ add
+ {
+ lock (_lock)
+ {
+ _activityChangedHandlers.Add(value);
+ }
+ }
+ remove
+ {
+ lock (_lock)
+ {
+ _activityChangedHandlers.Remove(value);
+ }
+ }
+ }
+
+ public event EventHandler 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;
+ }
+ }
+}
diff --git a/TeamsNetphoneLinkWPF/Communication/TeamsLocalAPI.cs b/TeamsNetphoneLinkWPF/Communication/TeamsLocalAPI.cs
new file mode 100644
index 0000000..ee096e9
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/Communication/TeamsLocalAPI.cs
@@ -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? ConnectionState;
+ public event EventHandler? TokenReceived;
+
+ private Client teamsClient;
+ private string token = Settings.Default.Token != string.Empty ? Settings.Default.Token : null;
+ private Dictionary> propertyChangedHandlers = new Dictionary>();
+ private CancellationTokenSource cts = new();
+
+ // Destructor
+ ~TeamsLocalAPI()
+ {
+ RemoveAllEventHandlers();
+ }
+
+ public async Task 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 handler)
+ {
+ TokenReceived += handler;
+ }
+
+ public void RemoveTokenRecievedHandler(EventHandler handler)
+ {
+ TokenReceived -= handler;
+ }
+
+ public void AddConnectionStateHandler(EventHandler handler)
+ {
+ ConnectionState += handler;
+ }
+
+ public void RemoveConnectionStateHandler(EventHandler 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();
+ }
+
+ propertyChangedHandlers[propertyName].Add(handler);
+ }
+
+ public void RemoveEventHandler(string propertyName, PropertyChangedEventHandler handler)
+ {
+ if (propertyChangedHandlers.ContainsKey(propertyName))
+ {
+ propertyChangedHandlers[propertyName].Remove(handler);
+ }
+ }
+
+ public void RemoveAllEventHandlers()
+ {
+ propertyChangedHandlers.Clear();
+ }
+ }
+}
diff --git a/TeamsNetphoneLinkWPF/Settings.Designer.cs b/TeamsNetphoneLinkWPF/Settings.Designer.cs
new file mode 100644
index 0000000..ce01015
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/Settings.Designer.cs
@@ -0,0 +1,110 @@
+//------------------------------------------------------------------------------
+//
+// 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.
+//
+//------------------------------------------------------------------------------
+
+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;
+ }
+ }
+ }
+}
diff --git a/TeamsNetphoneLinkWPF/Settings.settings b/TeamsNetphoneLinkWPF/Settings.settings
new file mode 100644
index 0000000..87a5c26
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/Settings.settings
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+ False
+
+
+ False
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TeamsNetphoneLinkWPF/Syncronisation.cs b/TeamsNetphoneLinkWPF/Syncronisation.cs
new file mode 100644
index 0000000..03ab84b
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/Syncronisation.cs
@@ -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
+ {
+ "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 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);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/TeamsNetphoneLinkWPF/TeamsNetphoneLinkWPF.csproj b/TeamsNetphoneLinkWPF/TeamsNetphoneLinkWPF.csproj
new file mode 100644
index 0000000..08e2cd3
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/TeamsNetphoneLinkWPF.csproj
@@ -0,0 +1,62 @@
+
+
+
+ WinExe
+ net8.0-windows
+ enable
+ enable
+ true
+ TeamsNetphoneLink
+ TeamsNetphoneSyncIcon.ico
+ ..\build\
+ False
+
+
+
+
+
+ {F8E552F7-4C00-11D3-80BC-00105A653379}
+ 2
+ 0
+ 0
+ tlbimp
+ False
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Settings.settings
+
+
+ Code
+
+
+
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
diff --git a/TeamsNetphoneLinkWPF/TeamsNetphoneSyncIcon.ico b/TeamsNetphoneLinkWPF/TeamsNetphoneSyncIcon.ico
new file mode 100644
index 0000000..df7009c
Binary files /dev/null and b/TeamsNetphoneLinkWPF/TeamsNetphoneSyncIcon.ico differ
diff --git a/TeamsNetphoneLinkWPF/UpdateCheck.cs b/TeamsNetphoneLinkWPF/UpdateCheck.cs
new file mode 100644
index 0000000..c2a02da
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/UpdateCheck.cs
@@ -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 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();
+ 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();
+ }
+ }
+ }
+}
diff --git a/TeamsNetphoneLinkWPF/WPF/DashboardWindow.xaml b/TeamsNetphoneLinkWPF/WPF/DashboardWindow.xaml
new file mode 100644
index 0000000..bd2e1aa
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/WPF/DashboardWindow.xaml
@@ -0,0 +1,263 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TeamsNetphoneLinkWPF/WPF/DashboardWindow.xaml.cs b/TeamsNetphoneLinkWPF/WPF/DashboardWindow.xaml.cs
new file mode 100644
index 0000000..688d411
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/WPF/DashboardWindow.xaml.cs
@@ -0,0 +1,95 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using TeamsNetphoneLink.WPF.MVVM;
+
+namespace TeamsNetphoneLink.WPF
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ ///
+ public partial class DashboardWindow : Window
+ {
+ private bool isDarkMode = true;
+ private Syncronisation sync;
+ private Task 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TeamsNetphoneLinkWPF/WPF/ExceptionWindow.xaml b/TeamsNetphoneLinkWPF/WPF/ExceptionWindow.xaml
new file mode 100644
index 0000000..380b7b8
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/WPF/ExceptionWindow.xaml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TeamsNetphoneLinkWPF/WPF/ExceptionWindow.xaml.cs b/TeamsNetphoneLinkWPF/WPF/ExceptionWindow.xaml.cs
new file mode 100644
index 0000000..5b2850f
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/WPF/ExceptionWindow.xaml.cs
@@ -0,0 +1,67 @@
+using System.Diagnostics;
+using System.Media;
+using System.Windows;
+
+namespace TeamsNetphoneLink.WPF
+{
+ ///
+ /// Interaktionslogik für ExceptionWindow.xaml
+ ///
+ 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();
+ });
+ }
+ }
+}
diff --git a/TeamsNetphoneLinkWPF/WPF/MVVM/DashboardViewModel.cs b/TeamsNetphoneLinkWPF/WPF/MVVM/DashboardViewModel.cs
new file mode 100644
index 0000000..4125810
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/WPF/MVVM/DashboardViewModel.cs
@@ -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}");
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/TeamsNetphoneLinkWPF/WPF/MVVM/FinishPanelViewModel.cs b/TeamsNetphoneLinkWPF/WPF/MVVM/FinishPanelViewModel.cs
new file mode 100644
index 0000000..d381bbf
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/WPF/MVVM/FinishPanelViewModel.cs
@@ -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));
+ }
+ }
+}
\ No newline at end of file
diff --git a/TeamsNetphoneLinkWPF/WPF/MVVM/LogViewModel.cs b/TeamsNetphoneLinkWPF/WPF/MVVM/LogViewModel.cs
new file mode 100644
index 0000000..a9fe1a5
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/WPF/MVVM/LogViewModel.cs
@@ -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 LogEntries { get; } = new ObservableCollection();
+
+ // 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));
+ }
+ }
+}
diff --git a/TeamsNetphoneLinkWPF/WPF/SettingsWindow.xaml b/TeamsNetphoneLinkWPF/WPF/SettingsWindow.xaml
new file mode 100644
index 0000000..1f1d508
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/WPF/SettingsWindow.xaml
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TeamsNetphoneLinkWPF/WPF/SettingsWindow.xaml.cs b/TeamsNetphoneLinkWPF/WPF/SettingsWindow.xaml.cs
new file mode 100644
index 0000000..00e87b9
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/WPF/SettingsWindow.xaml.cs
@@ -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
+{
+ ///
+ /// Interaktionslogik für SettingsWindow.xaml
+ ///
+ 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;
+ }
+ }
+ }
+}
diff --git a/TeamsNetphoneLinkWPF/WPF/SetupLocalTeamsAPI.xaml b/TeamsNetphoneLinkWPF/WPF/SetupLocalTeamsAPI.xaml
new file mode 100644
index 0000000..d5fd3a8
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/WPF/SetupLocalTeamsAPI.xaml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/TeamsNetphoneLinkWPF/WPF/SetupLocalTeamsAPI.xaml.cs b/TeamsNetphoneLinkWPF/WPF/SetupLocalTeamsAPI.xaml.cs
new file mode 100644
index 0000000..8f561e4
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/WPF/SetupLocalTeamsAPI.xaml.cs
@@ -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
+{
+ ///
+ /// Interaktionslogik für SetupLocalTeamsAPI.xaml
+ ///
+ 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();
+ }
+ }
+}
diff --git a/TeamsNetphoneLinkWPF/WPF/UpdateWindow.xaml b/TeamsNetphoneLinkWPF/WPF/UpdateWindow.xaml
new file mode 100644
index 0000000..0aa2156
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/WPF/UpdateWindow.xaml
@@ -0,0 +1,183 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TeamsNetphoneLinkWPF/WPF/UpdateWindow.xaml.cs b/TeamsNetphoneLinkWPF/WPF/UpdateWindow.xaml.cs
new file mode 100644
index 0000000..54a0cf5
--- /dev/null
+++ b/TeamsNetphoneLinkWPF/WPF/UpdateWindow.xaml.cs
@@ -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
+{
+ ///
+ /// Interaktionslogik für ExceptionWindow.xaml
+ ///
+ 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 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 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}%";
+ });
+ }
+ }
+ }
+ }
+
+
+ }
+}