协作头脑风暴
brainstorming
在实现前澄清需求、探索设计选择,并形成清晰的开发方向。
desktop-gnome【开发与工程】Use when the user asks about GNOME desktop development, GNOME Shell extensions, GTK under GNOME, GNOME Builder, or GNOME platform libraries (GLib, GIO, GSettings, GNOME Settings). Do NOT use for: GTK itself (desktop-gtk), or KDE (desktop-kde).
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Build applications and extensions for the GNOME desktop environment using GNOME platform libraries — GLib, GIO, GSettings, libadwaita, and GNOME Shell extensions. GNOME emphasizes simplicity, accessibility, and adherence to the GNOME Human Interface Guidelines (HIG).
Exact user phrases: "GNOME app", "GNOME Shell extension", "libadwaita", "GSettings", "GIO", "GNOME Builder", "GNOME HIG", "GNOME Circle", "GNOME platform", "adwaita", "GNOME design".
GNOME application architecture with widget hierarchy, data flow, settings storage, and Flatpak distribution.
250 lines.
What type of GNOME project?
├── Standard GTK application → GtkApplication + libadwaita
│ → AdwApplication, AdwWindow, AdwNavigationView
│ → GSettings for preferences, .gresource for assets
├── GNOME Shell extension → GJS (JavaScript) + GNOME Shell APIs
│ → Extension class, ExtensionState, PanelMenu.Button
│ → GObject-based, prefs.ui for settings
├── Background service → GApplication without GUI
│ → GApplication with flags: IS_SERVICE
│ → D-Bus interface for IPC
└── GNOME Builder plugin → GObject + template
→ IdePlugin, IdeObject, GObject signals + properties
Application (libadwaita widgets)
↕
libadwaita (AdwApplication, AdwWindow, AdwNavigationView)
↕
GTK4 (widgets, CSS styling, rendering)
↕
GDK (windowing system, Wayland/X11 abstraction)
↕
GLib (main loop, GObject system, GIO async I/O, GSettings)
↕
Linux Kernel (Wayland, udev, systemd)
// Rust with gtk4-rs and libadwaita-rs
use adw::prelude::*;
use gtk::prelude::*;
fn main() {
glib::set_application_name("My App");
let app = adw::Application::builder()
.application_id("com.example.MyApp")
.resource_base_path("/com/example/MyApp")
.build();
app.connect_activate(build_ui);
app.run();
}
fn build_ui(app: &adw::Application) {
// Create main window
let window = adw::ApplicationWindow::new(app);
window.set_default_size(800, 600);
window.set_title(Some("My App"));
// Build content with libadwaita widgets
let main_content = create_main_content();
window.set_content(Some(&main_content));
window.present();
}
# Python with PyGObject
import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Adw, GLib, Gio
class MyApp(Adw.Application):
def __init__(self):
super().__init__(application_id='com.example.MyApp')
self.connect('activate', self.on_activate)
def on_activate(self, app):
window = Adw.ApplicationWindow(application=app)
window.set_default_size(800, 600)
# ... build UI
window.present()
app = MyApp()
app.run(None)
<!-- UI file (myapp.ui) - libadwaita responsive layout -->
<interface>
<template class="MyAppWindow" parent="AdwApplicationWindow">
<property name="content">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<property name="title-widget">
<object class="AdwWindowTitle"/>
</property>
<child type="end">
<object class="GtkMenuButton">
<property name="icon-name">open-menu-symbolic</property>
<property name="menu-model">primary_menu</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwNavigationView">
<!-- Pages managed programmatically -->
</object>
</child>
</object>
</property>
</template>
</interface>
Key libadwaita Widgets:
// AdwBreakpointBin for responsive layouts
let breakpoint_bin = adw::BreakpointBin::new();
// Wide layout
let wide_bp = adw::Breakpoint::builder(800)
.condition(adw::BreakpointCondition::new_width(
adw::LengthUnit::Px,
adw::BreakpointConditionType::Minimum,
))
.build();
let wide_child = create_wide_layout(); // Sidebar + content
breakpoint_bin.add_child(&breakpoint_bin, &wide_child, glib::PRIORITY_DEFAULT);
// Narrow layout
let narrow_bp = adw::Breakpoint::builder(799)
.condition(adw::BreakpointCondition::new_width(
adw::LengthUnit::Px,
adw::BreakpointConditionType::Maximum,
))
.build();
let narrow_child = create_narrow_layout(); // Stacked
breakpoint_bin.add_child(&breakpoint_bin, &narrow_child, glib::PRIORITY_DEFAULT);
use glib::ObjectExt;
use glib::subclass::prelude::*;
use std::cell::RefCell;
// GObject subclass in Rust
mod imp {
use super::*;
pub struct MyModel {
pub name: RefCell<String>,
pub count: RefCell<u32>,
}
#[glib::object_subclass]
impl ObjectSubclass for MyModel {
const NAME: &'static str = "MyModel";
type Type = super::MyModel;
type ParentType = glib::Object;
}
impl ObjectImpl for MyModel {
fn properties() -> &'static [glib::ParamSpec] {
use once_cell::sync::Lazy;
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecString::builder("name").build(),
glib::ParamSpecUInt::builder("count")
.minimum(0).maximum(1000).default_value(0).build(),
]
});
PROPERTIES.as_ref()
}
fn set_property(&self, obj: &Self::Type, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"name" => self.name.replace(value.get().unwrap()),
"count" => self.count.replace(value.get().unwrap()),
_ => unimplemented!(),
}
}
fn get_property(&self, obj: &Self::Type, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"name" => self.name.borrow().to_value(),
"count" => self.count.borrow().to_value(),
_ => unimplemented!(),
}
}
}
}
glib::wrapper! {
pub struct MyModel(ObjectSubclass<imp::MyModel>);
}
<!-- gschema.xml -->
<schemalist>
<schema id="com.example.MyApp" path="/com/example/MyApp/">
<key name="window-width" type="i">
<default>800</default>
<summary>Window width</summary>
</key>
<key name="window-height" type="i">
<default>600</default>
<summary>Window height</summary>
</key>
<key name="theme" type="s">
<default>'default'</default>
<summary>UI theme preference</summary>
</key>
<key name="show-sidebar" type="b">
<default>true</default>
<summary>Show sidebar</summary>
</key>
</schema>
</schemalist>
// Access GSettings
let settings = glib::Settings::new("com.example.MyApp");
settings.connect_changed(Some("theme"), |settings, _key| {
let theme = settings.string("theme");
// Apply theme change
});
let width = settings.int("window-width");
# myapp.yml - Flatpak manifest
app-id: com.example.MyApp
runtime: org.gnome.Platform
runtime-version: '47'
sdk: org.gnome.Sdk
command: myapp
finish-args:
- --share=network
- --share=ipc
- --socket=fallback-x11
- --socket=wayland
- --device=dri
- --filesystem=home:ro
modules:
- name: myapp
buildsystem: meson
sources:
- type: dir
path: .
// extension.js - GNOME Shell extension
const { St, Shell, PanelMenu, Clutter } = imports.gi;
const Main = imports.ui.main;
const ExtensionUtils = imports.misc.extensionUtils;
class MyExtension {
constructor() {
this._indicator = null;
}
enable() {
log('Enabling MyExtension');
// Create panel button
this._indicator = new PanelMenu.Button(0.0, 'MyExtension', false);
// Add icon
const icon = new St.Icon({
icon_name: 'my-extension-icon',
style_class: 'system-status-icon'
});
this._indicator.add_child(icon);
// Add to panel
Main.panel.addToStatusArea('MyExtension', this._indicator, 1);
}
disable() {
log('Disabling MyExtension');
this._indicator?.destroy();
this._indicator = null;
}
}
function init() {
return new MyExtension();
}
| Pitfall | Description | Prevention |
|---|---|---|
| Ignoring HIG | Custom widgets that break GNOME design patterns | Follow GNOME HIG, use libadwaita widgets |
| Direct GtkWindow | Missing CSD, header bar | Use AdwApplicationWindow with AdwHeaderBar |
| No adaptive layout | App works only at one window size | Use AdwBreakpointBin, test at mobile width |
| Blocking main loop | Synchronous I/O freezes UI | Use GIO async, async/await, or threads |
| Missing GSchema | Settings don't persist | Always define GSettings schema XML |
| Flatpak permissions too broad | --filesystem=home balloon | Minimal file system access, portals |
| Hardcoded strings | No gettext translation support | Use _() macro everywhere |
| Improper GObject memory | Reference cycles, leaks | Use weak references, bind properly |
| Practice | Rationale |
|---|---|
| Use libadwaita, not pure GTK | Following GNOME HIG, adaptive by default |
| Flatpak as primary distribution | Sandboxed, runtime-managed, automatic updates |
| GSettings for preferences | Native integration, no custom config parser |
| .gresource for bundling | Compiled into binary, no file-system dependencies at runtime |
| GObject signals for events | Standard GNOME pattern, integration with Builder |
| Async I/O with GIO | Non-blocking, cancellable, progress reporting |
| Test on Wayland and X11 | Both display servers are common on Linux |
| Enable accessibility by default | AT-SPI, screen readers, Orca |
| Minimize permissions | Better user trust, Flathub review requirements |
| Internationalize early | Adding _() later is tedious, .pot regeneration |
let nav = adw::NavigationView::new();
let page1 = adw::NavigationPage::new();
page1.set_title("Main");
let page2 = adw::NavigationPage::new();
page2.set_title("Detail");
nav.push(page1);
// Navigate:
nav.push(page2); // Push detail
nav.pop(); // Back to main
let toast = adw::Toast::new("Changes saved");
toast.set_timeout(3);
toast.set_button_label("Undo");
toast.connect_button_clicked(|toast| {
// Handle undo
});
nav.add_toast(&toast);
let status_page = adw::StatusPage::new();
status_page.set_icon_name(Some("emblem-important-symbolic"));
status_page.set_title("No items yet");
status_page.set_description("Add your first item to get started");
Hand off to desktop-gtk for pure GTK details. Hand off to desktop-kde for KDE-specific integration patterns.
` interface EventObserver { onEvent(event: T): Promise; }
class EventBus { private observers: Set<EventObserver> = new Set(); subscribe(observer: EventObserver): void { this.observers.add(observer); } unsubscribe(observer: EventObserver): void { this.observers.delete(observer); } async emit(event: T): Promise { const results = Array.from(this.observers).map(o => o.onEvent(event)); await Promise.allSettled(results); } } `
config: defaults: timeout: 30s retryCount: 3 overrides: production: timeout: 60s retryCount: 5 development: timeout: 300s retryCount: 1
| Metric | Threshold | Severity | Action |
|---|---|---|---|
| Error rate | > 1% over 5min | Critical | Page on-call |
| p99 latency | > 2s over 5min | Warning | Investigate |
| Throughput drop | > 50% over 1min | Critical | Check upstream |
| Queue depth | > 1000 over 1min | Warning | Scale consumers |
| Disk usage | > 85% | Warning | Clean or expand |
| Memory usage | > 90% heap | Critical | Restart or scale |
| Anti-Pattern | Symptom | Root Cause | Solution |
|---|---|---|---|
| Premature optimization | Complex code for no measured benefit | Guessing instead of profiling | Measure first, optimize based on data |
| Copy-paste reuse | Duplicate code across codebase | Lack of abstraction | Extract shared logic into libraries |
| Gold-plating | Features with no current requirement | Over-engineering | YAGNI — build what's needed now |
| Magical thinking | Assumptions without validation | Skipping error handling | Handle all failure modes explicitly |
Cache hierarchy: L1 (in-memory local) → L2 (distributed Redis/Memcached) → L3 (CDN/Edge). Cache invalidation: TTL-based (simple, stale), event-based (complex, fresh), write-through (consistent, higher write latency), write-behind (fast writes, eventual consistency).
| Decision | GTK4 (Native) | GTK3 (Fallback) |
|---|---|---|
| Widget API | GtkWidget, GtkListView | GtkWidget, GtkTreeView |
| CSS styling | Full CSS + transitions | Limited CSS |
| Performance | Improved rendering | Slower for complex UIs |
| Accessibility | Better AT-SPI2 support | Partial |
| Libadwaita | Required for GNOME 45+ | Not available |
| Best for | New GNOME apps | Legacy app maintenance |
| Aspect | Libadwaita (libadwaita) | Plain GTK4 |
|---|---|---|
| HIG compliance | Automatic | Manual |
| Adaptive UI | Built-in breakpoints | Custom code needed |
| Dark mode | Auto-switch | Manual implementation |
| Animation | Built-in transitions | CSS animations |
| Dependency | Heavier | Lighter |