Skip to content

CyJimmy264/qt

Repository files navigation

qt

Ruby Qt Status Bridge

Ruby-first Qt 6.4.2+ bridge.

Build real Qt Widgets apps in pure Ruby, mutate them live from IRB, and keep C/C++ surface minimal via generated bridge code from system Qt headers.

Highlights

  • Pure Ruby usage: no QML, no extra UI language.
  • Real Qt power: QApplication, QWidget, QLabel, QPushButton, QVBoxLayout.
  • Ruby ergonomics: Qt-style and snake_case/property style in parallel.
  • Live GUI hacking: update widgets while the window is open.
  • Generated bridge: API is derived from system Qt headers.

Install

Quick install (RubyGems)

gem install qt

Quick install (Fedora, binary RPM via COPR)

sudo dnf copr enable cyjimmy264/ruby-qt -y
sudo dnf install -y ruby-qt

This installs a prebuilt package. Nothing is compiled on the target machine. Package name: ruby-qt.

Requirements (build from source)

  • Ruby 3.2+
  • Qt 6.4.2+ dev packages (Qt6Core, Qt6Gui, Qt6Widgets via pkg-config)
  • C++17 compiler

System Requirements

Minimum packages for Fedora:

dnf install @development-tools qt6-qtbase-devel ruby ruby-devel clang

Minimum packages for Ubuntu/Debian:

sudo apt update
sudo apt install -y build-essential pkg-config qt6-base-dev ruby ruby-dev clang

Check Qt:

pkg-config --modversion Qt6Widgets

Build from repo

bundle install
bundle exec rake compile
bundle exec rake install

rake install installs into your current Ruby environment (including active rbenv version). rake compile builds the full bridge with QT_RUBY_SCOPE=all by default.

Quick Start

bundle exec ruby examples/development_ordered_demos/02_live_layout_console.rb

Optional: run interactive commands in IRB while the app is open:

add_label("Release pipeline")
add_button("Run")
remove_last

gui { window.resize(1100, 700) }
items.last&.q_inspect

Hello Qt in Ruby

require 'qt'

app = QApplication.new(0, [])

window = QWidget.new do |w|
  w.set_window_title('Qt Ruby App')
  w.resize(800, 600)
end

label = QLabel.new(window)
label.text = 'Hello from Ruby + Qt'
label.set_alignment(Qt::AlignCenter)
label.set_geometry(0, 0, 800, 600)

app.exec

API Style: Qt + Ruby

# Qt style
label.setText('A')
window.setWindowTitle('Main')

# Ruby style
label.text = 'B'
window.window_title = 'Main 2'
puts label.text

API Compatibility Notes

Generated Ruby API is intentionally close to Qt API, but follows universal bridge policies.

  • snake_case aliases are generated for Qt camelCase methods.
  • Ruby keyword-safe renaming is applied when needed: next -> next_.
  • Default C++ arguments are surfaced as optional Ruby arguments.
  • Internal runtime name collisions are renamed consistently:
    • Qt handle(int) is exposed as handle_at(int) because handle is used for native object pointer access.
  • Property convenience API is generated from Qt setters/getters when available:
    • setText(...) -> text=(...), text.
  • Runtime event/signal convenience methods are Ruby-layer helpers (not raw Qt method names):
    • on(event, &block) / alias on_event
    • off(event = nil) / alias off_event
    • connect(signal, &block) / aliases on_signal, slot
    • disconnect(signal = nil) / alias off_signal
    • these helpers are mixed into generated QObject descendants (for example QWidget, QPushButton, QTimer)
    • non-QObject value classes (QIcon, QPixmap, QImage) intentionally do not expose connect/on
    • event delivery is target-first with nearest watched ancestor fallback for interactive events (mouse/key/focus/enter/leave)
  • Introspection helpers are Ruby-layer helpers:
    • q_inspect, aliases qt_inspect, to_h
  • Top-level constant aliases are provided for convenience:
    • QApplication, QWidget, QLabel, QPushButton, QLineEdit, QVBoxLayout, QTableWidget, QTableWidgetItem, QScrollArea
  • Methods with unsupported signatures are skipped by policy:
    • non-public, deprecated, operator/internal event hooks,
    • non-FFI-safe argument/return types.

Introspection

Every generated object exposes API snapshot helpers:

label.q_inspect
label.qt_inspect
label.to_h

Shape:

