Qt6 (C++) でアイドル検出+自動シャットダウンのトレイアプリを作った
はじめに
「しばらく放置していたら電源が切れていてほしい」——そんな単純な要望をきっかけに、以前 Python で作っていた AutoShutdown を今回 C++/Qt6 で書き直しました。
Python 版はライブラリの依存関係が地味に気になっていました。実行環境を汚さず、インストールも楽なバイナリにしてしまいたい。そう思って久しぶりに Qt プログラミングに取り組みました。
- リポジトリ: https://github.com/amekusa03/qt6-desktop-autoshutdown
- 環境: Linux (Fedora / Ubuntu 等) + GNOME または X11
アプリの概要
無操作(アイドル)状態を検出し、指定した時間が経過したら自動でシステムをシャットダウンします。ウィンドウを閉じてもシステムトレイに常駐して監視を継続するため、普段は意識せず使えます。
主な機能
| 機能 | 詳細 |
|---|---|
| バックグラウンド常駐 | ウィンドウを閉じてもトレイに残り監視を継続 |
| アイドル検出(GNOME) | D-Bus 経由で org.gnome.Mutter.IdleMonitor を叩く |
| アイドル検出(X11) |
xprintidle コマンドにフォールバック |
| タイムアウト設定 | 60秒〜3600秒でスライダー調整 |
| チェック間隔設定 | 5秒〜60秒でスライダー調整 |
| 安全なシャットダウン |
shutdown -h +1 で1分の猶予、UI からキャンセル可 |
| デスクトップ通知 |
notify-send で各種イベントを通知 |
| 設定の永続化 |
config.ini に自動保存 |
アーキテクチャ
クラスは2つだけのシンプルな構成です。
main.cpp
├── AutoShutdownCore ← ロジック担当(QTimer + D-Bus + QProcess)
└── MainWindow ← UI担当(QMainWindow + QSystemTrayIcon)
ロジックと UI を分離した理由
AutoShutdownCore は Qt の QObject を継承し、statusChanged シグナルを持ちます。MainWindow はこのシグナルを受け取って表示を更新するだけ。ロジックが UI に依存しない設計にしておくと、将来的に CLI 版に差し替えたり、ヘッドレス環境で動かすことも容易になります。
アイドル時間の取得
アイドル検出は環境によって方法が異なります。今回は GNOME を優先し、失敗したら X11 にフォールバックする方式を採りました。
GNOME 環境(D-Bus)
GNOME の Mutter コンポジターは、D-Bus 経由でアイドル時間をミリ秒単位で提供しています。
double AutoShutdownCore::getIdleTimeDBus()
{
QDBusInterface interface(
"org.gnome.Mutter.IdleMonitor",
"/org/gnome/Mutter/IdleMonitor/Core",
"org.gnome.Mutter.IdleMonitor",
QDBusConnection::sessionBus()
);
if (!interface.isValid()) return -1.0;
QDBusMessage reply = interface.call("GetIdletime");
if (reply.type() == QDBusMessage::ReplyMessage && !reply.arguments().isEmpty()) {
qulonglong idleMs = reply.arguments().at(0).toULongLong();
return static_cast<double>(idleMs) / 1000.0; // → 秒に変換
}
return -1.0;
}
QDBusInterface を使うと数行で D-Bus のメソッドを呼び出せます。isValid() で接続確認をしてから呼ぶのがポイントです。
X11 環境(xprintidle)
GNOME の D-Bus が使えない場合は xprintidle コマンドを QProcess 経由で実行します。
double AutoShutdownCore::getIdleTimeXprintidle()
{
QProcess process;
process.start("xprintidle", QStringList());
if (!process.waitForFinished(3000)) return -1.0;
if (process.exitCode() == 0) {
QString output = QString::fromUtf8(process.readAllStandardOutput()).trimmed();
bool ok;
qulonglong idleMs = output.toULongLong(&ok);
if (ok) return static_cast<double>(idleMs) / 1000.0;
}
return -1.0;
}
シャットダウンの仕組み
タイムアウトに達すると sudo shutdown -h +1 を実行し、1分間の猶予を設けます。その間は UI のキャンセルボタンが有効になり、誤作動時でも安全に止められます。
アイドル時間 >= タイムアウト
→ sudo shutdown -h +1 を実行
→ notify-send で通知
→ UI にキャンセルボタンを表示
└ ユーザーがキャンセル → sudo shutdown -c
トレイアイコンをプログラムで描く
専用のアイコンファイルを用意する代わりに、QPainter でアイコンをその場で生成しています。状態(有効/無効/警告)に応じて色を変えられるので、ファイル管理が不要になります。
QIcon MainWindow::createTrayIcon(bool enabled, bool warning)
{
QPixmap pixmap(64, 64);
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing);
// 状態に応じた色
QColor bgColor = enabled ? QColor(233, 69, 96) : QColor(80, 80, 100);
if (warning) bgColor = QColor(243, 156, 18);
painter.setBrush(bgColor);
painter.setPen(Qt::NoPen);
painter.drawEllipse(2, 2, 60, 60);
// 時計の弧っぽいアイコン
painter.setPen(QPen(Qt::white, 4, Qt::SolidLine, Qt::RoundCap));
painter.setBrush(Qt::NoBrush);
painter.drawArc(QRectF(12, 12, 40, 40), (90 - 130) * 16, (360 - 100) * 16);
return QIcon(pixmap);
}
システムトレイ常駐の実装
トレイ常駐で最も重要なのは「ウィンドウを閉じてもアプリを終了させない」設定です。
// main.cpp
app.setQuitOnLastWindowClosed(false); // ← これだけで OK
closeEvent を override してウィンドウを非表示にするだけで、バックグラウンド動作を維持できます。
void MainWindow::closeEvent(QCloseEvent *event)
{
if (m_trayIcon->isVisible()) {
hide();
event->ignore(); // ← アプリは終了しない
}
}
自動起動時に最小化で起動
// main.cpp
bool minimized = app.arguments().contains("--minimized");
if (!minimized) mainWindow.show();
--minimized 引数を付けることで、ログイン時の自動起動でウィンドウを表示せずトレイにだけ常駐させることができます。
セットアップ
依存パッケージ
# Ubuntu/Debian
sudo apt install build-essential qt6-base-dev qt6-tools-dev libqt6dbus6
# X11 環境でのアイドル検出(GNOME 以外)
sudo apt install xprintidle
# デスクトップ通知
sudo apt install libnotify-bin
ビルド
git clone https://github.com/amekusa03/qt6-desktop-autoshutdown
cd qt6-desktop-autoshutdown
qmake AutoShutdown.pro
make
./AutoShutdown
sudo なしでシャットダウンできるようにする
sudo visudo
末尾に追加(ユーザー名を自分のものに変更):
yourname ALL=(ALL) NOPASSWD: /usr/sbin/shutdown
自動起動の設定
mkdir -p ~/.config/autostart
nano ~/.config/autostart/autoshutdown.desktop
[Desktop Entry]
Type=Application
Name=AutoShutdown
Comment=Idle detection and auto shutdown service
Exec=/usr/local/bin/AutoShutdown --minimized
Icon=system-shutdown
Terminal=false
Categories=Utility;
X-GNOME-Autostart-enabled=true
感想
久しぶりの Qt6 でしたが、やはり C++ と Qt の組み合わせは快適です。D-Bus 連携も QDBusInterface のおかげで拍子抜けするほど簡単に書けました。
Python 版と比べてバイナリ一つで動くシンプルさが増したので、当初の目的は十分達成できました。
ソースはすべて公開していますので、興味のある方はご覧ください。