rustgtkgtk3appindicator

How to change CheckMenuItem's active state without triggering its active signal?


I have a tray icon created with libappindicator3, containing a CheckMenuItem which activates a system service.

Activating the system service can fail though, and when it fails to activate, I want the CheckMenuItem to be unchecked, too. However, whenever I run set_active(status), it triggers the connect_active callback, which I don't want to (since that will try to stop the service).

Here's a simplified version of my code:

use gtk::prelude::*;
use appindicator3::prelude::*;
use appindicator3::{Indicator, IndicatorStatus, IndicatorCategory};

fn try_activate() -> bool {
    false
}

fn main() {
    gtk::init().unwrap();

    let indicator = Indicator::builder("aaa")
        .category(IndicatorCategory::ApplicationStatus)
        .icon("computer-symbolic", "icon")
        .status(IndicatorStatus::Active)
        .build();

    let m = gtk::Menu::new();

    let mi_enabled = gtk::CheckMenuItem::with_label("Active");
    m.append(&mi_enabled);

    mi_enabled.connect_activate(|mi_enabled| {
        if mi_enabled.is_active() {
            println!("activating");
            if try_activate() {
                println!("activated successfully");
            } else {
                println!("failed to activate");
                mi_enabled.set_active(false);
            }
        } else {
            println!("deactivating");
            println!("deactivated successfully");
        }
    });

    indicator.set_menu(Some(&m));

    indicator.set_secondary_activate_target(Some(&mi_enabled));

    gtk::main();
}

This is the output when I try to activate it:

activating
failed to activate
deactivating
deactivated successfully

What I want is to fail to activate and set_active(false) without triggering "deactivating".

I think this can be solved with a global variable, which I can set when changing it programmatically, then check it in the signal handler. But that seems like the wrong way to go.


Solution

  • I managed to solve this by using block_signal and unblock_signal around the set_active call. However, since I'm trying to reference the return of the function around the closure, I used a Rc<RefCell<>> around it, which seems excessive, but I found no workaround for this.

    Resulting code:

        let handler_id: Rc<RefCell<Option<SignalHandlerId>>> = Rc::new(RefCell::new(None));
    
        let h_id = mi_enabled.connect_activate(glib::clone!(@strong handler_id => move |mi_enabled| {
            if mi_enabled.is_active() {
                println!("activating");
                if try_activate() {
                    println!("activated successfully");
                } else {
                    println!("failed to activate");
                    mi_enabled.block_signal(&handler_id.as_ref().borrow_mut().as_mut().unwrap());
                    mi_enabled.set_active(false);
                    mi_enabled.unblock_signal(&handler_id.as_ref().borrow_mut().as_mut().unwrap());
                }
            } else {
                println!("deactivating");
                println!("deactivated successfully");
            }
        }));
    
        handler_id.as_ref().borrow_mut().insert(h_id);