diff --git a/FluentFlyoutWPF/Classes/Utils/MediaPlayerData.cs b/FluentFlyoutWPF/Classes/Utils/MediaPlayerData.cs
index 7ee8a080..2f3d25d8 100644
--- a/FluentFlyoutWPF/Classes/Utils/MediaPlayerData.cs
+++ b/FluentFlyoutWPF/Classes/Utils/MediaPlayerData.cs
@@ -66,11 +66,8 @@ public static (string, ImageSource?) GetAndCacheMediaPlayerData(string mediaPlay
{
try
{
- // pre-filter processes without a main window handle
- if (p.MainWindowHandle == IntPtr.Zero)
- {
- return null;
- }
+ // we no longer filter processes without main window handle,
+ // as background media processes may not have one.
var mainModule = p.MainModule;
if (mainModule == null) return null;
diff --git a/FluentFlyoutWPF/Controls/TaskbarWidgetControl.xaml.cs b/FluentFlyoutWPF/Controls/TaskbarWidgetControl.xaml.cs
index a7957de9..18cb6e7f 100644
--- a/FluentFlyoutWPF/Controls/TaskbarWidgetControl.xaml.cs
+++ b/FluentFlyoutWPF/Controls/TaskbarWidgetControl.xaml.cs
@@ -409,10 +409,7 @@ private async void Previous_Click(object sender, RoutedEventArgs e)
{
if (_mainWindow == null) return;
- var mediaManager = _mainWindow.mediaManager;
- if (mediaManager == null) return;
-
- var focusedSession = mediaManager.GetFocusedSession();
+ var focusedSession = _mainWindow.GetActiveMediaSession();
if (focusedSession == null) return;
await focusedSession.ControlSession.TrySkipPreviousAsync();
@@ -422,30 +419,17 @@ private async void PlayPause_Click(object sender, RoutedEventArgs e)
{
if (_mainWindow == null) return;
- var mediaManager = _mainWindow.mediaManager;
- if (mediaManager == null) return;
-
- var focusedSession = mediaManager.GetFocusedSession();
+ var focusedSession = _mainWindow.GetActiveMediaSession();
if (focusedSession == null) return;
- if (_isPaused) // paused
- {
- await focusedSession.ControlSession.TryPlayAsync();
- }
- else // playing
- {
- await focusedSession.ControlSession.TryPauseAsync();
- }
+ await focusedSession.ControlSession.TryTogglePlayPauseAsync();
}
private async void Next_Click(object sender, RoutedEventArgs e)
{
if (_mainWindow == null) return;
- var mediaManager = _mainWindow.mediaManager;
- if (mediaManager == null) return;
-
- var focusedSession = mediaManager.GetFocusedSession();
+ var focusedSession = _mainWindow.GetActiveMediaSession();
if (focusedSession == null) return;
await focusedSession.ControlSession.TrySkipNextAsync();
diff --git a/FluentFlyoutWPF/MainWindow.xaml.cs b/FluentFlyoutWPF/MainWindow.xaml.cs
index 6bcb019c..66a712d3 100644
--- a/FluentFlyoutWPF/MainWindow.xaml.cs
+++ b/FluentFlyoutWPF/MainWindow.xaml.cs
@@ -173,7 +173,7 @@ public MainWindow()
RegisterShellHookWindow(new WindowInteropHelper(this).Handle);
_positionTimer = new Timer(SeekbarUpdateUi, null, Timeout.Infinite, Timeout.Infinite);
- if (_seekBarEnabled && mediaManager.GetFocusedSession() is { } session)
+ if (_seekBarEnabled && GetActiveMediaSession() is { } session)
{
UpdateSeekbarCurrentDuration(session.ControlSession.GetTimelineProperties().Position);
}
@@ -232,6 +232,73 @@ private async Task CheckForUpdatesOnStartupAsync()
}
}
+ public bool IsSessionAllowed(MediaSession? session)
+ {
+ if (session == null) return false;
+ if (!SettingsManager.Current.AppFilteringEnabled) return true;
+
+ string appId = session.Id ?? string.Empty;
+ string appName = MediaPlayerData.GetAndCacheMediaPlayerData(appId).Item1 ?? appId;
+
+ if (SettingsManager.Current.AppFilteringMode == 0) // Blacklist mode
+ {
+ if (SettingsManager.Current.BlockedApps != null && SettingsManager.Current.BlockedApps.Any(b =>
+ appName.Contains(b, StringComparison.OrdinalIgnoreCase) ||
+ appId.Contains(b, StringComparison.OrdinalIgnoreCase)))
+ return false;
+
+ return true;
+ }
+ else // Whitelist mode
+ {
+ if (SettingsManager.Current.AllowedApps != null && SettingsManager.Current.AllowedApps.Any(a =>
+ appName.Contains(a, StringComparison.OrdinalIgnoreCase) ||
+ appId.Contains(a, StringComparison.OrdinalIgnoreCase)))
+ return true;
+
+ return false;
+ }
+ }
+
+ public MediaSession? GetActiveMediaSession()
+ {
+ var validSessions = mediaManager.CurrentMediaSessions.Values.Where(IsSessionAllowed).ToList();
+
+ if (validSessions.Count == 0) return null;
+
+ var focused = mediaManager.GetFocusedSession();
+ if (focused != null && validSessions.Any(s => s.Id == focused.Id))
+ return focused;
+
+ var playing = validSessions.FirstOrDefault(s => s.ControlSession.GetPlaybackInfo()?.PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing);
+ if (playing != null)
+ return playing;
+
+ return validSessions.FirstOrDefault();
+ }
+
+ public void RefreshFilteredMedia()
+ {
+ UpdateTaskbar();
+
+ if (IsVisible)
+ {
+ var activeSession = GetActiveMediaSession();
+
+ // UpdateUI handles a null value internally so we haven't checked for null here.
+ UpdateUI(activeSession!);
+
+ if (activeSession != null)
+ {
+ HandlePlayBackState(activeSession.ControlSession.GetPlaybackInfo()?.PlaybackStatus);
+ }
+ else
+ {
+ HandlePlayBackState(GlobalSystemMediaTransportControlsSessionPlaybackStatus.Closed);
+ }
+ }
+ }
+
private static GlobalSystemMediaTransportControlsSessionMediaProperties? TryGetMediaProperties(GlobalSystemMediaTransportControlsSession controlSession)
{
try
@@ -482,17 +549,18 @@ public void CloseAnimation(MicaWindow window, MonitorInfo? selectedMonitor = nul
public void UpdateTaskbar()
{
- if (!mediaManager.IsStarted || mediaManager.GetFocusedSession() == null)
+ var activeSession = GetActiveMediaSession();
+ if (!mediaManager.IsStarted || activeSession == null)
{
taskbarWindow?.UpdateUi("-", "-", null, GlobalSystemMediaTransportControlsSessionPlaybackStatus.Closed);
return;
}
- var focusedSession = mediaManager.GetFocusedSession();
- var songInfo = TryGetMediaProperties(focusedSession.ControlSession);
+
+ var songInfo = TryGetMediaProperties(activeSession.ControlSession);
if (songInfo == null)
return;
- var playbackInfo = focusedSession.ControlSession.GetPlaybackInfo();
+ var playbackInfo = activeSession.ControlSession.GetPlaybackInfo();
var thumbnail = BitmapHelper.GetThumbnail(songInfo.Thumbnail);
BitmapHelper.GetDominantColors(1);
taskbarWindow?.UpdateUi(songInfo.Title, songInfo.Artist, thumbnail, playbackInfo.PlaybackStatus, playbackInfo.Controls);
@@ -546,20 +614,22 @@ private void CurrentSession_OnPlaybackStateChanged(MediaSession mediaSession, Gl
#endif
pauseOtherMediaSessionsIfNeeded(mediaSession);
- var focusedSession = mediaManager.GetFocusedSession();
+ var focusedSession = GetActiveMediaSession();
if (focusedSession == null)
{
taskbarWindow?.UpdateUi("-", "-", null, GlobalSystemMediaTransportControlsSessionPlaybackStatus.Closed);
return;
}
- var songInfo = TryGetMediaProperties(focusedSession.ControlSession);
- if (songInfo == null)
- return;
+ var tbSongInfo = TryGetMediaProperties(focusedSession.ControlSession);
+ if (tbSongInfo != null)
+ {
+ var tbThumbnail = BitmapHelper.GetThumbnail(tbSongInfo.Thumbnail);
+ BitmapHelper.GetDominantColors(1);
+ var tbPlayback = focusedSession.ControlSession.GetPlaybackInfo();
- var thumbnail = BitmapHelper.GetThumbnail(songInfo.Thumbnail);
- BitmapHelper.GetDominantColors(1);
- taskbarWindow?.UpdateUi(songInfo.Title, songInfo.Artist, thumbnail, playbackInfo?.PlaybackStatus, playbackInfo?.Controls);
+ taskbarWindow?.UpdateUi(tbSongInfo.Title, tbSongInfo.Artist, tbThumbnail, tbPlayback?.PlaybackStatus, tbPlayback?.Controls);
+ }
if (IsVisible)
{
@@ -580,17 +650,18 @@ private void MediaManager_OnAnyMediaPropertyChanged(MediaSession mediaSession, G
#if DEBUG
Logger.Debug("Media property changed: " + mediaProperties.Title + " " + mediaSession.ControlSession.GetPlaybackInfo().PlaybackStatus);
#endif
- if (mediaManager.GetFocusedSession() == null)
+ var currentActiveSession = GetActiveMediaSession();
+ if (currentActiveSession == null)
{
taskbarWindow?.UpdateUi("-", "-", null, GlobalSystemMediaTransportControlsSessionPlaybackStatus.Closed);
return;
}
- var songInfo = TryGetMediaProperties(mediaSession.ControlSession);
+ var songInfo = TryGetMediaProperties(currentActiveSession.ControlSession);
if (songInfo == null)
return;
- var playbackInfo = mediaSession.ControlSession.GetPlaybackInfo();
+ var playbackInfo = currentActiveSession.ControlSession.GetPlaybackInfo();
string check = songInfo.Title + songInfo.Artist + playbackInfo.PlaybackStatus;
int checkThumbnail = BitmapHelper.GetStableThumbnailHash(songInfo.Thumbnail);
@@ -607,6 +678,7 @@ private void MediaManager_OnAnyMediaPropertyChanged(MediaSession mediaSession, G
var thumbnail = BitmapHelper.GetThumbnail(songInfo.Thumbnail);
BitmapHelper.GetDominantColors(1);
+
taskbarWindow?.UpdateUi(songInfo.Title, songInfo.Artist, thumbnail, playbackInfo.PlaybackStatus, playbackInfo.Controls);
pauseOtherMediaSessionsIfNeeded(mediaSession);
@@ -617,7 +689,7 @@ void createNewNextUpWindow()
{
Dispatcher.Invoke(() =>
{
- if (nextUpWindow == null && playbackInfo.Controls.IsPauseEnabled) // double-check within the Dispatcher to prevent race conditions
+ if (nextUpWindow == null && playbackInfo.PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing) // double-check within the Dispatcher to prevent race conditions
{
nextUpWindow = new NextUpWindow(songInfo.Title, songInfo.Artist, thumbnail);
currentTitle = songInfo.Title;
@@ -653,9 +725,12 @@ void createNewNextUpWindow()
if (IsVisible)
{
- var focusedSession = mediaManager.GetFocusedSession();
- HandlePlayBackState(focusedSession.ControlSession.GetPlaybackInfo().PlaybackStatus);
- UpdateUI(focusedSession);
+ var focusedSession = GetActiveMediaSession();
+ if (focusedSession != null)
+ {
+ HandlePlayBackState(focusedSession.ControlSession.GetPlaybackInfo()?.PlaybackStatus);
+ UpdateUI(focusedSession);
+ }
}
}
@@ -663,7 +738,7 @@ private void MediaManager_OnAnyTimelinePropertyChanged(MediaSession mediaSession
{
_lastSelfUpdateTimestamp = DateTime.Now;
- if (mediaManager.GetFocusedSession() is not { } session) return;
+ if (GetActiveMediaSession() is not { } session || session.Id != mediaSession.Id) return;
if (_seekBarEnabled)
{
@@ -682,23 +757,7 @@ private void MediaManager_OnAnySessionClosed(MediaSession mediaSession)
#if DEBUG
Logger.Debug("Session closed: " + (mediaSession.Id).ToString());
#endif
- var focusedSession = mediaManager.GetFocusedSession();
-
- if (focusedSession == null)
- {
- taskbarWindow?.UpdateUi("-", "-", null, GlobalSystemMediaTransportControlsSessionPlaybackStatus.Closed);
- }
- else
- {
- var songInfo = TryGetMediaProperties(focusedSession.ControlSession);
- if (songInfo == null)
- return;
-
- var playbackInfo = focusedSession.ControlSession.GetPlaybackInfo();
- var thumbnail = BitmapHelper.GetThumbnail(songInfo.Thumbnail);
- BitmapHelper.GetDominantColors(1);
- taskbarWindow?.UpdateUi(songInfo.Title, songInfo.Artist, thumbnail, playbackInfo.PlaybackStatus, playbackInfo.Controls);
- }
+ UpdateTaskbar();
}
private static IntPtr SetHook(LowLevelKeyboardProc proc) // set the keyboard hook
@@ -786,7 +845,8 @@ private bool TryShowMediaFlyoutDebounced()
public async void ShowMediaFlyout(bool toggleMode = false, bool forceShow = false)
{
- if (mediaManager.GetFocusedSession() == null ||
+ var activeSession = GetActiveMediaSession();
+ if (activeSession == null ||
(!forceShow && !SettingsManager.Current.MediaFlyoutEnabled) ||
FullscreenDetector.IsFullscreenApplicationRunning())
return;
@@ -807,9 +867,9 @@ public async void ShowMediaFlyout(bool toggleMode = false, bool forceShow = fals
return;
}
- UpdateUI(mediaManager.GetFocusedSession());
+ UpdateUI(activeSession);
if (_seekBarEnabled)
- HandlePlayBackState(mediaManager.GetFocusedSession().ControlSession.GetPlaybackInfo().PlaybackStatus);
+ HandlePlayBackState(activeSession.ControlSession.GetPlaybackInfo().PlaybackStatus);
if (nextUpWindow != null) // close NextUpWindow if it's open
{
@@ -917,18 +977,26 @@ private void UpdateUI(MediaSession mediaSession)
var mediaProperties = controlSession.GetPlaybackInfo();
if (mediaProperties != null)
{
- if (mediaProperties.Controls.IsPauseEnabled)
+ if (mediaProperties.PlaybackStatus == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing)
{
- ControlPlayPause.IsEnabled = true;
- ControlPlayPause.Opacity = 1;
SymbolPlayPause.Symbol = Wpf.Ui.Controls.SymbolRegular.Pause16;
}
else
{
- ControlPlayPause.IsEnabled = true;
- ControlPlayPause.Opacity = 1;
SymbolPlayPause.Symbol = Wpf.Ui.Controls.SymbolRegular.Play16;
}
+
+ ControlPlayPause.IsEnabled = mediaProperties.Controls.IsPlayEnabled || mediaProperties.Controls.IsPauseEnabled;
+
+ if (ControlPlayPause.IsEnabled)
+ {
+ ControlPlayPause.Opacity = 1;
+ }
+ else
+ {
+ ControlPlayPause.Opacity = 0.35;
+ }
+
ControlBack.IsEnabled = ControlForward.IsEnabled = mediaProperties.Controls.IsNextEnabled;
ControlBack.Opacity = ControlForward.Opacity = mediaProperties.Controls.IsNextEnabled ? 1 : 0.35;
@@ -1152,77 +1220,64 @@ private void UpdateUILayout() // update the layout based on the settings
private async void Back_Click(object sender, RoutedEventArgs e)
{
- if (mediaManager.GetFocusedSession() == null)
- return;
+ var activeSession = GetActiveMediaSession();
+ if (activeSession == null) return;
- await mediaManager.GetFocusedSession().ControlSession.TrySkipPreviousAsync();
+ await activeSession.ControlSession.TrySkipPreviousAsync();
}
- private void PlayPause_Click(object sender, RoutedEventArgs e)
+ private async void PlayPause_Click(object sender, RoutedEventArgs e)
{
- keybd_event(0xB3, 0, 0, IntPtr.Zero);
-
- if (mediaManager.GetFocusedSession() == null)
- return;
+ var activeSession = GetActiveMediaSession();
+ if (activeSession == null) return;
- //var controlsInfo = mediaManager.GetFocusedSession().ControlSession.GetPlaybackInfo().Controls;
-
- //if (controlsInfo.IsPauseEnabled == true)
- //{
- // await mediaManager.GetFocusedSession().ControlSession.TryPauseAsync();
- //}
- //else if (controlsInfo.IsPlayEnabled == true)
- // await mediaManager.GetFocusedSession().ControlSession.TryPlayAsync();
- if (mediaManager.GetFocusedSession().ControlSession.GetPlaybackInfo().Controls.IsPauseEnabled)
- SymbolPlayPause.Dispatcher.Invoke(() => SymbolPlayPause.Symbol = Wpf.Ui.Controls.SymbolRegular.Pause16);
- else
- SymbolPlayPause.Dispatcher.Invoke(() => SymbolPlayPause.Symbol = Wpf.Ui.Controls.SymbolRegular.Play16);
+ await activeSession.ControlSession.TryTogglePlayPauseAsync();
}
private async void Forward_Click(object sender, RoutedEventArgs e)
{
- if (mediaManager.GetFocusedSession() == null)
- return;
+ var activeSession = GetActiveMediaSession();
+ if (activeSession == null) return;
- await mediaManager.GetFocusedSession().ControlSession.TrySkipNextAsync();
+ await activeSession.ControlSession.TrySkipNextAsync();
}
private async void Repeat_Click(object sender, RoutedEventArgs e)
{
- if (mediaManager.GetFocusedSession() == null)
- return;
+ var activeSession = GetActiveMediaSession();
+ if (activeSession == null) return;
- if (mediaManager.GetFocusedSession().ControlSession.GetPlaybackInfo().AutoRepeatMode == global::Windows.Media.MediaPlaybackAutoRepeatMode.None)
+ if (activeSession.ControlSession.GetPlaybackInfo().AutoRepeatMode == global::Windows.Media.MediaPlaybackAutoRepeatMode.None)
{
SymbolRepeat.Dispatcher.Invoke(() => SymbolRepeat.Symbol = Wpf.Ui.Controls.SymbolRegular.ArrowRepeatAll24);
- await mediaManager.GetFocusedSession().ControlSession.TryChangeAutoRepeatModeAsync(global::Windows.Media.MediaPlaybackAutoRepeatMode.List);
+ await activeSession.ControlSession.TryChangeAutoRepeatModeAsync(global::Windows.Media.MediaPlaybackAutoRepeatMode.List);
}
- else if (mediaManager.GetFocusedSession().ControlSession.GetPlaybackInfo().AutoRepeatMode == global::Windows.Media.MediaPlaybackAutoRepeatMode.List)
+ else if (activeSession.ControlSession.GetPlaybackInfo().AutoRepeatMode == global::Windows.Media.MediaPlaybackAutoRepeatMode.List)
{
SymbolRepeat.Dispatcher.Invoke(() => SymbolRepeat.Symbol = Wpf.Ui.Controls.SymbolRegular.ArrowRepeat124);
- await mediaManager.GetFocusedSession().ControlSession.TryChangeAutoRepeatModeAsync(global::Windows.Media.MediaPlaybackAutoRepeatMode.Track);
+ await activeSession.ControlSession.TryChangeAutoRepeatModeAsync(global::Windows.Media.MediaPlaybackAutoRepeatMode.Track);
}
- else if (mediaManager.GetFocusedSession().ControlSession.GetPlaybackInfo().AutoRepeatMode == global::Windows.Media.MediaPlaybackAutoRepeatMode.Track)
+ else if (activeSession.ControlSession.GetPlaybackInfo().AutoRepeatMode == global::Windows.Media.MediaPlaybackAutoRepeatMode.Track)
{
SymbolRepeat.Dispatcher.Invoke(() => SymbolRepeat.Symbol = Wpf.Ui.Controls.SymbolRegular.ArrowRepeatAllOff24);
- await mediaManager.GetFocusedSession().ControlSession.TryChangeAutoRepeatModeAsync(global::Windows.Media.MediaPlaybackAutoRepeatMode.None);
+ await activeSession.ControlSession.TryChangeAutoRepeatModeAsync(global::Windows.Media.MediaPlaybackAutoRepeatMode.None);
}
}
private async void Shuffle_Click(object sender, RoutedEventArgs e)
{
- if (mediaManager.GetFocusedSession() == null)
- return;
+ var activeSession = GetActiveMediaSession();
+ if (activeSession == null) return;
- if (mediaManager.GetFocusedSession().ControlSession.GetPlaybackInfo().IsShuffleActive == true)
+ if (activeSession.ControlSession.GetPlaybackInfo().IsShuffleActive == true)
{
SymbolShuffle.Dispatcher.Invoke(() => SymbolShuffle.Symbol = Wpf.Ui.Controls.SymbolRegular.ArrowShuffleOff24);
- await mediaManager.GetFocusedSession().ControlSession.TryChangeShuffleActiveAsync(false);
+ await activeSession.ControlSession.TryChangeShuffleActiveAsync(false);
}
else
{
SymbolShuffle.Dispatcher.Invoke(() => SymbolShuffle.Symbol = Wpf.Ui.Controls.SymbolRegular.ArrowShuffle24);
- await mediaManager.GetFocusedSession().ControlSession.TryChangeShuffleActiveAsync(true);
+ await activeSession.ControlSession.TryChangeShuffleActiveAsync(true);
}
}
@@ -1247,7 +1302,7 @@ private void Seekbar_OnPreviewMouseLeftButtonDown(object sender, MouseButtonEven
private async void Seekbar_OnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
- if (mediaManager.GetFocusedSession() is { } session)
+ if (GetActiveMediaSession() is { } session)
{
var seekPosition = TimeSpan.FromSeconds(Seekbar.Value);
if (seekPosition == TimeSpan.Zero) seekPosition = TimeSpan.FromSeconds(1);
@@ -1271,7 +1326,7 @@ private void SeekbarUpdateUi(object? sender)
if (DateTime.Now.Subtract(_lastSelfUpdateTimestamp).TotalSeconds < 1) return;
if (!_seekBarEnabled || Visibility != Visibility.Visible || _isDragging) return;
- if (mediaManager.GetFocusedSession() is not { } session) return;
+ if (GetActiveMediaSession() is not { } session) return;
var timeline = session.ControlSession.GetTimelineProperties();
var pos = timeline.Position + (DateTime.Now - timeline.LastUpdatedTime.DateTime);
diff --git a/FluentFlyoutWPF/Pages/AppFilteringPage.xaml b/FluentFlyoutWPF/Pages/AppFilteringPage.xaml
new file mode 100644
index 00000000..dc50d309
--- /dev/null
+++ b/FluentFlyoutWPF/Pages/AppFilteringPage.xaml
@@ -0,0 +1,270 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FluentFlyoutWPF/Pages/AppFilteringPage.xaml.cs b/FluentFlyoutWPF/Pages/AppFilteringPage.xaml.cs
new file mode 100644
index 00000000..14f0ca1f
--- /dev/null
+++ b/FluentFlyoutWPF/Pages/AppFilteringPage.xaml.cs
@@ -0,0 +1,207 @@
+// Copyright © 2024-2026 The FluentFlyout Authors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+using FluentFlyout.Classes.Settings;
+using FluentFlyout.Classes.Utils;
+
+using System.Windows;
+using System.Windows.Controls;
+
+namespace FluentFlyoutWPF.Pages;
+
+public partial class AppFilteringPage : Page
+{
+ public AppFilteringPage()
+ {
+ InitializeComponent();
+ DataContext = SettingsManager.Current;
+ }
+
+ ///
+ /// Saves the current settings and refreshes the media sessions in the main window.
+ /// This ensures that any changes made to the allowed or blocked apps are immediately reflected in the media sessions displayed.
+ ///
+ private static void SaveAndRefreshMedia()
+ {
+ SettingsManager.SaveSettings();
+ var mainWindow = Application.Current.MainWindow as MainWindow;
+
+ mainWindow?.RefreshFilteredMedia();
+ }
+
+ ///
+ /// Normalizes the specified application name by removing the ".exe" extension and matching it against known session names.
+ ///
+ /// If the application name matches or is contained within any current media session name, the
+ /// method returns the matched session name. This is helpful to compare against known sources and different aliases they may have.
+ /// The application name to normalize. This may include the ".exe" extension.
+ /// A normalized application name that matches a known media source, else the default or default with extension stripped.
+ private static string NormalizeAppName(string app)
+ {
+ if (app.EndsWith(".exe", System.StringComparison.OrdinalIgnoreCase))
+ {
+ app = app[..^4];
+ }
+
+ var mainWindow = Application.Current.MainWindow as MainWindow;
+ if (mainWindow?.mediaManager == null) return app;
+
+ var match = mainWindow.mediaManager.CurrentMediaSessions.Values
+ .Select(s => MediaPlayerData.GetAndCacheMediaPlayerData(s.Id).Item1)
+ .FirstOrDefault(name => name.Equals(app, System.StringComparison.OrdinalIgnoreCase) ||
+ name.Contains(app, System.StringComparison.OrdinalIgnoreCase) ||
+ app.Contains(name, System.StringComparison.OrdinalIgnoreCase));
+
+ return match ?? app;
+ }
+
+ ///
+ /// Populates the specified ComboBox with a distinct, alphabetically ordered list of applications that have media.
+ ///
+ /// If there are no active media sessions, the ComboBox will be populated with an empty list.
+ /// The ComboBox to populate with the list of applications. (Should not be null).
+ private static void PopulateComboBox(ComboBox comboBox)
+ {
+ var mainWindow = Application.Current.MainWindow as MainWindow;
+
+ if (mainWindow?.mediaManager == null) return;
+
+ var apps = mainWindow.mediaManager.CurrentMediaSessions.Values
+ .Select(s => MediaPlayerData.GetAndCacheMediaPlayerData(s.Id).Item1)
+ .Distinct()
+ .OrderBy(a => a)
+ .ToList();
+
+ comboBox.ItemsSource = apps;
+ }
+
+ ///
+ /// Handles the DropDownOpened event for the AllowComboBox control and populates its items when the dropdown is opened.
+ ///
+ /// The source of the event (typically AllowComboBox).
+ /// An EventArgs object that contains the event data.
+ private void AllowComboBox_DropDownOpened(object sender, System.EventArgs e)
+ {
+ PopulateComboBox(AllowComboBox);
+ }
+
+ ///
+ /// Handles the DropDownOpened event for the BlockComboBox control and populates its items when the dropdown is opened.
+ ///
+ /// The source of the event (typically BlockComboBox).
+ /// An EventArgs object that contains the event data.
+ private void BlockComboBox_DropDownOpened(object sender, System.EventArgs e)
+ {
+ PopulateComboBox(BlockComboBox);
+ }
+
+ ///
+ /// Handles the Click event of the AddAllow button to add the selected application to the allowed applications list.
+ ///
+ /// The source of the event (typically the AddAllow button).
+ /// The event data associated with the Click event.
+ private void AddAllow_Click(object sender, RoutedEventArgs e)
+ {
+ var app = AllowComboBox.SelectedItem?.ToString()?.Trim();
+
+ if (string.IsNullOrEmpty(app) || SettingsManager.Current.AllowedApps.Any(a => a.Equals(app, System.StringComparison.OrdinalIgnoreCase))) return;
+
+ SettingsManager.Current.AllowedApps.Add(app);
+ AllowComboBox.SelectedIndex = -1;
+
+ SaveAndRefreshMedia();
+ }
+
+ ///
+ /// Handles the Click event for adding a manually entered application to the allowed applications list.
+ ///
+ /// If the entered application name is empty or already exists in the allowed list, no action is
+ /// taken.
+ /// The source of the event (typically the Add button)
+ /// The event data associated with the Click event.
+ private void AddAllowManual_Click(object sender, RoutedEventArgs e)
+ {
+ var app = AllowTextBox.Text?.Trim();
+
+ if (string.IsNullOrEmpty(app)) return;
+
+ app = NormalizeAppName(app);
+
+ if (SettingsManager.Current.AllowedApps.Any(a => a.Equals(app, System.StringComparison.OrdinalIgnoreCase))) return;
+
+ SettingsManager.Current.AllowedApps.Add(app);
+ AllowTextBox.Text = string.Empty;
+
+ SaveAndRefreshMedia();
+ }
+
+ ///
+ /// Handles the click event to remove an application from the allowed list.
+ ///
+ /// This method updates the allowed applications list and refreshes the media settings after removal.
+ /// The source of the event (usually a Button with its Tag property set to the application identifier).
+ /// The event data associated with the click event.
+ private void RemoveAllow_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is not Button { Tag: string app }) return;
+
+ SettingsManager.Current.AllowedApps.Remove(app);
+ SaveAndRefreshMedia();
+ }
+
+ ///
+ /// Handles the Click event for adding a selected application to the blocked applications list.
+ ///
+ /// If the selected application is not already blocked and is not null or empty, it is added to
+ /// the blocked applications list. The media is then refreshed.
+ /// The source of the event (typically the button that was clicked).
+ /// The event data associated with the click event.
+ private void AddBlock_Click(object sender, RoutedEventArgs e)
+ {
+ var app = BlockComboBox.SelectedItem?.ToString()?.Trim();
+
+ if (string.IsNullOrEmpty(app) || SettingsManager.Current.BlockedApps.Any(b => b.Equals(app, System.StringComparison.OrdinalIgnoreCase))) return;
+
+ SettingsManager.Current.BlockedApps.Add(app);
+ BlockComboBox.SelectedIndex = -1;
+
+ SaveAndRefreshMedia();
+ }
+
+ ///
+ /// Handles the Click event for manually adding an application to the blocked list.
+ ///
+ /// Adds the trimmed text from the input box to the blocked applications list if it is not empty
+ /// and not already present. After adding, the input box is cleared and the media list is refreshed.
+ /// The source of the event (typically the button that was clicked).
+ /// The event data associated with the click event.
+ private void AddBlockManual_Click(object sender, RoutedEventArgs e)
+ {
+ var app = BlockTextBox.Text?.Trim();
+
+ if (string.IsNullOrEmpty(app)) return;
+
+ app = NormalizeAppName(app);
+
+ if (SettingsManager.Current.BlockedApps.Any(b => b.Equals(app, System.StringComparison.OrdinalIgnoreCase))) return;
+
+ SettingsManager.Current.BlockedApps.Add(app);
+ BlockTextBox.Text = string.Empty;
+
+ SaveAndRefreshMedia();
+ }
+
+ ///
+ /// Handles the click event to remove an application from the blocked list.
+ ///
+ /// The source of the event, expected to be a Button with its Tag property set to the application.
+ /// The event data associated with the click event.
+ private void RemoveBlock_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is not Button { Tag: string app }) return;
+
+ SettingsManager.Current.BlockedApps.Remove(app);
+
+ SaveAndRefreshMedia();
+ }
+}
\ No newline at end of file
diff --git a/FluentFlyoutWPF/Pages/SystemPage.xaml b/FluentFlyoutWPF/Pages/SystemPage.xaml
index bea875b0..20248eba 100644
--- a/FluentFlyoutWPF/Pages/SystemPage.xaml
+++ b/FluentFlyoutWPF/Pages/SystemPage.xaml
@@ -175,7 +175,17 @@
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FluentFlyoutWPF/Pages/SystemPage.xaml.cs b/FluentFlyoutWPF/Pages/SystemPage.xaml.cs
index 78f7557d..4c849c97 100644
--- a/FluentFlyoutWPF/Pages/SystemPage.xaml.cs
+++ b/FluentFlyoutWPF/Pages/SystemPage.xaml.cs
@@ -198,6 +198,11 @@ private async void ImportButton_Click(object sender, System.Windows.RoutedEventA
}
}
+ private void AppFiltering_Click(object sender, System.Windows.RoutedEventArgs e)
+ {
+ SettingsWindow.NavigateToPage(typeof(AppFilteringPage));
+ }
+
private void Advanced_Click(object sender, System.Windows.RoutedEventArgs e)
{
SettingsWindow.NavigateToPage(typeof(AdvancedPage));
diff --git a/FluentFlyoutWPF/Resources/Localization/Dictionary-en-US.xaml b/FluentFlyoutWPF/Resources/Localization/Dictionary-en-US.xaml
index 2a43079a..2e0db4b1 100644
--- a/FluentFlyoutWPF/Resources/Localization/Dictionary-en-US.xaml
+++ b/FluentFlyoutWPF/Resources/Localization/Dictionary-en-US.xaml
@@ -174,6 +174,22 @@ Note: As an open-source project, these features are also available for free via
Tray Icon
Updates
Configure
+ App Filtering
+ Control which applications are allowed or blocked from displaying their media.
+ Enable Filtering
+ Mode
+ Blacklist (Allow all except blocked apps)
+ Whitelist (Only allow selected apps)
+ Allowed apps
+ If any applications are listed here, only their media will be shown.
+ Excluded apps
+ Excludes an application from being displayed
+ Can't find your app? Type it below:
+ e.g. Spotify.exe
+ e.g. Chrome.exe
+ Select a currently playing app to add
+ Add
+ Remove
About
❤️ Meet Our Contributors
A big thank you to all the amazing people who have contributed to FluentFlyout's development and success!
diff --git a/FluentFlyoutWPF/ViewModels/UserSettings.cs b/FluentFlyoutWPF/ViewModels/UserSettings.cs
index d37adc0b..9235a74e 100644
--- a/FluentFlyoutWPF/ViewModels/UserSettings.cs
+++ b/FluentFlyoutWPF/ViewModels/UserSettings.cs
@@ -437,6 +437,30 @@ public string TaskbarWidgetManualPaddingText
[ObservableProperty]
public partial bool TaskbarVisualizerEnabled { get; set; }
+ ///
+ /// Returns whether app filtering is enabled or disabled.
+ ///
+ [ObservableProperty]
+ public partial bool AppFilteringEnabled { get; set; }
+
+ ///
+ /// Returns the active filtering mode. 0 for Blacklist, 1 for Whitelist.
+ ///
+ [ObservableProperty]
+ public partial int AppFilteringMode { get; set; }
+
+ ///
+ /// Returns a list of apps that are allowed to display media/update the taskbar.
+ ///
+ [ObservableProperty]
+ public partial ObservableCollection AllowedApps { get; set; } = new();
+
+ ///
+ /// Returns a list of apps that are NOT allowed to display media/update the taskbar.
+ ///
+ [ObservableProperty]
+ public partial ObservableCollection BlockedApps { get; set; } = new();
+
///
/// Position of the visualizer, where 0 and 1 are to the left or right of the widget.
///
@@ -631,6 +655,8 @@ public UserSettings()
TaskbarWidgetControlsPosition = 1;
TaskbarWidgetAnimated = true;
TaskbarVisualizerEnabled = false;
+ AppFilteringEnabled = false;
+ AppFilteringMode = 0;
TaskbarVisualizerPosition = 1;
TaskbarVisualizerClickable = false;
TaskbarVisualizerBarCount = 10;
@@ -795,6 +821,22 @@ partial void OnUseAlbumArtAsAccentColorChanged(bool oldValue, bool newValue)
BitmapHelper.GetDominantColors(1);
}
+ partial void OnAppFilteringEnabledChanged(bool oldValue, bool newValue)
+ {
+ if (oldValue == newValue || _initializing) return;
+
+ MainWindow mainWindow = (MainWindow)Application.Current.MainWindow;
+ mainWindow?.RefreshFilteredMedia();
+ }
+
+ partial void OnAppFilteringModeChanged(int oldValue, int newValue)
+ {
+ if (oldValue == newValue || _initializing) return;
+
+ MainWindow mainWindow = (MainWindow)Application.Current.MainWindow;
+ mainWindow?.RefreshFilteredMedia();
+ }
+
partial void OnVolumeMixerHighlightActiveAppsChanged(bool oldValue, bool newValue)
{
if (oldValue == newValue || _initializing) return;