Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
X
ximper-shell-notification-center
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Ximper Linux
ximper-shell-notification-center
Commits
0e025862
Verified
Commit
0e025862
authored
Mar 24, 2026
by
Kirill Unitsaev
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
mpris: add progress bar with seek support
parent
a67ff66e
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
176 additions
and
114 deletions
+176
-114
mpris_player.blp
data/ui/mpris_player.blp
+40
-0
config.json.in
src/config.json.in
+1
-4
configSchema.json
src/configSchema.json
+1
-23
interfaces.vala
src/controlCenter/widgets/mpris/interfaces.vala
+1
-0
mpris.vala
src/controlCenter/widgets/mpris/mpris.vala
+20
-77
mpris_player.vala
src/controlCenter/widgets/mpris/mpris_player.vala
+113
-10
No files found.
data/ui/mpris_player.blp
View file @
0e025862
...
...
@@ -67,6 +67,46 @@ template $XimperShellNotificationCenterWidgetsMprisMprisPlayer: $Underlay {
}
}
Box progress_box {
orientation: horizontal;
spacing: 6;
visible: false;
styles [
"mpris-progress",
]
Label position_label {
label: "0:00";
xalign: 0;
styles [
"caption",
"dim-label",
]
}
Scale progress_scale {
hexpand: true;
draw-value: false;
adjustment: Adjustment {
lower: 0;
upper: 100;
value: 0;
};
}
Label duration_label {
label: "0:00";
xalign: 1;
styles [
"caption",
"dim-label",
]
}
}
Box {
spacing: 6;
halign: center;
...
...
src/config.json.in
View file @
0e025862
...
...
@@ -60,10 +60,7 @@
"text": "Label Text"
},
"mpris": {
"blacklist": [],
"autohide": false,
"show-album-art": "always",
"loop-carousel": false
"blacklist": []
}
}
}
src/configSchema.json
View file @
0e025862
...
...
@@ -402,35 +402,13 @@
"description"
:
"A widget that displays multiple music players"
,
"additionalProperties"
:
false
,
"properties"
:
{
"image-size"
:
{
"type"
:
"integer"
,
"deprecated"
:
true
,
"description"
:
"deprecated (change the CSS root variable
\"
--mpris-album-art-icon-size
\"
): The size of the album art"
,
"default"
:
-1
},
"show-album-art"
:
{
"type"
:
"string"
,
"description"
:
"Whether or not the album art should be hidden, always visible, or only visible when a valid album art is provided."
,
"default"
:
"always"
,
"enum"
:
[
"always"
,
"when-available"
,
"never"
]
},
"autohide"
:
{
"type"
:
"boolean"
,
"description"
:
"Whether to hide the widget when the player has no metadata."
,
"default"
:
false
},
"blacklist"
:
{
"type"
:
"array"
,
"description"
:
"Audio sources for the mpris widget to ignore."
,
"description"
:
"Audio sources for the mpris widget to ignore.
Regex allowed.
"
,
"items"
:
{
"type"
:
"string"
,
"description"
:
"Audio source/app name. Regex allowed."
}
},
"loop-carousel"
:
{
"type"
:
"boolean"
,
"description"
:
"Whether to loop through the mpris carousel."
,
"default"
:
"false"
}
}
},
...
...
src/controlCenter/widgets/mpris/interfaces.vala
View file @
0e025862
...
...
@@ -76,6 +76,7 @@ namespace XimperShellNotificationCenter.Widgets.Mpris {
public
abstract
async
void
play_pause
()
throws
Error
;
public
abstract
async
void
stop
()
throws
Error
;
public
abstract
async
void
play
()
throws
Error
;
public
abstract
async
void
seek
(
int64
offset
)
throws
Error
;
public
abstract
string
playback_status
{
owned
get
;
}
public
abstract
HashTable
<
string
,
Variant
>
metadata
{
owned
get
;
}
...
...
src/controlCenter/widgets/mpris/mpris.vala
View file @
0e025862
namespace
XimperShellNotificationCenter.Widgets.Mpris
{
public
enum
AlbumArtState
{
ALWAYS
,
WHEN_AVAILABLE
,
NEVER
;
public
static
AlbumArtState
parse
(
string
value
)
{
switch
(
value
)
{
default
:
case
"always"
:
return
AlbumArtState
.
ALWAYS
;
case
"when-available"
:
return
AlbumArtState
.
WHEN_AVAILABLE
;
case
"never"
:
return
AlbumArtState
.
NEVER
;
}
}
}
public
struct
Config
{
[
Version
(
deprecated
=
true
,
replacement
=
"CSS root variable"
)]
int
image_size
;
AlbumArtState
show_album_art
;
bool
autohide
;
string
[]
blacklist
;
bool
loop_carousel
;
}
public
class
Mpris
:
BaseWidget
{
...
...
@@ -45,10 +24,6 @@ namespace XimperShellNotificationCenter.Widgets.Mpris {
// Default config values
Config
mpris_config
=
Config
()
{
image_size
=
-
1
,
show_album_art
=
AlbumArtState
.
ALWAYS
,
autohide
=
false
,
loop_carousel
=
false
,
};
public
Mpris
(
string
suffix
)
{
...
...
@@ -83,9 +58,8 @@ namespace XimperShellNotificationCenter.Widgets.Mpris {
button_next
.
sensitive
=
false
;
return
;
}
button_prev
.
sensitive
=
(
index
>
0
)
||
mpris_config
.
loop_carousel
;
button_next
.
sensitive
=
(
index
<
carousel
.
n_pages
-
1
)
||
mpris_config
.
loop_carousel
;
button_prev
.
sensitive
=
true
;
button_next
.
sensitive
=
true
;
});
carousel_box
.
append
(
button_prev
);
...
...
@@ -103,46 +77,21 @@ namespace XimperShellNotificationCenter.Widgets.Mpris {
// Config
Json
.
Object
?
config
=
get_config
(
this
);
if
(
config
!=
null
)
{
// Get image-size
bool
image_size_found
;
int
?
image_size
=
get_prop
<
int
>
(
config
,
"image-size"
,
out
image_size_found
);
if
(
image_size_found
&&
image_size
!=
null
)
{
mpris_config
.
image_size
=
image_size
;
}
bool
show_art_found
;
string
?
show_album_art
=
get_prop
<
string
>
(
config
,
"show-album-art"
,
out
show_art_found
);
if
(
show_art_found
&&
show_album_art
!=
null
)
{
mpris_config
.
show_album_art
=
AlbumArtState
.
parse
(
show_album_art
);
}
Json
.
Array
?
blacklist
=
get_prop_array
(
config
,
"blacklist"
);
Json
.
Array
?
blacklist
=
get_prop_array
(
config
,
"blacklist"
);
if
(
blacklist
!=
null
)
{
mpris_config
.
blacklist
=
new
string
[
blacklist
.
get_length
()];
mpris_config
.
blacklist
=
new
string
[
blacklist
.
get_length
()];
for
(
int
i
=
0
;
i
<
blacklist
.
get_length
();
i
++)
{
if
(
blacklist
.
get_element
(
i
).
get_node_type
()
!=
Json
.
NodeType
.
VALUE
)
{
if
(
blacklist
.
get_element
(
i
).
get_node_type
()
!=
Json
.
NodeType
.
VALUE
)
{
warning
(
"Blacklist entries should be strings"
);
continue
;
}
mpris_config
.
blacklist
[
i
]
=
blacklist
.
get_string_element
(
i
);
mpris_config
.
blacklist
[
i
]
=
blacklist
.
get_string_element
(
i
);
}
}
// Get autohide
bool
autohide_found
;
bool
?
autohide
=
get_prop
<
bool
>
(
config
,
"autohide"
,
out
autohide_found
);
if
(
autohide_found
)
{
mpris_config
.
autohide
=
autohide
;
}
// Get loop
bool
loop_carousel_found
;
bool
?
loop_carousel
=
get_prop
<
bool
>
(
config
,
"loop-carousel"
,
out
loop_carousel_found
);
if
(
loop_carousel_found
)
{
mpris_config
.
loop_carousel
=
loop_carousel
;
}
}
hide
();
...
...
@@ -249,17 +198,15 @@ namespace XimperShellNotificationCenter.Widgets.Mpris {
player
.
add_css_class
(
"%s-player"
.
printf
(
css_class_name
));
players
.
set
(
name
,
player
);
if
(
mpris_config
.
autohide
)
{
player
.
content_updated
.
connect
(()
=>
{
if
(!
check_player_metadata_empty
(
name
))
{
add_player_to_carousel
(
name
);
}
else
{
remove_player_from_carousel
(
name
);
}
});
if
(
check_player_metadata_empty
(
name
))
{
return
;
player
.
content_updated
.
connect
(()
=>
{
if
(!
check_player_metadata_empty
(
name
))
{
add_player_to_carousel
(
name
);
}
else
{
remove_player_from_carousel
(
name
);
}
});
if
(
check_player_metadata_empty
(
name
))
{
return
;
}
add_player_to_carousel
(
name
);
...
...
@@ -303,12 +250,8 @@ namespace XimperShellNotificationCenter.Widgets.Mpris {
return
;
}
uint
position
;
if
(
mpris_config
.
loop_carousel
)
{
position
=
((
uint
)
carousel
.
position
+
delta
)
%
children_length
;
}
else
{
position
=
((
uint
)
carousel
.
position
+
delta
)
.
clamp
(
0
,
(
children_length
-
1
));
}
position
=
((
uint
)
carousel
.
position
+
delta
)
%
children_length
;
carousel
.
scroll_to
(
carousel
.
get_nth_page
(
position
),
true
);
}
...
...
src/controlCenter/widgets/mpris/mpris_player.vala
View file @
0e025862
...
...
@@ -23,6 +23,18 @@ namespace XimperShellNotificationCenter.Widgets.Mpris {
[
GtkChild
]
unowned
Gtk
.
Button
button_repeat
;
[
GtkChild
]
unowned
Gtk
.
Box
progress_box
;
[
GtkChild
]
unowned
Gtk
.
Scale
progress_scale
;
[
GtkChild
]
unowned
Gtk
.
Label
position_label
;
[
GtkChild
]
unowned
Gtk
.
Label
duration_label
;
private
int64
track_length
=
0
;
private
bool
seeking
=
false
;
public
MprisSource
source
{
construct
;
get
;
}
private
const
double
UNSELECTED_OPACITY
=
0.5
;
...
...
@@ -94,12 +106,98 @@ namespace XimperShellNotificationCenter.Widgets.Mpris {
update_buttons
(
source
.
media_player
.
metadata
);
});
});
album_art
.
set_pixel_size
(
mpris_config
.
image_size
);
album_art
.
set_visible
(
mpris_config
.
show_album_art
==
AlbumArtState
.
ALWAYS
);
album_art
.
set_visible
(
true
);
// Progress bar
progress_scale
.
change_value
.
connect
((
type
,
val
)
=>
{
if
(
track_length
<=
0
)
return
false
;
seeking
=
true
;
int64
pos
=
(
int64
)
(
val
*
track_length
/
100
);
position_label
.
set_text
(
format_time
(
pos
));
return
false
;
});
progress_scale
.
value_changed
.
connect
(()
=>
{
if
(!
seeking
||
track_length
<=
0
)
return
;
seeking
=
false
;
int64
pos
=
(
int64
)
(
progress_scale
.
get_value
()
*
track_length
/
100
);
seek_to
.
begin
(
pos
);
});
}
private
void
update_position
()
{
if
(
seeking
||
track_length
<=
0
)
return
;
Variant
?
pos_var
=
source
.
get_mpris_player_prop
(
"Position"
);
if
(
pos_var
==
null
||
!
pos_var
.
is_of_type
(
VariantType
.
INT64
))
{
return
;
}
int64
pos
=
pos_var
.
get_int64
();
double
percent
=
(
double
)
pos
/
track_length
*
100
;
progress_scale
.
set_value
(
percent
.
clamp
(
0
,
100
));
position_label
.
set_text
(
format_time
(
pos
));
}
private
void
update_track_length
(
HashTable
<
string
,
Variant
>
metadata
)
{
Variant
?
length
=
metadata
.
lookup
(
"mpris:length"
);
if
(
length
!=
null
)
{
if
(
length
.
is_of_type
(
VariantType
.
INT64
))
{
track_length
=
length
.
get_int64
();
}
else
if
(
length
.
is_of_type
(
VariantType
.
INT32
))
{
track_length
=
length
.
get_int32
();
}
else
{
track_length
=
0
;
}
}
else
{
track_length
=
0
;
}
bool
show
=
track_length
>
0
;
progress_box
.
set_visible
(
show
);
if
(
show
)
{
duration_label
.
set_text
(
format_time
(
track_length
));
update_position
();
}
}
private
async
void
seek_to
(
int64
position_us
)
{
try
{
// SetPosition needs track id
var
metadata
=
source
.
media_player
.
metadata
;
Variant
?
trackid
=
metadata
.
lookup
(
"mpris:trackid"
);
if
(
trackid
!=
null
)
{
// Use Seek with offset from current
Variant
?
cur
=
source
.
get_mpris_player_prop
(
"Position"
);
if
(
cur
!=
null
&&
cur
.
is_of_type
(
VariantType
.
INT64
))
{
int64
offset
=
position_us
-
cur
.
get_int64
();
yield
source
.
media_player
.
seek
(
offset
);
}
}
}
catch
(
Error
e
)
{
debug
(
"Seek failed: %s"
,
e
.
message
);
}
}
private
static
string
format_time
(
int64
microseconds
)
{
int64
seconds
=
microseconds
/
1000000
;
int64
minutes
=
seconds
/
60
;
seconds
=
seconds
%
60
;
return
"%d:%02d"
.
printf
(
(
int
)
minutes
,
(
int
)
seconds
);
}
public
void
before_destroy
()
{
source
.
properties_changed
.
disconnect
(
properties_changed
);
source
.
properties_changed
.
disconnect
(
properties_changed
);
}
private
void
properties_changed
(
string
iface
,
...
...
@@ -131,6 +229,9 @@ namespace XimperShellNotificationCenter.Widgets.Mpris {
case
"CanGoNext"
:
update_button_forward
(
metadata
);
break
;
case
"Position"
:
update_position
();
break
;
case
"CanControl"
:
update_buttons
(
metadata
);
break
;
...
...
@@ -154,6 +255,9 @@ namespace XimperShellNotificationCenter.Widgets.Mpris {
// Subtitle
update_sub_title
(
metadata
);
// Progress bar
update_track_length
(
metadata
);
// Update the buttons
update_buttons
(
metadata
);
...
...
@@ -314,18 +418,17 @@ namespace XimperShellNotificationCenter.Widgets.Mpris {
}
if
(
album_art_texture
!=
null
)
{
// Set album art
int
icon_size
=
mpris_config
.
image_size
;
if
(
icon_size
<
0
)
{
icon_size
=
album_art_texture
.
width
>
album_art_texture
.
height
?
album_art_texture
.
height
:
album_art_texture
.
width
;
}
int
icon_size
=
album_art_texture
.
width
>
album_art_texture
.
height
?
album_art_texture
.
height
:
album_art_texture
.
width
;
Gtk
.
Snapshot
snapshot
=
new
Gtk
.
Snapshot
();
Functions
.
scale_texture
(
album_art_texture
,
icon_size
,
icon_size
,
get_scale_factor
(),
snapshot
);
Graphene
.
Size
size
=
Graphene
.
Size
().
init
(
icon_size
,
icon_size
);
album_art
.
set_from_paintable
(
snapshot
.
free_to_paintable
(
size
));
album_art
.
set_visible
(
mpris_config
.
show_album_art
!=
AlbumArtState
.
NEVER
);
album_art
.
set_visible
(
true
);
// Set background album art
background_picture
.
set_paintable
(
album_art_texture
);
...
...
@@ -335,7 +438,7 @@ namespace XimperShellNotificationCenter.Widgets.Mpris {
}
}
album_art
.
set_visible
(
mpris_config
.
show_album_art
==
AlbumArtState
.
ALWAYS
);
album_art
.
set_visible
(
true
);
// Get the app icon
Icon
?
icon
=
null
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment