Token导航 LogoToken导航TokenDH.com
前端设计执行命令github未标认证来源可访问许可证需确认审计通过

building-qt-apps构建 qt 应用程序

Agent Skill

building-qt-apps 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

612

周安装

25

GitHub Stars

公开资料未说明

下载量

198
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:building-qt-apps(构建 qt 应用程序)
来源仓库:https://github.com/quick-brown-foxxx/coding_rules_python
仓库路径:skills/building-qt-apps
安装命令:
npx skills add https://github.com/quick-brown-foxxx/coding_rules_python --skill building-qt-apps
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/quick-brown-foxxx/coding_rules_python --skill building-qt-apps

简介

该技能指导 Qt 桌面应用开发,采用 PySide6 + qasync 技术栈实现异步 GUI 编程。

  • 适用于需要跨平台桌面程序、音频处理或多线程服务的应用场景。
  • 遵循 Manager → Service → Wrapper 分层架构,避免阻塞事件循环影响用户体验。
  • 安装方式:通过 npx skills add 命令从 GitHub 仓库添加,需配置 PySide6 运行环境。
  • building-qt-apps 属于前端设计类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

Building Qt Apps

Qt apps use PySide6 with qasync for async integration. Architecture follows Manager → Service → Wrapper layering. Never block the event loop.


Why PySide6

  • LGPL license (no additional restrictions)
  • No extra system dependencies (ships with wheels)
  • Same API as PyQt6, but freely redistributable

Architecture: Manager → Service → Wrapper

For dependency wiring patterns (composition root), see building-multi-ui-apps skill.

UI Layer (MainWindow, Dialogs, TrayIcon)
    |  Qt signals/slots
    v
Manager Layer (AudioManager, TranscriptionManager)
    |  orchestrates, emits signals
    v
Service Layer (TranscriptionService, RecordingService)
    |  async operations
    v
Wrapper Layer (WhisperWrapper, SoundcardWrapper)
    |  typed interfaces to third-party libs
    v
Third-Party Libraries

Manager Pattern

Managers coordinate operations and emit Qt signals:

class TranscriptionManager(QObject):
    transcription_finished = Signal(str)
    transcription_error = Signal(str)
    model_changed = Signal(str)

    def __init__(self, settings: Settings) -> None:
        super().__init__()
        self._service: TranscriptionService | None = None
        self._bridge = QAsyncSignalBridge()

    def transcribe(self, audio_data: np.ndarray) -> bool:
        if not self._service:
            self.transcription_error.emit("Service not initialized")
            return False

        self._bridge.run_async(
            self._service.transcribe(audio_data),
            on_success=self._on_finished,
            on_error=self._on_error,
        )
        return True

    def _on_finished(self, text: str) -> None:
        self.transcription_finished.emit(text)

    def _on_error(self, error: str) -> None:
        self.transcription_error.emit(error)

Wrapper Pattern

Typed wrappers isolate untyped third-party APIs:

class WhisperModelWrapper:
    """Typed wrapper for faster-whisper."""

    def __init__(self, model_size: str, device: str = "auto") -> None:
        from faster_whisper import WhisperModel as _WhisperModel
        self._model = _WhisperModel(model_size, device=device)

    def transcribe(self, audio: np.ndarray, language: str | None = None) -> TranscriptionResult:
        segments_gen, info = self._model.transcribe(audio, language=language)
        return TranscriptionResult(
            text="".join(s.text for s in segments_gen),
            language=str(info.language),
        )

Async Integration with qasync (over QtAsyncio, which is still in technical preview)

Setup

import asyncio
import signal
import qasync
from PySide6.QtWidgets import QApplication

def main() -> int:
    app = QApplication(sys.argv)
    loop = qasync.QEventLoop(app)
    asyncio.set_event_loop(loop)
    signal.signal(signal.SIGINT, signal.SIG_DFL)  # Make Ctrl+C work (Qt blocks it)
    with loop:
        window = MainWindow()
        window.show()
        loop.run_forever()
    return 0

QAsyncSignalBridge

Bridge async coroutines to Qt signals:

class QAsyncSignalBridge(QObject):
    finished = Signal(object)
    error = Signal(str)

    def run_async(
        self,
        coro: Coroutine[object, None, T],
        on_success: Callable[[T], None] | None = None,
        on_error: Callable[[str], None] | None = None,
    ) -> None:
        async def _wrapped() -> None:
            try:
                result = await coro
                if on_success:
                    on_success(result)
                else:
                    self.finished.emit(result)
            except Exception as e:
                if on_error:
                    on_error(str(e))
                else:
                    self.error.emit(str(e))

        loop = asyncio.get_running_loop()
        self._task = loop.create_task(_wrapped())

ThreadPoolExecutor for Blocking Libraries

When a library only provides sync API:

class AsyncRecorder(QObject):
    recording_completed = Signal(np.ndarray)

    def __init__(self) -> None:
        super().__init__()
        self._executor = ThreadPoolExecutor(max_workers=1)

    async def start_recording(self) -> None:
        loop = asyncio.get_running_loop()
        result = await loop.run_in_executor(self._executor, self._sync_record)
        self.recording_completed.emit(result)

Key Rules

  1. PySide6 (LGPL, no system deps) over PyQt
  2. Never block event loop: no subprocess.run(), no time.sleep(), no sync HTTP
  3. qasync bridges asyncio and Qt event loops
  4. ThreadPoolExecutor wraps blocking third-party APIs
  5. Typed wrappers around untyped libraries, enforced via ruff banned-api
  6. Signals at class level, not in __init__
  7. camelCase for Qt event handlers (ignore ruff N802), snake_case for our slots

Ctrl+C and Shutdown

Qt's event loop blocks Python signal handling, making Ctrl+C appear to do nothing. Fix: signal.signal(signal.SIGINT, signal.SIG_DFL) before loop.run_forever() — lets the OS handle SIGINT directly (shown in the setup example above).

If the app needs cleanup on Ctrl+C (save state, release locks, stop recordings), use a handler that calls QApplication.quit() instead of SIG_DFL, so Qt's shutdown sequence runs:

def _sigint_handler(*_args: object) -> None:
    QApplication.quit()

signal.signal(signal.SIGINT, _sigint_handler)

# Timer lets Python process the signal between Qt events
timer = QTimer()
timer.start(200)
timer.timeout.connect(lambda: None)

For subprocess shutdown patterns, see setting-up-python-projects skill.


Signal/Slot Conventions

  • Define signals at class level (not in __init__)
  • Connect signals in the component that owns the relationship
  • Use typed signals: Signal(str), Signal(float). Use Signal(object) only when PySide6 lacks generic signal support — add # PySide6 limitation: no generic signals comment
class AudioManager(QObject):
    volume_changed = Signal(float)
    recording_completed = Signal(np.ndarray)
    recording_failed = Signal(str)

    def __init__(self) -> None:
        super().__init__()
        self._recorder = AsyncRecorder()
        self._recorder.recording_completed.connect(self.recording_completed)

Naming Convention Exception

Qt event handlers use camelCase per Qt convention:

[tool.ruff.lint]
ignore = ["N802"]  # Qt event handlers use camelCase
class CustomWidget(QWidget):
    def mousePressEvent(self, event: QMouseEvent) -> None:  # Qt convention
        ...

    def on_button_clicked(self) -> None:  # Our slots use snake_case
        ...

Declarative Label → Callback Pattern

Whenever bootstrapping a fixed set of labeled actions — tray menus, button bars, context menus, toolbar items — avoid imperative addAction/addButton chains. Instead, declare all entries as data at the top of the setup method (where self is in scope for type-safe bound-method references) and drive the construction with a generic loop at the bottom.

"SEPARATOR" is a Literal sentinel: basedpyright rejects any other string in that position, so both the sentinel and the callbacks are fully type-checked.

from typing import Callable, Final, Literal

_SEPARATOR: Final = "SEPARATOR"
_Entry = tuple[str, Callable[[], None]] | Literal["SEPARATOR"]

class ApplicationTrayIcon(QSystemTrayIcon):
    def __init__(self) -> None:
        super().__init__()
        self.setIcon(QIcon("icon.png"))
        self._setup_menu()

    def _setup_menu(self) -> None:
        entries: list[_Entry] = [
            ("Settings", self._open_settings),
            _SEPARATOR,
            ("Quit", QApplication.quit),
        ]

        menu = QMenu()
        for entry in entries:
            if entry is _SEPARATOR:
                menu.addSeparator()
            else:
                label, cb = entry
                menu.addAction(label, cb)
        self.setContextMenu(menu)

    def _open_settings(self) -> None: ...

entries is the single place to add, remove, or reorder items. The loop is generic boilerplate that never changes. Mistyping self._poen_settings is caught by basedpyright at check time — no runtime surprises. The same pattern applies to button bars, context menus, or any other label → callback mapping.


Single Instance Enforcement

class LockManager:
    def __init__(self, lock_path: Path) -> None:
        self._lock_path = lock_path

    def acquire(self) -> Result[None, str]:
        if self._lock_path.exists():
            pid = int(self._lock_path.read_text())
            if self._is_process_running(pid):
                return Err(f"Another instance running (PID {pid})")
            # Stale lock file
        self._lock_path.write_text(str(os.getpid()))
        return Ok(None)

    def release(self) -> None:
        self._lock_path.unlink(missing_ok=True)

Keyboard Shortcuts

Customizable via TOML config:

class ActionID(enum.Enum):
    NEW_PROFILE = "new_profile"
    START_PROFILE = "start_profile"

@dataclass
class ActionShortcut:
    id: str
    label: str
    default_key: str

DEFAULT_SHORTCUTS = (
    ActionShortcut(ActionID.NEW_PROFILE.value, "New Profile", "Ctrl+N"),
    ActionShortcut(ActionID.START_PROFILE.value, "Start Profile", "Return"),
)

User overrides stored in ~/.config/appname/shortcuts.toml.


Settings Management

Type-safe QSettings wrapper:

class Settings:
    def __init__(self) -> None:
        self._settings = QSettings(APP_NAME, APP_NAME)
        self._init_defaults()

    def get_str(self, key: str, default: str = "") -> str:
        value = self._settings.value(key, default)
        return str(value) if value is not None else default

    def get_int(self, key: str, default: int = 0) -> int:
        value = self._settings.value(key, default)
        return int(value) if value is not None else default

    def set(self, key: str, value: str | int | bool) -> None:
        self._settings.setValue(key, value)

Testing Qt Components

Use pytest-qt:

def test_main_window_creates(qtbot: QtBot) -> None:
    window = MainWindow()
    qtbot.addWidget(window)
    assert window.isVisible() is False  # Not shown until .show()

def test_button_click(qtbot: QtBot) -> None:
    widget = MyWidget()
    qtbot.addWidget(widget)
    with qtbot.waitSignal(widget.action_triggered, timeout=1000):
        qtbot.mouseClick(widget.button, Qt.LeftButton)

Routing QML Logs to Python Logger

QML console.log/info/warn/error calls print to stderr by default with no structure or log levels. Install a custom Qt message handler before creating the QML engine to route them through Python's logging module.

The Handler

import logging
from PySide6.QtCore import QMessageLogContext, QtMsgType, qInstallMessageHandler

_qt_logger = logging.getLogger("qt.qml")

def _qt_message_handler(msg_type: QtMsgType, context: QMessageLogContext, message: str) -> None:
    file: str = context.file or ""
    line: int = context.line or 0
    location = f" ({file}:{line})" if file else ""
    log_message = f"{message}{location}"

    if msg_type == QtMsgType.QtDebugMsg:
        _qt_logger.debug(log_message)
    elif msg_type == QtMsgType.QtInfoMsg:
        _qt_logger.info(log_message)
    elif msg_type == QtMsgType.QtWarningMsg:
        _qt_logger.warning(log_message)
    else:  # QtCriticalMsg, QtFatalMsg
        _qt_logger.error(log_message)

Install Before QML Engine

qInstallMessageHandler(_qt_message_handler)
engine = QQmlApplicationEngine()

Order matters — install before QQmlApplicationEngine() so early QML load warnings are captured.

QML Usage

Component.onCompleted: {
    console.info("Panel loaded, items: " + listModel.count)
    console.warn("Missing optional property")
    console.error("Failed to load resource")
}

Gotcha: console.log() Is Silently Dropped

Qt maps console.log() to QtDebugMsg, which Qt's own message filtering suppresses before the handler is called. The handler never sees it.

QML callQt typeReaches handlerRecommendation
console.log()QtDebugMsgNoDon't use
console.info()QtInfoMsgYesUse for debug output
console.warn()QtWarningMsgYesRecoverable issues
console.error()QtCriticalMsgYesErrors

Always use console.info() instead of console.log().

The logger name qt.qml lets you filter or suppress QML messages independently:

logging.getLogger("qt.qml").setLevel(logging.WARNING)  # silence info-level QML noise

See the setting-up-logging skill for colored stdout/file logging setup that works with this handler.


Platform Integration - File Dialogs (XDG Desktop Portals)

On Linux, file dialogs use XDG Desktop Portals for native system pickers (with favorites, bookmarks, etc.). The app sets QT_QPA_PLATFORMTHEME=xdgdesktopportal at startup if no platform theme is configured.

Requirements: xdg-desktop-portal + a desktop backend (xdg-desktop-portal-kde, xdg-desktop-portal-gnome, etc.).

No code changes needed — standard QFileDialog calls automatically use portals when the platform theme is set. In Flatpak environments, portals are used transparently without any configuration.

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

34.23%
按下载量换算68

Claude

28.31%
按下载量换算56

Cursor

18.27%
按下载量换算36

Gemini CLI

9.22%
按下载量换算18

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

执行命令

安装流程涉及命令执行,可能通过 npx skills add https://github.com/quick-brown-foxxx/coding_rules_python --skill building-qt-apps 联网下载 Skill 或依赖。用户安装前应确认命令来源、仓库内容和执行环境。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills