mirror of
https://github.com/edu4rdshl/gnome-shell.git
synced 2026-07-17 23:24:51 +00:00
Merge branch 'add_notification_groups' into 'main'
Draft: notifications: Implement per app grouping See merge request GNOME/gnome-shell!3012
This commit is contained in:
commit
f5f594063d
9 changed files with 1426 additions and 449 deletions
|
|
@ -5,6 +5,7 @@
|
|||
<file preprocess="xml-stripblanks">scalable/actions/carousel-arrow-next-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">scalable/actions/carousel-arrow-previous-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">scalable/actions/dark-mode-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">scalable/actions/group-collapse-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">scalable/actions/notification-expand-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">scalable/actions/ornament-check-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">scalable/actions/ornament-dot-checked-symbolic.svg</file>
|
||||
|
|
|
|||
37
data/icons/scalable/actions/group-collapse-symbolic.svg
Normal file
37
data/icons/scalable/actions/group-collapse-symbolic.svg
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="16px"
|
||||
viewBox="0 0 16 16"
|
||||
width="16px"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
id="g456"
|
||||
transform="matrix(1,0,0,-1,392,1185.9336)"
|
||||
style="fill:#2e3436;fill-opacity:1">
|
||||
<g
|
||||
id="g143"
|
||||
transform="translate(0,-0.50051875)"
|
||||
style="fill:#2e3436;fill-opacity:1">
|
||||
<g
|
||||
id="g144"
|
||||
transform="translate(0,0.5)"
|
||||
style="fill:#2e3436;fill-opacity:1">
|
||||
<path
|
||||
d="m -388,1172.4341 c 0,-0.2656 0.1055,-0.5195 0.293,-0.707 0.3906,-0.3906 1.0234,-0.3906 1.414,0 l 2.293,2.293 2.293,-2.293 c 0.3906,-0.3906 1.0234,-0.3906 1.414,0 0.1875,0.1875 0.293,0.4414 0.293,0.707 0,0.2656 -0.1055,0.5195 -0.293,0.707 l -3,3 c -0.3906,0.3907 -1.0234,0.3907 -1.414,0 l -3,-3 c -0.1875,-0.1875 -0.293,-0.4414 -0.293,-0.707 z"
|
||||
fill="#2e3436"
|
||||
id="path177459-1"
|
||||
style="fill:#2e3436;fill-opacity:1" />
|
||||
<path
|
||||
d="m -388,1183.4342 c 0,0.2656 0.1055,0.5195 0.293,0.707 0.3906,0.3906 1.0234,0.3906 1.414,0 l 2.293,-2.293 2.293,2.293 c 0.3906,0.3906 1.0234,0.3906 1.414,0 0.1875,-0.1875 0.293,-0.4414 0.293,-0.707 0,-0.2656 -0.1055,-0.5195 -0.293,-0.707 l -3,-3 c -0.3906,-0.3907 -1.0234,-0.3907 -1.414,0 l -3,3 c -0.1875,0.1875 -0.293,0.4414 -0.293,0.707 z"
|
||||
fill="#2e3436"
|
||||
id="path142"
|
||||
style="fill:#2e3436;fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -25,17 +25,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
.message-list-sections {
|
||||
.message-view {
|
||||
// to account for scrollbar
|
||||
&:ltr {margin-right: $base_margin * 3; }
|
||||
&:rtl {margin-left: $base_margin * 3;}
|
||||
|
||||
spacing: $base_padding * 2;
|
||||
}
|
||||
.message {
|
||||
margin-bottom: $base_padding * 2 !important;
|
||||
}
|
||||
|
||||
.message-list-section,
|
||||
.message-list-section-list {
|
||||
spacing: $base_padding * 2;
|
||||
> :last-child > .message {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
// do-not-disturb + clear button
|
||||
|
|
@ -61,6 +62,30 @@
|
|||
}
|
||||
}
|
||||
|
||||
// message notification group
|
||||
.message-notification-group {
|
||||
spacing: $base_padding * 2;
|
||||
|
||||
.message-group-header {
|
||||
padding: $base_padding;
|
||||
.message-group-title {
|
||||
@extend %title_2;
|
||||
margin: 0 $base_margin;
|
||||
}
|
||||
}
|
||||
|
||||
// close button
|
||||
.message-collapse-button {
|
||||
@extend .icon-button;
|
||||
color: $fg_color;
|
||||
background-color: transparentize($fg_color, 0.8);
|
||||
padding: 4px !important;
|
||||
border: 4px transparent solid;
|
||||
&:hover {background-color: transparentize($fg_color, 0.7);}
|
||||
&:active {background-color: transparentize($fg_color, 0.8);}
|
||||
}
|
||||
}
|
||||
|
||||
// message bubbles
|
||||
.message {
|
||||
@extend %card;
|
||||
|
|
|
|||
|
|
@ -7,18 +7,13 @@ import GObject from 'gi://GObject';
|
|||
import Shell from 'gi://Shell';
|
||||
import St from 'gi://St';
|
||||
|
||||
import * as Main from './main.js';
|
||||
import * as MessageList from './messageList.js';
|
||||
import * as MessageTray from './messageTray.js';
|
||||
import * as Mpris from './mpris.js';
|
||||
import * as PopupMenu from './popupMenu.js';
|
||||
import {ensureActorVisibleInScrollView} from '../misc/animationUtils.js';
|
||||
|
||||
import {formatDateWithCFormatString} from '../misc/dateUtils.js';
|
||||
import {loadInterfaceXML} from '../misc/fileUtils.js';
|
||||
|
||||
const SHOW_WEEKDATE_KEY = 'show-weekdate';
|
||||
const MAX_NOTIFICATION_BUTTONS = 3;
|
||||
|
||||
const NC_ = (context, str) => `${context}\u0004${str}`;
|
||||
|
||||
|
|
@ -765,146 +760,122 @@ export const Calendar = GObject.registerClass({
|
|||
}
|
||||
});
|
||||
|
||||
export const NotificationMessage = GObject.registerClass(
|
||||
class NotificationMessage extends MessageList.Message {
|
||||
constructor(notification) {
|
||||
super(notification.source);
|
||||
const FadeEffect = GObject.registerClass({
|
||||
Properties: {
|
||||
'top-fade': GObject.ParamSpec.float(
|
||||
'top-fade', 'top-fade', 'top-fade',
|
||||
GObject.ParamFlags.READWRITE,
|
||||
0, GLib.MAXINT32, 0),
|
||||
'bottom-fade': GObject.ParamSpec.float(
|
||||
'bottom-fade', 'bottom-fade', 'bottom-fade',
|
||||
GObject.ParamFlags.READWRITE,
|
||||
0, GLib.MAXINT32, 0),
|
||||
'opacity': GObject.ParamSpec.float(
|
||||
'opacity', 'opacity', 'opacity',
|
||||
GObject.ParamFlags.READWRITE,
|
||||
0, 1, 0),
|
||||
},
|
||||
}, class FadeEffect extends Shell.GLSLEffect {
|
||||
_init(params) {
|
||||
super._init(params);
|
||||
|
||||
this.notification = notification;
|
||||
this._heightLocation = this.get_uniform_location('height');
|
||||
this._widthLocation = this.get_uniform_location('width');
|
||||
this._topFadeHeightLocation = this.get_uniform_location('top_fade_height');
|
||||
this._bottomFadeHeightLocation = this.get_uniform_location('bottom_fade_height');
|
||||
this._opacityLocation = this.get_uniform_location('opacity');
|
||||
|
||||
this.connect('close', () => {
|
||||
this._closed = true;
|
||||
if (this.notification)
|
||||
this.notification.destroy(MessageTray.NotificationDestroyedReason.DISMISSED);
|
||||
});
|
||||
notification.connectObject(
|
||||
'action-added', (_, action) => this._addAction(action),
|
||||
'action-removed', (_, action) => this._removeAction(action),
|
||||
'destroy', () => {
|
||||
this.notification = null;
|
||||
if (!this._closed)
|
||||
this.close();
|
||||
}, this);
|
||||
|
||||
notification.bind_property('title',
|
||||
this, 'title',
|
||||
GObject.BindingFlags.SYNC_CREATE);
|
||||
notification.bind_property('body',
|
||||
this, 'body',
|
||||
GObject.BindingFlags.SYNC_CREATE);
|
||||
notification.bind_property('use-body-markup',
|
||||
this, 'use-body-markup',
|
||||
GObject.BindingFlags.SYNC_CREATE);
|
||||
notification.bind_property('datetime',
|
||||
this, 'datetime',
|
||||
GObject.BindingFlags.SYNC_CREATE);
|
||||
notification.bind_property('gicon',
|
||||
this, 'icon',
|
||||
GObject.BindingFlags.SYNC_CREATE);
|
||||
|
||||
this._actions = new Map();
|
||||
this.notification.actions.forEach(action => {
|
||||
this._addAction(action);
|
||||
});
|
||||
this._topFade = 0;
|
||||
this._bottomFade = 0;
|
||||
this._opacity = 0;
|
||||
}
|
||||
|
||||
vfunc_clicked() {
|
||||
this.notification.activate();
|
||||
get opacity() {
|
||||
return this._opacity;
|
||||
}
|
||||
|
||||
canClose() {
|
||||
return true;
|
||||
}
|
||||
|
||||
_addAction(action) {
|
||||
if (!this._buttonBox) {
|
||||
this._buttonBox = new St.BoxLayout({
|
||||
x_expand: true,
|
||||
style_class: 'notification-buttons-bin',
|
||||
});
|
||||
this.setActionArea(this._buttonBox);
|
||||
global.focus_manager.add_group(this._buttonBox);
|
||||
}
|
||||
|
||||
if (this._buttonBox.get_n_children() >= MAX_NOTIFICATION_BUTTONS)
|
||||
set opacity(opacity) {
|
||||
if (this._opacity === opacity)
|
||||
return;
|
||||
|
||||
const button = new St.Button({
|
||||
style_class: 'notification-button',
|
||||
x_expand: true,
|
||||
label: action.label,
|
||||
});
|
||||
this._opacity = opacity;
|
||||
this.set_uniform_float(this._opacityLocation, 1, [opacity]);
|
||||
this.actor.queue_redraw();
|
||||
|
||||
button.connect('clicked', () => action.activate());
|
||||
|
||||
this._actions.set(action, button);
|
||||
this._buttonBox.add_child(button);
|
||||
this.notify('opacity');
|
||||
}
|
||||
|
||||
_removeAction(action) {
|
||||
this._actions.get(action)?.destroy();
|
||||
this._actions.delete(action);
|
||||
}
|
||||
});
|
||||
|
||||
const NotificationSection = GObject.registerClass(
|
||||
class NotificationSection extends MessageList.MessageListSection {
|
||||
_init() {
|
||||
super._init();
|
||||
|
||||
this._nUrgent = 0;
|
||||
|
||||
Main.messageTray.connect('source-added', this._sourceAdded.bind(this));
|
||||
Main.messageTray.getSources().forEach(source => {
|
||||
this._sourceAdded(Main.messageTray, source);
|
||||
});
|
||||
get topFade() {
|
||||
return this._topFade;
|
||||
}
|
||||
|
||||
get allowed() {
|
||||
return Main.sessionMode.hasNotifications &&
|
||||
!Main.sessionMode.isGreeter;
|
||||
set topFade(height) {
|
||||
if (this._topFade === height)
|
||||
return;
|
||||
|
||||
this._topFade = height;
|
||||
this.set_uniform_float(this._topFadeHeightLocation, 1, [height]);
|
||||
this.actor.queue_redraw();
|
||||
|
||||
this.notify('top-fade');
|
||||
}
|
||||
|
||||
_sourceAdded(tray, source) {
|
||||
source.connectObject('notification-added',
|
||||
this._onNotificationAdded.bind(this), this);
|
||||
get bottomFade() {
|
||||
return this._bottomFade;
|
||||
}
|
||||
|
||||
_onNotificationAdded(source, notification) {
|
||||
let message = new NotificationMessage(notification);
|
||||
set bottomFade(height) {
|
||||
if (this._bottomFade === height)
|
||||
return;
|
||||
|
||||
let isUrgent = notification.urgency === MessageTray.Urgency.CRITICAL;
|
||||
this._bottomFade = height;
|
||||
this.set_uniform_float(this._bottomFadeHeightLocation, 1, [height]);
|
||||
this.actor.queue_redraw();
|
||||
|
||||
notification.connectObject(
|
||||
'destroy', () => {
|
||||
if (isUrgent)
|
||||
this._nUrgent--;
|
||||
},
|
||||
'notify::datetime', () => {
|
||||
// The datetime property changes whenever the notification is updated
|
||||
this.moveMessage(message, isUrgent ? 0 : this._nUrgent, this.mapped);
|
||||
this.notify('bottom-fade');
|
||||
}
|
||||
|
||||
vfunc_set_actor(actor) {
|
||||
if (this.actor)
|
||||
this.actor.disconnectObject(this);
|
||||
|
||||
if (actor) {
|
||||
actor.connectObject('notify::allocation', () => {
|
||||
this.set_uniform_float(this._heightLocation, 1, [this.get_actor().height]);
|
||||
this.set_uniform_float(this._widthLocation, 1, [this.get_actor().width]);
|
||||
}, this);
|
||||
|
||||
if (isUrgent) {
|
||||
// Keep track of urgent notifications to keep them on top
|
||||
this._nUrgent++;
|
||||
} else if (this.mapped) {
|
||||
// Only acknowledge non-urgent notifications in case it
|
||||
// has important actions that are inaccessible when not
|
||||
// shown as banner
|
||||
notification.acknowledged = true;
|
||||
}
|
||||
|
||||
let index = isUrgent ? 0 : this._nUrgent;
|
||||
this.addMessageAtIndex(message, index, this.mapped);
|
||||
super.vfunc_set_actor(actor);
|
||||
}
|
||||
|
||||
vfunc_map() {
|
||||
this._messages.forEach(message => {
|
||||
if (message.notification.urgency !== MessageTray.Urgency.CRITICAL)
|
||||
message.notification.acknowledged = true;
|
||||
});
|
||||
super.vfunc_map();
|
||||
vfunc_build_pipeline() {
|
||||
const dec = `uniform sampler2D tex; \n
|
||||
uniform float height; \n
|
||||
uniform float width; \n
|
||||
uniform float opacity; \n
|
||||
uniform float top_fade_height; \n
|
||||
uniform float bottom_fade_height; \n`;
|
||||
|
||||
const src = `cogl_color_out = cogl_color_in * texture2D (tex, vec2 (cogl_tex_coord_in[0].xy)); \n
|
||||
float base_fade = 200; \n
|
||||
float y = height * cogl_tex_coord_in[0].y; \n
|
||||
float y_from_bottom = height - y; \n
|
||||
float ratio = 1.0; \n
|
||||
|
||||
if (y < top_fade_height) { \n
|
||||
ratio = y / (top_fade_height + base_fade); \n
|
||||
ratio = 1 - ((1 - ratio) * opacity); \n
|
||||
} \n
|
||||
|
||||
if (y_from_bottom < bottom_fade_height) { \n
|
||||
ratio = y_from_bottom / (bottom_fade_height + base_fade); \n
|
||||
ratio = 1 - ((1 - ratio) * opacity); \n
|
||||
} \n
|
||||
|
||||
cogl_color_out *= ratio; \n`;
|
||||
|
||||
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, dec, src, true);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -944,8 +915,8 @@ class DoNotDisturbSwitch extends PopupMenu.Switch {
|
|||
|
||||
export const CalendarMessageList = GObject.registerClass(
|
||||
class CalendarMessageList extends St.Widget {
|
||||
_init() {
|
||||
super._init({
|
||||
constructor() {
|
||||
super({
|
||||
style_class: 'message-list',
|
||||
layout_manager: new Clutter.BinLayout(),
|
||||
x_expand: true,
|
||||
|
|
@ -962,10 +933,14 @@ class CalendarMessageList extends St.Widget {
|
|||
});
|
||||
this.add_child(box);
|
||||
|
||||
this._messageView = new MessageList.MessageView();
|
||||
|
||||
this._scrollView = new St.ScrollView({
|
||||
style_class: 'vfade',
|
||||
effect: new FadeEffect({name: 'highlight'}),
|
||||
overlay_scrollbars: true,
|
||||
x_expand: true, y_expand: true,
|
||||
child: this._messageView,
|
||||
});
|
||||
box.add_child(this._scrollView);
|
||||
|
||||
|
|
@ -1001,58 +976,36 @@ class CalendarMessageList extends St.Widget {
|
|||
accessible_name: C_('action', 'Clear all notifications'),
|
||||
});
|
||||
this._clearButton.connect('clicked', () => {
|
||||
this._sectionList.get_children().forEach(s => s.clear());
|
||||
this._messageView.clear();
|
||||
});
|
||||
hbox.add_child(this._clearButton);
|
||||
|
||||
this._placeholder.bind_property('visible',
|
||||
this._clearButton, 'visible',
|
||||
GObject.BindingFlags.INVERT_BOOLEAN);
|
||||
|
||||
this._sectionList = new St.BoxLayout({
|
||||
style_class: 'message-list-sections',
|
||||
vertical: true,
|
||||
x_expand: true,
|
||||
y_expand: true,
|
||||
});
|
||||
this._sectionList.connectObject(
|
||||
'child-added', this._sync.bind(this),
|
||||
'child-removed', this._sync.bind(this),
|
||||
this);
|
||||
this._scrollView.child = this._sectionList;
|
||||
|
||||
this._mediaSection = new Mpris.MediaSection();
|
||||
this._addSection(this._mediaSection);
|
||||
|
||||
this._notificationSection = new NotificationSection();
|
||||
this._addSection(this._notificationSection);
|
||||
|
||||
Main.sessionMode.connect('updated', this._sync.bind(this));
|
||||
this._messageView.bind_property('empty',
|
||||
this._placeholder, 'visible',
|
||||
GObject.BindingFlags.SYNC_CREATE);
|
||||
this._messageView.bind_property('can-clear',
|
||||
this._clearButton, 'reactive',
|
||||
GObject.BindingFlags.SYNC_CREATE);
|
||||
}
|
||||
|
||||
_addSection(section) {
|
||||
section.connectObject(
|
||||
'notify::visible', this._sync.bind(this),
|
||||
'notify::empty', this._sync.bind(this),
|
||||
'notify::can-clear', this._sync.bind(this),
|
||||
'destroy', () => this._sectionList.remove_child(section),
|
||||
'message-focused', (_s, messageActor) => {
|
||||
ensureActorVisibleInScrollView(this._scrollView, messageActor);
|
||||
}, this);
|
||||
this._sectionList.add_child(section);
|
||||
}
|
||||
maybeCollapseForEvent(event) {
|
||||
if (!this._messageView.expandedGroup)
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
|
||||
_sync() {
|
||||
let sections = this._sectionList.get_children();
|
||||
let visible = sections.some(s => s.allowed);
|
||||
this.visible = visible;
|
||||
if (!visible)
|
||||
return;
|
||||
const targetActor = global.stage.get_event_actor(event);
|
||||
if ((event.type() === Clutter.EventType.BUTTON_PRESS ||
|
||||
event.type() === Clutter.EventType.TOUCH_BEGIN) &&
|
||||
!this._messageView.expandedGroup.contains(targetActor)) {
|
||||
this._messageView.collapse();
|
||||
} else if (event.type() === Clutter.EventType.KEY_PRESS &&
|
||||
event.get_key_symbol() === Clutter.KEY_Escape) {
|
||||
this._messageView.collapse();
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
|
||||
let empty = sections.every(s => s.empty || !s.visible);
|
||||
this._placeholder.visible = empty;
|
||||
|
||||
let canClear = sections.some(s => s.canClear && s.visible);
|
||||
this._clearButton.reactive = canClear;
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -800,7 +800,7 @@ class MessagesIndicator extends St.Icon {
|
|||
}
|
||||
});
|
||||
|
||||
const FreezableBinLayout = GObject.registerClass(
|
||||
export const FreezableBinLayout = GObject.registerClass(
|
||||
class FreezableBinLayout extends Clutter.BinLayout {
|
||||
_init() {
|
||||
super._init();
|
||||
|
|
@ -922,6 +922,11 @@ class DateMenuButton extends PanelMenu.Button {
|
|||
this._messageList = new Calendar.CalendarMessageList();
|
||||
hbox.add_child(this._messageList);
|
||||
|
||||
// Collapse notification groups when the user clicks outside of the expanded group
|
||||
this.menu.actor.connect('captured-event', (_, event) => {
|
||||
return this._messageList.maybeCollapseForEvent(event);
|
||||
});
|
||||
|
||||
// Fill up the second column
|
||||
const boxLayout = new CalendarColumnLayout([this._calendar, this._date]);
|
||||
const vbox = new St.Widget({
|
||||
|
|
@ -1002,5 +1007,7 @@ class DateMenuButton extends PanelMenu.Button {
|
|||
// but the corresponding app (clocks, weather); however we can consider
|
||||
// that display-specific settings, so re-use "allowSettings" here ...
|
||||
this._displaysSection.visible = Main.sessionMode.allowSettings;
|
||||
|
||||
this._messageList.visible = Main.sessionMode.hasNotifications && !Main.sessionMode.isGreeter;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
1331
js/ui/messageList.js
1331
js/ui/messageList.js
File diff suppressed because it is too large
Load diff
|
|
@ -8,7 +8,6 @@ import Meta from 'gi://Meta';
|
|||
import Shell from 'gi://Shell';
|
||||
import St from 'gi://St';
|
||||
|
||||
import * as Calendar from './calendar.js';
|
||||
import * as GnomeSession from '../misc/gnomeSession.js';
|
||||
import * as Layout from './layout.js';
|
||||
import * as Main from './main.js';
|
||||
|
|
@ -17,15 +16,13 @@ import * as SignalTracker from '../misc/signalTracker.js';
|
|||
|
||||
const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
|
||||
|
||||
export const ANIMATION_TIME = 200;
|
||||
|
||||
const NOTIFICATION_TIMEOUT = 4000;
|
||||
|
||||
const HIDE_TIMEOUT = 200;
|
||||
const LONGER_HIDE_TIMEOUT = 600;
|
||||
|
||||
const MAX_NOTIFICATIONS_IN_QUEUE = 3;
|
||||
const MAX_NOTIFICATIONS_PER_SOURCE = 3;
|
||||
const MAX_NOTIFICATIONS_PER_SOURCE = 10;
|
||||
|
||||
// We delay hiding of the tray if the mouse is within MOUSE_LEFT_ACTOR_THRESHOLD
|
||||
// range from the point where it left the tray.
|
||||
|
|
@ -597,13 +594,6 @@ export const Source = GObject.registerClass({
|
|||
// To be overridden by subclasses
|
||||
open() {
|
||||
}
|
||||
|
||||
destroyNonResidentNotifications() {
|
||||
for (let i = this.notifications.length - 1; i >= 0; i--) {
|
||||
if (!this.notifications[i].resident)
|
||||
this.notifications[i].destroy();
|
||||
}
|
||||
}
|
||||
});
|
||||
SignalTracker.registerDestroyableType(Source);
|
||||
|
||||
|
|
@ -1111,7 +1101,7 @@ export const MessageTray = GObject.registerClass({
|
|||
this.idleMonitor.add_user_active_watch(this._onIdleMonitorBecameActive.bind(this));
|
||||
}
|
||||
|
||||
this._banner = new Calendar.NotificationMessage(this._notification);
|
||||
this._banner = new MessageList.NotificationMessage(this._notification);
|
||||
this._banner.can_focus = false;
|
||||
this._banner._header.expandButton.visible = false;
|
||||
this._banner.add_style_class_name('notification-banner');
|
||||
|
|
@ -1167,12 +1157,12 @@ export const MessageTray = GObject.registerClass({
|
|||
this._bannerBin.remove_all_transitions();
|
||||
this._bannerBin.ease({
|
||||
opacity: 255,
|
||||
duration: ANIMATION_TIME,
|
||||
duration: MessageList.MESSAGE_ANIMATION_TIME,
|
||||
mode: Clutter.AnimationMode.LINEAR,
|
||||
});
|
||||
this._bannerBin.ease({
|
||||
y: 0,
|
||||
duration: ANIMATION_TIME,
|
||||
duration: MessageList.MESSAGE_ANIMATION_TIME,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_BACK,
|
||||
onComplete: () => {
|
||||
this._notificationState = State.SHOWN;
|
||||
|
|
@ -1232,7 +1222,7 @@ export const MessageTray = GObject.registerClass({
|
|||
this._resetNotificationLeftTimeout();
|
||||
this._bannerBin.remove_all_transitions();
|
||||
|
||||
const duration = animate ? ANIMATION_TIME : 0;
|
||||
const duration = animate ? MessageList.MESSAGE_ANIMATION_TIME : 0;
|
||||
this._notificationState = State.HIDING;
|
||||
this._bannerBin.ease({
|
||||
opacity: 0,
|
||||
|
|
|
|||
154
js/ui/mpris.js
154
js/ui/mpris.js
|
|
@ -1,9 +1,7 @@
|
|||
import Gio from 'gi://Gio';
|
||||
import GObject from 'gi://GObject';
|
||||
import Shell from 'gi://Shell';
|
||||
import * as Signals from '../misc/signals.js';
|
||||
|
||||
import * as Main from './main.js';
|
||||
import * as MessageList from './messageList.js';
|
||||
|
||||
import {loadInterfaceXML} from '../misc/fileUtils.js';
|
||||
|
|
@ -19,71 +17,17 @@ const MprisPlayerProxy = Gio.DBusProxy.makeProxyWrapper(MprisPlayerIface);
|
|||
|
||||
const MPRIS_PLAYER_PREFIX = 'org.mpris.MediaPlayer2.';
|
||||
|
||||
export const MediaMessage = GObject.registerClass(
|
||||
class MediaMessage extends MessageList.Message {
|
||||
constructor(player) {
|
||||
super(player.source);
|
||||
|
||||
this._player = player;
|
||||
this.add_style_class_name('media-message');
|
||||
|
||||
this._prevButton = this.addMediaControl('media-skip-backward-symbolic',
|
||||
() => {
|
||||
this._player.previous();
|
||||
});
|
||||
|
||||
this._playPauseButton = this.addMediaControl('',
|
||||
() => {
|
||||
this._player.playPause();
|
||||
});
|
||||
|
||||
this._nextButton = this.addMediaControl('media-skip-forward-symbolic',
|
||||
() => {
|
||||
this._player.next();
|
||||
});
|
||||
|
||||
this._player.connectObject(
|
||||
'changed', this._update.bind(this),
|
||||
'closed', this.close.bind(this), this);
|
||||
this._update();
|
||||
}
|
||||
|
||||
vfunc_clicked() {
|
||||
this._player.raise();
|
||||
Main.panel.closeCalendar();
|
||||
}
|
||||
|
||||
_updateNavButton(button, sensitive) {
|
||||
button.reactive = sensitive;
|
||||
}
|
||||
|
||||
_update() {
|
||||
let icon;
|
||||
if (this._player.trackCoverUrl) {
|
||||
const file = Gio.File.new_for_uri(this._player.trackCoverUrl);
|
||||
icon = new Gio.FileIcon({file});
|
||||
} else {
|
||||
icon = new Gio.ThemedIcon({name: 'audio-x-generic-symbolic'});
|
||||
}
|
||||
|
||||
this.set({
|
||||
title: this._player.trackTitle,
|
||||
body: this._player.trackArtists.join(', '),
|
||||
icon,
|
||||
});
|
||||
|
||||
let isPlaying = this._player.status === 'Playing';
|
||||
let iconName = isPlaying
|
||||
? 'media-playback-pause-symbolic'
|
||||
: 'media-playback-start-symbolic';
|
||||
this._playPauseButton.child.icon_name = iconName;
|
||||
|
||||
this._updateNavButton(this._prevButton, this._player.canGoPrevious);
|
||||
this._updateNavButton(this._nextButton, this._player.canGoNext);
|
||||
}
|
||||
});
|
||||
|
||||
export class MprisPlayer extends Signals.EventEmitter {
|
||||
export const MprisPlayer = GObject.registerClass({
|
||||
Properties: {
|
||||
'can-play': GObject.ParamSpec.boolean(
|
||||
'can-play', 'can-play', 'can-play',
|
||||
GObject.ParamFlags.READABLE,
|
||||
false),
|
||||
},
|
||||
Signals: {
|
||||
'changed': {},
|
||||
},
|
||||
}, class MprisPlayer extends GObject.Object {
|
||||
constructor(busName) {
|
||||
super();
|
||||
|
||||
|
|
@ -94,7 +38,7 @@ export class MprisPlayer extends Signals.EventEmitter {
|
|||
'/org/mpris/MediaPlayer2',
|
||||
this._onPlayerProxyReady.bind(this));
|
||||
|
||||
this._visible = false;
|
||||
this._canPlay = false;
|
||||
this._trackArtists = [];
|
||||
this._trackTitle = '';
|
||||
this._trackCoverUrl = '';
|
||||
|
|
@ -102,6 +46,10 @@ export class MprisPlayer extends Signals.EventEmitter {
|
|||
this.source = new MessageList.Source();
|
||||
}
|
||||
|
||||
get canPlay() {
|
||||
return this._canPlay;
|
||||
}
|
||||
|
||||
get status() {
|
||||
return this._playerProxy.PlaybackStatus;
|
||||
}
|
||||
|
|
@ -157,8 +105,6 @@ export class MprisPlayer extends Signals.EventEmitter {
|
|||
|
||||
this._playerProxy.disconnectObject(this);
|
||||
this._playerProxy = null;
|
||||
|
||||
this.emit('closed');
|
||||
}
|
||||
|
||||
_onMprisProxyReady() {
|
||||
|
|
@ -175,8 +121,7 @@ export class MprisPlayer extends Signals.EventEmitter {
|
|||
}
|
||||
|
||||
_onPlayerProxyReady() {
|
||||
this._playerProxy.connectObject(
|
||||
'g-properties-changed', () => this._updateState(), this);
|
||||
this._playerProxy.connectObject('g-properties-changed', this._updateState.bind(this), this);
|
||||
this._updateState();
|
||||
}
|
||||
|
||||
|
|
@ -230,22 +175,22 @@ export class MprisPlayer extends Signals.EventEmitter {
|
|||
icon: this._app?.get_icon() ?? null,
|
||||
});
|
||||
|
||||
this.emit('changed');
|
||||
const canPlay = !!this._playerProxy.CanPlay;
|
||||
|
||||
let visible = this._playerProxy.CanPlay;
|
||||
|
||||
if (this._visible !== visible) {
|
||||
this._visible = visible;
|
||||
if (visible)
|
||||
this.emit('show');
|
||||
else
|
||||
this.emit('hide');
|
||||
if (this.canPlay !== canPlay) {
|
||||
this._canPlay = canPlay;
|
||||
this.notify('can-play');
|
||||
}
|
||||
this.emit('changed');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const MediaSection = GObject.registerClass(
|
||||
class MediaSection extends MessageList.MessageListSection {
|
||||
export const MediaSource = GObject.registerClass({
|
||||
Signals: {
|
||||
'player-added': {param_types: [MprisPlayer]},
|
||||
'player-removed': {param_types: [MprisPlayer]},
|
||||
},
|
||||
}, class MediaSource extends GObject.Object {
|
||||
_init() {
|
||||
super._init();
|
||||
|
||||
|
|
@ -257,30 +202,24 @@ class MediaSection extends MessageList.MessageListSection {
|
|||
this._onProxyReady.bind(this));
|
||||
}
|
||||
|
||||
get allowed() {
|
||||
return !Main.sessionMode.isGreeter;
|
||||
get players() {
|
||||
return [...this._players.values()];
|
||||
}
|
||||
|
||||
_addPlayer(busName) {
|
||||
if (this._players.get(busName))
|
||||
if (this._players.has(busName))
|
||||
return;
|
||||
|
||||
let player = new MprisPlayer(busName);
|
||||
let message = null;
|
||||
player.connect('closed',
|
||||
() => {
|
||||
this._players.delete(busName);
|
||||
});
|
||||
player.connect('show', () => {
|
||||
message = new MediaMessage(player);
|
||||
this.addMessage(message, true);
|
||||
});
|
||||
player.connect('hide', () => {
|
||||
this.removeMessage(message, true);
|
||||
message = null;
|
||||
});
|
||||
|
||||
const player = new MprisPlayer(busName);
|
||||
this._players.set(busName, player);
|
||||
|
||||
player.connectObject('notify::can-play',
|
||||
() => {
|
||||
if (player.canPlay)
|
||||
this.emit('player-added', player);
|
||||
else
|
||||
this.emit('player-removed', player);
|
||||
}, this);
|
||||
}
|
||||
|
||||
async _onProxyReady() {
|
||||
|
|
@ -299,7 +238,16 @@ class MediaSection extends MessageList.MessageListSection {
|
|||
if (!name.startsWith(MPRIS_PLAYER_PREFIX))
|
||||
return;
|
||||
|
||||
if (newOwner && !oldOwner)
|
||||
if (oldOwner) {
|
||||
const player = this._players.get(name);
|
||||
if (player) {
|
||||
this._players.delete(name);
|
||||
player.disconnectObject(this);
|
||||
this.emit('player-removed', player);
|
||||
}
|
||||
}
|
||||
|
||||
if (newOwner)
|
||||
this._addPlayer(name);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -368,7 +368,6 @@ class FdoNotificationDaemonSource extends MessageTray.Source {
|
|||
|
||||
open() {
|
||||
this.openApp();
|
||||
this.destroyNonResidentNotifications();
|
||||
}
|
||||
|
||||
openApp() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue