You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在gtk-rs的两个闭包中共享window_id_output变量?

解决方案:在gtk-rs闭包间共享可变状态

问题描述

我在gtk-rs的输入元素中监听用户输入,input.connect_changed会在输入内容变化时触发,input.connect_activate会在按下回车键时触发。我需要在connect_changed闭包中获取xdotool搜索到的窗口ID,然后在connect_activate闭包中用这个ID执行窗口激活命令,但原代码里两个闭包无法共享变量,请问该如何实现?

原代码:

use gtk::prelude::*;
use gtk::{Application, ApplicationWindow};
use std::process::{Command, Output};

fn main() {
    let app = Application::builder()
        .application_id("com.jwestall.ui-demo")
        .build();

    app.connect_activate(build_ui);
    app.run();
}

fn run_command(command: &str) -> Output {
    Command::new("sh")
        .arg("-c")
        .arg(command)
        .output()
        .unwrap_or_else(|_| panic!("failed to execute {}'", command))
}

fn build_ui(app: &Application) {
    let input = gtk::Entry::builder()
        .placeholder_text("input")
        .margin_top(12)
        .margin_bottom(12)
        .margin_start(12)
        .margin_end(12)
        .build();

    let window = ApplicationWindow::builder()
        .application(app)
        .title("gtk-app")
        .child(&input)
        .build();

    window.show_all();

    input.connect_changed(|entry| {
        let input_text = entry.text();

        let command = format!("xdotool search --onlyvisible --name {}", input_text);
        let window_id_output = run_command(&command);

        if window_id_output.status.success() {
            println!(
                "stdout: {}",
                String::from_utf8_lossy(&window_id_output.stdout)
            );
        } else {
            println!(
                "sterr: {}",
                String::from_utf8_lossy(&window_id_output.stderr)
            );
        }
    });

    input.connect_activate(move |entry| {
        let input_text = entry.text();

        // // `xdotool windowactivate` doesn't produce any output
        let command = format!("xdotool windowactivate {}", window_id_output);
        let window_activate_output = run_command(&command);

        println!("window_activate: {}", window_activate_output);

        window.hide();
        window.close();
    });
}

实现方案

要在两个闭包间共享可变状态,可使用Rc<RefCell<T>>组合:Rc提供引用计数实现共享所有权,RefCell提供内部可变性,让你在不可变引用的上下文中修改内部值。具体步骤如下:

  1. 引入必要的模块:std::cell::RefCell 和 std::rc::Rc
  2. 创建共享的可变变量存储窗口ID(用Option<String>处理未找到窗口的情况)
  3. 在connect_changed闭包中克隆Rc实例,更新内部的窗口ID
  4. 在connect_activate闭包中克隆Rc实例,读取并使用存储的窗口ID

修改后的完整代码:

use gtk::prelude::*;
use gtk::{Application, ApplicationWindow};
use std::cell::RefCell;
use std::process::{Command, Output};
use std::rc::Rc;

fn main() {
    let app = Application::builder()
        .application_id("com.jwestall.ui-demo")
        .build();

    app.connect_activate(build_ui);
    app.run();
}

fn run_command(command: &str) -> Output {
    Command::new("sh")
        .arg("-c")
        .arg(command)
        .output()
        .unwrap_or_else(|_| panic!("failed to execute '{}'", command))
}

fn build_ui(app: &Application) {
    let input = gtk::Entry::builder()
        .placeholder_text("input")
        .margin_top(12)
        .margin_bottom(12)
        .margin_start(12)
        .margin_end(12)
        .build();

    let window = ApplicationWindow::builder()
        .application(app)
        .title("gtk-app")
        .child(&input)
        .build();

    window.show_all();

    // 创建共享的可变状态,存储窗口ID(Option处理未找到的情况)
    let window_id = Rc::new(RefCell::new(None::<String>));

    // 克隆Rc用于connect_changed闭包
    let window_id_clone = Rc::clone(&window_id);
    input.connect_changed(move |entry| {
        let input_text = entry.text();

        let command = format!("xdotool search --onlyvisible --name {}", input_text);
        let window_id_output = run_command(&command);

        if window_id_output.status.success() {
            let stdout = String::from_utf8_lossy(&window_id_output.stdout).trim().to_string();
            println!("stdout: {}", stdout);
            // 更新共享的窗口ID
            *window_id_clone.borrow_mut() = Some(stdout);
        } else {
            let stderr = String::from_utf8_lossy(&window_id_output.stderr);
            println!("stderr: {}", stderr);
            // 未找到窗口时清空ID
            *window_id_clone.borrow_mut() = None;
        }
    });

    // 克隆Rc用于connect_activate闭包
    let window_id_clone = Rc::clone(&window_id);
    input.connect_activate(move |_| {
        // 读取共享的窗口ID
        if let Some(id) = &*window_id_clone.borrow() {
            let command = format!("xdotool windowactivate {}", id);
            let window_activate_output = run_command(&command);
            
            // 由于windowactivate无输出,打印状态即可
            println!("window_activate status: {}", window_activate_output.status.success());
        } else {
            println!("No valid window ID found");
        }

        window.hide();
        window.close();
    });
}

关键说明

  • Rc<RefCell<Option<String>>>:Rc让两个闭包都能持有变量的引用,RefCell允许在闭包的不可变环境中修改内部值,Option处理未搜索到窗口的边界情况
  • 每次连接信号时都需要克隆Rc,因为每个闭包需要自己的引用计数实例
  • 修改RefCell内部值时使用borrow_mut(),读取时使用borrow(),这两个方法会在运行时检查借用规则,避免数据竞争

内容的提问来源于stack exchange,提问作者wyc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 22:25:30