Skip to content

Commit 953f6a8

Browse files
committed
Add back navigation option with exit-on-escape as default behavior
Introduces a "Back" option in sub-menus and makes escaping any menu exit the program by default. Adds a --back-on-escape flag to restore the legacy behavior where escape returns to the previous menu without showing explicit "Back" options.
1 parent 84ab208 commit 953f6a8

6 files changed

Lines changed: 66 additions & 6 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ bzmenu -l custom --launcher-command "fuzzel -d --placeholder '{hint}'"
154154
| `-i`, `--icon` | Specify the icon type to use. | `font`, `xdg` | `font` |
155155
| `-s`, `--spaces` | Specify icon to text space count (font icons only). | Any positive integer | `1` |
156156
| `--scan-duration` | Specify the duration of device discovery in seconds. | Any positive integer | `10` |
157+
| `--back-on-escape` | Return to previous menu on escape. | N/A | `false` |
157158

158159
## Contributing
159160

locales/app.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
_version: 2
22

33
menus:
4+
common:
5+
back:
6+
en: "Back"
7+
fr: "Retour"
48
main:
59
options:
610
scan:

src/app.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use tokio::runtime::Builder;
2020
pub struct App {
2121
pub running: bool,
2222
pub reset_mode: bool,
23+
pub back_on_escape: bool,
2324
session: Arc<Session>,
2425
controller: Controller,
2526
agent_manager: AgentManager,
@@ -38,7 +39,7 @@ impl App {
3839
&self.agent_manager
3940
}
4041

41-
pub async fn new(icons: Arc<Icons>, scan_duration: u64) -> Result<Self> {
42+
pub async fn new(icons: Arc<Icons>, scan_duration: u64, back_on_escape: bool) -> Result<Self> {
4243
let session = Arc::new(Session::new().await?);
4344
let notification_manager = Arc::new(NotificationManager::new(icons.clone()));
4445

@@ -61,6 +62,7 @@ impl App {
6162
Ok(Self {
6263
running: true,
6364
reset_mode: false,
65+
back_on_escape,
6466
session,
6567
controller,
6668
agent_manager,
@@ -160,10 +162,18 @@ impl App {
160162
self.controller.refresh().await?;
161163

162164
if let Some(option) = menu
163-
.show_settings_menu(menu_command, &self.controller, icon_type, spaces)
165+
.show_settings_menu(
166+
menu_command,
167+
&self.controller,
168+
icon_type,
169+
spaces,
170+
self.back_on_escape,
171+
)
164172
.await?
165173
{
166-
if matches!(option, SettingsMenuOptions::DisableAdapter) {
174+
if matches!(option, SettingsMenuOptions::Back) {
175+
stay_in_settings = false;
176+
} else if matches!(option, SettingsMenuOptions::DisableAdapter) {
167177
self.handle_settings_options(menu, menu_command, icon_type, spaces, option)
168178
.await?;
169179
stay_in_settings = false;
@@ -172,6 +182,9 @@ impl App {
172182
.await?;
173183
}
174184
} else {
185+
if !self.back_on_escape {
186+
self.running = false;
187+
}
175188
stay_in_settings = false;
176189
debug!("Exited settings menu");
177190
}
@@ -189,6 +202,7 @@ impl App {
189202
option: SettingsMenuOptions,
190203
) -> Result<()> {
191204
match option {
205+
SettingsMenuOptions::Back => {}
192206
SettingsMenuOptions::ToggleDiscoverable => {
193207
let new_state = !self.controller.is_discoverable;
194208
self.controller.set_discoverable(new_state).await?;
@@ -303,11 +317,15 @@ impl App {
303317
spaces,
304318
available_options,
305319
&device_clone.alias,
320+
self.back_on_escape,
306321
)
307322
.await?
308323
{
309324
Some(option) => {
310325
match option {
326+
DeviceMenuOptions::Back => {
327+
stay_in_device_menu = false;
328+
}
311329
DeviceMenuOptions::Connect => {
312330
if !device_clone.is_connected {
313331
self.perform_device_connection(&device_clone).await?;
@@ -337,6 +355,9 @@ impl App {
337355
self.controller.refresh().await?;
338356
}
339357
None => {
358+
if !self.back_on_escape {
359+
self.running = false;
360+
}
340361
stay_in_device_menu = false;
341362
debug!("Exited device menu for {}", device_clone.alias);
342363
}

src/icons.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ impl Icons {
8787
font_icons.insert("battery_20", '\u{f007b}');
8888
font_icons.insert("battery_10", '\u{f007a}');
8989

90+
font_icons.insert("back", '\u{f004d}');
9091
font_icons.insert("ok", '\u{f05e1}');
9192
font_icons.insert("error", '\u{f05d6}');
9293
font_icons.insert("paired", '\u{f119f}');
@@ -310,6 +311,7 @@ impl Icons {
310311
),
311312
);
312313

314+
xdg_icons.insert("back", IconDefinition::simple("go-previous-symbolic"));
313315
xdg_icons.insert("ok", IconDefinition::simple("emblem-default-symbolic"));
314316
xdg_icons.insert("error", IconDefinition::simple("dialog-error-symbolic"));
315317
xdg_icons.insert("paired", IconDefinition::simple("emblem-checked-symbolic"));

src/main.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ async fn main() -> Result<()> {
9090
.default_value("10")
9191
.help("Duration of Bluetooth device discovery in seconds"),
9292
)
93+
.arg(
94+
Arg::new("back_on_escape")
95+
.long("back-on-escape")
96+
.takes_value(false)
97+
.help("Return to previous menu on escape instead of exiting"),
98+
)
9399
.get_matches();
94100

95101
let launcher_type: LauncherType = if matches.contains_id("launcher") {
@@ -129,13 +135,16 @@ async fn main() -> Result<()> {
129135
.and_then(|s| s.parse::<u64>().ok())
130136
.unwrap_or(10);
131137

138+
let back_on_escape = matches.contains_id("back_on_escape");
139+
132140
run_app_loop(
133141
&menu,
134142
&command_str,
135143
&icon_type,
136144
spaces,
137145
icons,
138146
scan_duration,
147+
back_on_escape,
139148
)
140149
.await?;
141150
Ok(())
@@ -148,8 +157,9 @@ async fn run_app_loop(
148157
spaces: usize,
149158
icons: Arc<Icons>,
150159
scan_duration: u64,
160+
back_on_escape: bool,
151161
) -> Result<()> {
152-
let mut app = App::new(icons.clone(), scan_duration).await?;
162+
let mut app = App::new(icons.clone(), scan_duration, back_on_escape).await?;
153163

154164
loop {
155165
match app.run(menu, command_str, icon_type, spaces).await {
@@ -167,7 +177,7 @@ async fn run_app_loop(
167177
}
168178

169179
if app.reset_mode {
170-
app = App::new(icons.clone(), scan_duration).await?;
180+
app = App::new(icons.clone(), scan_duration, back_on_escape).await?;
171181
app.reset_mode = false;
172182
}
173183
}

src/menu.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub enum DeviceMenuOptions {
3838
Trust,
3939
RevokeTrust,
4040
Forget,
41+
Back,
4142
}
4243

4344
impl DeviceMenuOptions {
@@ -52,6 +53,7 @@ impl DeviceMenuOptions {
5253
Some(DeviceMenuOptions::RevokeTrust)
5354
}
5455
s if s == t!("menus.device.options.forget.name") => Some(DeviceMenuOptions::Forget),
56+
s if s == t!("menus.common.back") => Some(DeviceMenuOptions::Back),
5557
_ => None,
5658
}
5759
}
@@ -63,6 +65,7 @@ impl DeviceMenuOptions {
6365
DeviceMenuOptions::Trust => t!("menus.device.options.trust.name"),
6466
DeviceMenuOptions::RevokeTrust => t!("menus.device.options.revoke_trust.name"),
6567
DeviceMenuOptions::Forget => t!("menus.device.options.forget.name"),
68+
DeviceMenuOptions::Back => t!("menus.common.back"),
6669
}
6770
}
6871
}
@@ -72,6 +75,7 @@ pub enum SettingsMenuOptions {
7275
ToggleDiscoverable,
7376
TogglePairable,
7477
DisableAdapter,
78+
Back,
7579
}
7680

7781
impl SettingsMenuOptions {
@@ -86,6 +90,7 @@ impl SettingsMenuOptions {
8690
s if s == t!("menus.settings.options.disable_adapter.name") => {
8791
Some(SettingsMenuOptions::DisableAdapter)
8892
}
93+
s if s == t!("menus.common.back") => Some(SettingsMenuOptions::Back),
8994
_ => None,
9095
}
9196
}
@@ -101,6 +106,7 @@ impl SettingsMenuOptions {
101106
SettingsMenuOptions::DisableAdapter => {
102107
t!("menus.settings.options.disable_adapter.name")
103108
}
109+
SettingsMenuOptions::Back => t!("menus.common.back"),
104110
}
105111
}
106112
}
@@ -321,6 +327,7 @@ impl Menu {
321327
spaces: usize,
322328
available_options: Vec<DeviceMenuOptions>,
323329
device_name: &str,
330+
back_on_escape: bool,
324331
) -> Result<Option<DeviceMenuOptions>> {
325332
let mut input = String::new();
326333

@@ -331,13 +338,20 @@ impl Menu {
331338
DeviceMenuOptions::Trust => "trust",
332339
DeviceMenuOptions::RevokeTrust => "revoke_trust",
333340
DeviceMenuOptions::Forget => "forget",
341+
DeviceMenuOptions::Back => "back",
334342
};
335343

336344
let option_text =
337345
self.get_icon_text(vec![(icon_key, option.to_str())], icon_type, spaces);
338346
input.push_str(&format!("{option_text}\n"));
339347
}
340348

349+
if !back_on_escape {
350+
let back_text =
351+
self.get_icon_text(vec![("back", t!("menus.common.back"))], icon_type, spaces);
352+
input.push_str(&format!("{back_text}\n"));
353+
}
354+
341355
let hint = t!("menus.device.hint", device_name = device_name);
342356

343357
let menu_output =
@@ -377,6 +391,7 @@ impl Menu {
377391
controller: &Controller,
378392
icon_type: &str,
379393
spaces: usize,
394+
back_on_escape: bool,
380395
) -> Result<Option<SettingsMenuOptions>> {
381396
let (discoverable_text, discoverable_icon) = if controller.is_discoverable {
382397
(
@@ -403,13 +418,18 @@ impl Menu {
403418
};
404419

405420
let disable_adapter_text = t!("menus.settings.options.disable_adapter.name");
421+
let back_text = t!("menus.common.back");
406422

407-
let options = vec![
423+
let mut options = vec![
408424
(discoverable_icon, discoverable_text.as_ref()),
409425
(pairable_icon, pairable_text.as_ref()),
410426
("disable_adapter", disable_adapter_text.as_ref()),
411427
];
412428

429+
if !back_on_escape {
430+
options.push(("back", back_text.as_ref()));
431+
}
432+
413433
let input = self.get_icon_text(options, icon_type, spaces);
414434

415435
let menu_output = self.run_launcher(launcher_command, Some(&input), icon_type, None)?;
@@ -423,6 +443,8 @@ impl Menu {
423443
return Ok(Some(SettingsMenuOptions::TogglePairable));
424444
} else if cleaned_output == disable_adapter_text.as_ref() {
425445
return Ok(Some(SettingsMenuOptions::DisableAdapter));
446+
} else if cleaned_output == back_text.as_ref() {
447+
return Ok(Some(SettingsMenuOptions::Back));
426448
}
427449
}
428450

0 commit comments

Comments
 (0)