From 3e27acdde99c97a875ec53cda93330b02c35e6a5 Mon Sep 17 00:00:00 2001 From: Moacyr Prado Date: Sat, 11 Jul 2026 15:25:36 -0300 Subject: [PATCH 1/4] ci: apply GUI runtime availability gate --- .github/workflows/apply-gui-runtime-gate.yml | 97 ++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 .github/workflows/apply-gui-runtime-gate.yml diff --git a/.github/workflows/apply-gui-runtime-gate.yml b/.github/workflows/apply-gui-runtime-gate.yml new file mode 100644 index 0000000..569a175 --- /dev/null +++ b/.github/workflows/apply-gui-runtime-gate.yml @@ -0,0 +1,97 @@ +name: Apply GUI runtime gate + +on: + push: + branches: + - fix/gui-runtime-gate + +permissions: + contents: write + +jobs: + apply: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: fix/gui-runtime-gate + + - name: Apply focused source edit + shell: python + run: | + from pathlib import Path + import re + + path = Path('gui/gnome/mb-gnome-window.c') + text = path.read_text(encoding='utf-8') + original = text + + def replace_once(old: str, new: str, label: str) -> None: + global text + count = text.count(old) + if count != 1: + raise SystemExit(f'{label}: expected exactly one match, found {count}') + text = text.replace(old, new, 1) + + replace_once( + 'static bool bluez_can_scan_for_gui(const MbGnomeWindowState *state) {', + 'static bool gui_runtime_available(const MbGnomeWindowState *state) {', + 'rename availability helper', + ) + text = text.replace('bluez_can_scan_for_gui(state)', + 'gui_runtime_available(state)') + + replace_once( + ''' if (state->sidebar_list)\n gtk_widget_set_sensitive(state->sidebar_list, TRUE);''', + ''' if (state->sidebar_list)\n gtk_widget_set_sensitive(state->sidebar_list,\n gui_runtime_available(state));''', + 'sidebar runtime gate', + ) + + replace_once( + ''' bool sensitive =\n gui_runtime_available(state) &&\n !state->scan_in_flight &&\n !state->command_in_flight;''', + ''' bool sensitive = gui_runtime_available(state);''', + 'scan button gate', + ) + + replace_once( + ''' gtk_widget_set_sensitive(state->scan_button, sensitive);''', + ''' gtk_widget_set_sensitive(state->scan_button, sensitive);\n\n /*\n * One availability gate for the musician-facing main window.\n * Operation serialization belongs to the daemon; local busy flags must\n * not make the main controls look unavailable.\n */\n if (state->sidebar_list)\n gtk_widget_set_sensitive(state->sidebar_list, sensitive);\n\n if (state->connect_button) {\n const MbUiDevice *device = selected_device(state);\n gtk_widget_set_sensitive(state->connect_button,\n sensitive && device != NULL);\n }''', + 'propagate runtime gate to main controls', + ) + + replace_once( + ''' gtk_widget_set_sensitive(state->connect_button,\n state->daemon_functional &&\n has_selection &&\n !busy);''', + ''' gtk_widget_set_sensitive(state->connect_button,\n gui_runtime_available(state) &&\n has_selection);''', + 'connect button gate', + ) + + replace_once( + ''' if (!state->daemon_functional) {\n show_error_dialog(state,\n "Serviço MIDI-BLE inativo",\n "Inicie o serviço MIDI-BLE antes de buscar instrumentos.");\n return;\n }''', + ''' if (!state->daemon_dbus_ready) {\n show_error_dialog(state,\n "Serviço MIDI-BLE inativo",\n "Inicie o serviço MIDI-BLE antes de buscar instrumentos.");\n return;\n }''', + 'scan callback daemon gate', + ) + + replace_once( + '''\n if (state->scan_in_flight)\n return;\n\n show_scan_pair_dialog(state);''', + '''\n show_scan_pair_dialog(state);''', + 'remove main-window scan busy gate', + ) + + if 'bluez_can_scan_for_gui' in text: + raise SystemExit('old availability helper name remains') + if text == original: + raise SystemExit('no source changes were produced') + + path.write_text(text, encoding='utf-8') + + - name: Remove one-shot workflow + run: rm -f .github/workflows/apply-gui-runtime-gate.yml + + - name: Commit focused change + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add gui/gnome/mb-gnome-window.c .github/workflows/apply-gui-runtime-gate.yml + git commit -m "gui: use daemon and Bluetooth as sole availability gate" + git push origin HEAD:fix/gui-runtime-gate From 858687d60724a8a6e03a3933ceb6adc4acb0f3c3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 11 Jul 2026 18:25:43 +0000 Subject: [PATCH 2/4] gui: use daemon and Bluetooth as sole availability gate --- .github/workflows/apply-gui-runtime-gate.yml | 97 -------------------- gui/gnome/mb-gnome-window.c | 34 ++++--- 2 files changed, 21 insertions(+), 110 deletions(-) delete mode 100644 .github/workflows/apply-gui-runtime-gate.yml diff --git a/.github/workflows/apply-gui-runtime-gate.yml b/.github/workflows/apply-gui-runtime-gate.yml deleted file mode 100644 index 569a175..0000000 --- a/.github/workflows/apply-gui-runtime-gate.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: Apply GUI runtime gate - -on: - push: - branches: - - fix/gui-runtime-gate - -permissions: - contents: write - -jobs: - apply: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: fix/gui-runtime-gate - - - name: Apply focused source edit - shell: python - run: | - from pathlib import Path - import re - - path = Path('gui/gnome/mb-gnome-window.c') - text = path.read_text(encoding='utf-8') - original = text - - def replace_once(old: str, new: str, label: str) -> None: - global text - count = text.count(old) - if count != 1: - raise SystemExit(f'{label}: expected exactly one match, found {count}') - text = text.replace(old, new, 1) - - replace_once( - 'static bool bluez_can_scan_for_gui(const MbGnomeWindowState *state) {', - 'static bool gui_runtime_available(const MbGnomeWindowState *state) {', - 'rename availability helper', - ) - text = text.replace('bluez_can_scan_for_gui(state)', - 'gui_runtime_available(state)') - - replace_once( - ''' if (state->sidebar_list)\n gtk_widget_set_sensitive(state->sidebar_list, TRUE);''', - ''' if (state->sidebar_list)\n gtk_widget_set_sensitive(state->sidebar_list,\n gui_runtime_available(state));''', - 'sidebar runtime gate', - ) - - replace_once( - ''' bool sensitive =\n gui_runtime_available(state) &&\n !state->scan_in_flight &&\n !state->command_in_flight;''', - ''' bool sensitive = gui_runtime_available(state);''', - 'scan button gate', - ) - - replace_once( - ''' gtk_widget_set_sensitive(state->scan_button, sensitive);''', - ''' gtk_widget_set_sensitive(state->scan_button, sensitive);\n\n /*\n * One availability gate for the musician-facing main window.\n * Operation serialization belongs to the daemon; local busy flags must\n * not make the main controls look unavailable.\n */\n if (state->sidebar_list)\n gtk_widget_set_sensitive(state->sidebar_list, sensitive);\n\n if (state->connect_button) {\n const MbUiDevice *device = selected_device(state);\n gtk_widget_set_sensitive(state->connect_button,\n sensitive && device != NULL);\n }''', - 'propagate runtime gate to main controls', - ) - - replace_once( - ''' gtk_widget_set_sensitive(state->connect_button,\n state->daemon_functional &&\n has_selection &&\n !busy);''', - ''' gtk_widget_set_sensitive(state->connect_button,\n gui_runtime_available(state) &&\n has_selection);''', - 'connect button gate', - ) - - replace_once( - ''' if (!state->daemon_functional) {\n show_error_dialog(state,\n "Serviço MIDI-BLE inativo",\n "Inicie o serviço MIDI-BLE antes de buscar instrumentos.");\n return;\n }''', - ''' if (!state->daemon_dbus_ready) {\n show_error_dialog(state,\n "Serviço MIDI-BLE inativo",\n "Inicie o serviço MIDI-BLE antes de buscar instrumentos.");\n return;\n }''', - 'scan callback daemon gate', - ) - - replace_once( - '''\n if (state->scan_in_flight)\n return;\n\n show_scan_pair_dialog(state);''', - '''\n show_scan_pair_dialog(state);''', - 'remove main-window scan busy gate', - ) - - if 'bluez_can_scan_for_gui' in text: - raise SystemExit('old availability helper name remains') - if text == original: - raise SystemExit('no source changes were produced') - - path.write_text(text, encoding='utf-8') - - - name: Remove one-shot workflow - run: rm -f .github/workflows/apply-gui-runtime-gate.yml - - - name: Commit focused change - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add gui/gnome/mb-gnome-window.c .github/workflows/apply-gui-runtime-gate.yml - git commit -m "gui: use daemon and Bluetooth as sole availability gate" - git push origin HEAD:fix/gui-runtime-gate diff --git a/gui/gnome/mb-gnome-window.c b/gui/gnome/mb-gnome-window.c index 5489d28..eb9af7d 100644 --- a/gui/gnome/mb-gnome-window.c +++ b/gui/gnome/mb-gnome-window.c @@ -438,7 +438,8 @@ static void daemon_root_observer_apply(MbGnomeWindowState *state) { update_scan_button_state(state); if (state->sidebar_list) - gtk_widget_set_sensitive(state->sidebar_list, TRUE); + gtk_widget_set_sensitive(state->sidebar_list, + gui_runtime_available(state)); update_daemon_switch_state(state); @@ -498,7 +499,7 @@ static void daemon_observer_state_changed_cb(bool active, } -static bool bluez_can_scan_for_gui(const MbGnomeWindowState *state) { +static bool gui_runtime_available(const MbGnomeWindowState *state) { /* * Scan/import depende da API D-Bus real do daemon, não do estado do unit * systemd nem de um snapshot assíncrono potencialmente atrasado. @@ -527,10 +528,7 @@ static void update_scan_button_state(MbGnomeWindowState *state) { button_set_icon_text(state->scan_button, "edit-find-symbolic", label); - bool sensitive = - bluez_can_scan_for_gui(state) && - !state->scan_in_flight && - !state->command_in_flight; + bool sensitive = gui_runtime_available(state); g_printerr("[midi-ble-rt-gui] scan gate: sensitive=%d dbus_ready=%d daemon=%d available=%d powered_known=%d powered=%d scan_busy=%d command_busy=%d\n", sensitive, @@ -543,6 +541,20 @@ static void update_scan_button_state(MbGnomeWindowState *state) { state->command_in_flight); gtk_widget_set_sensitive(state->scan_button, sensitive); + + /* + * One availability gate for the musician-facing main window. + * Operation serialization belongs to the daemon; local busy flags must + * not make the main controls look unavailable. + */ + if (state->sidebar_list) + gtk_widget_set_sensitive(state->sidebar_list, sensitive); + + if (state->connect_button) { + const MbUiDevice *device = selected_device(state); + gtk_widget_set_sensitive(state->connect_button, + sensitive && device != NULL); + } } static void daemon_dbus_apply_bluetooth_state( @@ -839,9 +851,8 @@ static void update_action_sensitivity(MbGnomeWindowState *state, const MbUiDevic streaming ? "Desconectar" : "Conectar"); gtk_widget_set_sensitive(state->connect_button, - state->daemon_functional && - has_selection && - !busy); + gui_runtime_available(state) && + has_selection); } if (state->disconnect_button) @@ -1691,7 +1702,7 @@ static void scan_clicked_cb(GtkButton *button, gpointer user_data) { * adapter power, discovery state and BlueZ errors are daemon-owned concerns * and must be reported by ScanDevices() through the D-Bus facade. */ - if (!state->daemon_functional) { + if (!state->daemon_dbus_ready) { show_error_dialog(state, "Serviço MIDI-BLE inativo", "Inicie o serviço MIDI-BLE antes de buscar instrumentos."); @@ -1707,9 +1718,6 @@ static void scan_clicked_cb(GtkButton *button, gpointer user_data) { return; } - if (state->scan_in_flight) - return; - show_scan_pair_dialog(state); } From 3dbe6f9df2a7640fad77fa4a6edbd72f4fec38a1 Mon Sep 17 00:00:00 2001 From: Moacyr Prado Date: Sat, 11 Jul 2026 15:28:25 -0300 Subject: [PATCH 3/4] ci: finalize GUI runtime gate declaration --- .../fix-gui-runtime-gate-declaration.yml | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/fix-gui-runtime-gate-declaration.yml diff --git a/.github/workflows/fix-gui-runtime-gate-declaration.yml b/.github/workflows/fix-gui-runtime-gate-declaration.yml new file mode 100644 index 0000000..b11531c --- /dev/null +++ b/.github/workflows/fix-gui-runtime-gate-declaration.yml @@ -0,0 +1,51 @@ +name: Finalize GUI runtime gate declaration + +on: + push: + branches: + - fix/gui-runtime-gate + +permissions: + contents: write + +jobs: + apply: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: fix/gui-runtime-gate + + - name: Add declaration and align documentation + shell: python + run: | + from pathlib import Path + + path = Path('gui/gnome/mb-gnome-window.c') + text = path.read_text(encoding='utf-8') + + old = '''static void forget_clicked_cb(GtkButton *button, gpointer user_data);\nstatic void daemon_root_observer_apply(MbGnomeWindowState *state);''' + new = '''static void forget_clicked_cb(GtkButton *button, gpointer user_data);\nstatic bool gui_runtime_available(const MbGnomeWindowState *state);\nstatic void daemon_root_observer_apply(MbGnomeWindowState *state);''' + if text.count(old) != 1: + raise SystemExit(f'forward declaration anchor count={text.count(old)}') + text = text.replace(old, new, 1) + + old_comment = ''' * - Main list availability comes from the persisted catalog.\n * - Basic panel and local actions come from list selection.\n * - Runtime connect/disconnect additionally requires daemon_functional.''' + new_comment = ''' * - Main list and runtime actions share gui_runtime_available().\n * - Connect/Disconnect additionally requires a selected instrument.\n * - Daemon-side serialization handles concurrent mutable operations.''' + if text.count(old_comment) != 1: + raise SystemExit(f'action comment anchor count={text.count(old_comment)}') + text = text.replace(old_comment, new_comment, 1) + + path.write_text(text, encoding='utf-8') + + - name: Remove one-shot workflow + run: rm -f .github/workflows/fix-gui-runtime-gate-declaration.yml + + - name: Commit final adjustment + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add gui/gnome/mb-gnome-window.c .github/workflows/fix-gui-runtime-gate-declaration.yml + git commit -m "gui: finalize shared runtime availability gate" + git push origin HEAD:fix/gui-runtime-gate From 7a24c0bcf7ba9bd895f2a5821db2c540e92b9a0b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 11 Jul 2026 18:28:32 +0000 Subject: [PATCH 4/4] gui: finalize shared runtime availability gate --- .../fix-gui-runtime-gate-declaration.yml | 51 ------------------- gui/gnome/mb-gnome-window.c | 7 +-- 2 files changed, 4 insertions(+), 54 deletions(-) delete mode 100644 .github/workflows/fix-gui-runtime-gate-declaration.yml diff --git a/.github/workflows/fix-gui-runtime-gate-declaration.yml b/.github/workflows/fix-gui-runtime-gate-declaration.yml deleted file mode 100644 index b11531c..0000000 --- a/.github/workflows/fix-gui-runtime-gate-declaration.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Finalize GUI runtime gate declaration - -on: - push: - branches: - - fix/gui-runtime-gate - -permissions: - contents: write - -jobs: - apply: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: fix/gui-runtime-gate - - - name: Add declaration and align documentation - shell: python - run: | - from pathlib import Path - - path = Path('gui/gnome/mb-gnome-window.c') - text = path.read_text(encoding='utf-8') - - old = '''static void forget_clicked_cb(GtkButton *button, gpointer user_data);\nstatic void daemon_root_observer_apply(MbGnomeWindowState *state);''' - new = '''static void forget_clicked_cb(GtkButton *button, gpointer user_data);\nstatic bool gui_runtime_available(const MbGnomeWindowState *state);\nstatic void daemon_root_observer_apply(MbGnomeWindowState *state);''' - if text.count(old) != 1: - raise SystemExit(f'forward declaration anchor count={text.count(old)}') - text = text.replace(old, new, 1) - - old_comment = ''' * - Main list availability comes from the persisted catalog.\n * - Basic panel and local actions come from list selection.\n * - Runtime connect/disconnect additionally requires daemon_functional.''' - new_comment = ''' * - Main list and runtime actions share gui_runtime_available().\n * - Connect/Disconnect additionally requires a selected instrument.\n * - Daemon-side serialization handles concurrent mutable operations.''' - if text.count(old_comment) != 1: - raise SystemExit(f'action comment anchor count={text.count(old_comment)}') - text = text.replace(old_comment, new_comment, 1) - - path.write_text(text, encoding='utf-8') - - - name: Remove one-shot workflow - run: rm -f .github/workflows/fix-gui-runtime-gate-declaration.yml - - - name: Commit final adjustment - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add gui/gnome/mb-gnome-window.c .github/workflows/fix-gui-runtime-gate-declaration.yml - git commit -m "gui: finalize shared runtime availability gate" - git push origin HEAD:fix/gui-runtime-gate diff --git a/gui/gnome/mb-gnome-window.c b/gui/gnome/mb-gnome-window.c index eb9af7d..e1631ca 100644 --- a/gui/gnome/mb-gnome-window.c +++ b/gui/gnome/mb-gnome-window.c @@ -85,6 +85,7 @@ static void show_error_dialog(MbGnomeWindowState *state, const char *title, const char *message); static void forget_clicked_cb(GtkButton *button, gpointer user_data); +static bool gui_runtime_available(const MbGnomeWindowState *state); static void daemon_root_observer_apply(MbGnomeWindowState *state); static void daemon_root_observer_set_functional(MbGnomeWindowState *state, bool daemon_functional); @@ -841,9 +842,9 @@ static void update_action_sensitivity(MbGnomeWindowState *state, const MbUiDevic /* * Observer rule: * - * - Main list availability comes from the persisted catalog. - * - Basic panel and local actions come from list selection. - * - Runtime connect/disconnect additionally requires daemon_functional. + * - Main list and runtime actions share gui_runtime_available(). + * - Connect/Disconnect additionally requires a selected instrument. + * - Daemon-side serialization handles concurrent mutable operations. */ if (state->connect_button) { button_set_icon_text(state->connect_button,