Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions i18n/de-DE/ashell.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ settings-power-status-full = Voll
## Einstellungen — Kein Ruhezustand
settings-idle-inhibitor = Kein Ruhezustand

## Einstellungen — Nicht stören
settings-dnd = Nicht stören

## Einstellungen — tooltips
settings-tooltip-empty = Ziemlich leer hier
settings-tooltip-empty-audio = Kein aktives Audio-Gerät
Expand Down
3 changes: 3 additions & 0 deletions i18n/en-US/ashell.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ settings-power-status-full = Full
## Settings — idle inhibitor
settings-idle-inhibitor = Idle Inhibitor

## Settings — do not disturb
settings-dnd = Do Not Disturb

## Settings — tooltips
settings-tooltip-empty = Nothing to show
settings-tooltip-empty-audio = No active audio device
Expand Down
3 changes: 3 additions & 0 deletions i18n/fr-FR/ashell.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ settings-power-status-unknown = Inconnu
## Paramètres — inhibiteur de veille
settings-idle-inhibitor = Inhibiteur de veille

## Paramètres — ne pas déranger
settings-dnd = Ne pas déranger

## Paramètres — infobulles
settings-tooltip-empty = Rien à afficher
settings-tooltip-empty-audio = Aucun périphérique audio actif
Expand Down
19 changes: 18 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ impl App {

let notifications = Notifications::new(config.notifications, config.animations.enabled);

let notifications_module_enabled = config.modules.contains(&ModuleName::Notifications);

let needs_icons = config.modules.contains(&ModuleName::Tray)
|| config.workspaces.indicator_format == WorkspaceIndicatorFormat::NameAndIcons;
let warm_icons = if needs_icons {
Expand Down Expand Up @@ -136,7 +138,7 @@ impl App {
tray: TrayModule::new(config.tray),
tempo: Tempo::new(config.tempo),
privacy: Privacy::default(),
settings: Settings::new(config.settings),
settings: Settings::new(config.settings, notifications_module_enabled),
notifications,
media_player: MediaPlayer::new(config.media_player),
osd: Osd::new(config.osd),
Expand Down Expand Up @@ -199,6 +201,11 @@ impl App {
self.keyboard_submap = KeyboardSubmap::default();
self.tempo
.update(modules::tempo::Message::ConfigReloaded(config.tempo));
self.settings.set_notifications_module_enabled(
self.general_config
.modules
.contains(&ModuleName::Notifications),
);
self.settings
.update(modules::settings::Message::ConfigReloaded(config.settings));
self.media_player
Expand Down Expand Up @@ -401,6 +408,11 @@ impl App {
modules::settings::Action::CloseTooltipMenu(id, menu_type) => self
.outputs
.close_menu(id, Some(menu_type), self.general_config.enable_esc_key),
modules::settings::Action::SetDnd(dnd) => {
self.settings.set_dnd(dnd);
self.notifications.set_do_not_disturb(dnd);
Task::none()
}
},
Message::OutputEvent(event) => match event {
OutputEvent::Added(info) => {
Expand Down Expand Up @@ -502,6 +514,11 @@ impl App {
self.outputs
.update_toast_input_region(content_size, position)
}
modules::notifications::Action::SetDnd(dnd) => {
self.settings.set_dnd(dnd);
self.notifications.set_do_not_disturb(dnd);
Task::none()
}
},
Message::IpcOsdCommand(cmd) => {
let mut tasks = vec![];
Expand Down
2 changes: 2 additions & 0 deletions src/components/icons.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ pub enum StaticIcon {
Remove,
Bell,
BellBadge,
BellOff,
Delete,
}

Expand Down Expand Up @@ -247,6 +248,7 @@ impl StaticIcon {
StaticIcon::Remove => "\u{f0377}",
StaticIcon::Bell => "\u{eaa2}",
StaticIcon::BellBadge => "\u{eb9a}",
StaticIcon::BellOff => "\u{ec08}",
StaticIcon::Delete => "\u{f01b4}",
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,7 @@ pub struct SettingsModuleConfig {
pub bluetooth_more_cmd: Option<String>,
pub remove_airplane_btn: bool,
pub remove_idle_btn: bool,
pub remove_dnd_btn: bool,
pub enable_tooltips: bool,
pub indicators: Vec<SettingsIndicator>,
#[serde(rename = "CustomButton")]
Expand Down Expand Up @@ -674,6 +675,7 @@ impl Default for SettingsModuleConfig {
bluetooth_more_cmd: Default::default(),
remove_airplane_btn: Default::default(),
remove_idle_btn: Default::default(),
remove_dnd_btn: Default::default(),
enable_tooltips: true,
indicators: vec![
SettingsIndicator::IdleInhibitor,
Expand Down
9 changes: 8 additions & 1 deletion src/modules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,14 @@ impl App {
)),
ModuleName::Notifications => Some((
self.notifications.view().map(Message::Notifications),
Some(OnModulePress::ToggleMenu(MenuType::Notifications)),
Some(OnModulePress::ToggleMenuWithExtra {
menu_type: MenuType::Notifications,
on_right_press: Some(Box::new(Message::Notifications(
notifications::Message::ToggleDnd,
))),
on_scroll_up: None,
on_scroll_down: None,
}),
)),
}
}
Expand Down
49 changes: 44 additions & 5 deletions src/modules/notifications.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ pub enum Message {
GroupCleared(String, Vec<u32>),
Event(ServiceEvent<NotificationsService>),
ToggleGroup(String),
ToggleDnd,
ExpireToast(u32),
DismissToast(u32),
StartCollapse(u32),
Expand All @@ -143,6 +144,7 @@ pub enum Action {
Show(Task<Message>),
Hide(Task<Message>),
UpdateToastInputRegion(Size),
SetDnd(bool),
}

// Must match the widget durations so task delays align with animation end.
Expand All @@ -164,6 +166,7 @@ pub struct Notifications {
toasts: VecDeque<u32>,
dismiss_phases: HashMap<u32, DismissPhase>,
animations_enabled: bool,
dnd: bool,
}

impl Notifications {
Expand All @@ -178,13 +181,18 @@ impl Notifications {
toasts: VecDeque::new(),
dismiss_phases: HashMap::new(),
animations_enabled,
dnd: false,
}
}

pub fn set_animations_enabled(&mut self, enabled: bool) {
self.animations_enabled = enabled;
}

pub fn set_do_not_disturb(&mut self, dnd: bool) {
self.dnd = dnd;
}

fn is_blocklisted(&self, app_name: &str) -> bool {
self.blocklist
.iter()
Expand Down Expand Up @@ -338,7 +346,17 @@ impl Notifications {
return Action::None;
}

let toast_action = self.toast_action_for_update_event(&update_event);
let suppress_toast = self.dnd
&& matches!(
&update_event,
NotificationEvent::Received(notification)
if notification.urgency != Urgency::Critical
);
let toast_action = if suppress_toast {
Action::None
} else {
self.toast_action_for_update_event(&update_event)
};
self.apply_update_event(update_event);

toast_action
Expand All @@ -351,6 +369,10 @@ impl Notifications {
Action::Task(invoke_and_close_task(connection, id, action_key))
}
Message::NotificationClosed => Action::None,
Message::ToggleDnd => {
self.dnd = !self.dnd;
Action::SetDnd(self.dnd)
}
Message::ClearNotifications => {
let connection = self.connection.clone();
let notification_ids: Vec<u32> = self.notifications.iter().map(|n| n.id).collect();
Expand Down Expand Up @@ -627,11 +649,14 @@ impl Notifications {
}

pub fn view(&'_ self) -> Element<'_, Message> {
if !self.notifications.is_empty() {
icon(StaticIcon::BellBadge).into()
let bell_icon = if self.dnd {
StaticIcon::BellOff
} else if !self.notifications.is_empty() {
StaticIcon::BellBadge
} else {
icon(StaticIcon::Bell).into()
}
StaticIcon::Bell
};
icon(bell_icon).into()
}

pub fn menu_view<'a>(&'a self) -> Element<'a, Message> {
Expand All @@ -657,6 +682,20 @@ impl Notifications {
.width(Length::Fill)
.size(font_size.lg)
)
.push(
icon_button(if self.dnd {
StaticIcon::BellOff
} else {
StaticIcon::Bell
})
.kind(ButtonKind::Transparent)
.hierarchy(if self.dnd {
ButtonHierarchy::Danger
} else {
ButtonHierarchy::Secondary
})
.on_press(Message::ToggleDnd)
)
.push((!is_empty).then(|| {
icon_button(StaticIcon::Delete).on_press(Message::ClearNotifications)
})),
Expand Down
41 changes: 40 additions & 1 deletion src/modules/settings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ pub struct Settings {
network: NetworkSettings,
bluetooth: BluetoothSettings,
idle_inhibitor: Option<IdleInhibitorManager>,
dnd: bool,
remove_dnd_btn: bool,
notifications_module_enabled: bool,
sub_menu: Option<SubMenu>,
network_dialog: Option<NetworkDialogState>,
network_dialog_show_password: bool,
Expand Down Expand Up @@ -98,6 +101,7 @@ pub enum Message {
Audio(audio::Message),
Brightness(brightness::Message),
ToggleInhibitIdle,
ToggleDnd,
Lock,
Power(power::Message),
ToggleSubMenu(SubMenu),
Expand All @@ -124,6 +128,7 @@ pub enum Action {
ReleaseKeyboardWithCommand(SurfaceId, Task<Message>),
OpenTooltipMenu(SurfaceId, MenuType, ButtonUIRef),
CloseTooltipMenu(SurfaceId, MenuType),
SetDnd(bool),
}

#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
Expand Down Expand Up @@ -200,7 +205,15 @@ impl Settings {
Action::None
}

pub fn new(config: SettingsModuleConfig) -> Self {
pub fn set_dnd(&mut self, dnd: bool) {
self.dnd = dnd;
}

pub fn set_notifications_module_enabled(&mut self, enabled: bool) {
self.notifications_module_enabled = enabled;
}

pub fn new(config: SettingsModuleConfig, notifications_module_enabled: bool) -> Self {
Settings {
lock_cmd: config.lock_cmd,
power: PowerSettings::new(PowerSettingsConfig::new(
Expand Down Expand Up @@ -239,6 +252,9 @@ impl Settings {
} else {
IdleInhibitorManager::new()
},
dnd: false,
remove_dnd_btn: config.remove_dnd_btn,
notifications_module_enabled,
sub_menu: None,
network_dialog: None,
indicators: config.indicators,
Expand Down Expand Up @@ -366,6 +382,10 @@ impl Settings {
}
Action::None
}
Message::ToggleDnd => {
self.dnd = !self.dnd;
Action::SetDnd(self.dnd)
}
Message::Lock => {
if let Some(lock_cmd) = &self.lock_cmd {
crate::utils::launcher::execute_command(lock_cmd);
Expand Down Expand Up @@ -546,6 +566,7 @@ impl Settings {
} else if self.idle_inhibitor.is_none() {
self.idle_inhibitor = IdleInhibitorManager::new();
}
self.remove_dnd_btn = config.remove_dnd_btn;
self.indicators = config.indicators;
self.custom_buttons = config.custom_buttons;
Action::None
Expand Down Expand Up @@ -660,6 +681,24 @@ impl Settings {
None,
)
}),
(!self.remove_dnd_btn && self.notifications_module_enabled).then(|| {
(
quick_setting_button(
if self.dnd {
StaticIcon::BellOff
} else {
StaticIcon::Bell
},
t!("settings-dnd"),
None,
self.dnd,
Message::ToggleDnd,
None,
None,
),
None,
)
}),
self.power.quick_setting_button().map(|(button, submenu)| {
(
button.map(Message::Power),
Expand Down
1 change: 1 addition & 0 deletions website/docs/configuration/full_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ volume_step = 5 # (default) step size for IPC volume up/down, range 1..=50
max_volume = 100 # (default) max volume level, range 1..=200 (>100 enables overdrive)
# remove_airplane_btn = false # (default) set true to hide airplane mode button
# remove_idle_btn = false # (default) set true to hide idle inhibitor button
# remove_dnd_btn = false # (default) set true to hide do not disturb button
indicators = [ "IdleInhibitor", "PowerProfile", "Audio", "Microphone", "Bluetooth", "Network", "Vpn", "Battery", "Brightness" ]
# indicators = [ "IdleInhibitor", "PowerProfile", "Audio", "Microphone", "Bluetooth", "Network", "Vpn", "Battery", "PeripheralBattery", "Brightness" ]

Expand Down
20 changes: 20 additions & 0 deletions website/docs/configuration/modules/notifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,32 @@ The `expire_timeout` hint sent by applications is respected: a value of `-1` fal

If you prefer no popups and only the panel indicator, set `toast = false`.

## Do Not Disturb

Do not disturb suppresses toast popups without dropping notifications: they are
still collected and shown in the menu, and notifications with `Critical`
urgency still pop up as toasts.

While it is active the bar indicator switches to a crossed-out bell icon.

You can toggle it in three ways:

- **Right-click the notification indicator** in the status bar
- **The bell button** in the notifications menu header
- **The "Do Not Disturb" quick setting** in the [Settings](settings.md) menu
(can be hidden with `remove_dnd_btn`; it is only shown when this module is
part of the bar layout)

The state is not persisted across restarts, ashell always starts with do not
disturb off.

## Notification Menu

Click the notification indicator to open the notifications menu. The menu displays all active notifications with the following features:

- **Individual notifications**: Shows the app icon, name, summary, and optional body text and timestamp
- **Clear button**: Removes all notifications at once
- **Do not disturb button**: Toggles do not disturb (see above)
- **Grouped mode** (optional): Organizes notifications by application with expandable groups
- **Clicking a notification**: Invokes its default action (if provided by the app) and closes it

Expand Down
5 changes: 5 additions & 0 deletions website/docs/configuration/modules/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ With the `remove_airplane_btn` option you can remove the airplane mode button.

With the `remove_idle_btn` option you can remove the idle inhibitor button.

With the `remove_dnd_btn` option you can remove the do not disturb button.
The button is only shown when the [Notifications](notifications.md) module is
part of the bar layout, since do not disturb only applies while ashell is the
notification daemon.

## Tooltips

By default, hovering over the status bar indicators shows a tooltip describing
Expand Down