Skip to content

Commit aece4e5

Browse files
committed
Merge PR Jas-SinghFSU#1139: blacklist MPRIS players
2 parents 2fa8def + c26e7e4 commit aece4e5

File tree

8 files changed

+199
-46
lines changed

8 files changed

+199
-46
lines changed

src/components/bar/modules/media/index.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import options from 'src/configuration';
99
import { runAsyncCommand } from '../../utils/input/commandExecutor';
1010
import { throttledScrollHandler } from '../../utils/input/throttle';
1111
import { openDropdownMenu } from '../../utils/menu';
12+
import { filterPlayers } from 'src/lib/shared/media';
1213

1314
const mprisService = AstalMpris.get_default();
1415
const {
@@ -22,15 +23,22 @@ const {
2223
scrollDown,
2324
format,
2425
} = options.bar.media;
26+
const { ignore } = options.menus.media;
2527

2628
const isVis = Variable(!show_active_only.get());
2729

28-
Variable.derive([bind(show_active_only), bind(mprisService, 'players')], (showActive, players) => {
29-
isVis.set(!showActive || players?.length > 0);
30-
});
30+
Variable.derive(
31+
[bind(show_active_only), bind(mprisService, 'players'), bind(ignore)],
32+
(showActive, players, ignoredApps) => {
33+
const filteredPlayers = filterPlayers(players, ignoredApps);
34+
isVis.set(!showActive || filteredPlayers?.length > 0);
35+
},
36+
);
3137

3238
const Media = (): BarBoxChild => {
33-
activePlayer.set(mprisService.get_players()[0]);
39+
const allPlayers = mprisService.get_players();
40+
const filteredPlayers = filterPlayers(allPlayers, ignore.get());
41+
activePlayer.set(filteredPlayers[0]);
3442

3543
const songIcon = Variable('');
3644

src/components/menus/media/components/controls/Players.tsx

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,34 @@
1-
import { bind } from 'astal';
1+
import { bind, Variable } from 'astal';
22
import { Astal, Gtk, Widget } from 'astal/gtk3';
33
import { getNextPlayer, getPreviousPlayer } from './helpers';
44
import AstalMpris from 'gi://AstalMpris?version=0.1';
55
import { isPrimaryClick } from 'src/lib/events/mouse';
6+
import options from 'src/configuration';
7+
import { filterPlayers } from 'src/lib/shared/media';
68

79
const mprisService = AstalMpris.get_default();
810

911
export const PreviousPlayer = (): JSX.Element => {
10-
const className = bind(mprisService, 'players').as((players) => {
11-
const isDisabled = players.length <= 1 ? 'disabled' : 'enabled';
12+
const { ignore } = options.menus.media;
1213

13-
return `media-indicator-control-button ${isDisabled}`;
14-
});
14+
const className = Variable.derive(
15+
[bind(mprisService, 'players'), bind(ignore)],
16+
(players: AstalMpris.Player[], ignoredApps: string[]) => {
17+
const filteredPlayers = filterPlayers(players, ignoredApps);
18+
const isDisabled = filteredPlayers.length <= 1 ? 'disabled' : 'enabled';
19+
20+
return `media-indicator-control-button ${isDisabled}`;
21+
},
22+
);
1523

1624
const onClick = (_: Widget.Button, event: Astal.ClickEvent): void => {
1725
if (!isPrimaryClick(event)) {
1826
return;
1927
}
2028

21-
const isDisabled = mprisService.get_players().length <= 1;
29+
const allPlayers = mprisService.get_players();
30+
const filteredPlayers = filterPlayers(allPlayers, ignore.get());
31+
const isDisabled = filteredPlayers.length <= 1;
2232

2333
if (!isDisabled) {
2434
getPreviousPlayer();
@@ -27,40 +37,54 @@ export const PreviousPlayer = (): JSX.Element => {
2737

2838
return (
2939
<button
30-
className={className}
40+
className={className()}
3141
halign={Gtk.Align.CENTER}
3242
hasTooltip
3343
tooltipText={'Previous Player'}
3444
onClick={onClick}
45+
onDestroy={() => {
46+
className.drop();
47+
}}
3548
>
3649
<label label={'󰅁'} />
3750
</button>
3851
);
3952
};
4053

4154
export const NextPlayer = (): JSX.Element => {
42-
const className = bind(mprisService, 'players').as((players) => {
43-
const isDisabled = players.length <= 1 ? 'disabled' : 'enabled';
44-
return `media-indicator-control-button ${isDisabled}`;
45-
});
55+
const { ignore } = options.menus.media;
56+
57+
const className = Variable.derive(
58+
[bind(mprisService, 'players'), bind(ignore)],
59+
(players: AstalMpris.Player[], ignoredApps: string[]) => {
60+
const filteredPlayers = filterPlayers(players, ignoredApps);
61+
const isDisabled = filteredPlayers.length <= 1 ? 'disabled' : 'enabled';
62+
return `media-indicator-control-button ${isDisabled}`;
63+
},
64+
);
4665
const onClick = (_: Widget.Button, event: Astal.ClickEvent): void => {
4766
if (!isPrimaryClick(event)) {
4867
return;
4968
}
5069

51-
const isDisabled = mprisService.get_players().length <= 1;
70+
const allPlayers = mprisService.get_players();
71+
const filteredPlayers = filterPlayers(allPlayers, ignore.get());
72+
const isDisabled = filteredPlayers.length <= 1;
5273

5374
if (!isDisabled) {
5475
getNextPlayer();
5576
}
5677
};
5778
return (
5879
<button
59-
className={className}
80+
className={className()}
6081
halign={Gtk.Align.CENTER}
6182
hasTooltip
6283
tooltipText={'Next Player'}
6384
onClick={onClick}
85+
onDestroy={() => {
86+
className.drop();
87+
}}
6488
>
6589
<label label={'󰅂'} />
6690
</button>

src/components/menus/media/components/controls/helpers.ts

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import AstalMpris from 'gi://AstalMpris?version=0.1';
22
import icons2 from 'src/lib/icons/icons';
33
import { activePlayer } from 'src/services/media';
44
import { PlaybackIconMap } from './types';
5+
import options from 'src/configuration';
6+
import { filterPlayers } from 'src/lib/shared/media';
57

68
const mprisService = AstalMpris.get_default();
79

@@ -75,7 +77,7 @@ export const isShuffleActive = (status: AstalMpris.Shuffle): string => {
7577
/**
7678
* Sets the next active player.
7779
*
78-
* This function sets the next player in the `mprisService.players` array as the active player.
80+
* This function sets the next player in the filtered players array as the active player.
7981
* If there is only one player, it sets that player as the active player.
8082
*
8183
* @returns void
@@ -87,22 +89,26 @@ export const getNextPlayer = (): void => {
8789
return;
8890
}
8991

90-
const currentPlayerIndex = mprisService
91-
.get_players()
92-
.findIndex((player) => player.busName === currentPlayer.busName);
93-
const totalPlayers = mprisService.get_players().length;
92+
const { ignore } = options.menus.media;
93+
const allPlayers = mprisService.get_players();
94+
const filteredPlayers = filterPlayers(allPlayers, ignore.get());
95+
96+
const currentPlayerIndex = filteredPlayers.findIndex(
97+
(player) => player.busName === currentPlayer.busName,
98+
);
99+
const totalPlayers = filteredPlayers.length;
94100

95101
if (totalPlayers === 1) {
96-
return activePlayer.set(mprisService.get_players()[0]);
102+
return activePlayer.set(filteredPlayers[0]);
97103
}
98104

99-
return activePlayer.set(mprisService.get_players()[(currentPlayerIndex + 1) % totalPlayers]);
105+
return activePlayer.set(filteredPlayers[(currentPlayerIndex + 1) % totalPlayers]);
100106
};
101107

102108
/**
103109
* Sets the previous active player.
104110
*
105-
* This function sets the previous player in the `mprisService.players` array as the active player.
111+
* This function sets the previous player in the filtered players array as the active player.
106112
* If there is only one player, it sets that player as the active player.
107113
*
108114
* @returns void
@@ -114,16 +120,18 @@ export const getPreviousPlayer = (): void => {
114120
return;
115121
}
116122

117-
const currentPlayerIndex = mprisService
118-
.get_players()
119-
.findIndex((player) => player.busName === currentPlayer.busName);
120-
const totalPlayers = mprisService.get_players().length;
123+
const { ignore } = options.menus.media;
124+
const allPlayers = mprisService.get_players();
125+
const filteredPlayers = filterPlayers(allPlayers, ignore.get());
126+
127+
const currentPlayerIndex = filteredPlayers.findIndex(
128+
(player) => player.busName === currentPlayer.busName,
129+
);
130+
const totalPlayers = filteredPlayers.length;
121131

122132
if (totalPlayers === 1) {
123-
return activePlayer.set(mprisService.get_players()[0]);
133+
return activePlayer.set(filteredPlayers[0]);
124134
}
125135

126-
return activePlayer.set(
127-
mprisService.get_players()[(currentPlayerIndex - 1 + totalPlayers) % totalPlayers],
128-
);
136+
return activePlayer.set(filteredPlayers[(currentPlayerIndex - 1 + totalPlayers) % totalPlayers]);
129137
};

src/components/settings/pages/config/menus/media.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ export const MediaMenuSettings = (): JSX.Element => {
88
<scrollable name={'Media Menu'} vscroll={Gtk.PolicyType.AUTOMATIC}>
99
<box className="bar-theme-page paged-container" vertical>
1010
<Header title="Media" />
11+
<Option
12+
opt={options.menus.media.preferredPlayer}
13+
title="Preferred Player"
14+
subtitle="Identity name of the player to prioritize. Use 'hyprpanel mprisPlayers' to see available players with identities"
15+
type="string"
16+
/>
17+
<Option
18+
opt={options.menus.media.ignore}
19+
title="Ignored Applications"
20+
subtitle="MPRIS clients to hide from the media player"
21+
type="object"
22+
/>
1123
<Option opt={options.menus.media.hideAuthor} title="Hide Author" type="boolean" />
1224
<Option opt={options.menus.media.hideAlbum} title="Hide Album" type="boolean" />
1325
<Option opt={options.menus.media.displayTime} title="Display Time Info" type="boolean" />

src/configuration/modules/config/menus/media/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { opt } from 'src/lib/options';
22

33
export default {
4+
ignore: opt<string[]>([]),
5+
preferredPlayer: opt<string>(''),
46
hideAuthor: opt(false),
57
hideAlbum: opt(false),
68
displayTime: opt(false),

src/lib/shared/media/index.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import AstalMpris from 'gi://AstalMpris?version=0.1';
2+
3+
const normalizeName = (name: string): string => name.toLowerCase().replace(/\s+/g, '_');
4+
5+
/**
6+
* Checks if a media player should be ignored based on the filter list.
7+
*
8+
* This function determines whether the provided MPRIS player should be filtered out
9+
* by checking the identity property.
10+
*
11+
* @param player The MPRIS player to check.
12+
* @param filter Array of identity names to ignore (e.g., ["Brave", "TIDAL Hi-Fi", "Zaap"]).
13+
*
14+
* @returns True if the player should be ignored, false otherwise.
15+
*/
16+
export const isPlayerIgnored = (player: AstalMpris.Player | null | undefined, filter: string[]): boolean => {
17+
if (!player) {
18+
return false;
19+
}
20+
21+
const playerFilters = new Set(filter.map(normalizeName));
22+
23+
const identity = player.identity || '';
24+
const normalizedIdentity = normalizeName(identity);
25+
26+
return playerFilters.has(normalizedIdentity);
27+
};
28+
29+
/**
30+
* Filters a list of MPRIS players based on the ignore list.
31+
*
32+
* This function removes players from the list that match entries in the filter array.
33+
*
34+
* @param players Array of MPRIS players to filter.
35+
* @param filter Array of application names to ignore.
36+
*
37+
* @returns Filtered array of players.
38+
*/
39+
export const filterPlayers = (players: AstalMpris.Player[], filter: string[]): AstalMpris.Player[] => {
40+
const filteredPlayers = players.filter((player: AstalMpris.Player) => {
41+
return !isPlayerIgnored(player, filter);
42+
});
43+
44+
return filteredPlayers;
45+
};

src/services/cli/commander/commands/modules/media/index.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,32 @@ export const mediaCommands: Command[] = [
120120
}
121121
},
122122
},
123+
{
124+
name: 'mprisPlayers',
125+
aliases: ['mpl'],
126+
description: 'Lists all detected MPRIS players with their identity names.',
127+
category: 'Media',
128+
args: [],
129+
handler: (): string => {
130+
try {
131+
const players = mprisService.get_players();
132+
133+
if (players.length === 0) {
134+
return 'No MPRIS players detected';
135+
}
136+
137+
const playerList = players
138+
.map((player, index) => {
139+
const identity = player.identity || 'Unknown';
140+
const busName = player.busName || 'Unknown';
141+
return `${index + 1}. ${identity}\n Bus: ${busName}`;
142+
})
143+
.join('\n\n');
144+
145+
return `Detected MPRIS Players:\n\n${playerList}\n\nUse the identity names in the ignore list (Settings > Media Menu > Ignored Applications)`;
146+
} catch (error) {
147+
errorHandler(error);
148+
}
149+
},
150+
},
123151
];

0 commit comments

Comments
 (0)