{
  qt_class: "QLabel",
  ruby_class: "Qt::QLabel",
  qt_methods: ["setText", "setAlignment", "text", ...],
  ruby_methods: [:setText, :set_text, :text, ...],
  properties: { text: "A", alignment: 129 }
}

Examples

See all demos in examples/development_ordered_demos.

QObject signal example:

timer = QTimer.new
timer.set_interval(1000)
timer.connect('timeout') { puts 'tick' }
timer.start

Projects

  • qtimetrap - timetrap desktop UI built with this bridge.

Architecture

  1. scripts/generate_bridge.rb reads Qt API from system headers.
  2. Generates:
  • build/generated/qt_ruby_bridge.cpp
  • build/generated/bridge_api.rb
  • build/generated/widgets.rb
  1. Compiles native extension into build/qt/qt_ruby_bridge.so.
  2. Ruby layer calls bridge functions via ffi.

Everything generated/build-related is under build/ and should stay out of git.

Layout

  • lib/qt public Ruby API
  • scripts/generate_bridge.rb AST-driven bridge generator
  • ext/qt_ruby_bridge native extension entrypoint
  • build/generated generated sources
  • build/qt compiled bridge .so
  • examples demos
  • test tests

Roadmap

Done

  • AST-driven generation with scope support: QT_RUBY_SCOPE=widgets|qobject|all
  • default compile path switched to all (widgets + qobject)
  • generated Qt inheritance in Ruby classes (including intermediate Qt wrappers)
  • Qt-native event/signal runtime wired to Ruby at QObject level (on, connect, disconnect)
  • QTimer available in generated API with connect('timeout') support
  • 06_timetrap_clockify moved to app.exec + QTimer update loop (no manual polling loop)
  • QObject styling hooks exposed for QSS selectors:
    • setObjectName / object_name=
    • setProperty / property (via QVariant bridge codec)
  • window icon support from generated API:
    • QIcon.new(path)
    • QWidget#setWindowIcon / set_window_icon

Next

  • typed signal payloads (not only raw/placeholder payload)
  • richer QObject metaobject Ruby API (meta_object, methods/signatures/properties introspection)
  • normalize signal naming rules for overloads and deterministic connect behavior

Later

  • expand generated surface for additional Qt modules (network, sql, xml, etc.) using the same generator policy
  • packaging hardening for Linux/macOS (install/build paths, gem install reliability)
  • CI matrix for Ruby/Qt combinations and scope modes (widgets, qobject, all)
  • add performance checks for generator traversal and compile size/time regression tracking

Development

bundle exec rake compile
bundle exec rake test
bundle exec rake rubocop

Test Environment Variables

Tests force QT_QPA_PLATFORM=offscreen by default to avoid opening GUI windows.

  • QT_QPA_PLATFORM_FORCE_XCB=true
    • override test default and run with QT_QPA_PLATFORM=xcb (real X11 backend)
  • QT_RUBY_MANUAL_MODIFIERS=1
    • enable manual keyboard-modifier smoke test (Ctrl/Shift must be pressed during test window)

Examples:

# default headless test run
bundle exec rake test

# run tests on xcb backend
QT_QPA_PLATFORM_FORCE_XCB=true bundle exec rake test

# run only manual modifiers smoke test
QT_QPA_PLATFORM_FORCE_XCB=true QT_RUBY_MANUAL_MODIFIERS=1 \
bundle exec ruby -Itest test/application_test.rb \
  --name test_qapplication_keyboard_modifiers_manual_ctrl_shift_smoke --verbose

Generation Scope

Default build scope is all. You can still override scope manually with QT_RUBY_SCOPE:

  • widgets (default): QWidget/QLayout-oriented classes.
  • qobject: QObject descendants excluding QWidget/QLayout branch.
  • all: combined public surface from widgets + qobject scopes (default build mode).

Examples:

QT_RUBY_SCOPE=widgets bundle exec rake compile
QT_RUBY_SCOPE=qobject bundle exec rake compile
QT_RUBY_SCOPE=all bundle exec rake compile

If Qt is in a custom prefix:

export PKG_CONFIG_PATH="/path/to/qt/lib/pkgconfig:$PKG_CONFIG_PATH"

Native event-runtime debug logs:

QT_RUBY_EVENT_DEBUG=1 ruby your_app.rb

Optional tuning:

# enable ancestor fallback for MouseMove events (off by default)
QT_RUBY_EVENT_ANCESTOR_MOUSE_MOVE=1 ruby your_app.rb

About

Ruby-first Qt 6.4.2+ bridge.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors