2026年7月5日日曜日

QtのQThreadとQTimerについて

QtつかっててQThreadとタイマー使ってると以下のエラーが出ることがある。

QObject::~QObject: Timers cannot be stopped from another thread

何がなんだか分からないまま闇雲になんとかしてたけど、なんか気持ち悪いので調べ直した。素人調べなので間違っているかもしれない。

QThreadの使い方

そもそもQThreadがどういうものなのか?調べた限りでは「slotで動かしたものがスレッドで動く」という仕組みのもののようだ。

class Worker : public QObject
{
/* 略 */
public slots:
    void exec();
    void exec2();
};

こんなのがあったとして、MainWindowから使う場合、

// worker作る
auto obj = new Worker();

// スレッドに移動
auto thr = new QThread();
obj->moveToThread(thr);
thr->start();

obj->exec(); // MainWindowのスレッドで動く
obj->exec2(); // MainWindowのスレッドで動く

// connect & emit
connect(this,&MainWindow::exec,obj,&Worker::exec);
connect(this,&MainWindow::exec2,obj,&Worker::exec2);

emit exec(); // Workerのスレッドで動く
emit exec2(); // Workerのスレッドで動く

emitでexec,exec2をslotで動かした場合にWorkerスレッドで動く 。execが終わるまではexec2は動かないようなので、同一スレッド内では通常は同時に動かないっぽい。

※ ただし、QEventloopでイベントを受け付けたりするとその限りではないようだ。QTcpSocketとかのwaitForConnect中とかでも他のスロットが動いたりした。内部でイベントループがあるのかもしれない。同時に動いて困る場合は念の為QMutexとかで排他したほうが良いだろう。

サンプルで、startedのsignalにconnectしたりしてるのをよく見る。下記の例だと単にthr->startを実行する時にexecも実行するというだけでしかない。

ちなみにobj->quit()したときにスロットを実行するsignalでfinishedというのもある。
以下だとexecは2回動く(はず)。

connect(thr, &QThread::started, obj, &Worker::exec);
connect(thr, &QThread::finished, obj, &Worker::exec);
thr->start();
thr->quit();

QThreadを継承する場合

WorkerをQThreadの子クラスにするやり方もあるようだが、上記の使い方だとそんなに違いはない。

class Worker : public QThread
以下略
// worker作る
auto obj = new Worker();
// スレッドに移動
obj->moveToThread(obj);
obj->start();

obj->exec(); // MainWindowのスレッドで動く

// connect & emit
connect(this,&MainWindow::exec,obj,&Worker::exec);

emit exec(); // QThreadのスレッドで動く

QThreadを継承しても、moveToThreadするまではMainWindowのスレッドで動くっぽい。moveToThreadしないとQThreadの意味はない。上記はMainWindow側でmoveToThreadをやっているが、Workerのコンストラクタでやっても良さそう。

この使い方なら別でスレッドオブジェクトを作らなくていいくらいしか違いはなさそうだが、runをオーバーライドする場合は違いがある。

class Worker : public QThread
{
/* 略 */
void run() override;
};

この場合はthr->start();するだけでrunが実行され、関数終了でsignal finishedが発行される。よく見る例で便利そうに見えるが、run以外の処理を実行するにはQEventloopを使わないといけない。関数一つだけ別スレッドで動かすやり方のようだ。関数一つだけのためにクラスを作らないといけない。こういう用途だとQtConcurrent使ったほうが簡単じゃないのか。個人的にはあんまり実用的じゃない気がする。

QThreadでタイマーを使うとき

さて、このQThread上でタイマーを使おうとしたとき、 「Timers cannot be stopped from another thread」と出てきて、なんでだよこのクソ野郎が!と思うことが稀によくある。

以下のような場合だ。QtのタイマーはQObjectに備え付けのstartTimer/killTimerとQTimerのstart/stopを使ってみている。

class Worker : public QThread
{
    Q_OBJECT
    QTimer mtimer;
    int m_id;
    void timerEvent(QTimerEvent *event) override;
public slots:
    void exec();
/* 略 */
};

Worker::Worker(QObject *parent)
    : QThread{parent}
{
    m_id = startTimer(200);
    connect(&mtimer,&QTimer::timeout,this,&Worker::exec);
    mtimer.start(200);
}

Worker::~Worker()
{
    mtimer.stop();
    killTimer(m_id);
}

MainWindowから以下のように使う

    auto obj = new Worker();
    obj->moveToThread(obj);
    // start thread
    obj->start();
    QThread::msleep(1000);

    // end
    qDebug() << "stop thread";
    obj->quit();
    obj->wait();
    qDebug() << "delete worker";
    delete obj;

deleteするときにmtimerは止められるが、killTimerはエラーになる。 逆にQThread上でタイマーを止めようとするとmtimerは止められず、killTimerは止められる。mtimerはWorkerのスレッドではなく、コンストラクタを動かしたMainWindowのスレッドで動いているということになる。

moveThreadでobjはQThreadに移動されたが、その中のmtimerは移動してくれなかったということだ。 

MainWindowからしかstart/stopしないという使い方の場合はむしろこのままmtimerを使うというのでも良いのかもしれないが、以下のようにmtimer作成時に親を自分に設定すると、moveThreadのときに一緒に子どもであるmtimerも動いてくれる。
そうでなければコンストラクタを動かしたMainWindowが親になってしまう。

Worker::Worker(QObject *parent)
    : QThread{parent},mtimer(this)
{
/* 略 */

これでスレッドの中ではstart/stopができるようになるが、やはりデストラクタでは止められない。むしろkillTimerもmtimer.stopも両方NGになる。

結局はこれらもデストラクタでストップするのではなく、スレッドの中で止めなければならないということだ。停止するslotを作成し、slotで止めてからdeleteすれば問題ない。

    connect(this,&MainWindow::kikktimer,obj,&Worker::killtimer);
    connect(this,&MainWindow::mtimer_stop,obj,&Worker::mtimer_stop);

    // stop timer
    emit mtimer_stop(); // タイマーを止めるslot
    emit killtimer(); // タイマーを止めるslot
    QThread::msleep(1000);

    // end
    qDebug() << "stop thread";
    obj->quit();
    obj->wait();
    qDebug() << "delete worker";
    delete obj;

runをオーバーライドしている場合はイベントループとか使わないと別のスロットを動かせない。何かフラグを立てて、run関数の中で止めるか、finishedシグナルでrun関数が終わったあとに何とかするしかない。 

finishedで後始末 

runをオーバーライドしているとか、面倒くさいからスレッドが終わったら止まれよ。という人向けにfinishedシグナルがあるんだろう。finishedでタイマーを止める。

    connect(obj,&QThread::finished,obj,&Worker::killtimer);
    connect(obj,&QThread::finished,obj,&Worker::mtimer_stop);
    // end
    qDebug() << "stop thread";
    obj->quit();
    obj->wait(); // この辺でタイマーが止まる
    qDebug() << "delete worker";
    delete obj; 

終わったらdeleteもやれよという人にはdeleteLaterがあるが、ちょっと罠がある。 

    connect(obj,&QThread::finished,obj,&Worker::killtimer);
    connect(obj,&QThread::finished,obj,&Worker::mtimer_stop);
    connect(obj,&QThread::finished,obj,&Worker::deleteLater);
    // end
    qDebug() << "stop thread";
    obj->quit();
    // obj->wait(); // これやると死ぬ

obj->quit()した時点でkillTimer/mtimer_stop/deleteがWorkerスレッドのタイミングで始まる。MainWindowスレッドのobj->waitしている間にdeleteまでされるので、obj->wait実行中にobjが消えることになる。finishedでdeleteLaterを呼ぶ場合はquitしたらもうobjには触ってはいけないということだろう。 

一回スタートしたらあとはsignal/slotで自動で消えてくれる。Qtをうまく活用した処理と言えるけど、個人的にはスレッドが終わるのを確認しないと気持ち悪いし、ちゃんと自分でdeleteしないと気持ち悪いんだけどな。 

2026年6月13日土曜日

tailscaleでローカルIPアクセスする

自宅wifiでもtailscale DNSの名前を使おうとして四苦八苦したが、中途半端にできた状態でうまく行ったとは言い難い。

DNSの名前を使うのは諦めて、local IPでアクセスすることにした。

自宅サーバ側 

自宅内のraspiサーバーでtailscaleを起動する。このとき192.168.11.0/24を外からアクセスできるようにアドバタイズする。

tailscale up --advertise-routes=192.168.11.0/24

tailscaleのページでedit routes settingから、アドバタイズしたsubnet routesをチェックして許可?する。

 

exit node

ついでにexit nodeにしても良さそうだが、しなくても良さそう?exit nodeにすれば自宅サーバ経由でインターネットアクセスができる。が、家のネットはあんまり早くないので積極的に使うほどでもない。怪しげなパブリックwifiを使うときとかに使えたら便利そうなので一応設定しておく。

tailscale up --advartise-exit-node --advertise-routes=192.168.11.0/24

exit nodeにする場合もtaiscaleのページから確定する必要がありそう。 

android側

ローカルLANにアクセスしたいだけなら、exit nodeのところからAllow LAN accessをONにすれば良さそう。

exit node経由でネットアクセスしたいなら、exit nodeで上記設定したサーバを使うよう設定する。 

これだけで良かったのか。苦労したのは何だったのか。。 

2026年6月7日日曜日

XR_Animatorで3Dモデルを動かす

以前作ったモデルをモーションキャプチャーで動かしたかった。 

ちょっと調べてみたところXR_Animatorとかいうソフトがあるらしい。electronで作ってあり、windowsでもlinuxでも動くようなのでやってみた。

起動して、vrmファイルをドラッグアンドドロップする。 

 

 一番左のアイコンをダブルクリックする。

できた。簡単。
 

2026年6月3日水曜日

pythonのkeyboardモジュールがroot権限を要求してきて困る

windowsのpythonでkeyboardモジュールを使って作ったプログラムがある。

キー入力「ctr l+g」とかで何かを実行する みたいな処理をするのに使っていたが、最近はほぼlinuxをメインで使ってるのでlinuxで動かしたい。

が、linuxでpythonのkeyboardを使うとroot権限が必要と言われて動かない。それでは困るのだ。

なんでも良いから動けばいいので、とりあえず以下のように回避した。

回避方法

とりあえずvenv環境でpythonを動かしてkeyboardを入れる。 (keyboard version 0.13.5)

python -m venv venv
. venv/bin/activate
pip install keyboard 

このvenvの中のkeyboardをいじって動くようにする。まずは以下の部分でrootを要求しているので、無効にする。

venv/lib/python3.13/site-packages/keyboard/_nixcommon.py

def ensure_root():
    if os.geteuid() != 0:
-        raise ImportError('You must be root to use this library on linux.')
+       pass

次は/dev/input/event*へのアクセスができれば良い。見たところinputグループに入っていればアクセスできる。

usermod -aG input ユーザ名

これでも以下でエラーが出る。

File "/usr/lib/python3.13/subprocess.py", line 577, in run
    raise CalledProcessError(retcode, process.args,
                             output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['dumpkeys', '--keys-only']' returned non-zero exit status 1.

「dumpkeys --keys-only」を使っているようだが、rootじゃないとエラーになる。ディストリによるのかもしれないが、こっちはグループでどうにかなるものじゃなさそう。/dev/consoleをa+rwにしてもいいとは思うが、なんか怖いので以下のようにした。

一旦sudoで動かして結果を取っておく。

sudo dumpkeys --keys-only > keys

そして、dumpkeysで取得すべき出力をcatで代替する。(2箇所有ったと思う)

    synonyms_template = r'^(\S+)\s+for (.+)$'
-    dump = check_output(['dumpkeys', '--keys-only'], universal_newlines=True)
+    dump = check_output(['cat', 'keys'], universal_newlines=True)
    for synonym_str, original_str in re.findall(synonyms_template, dump, re.MULTILINE):

問題なく動いた。 

その後 

ふと公式を見ると、masterブランチでは解決されているっぽい?また車輪の再発明をしてしまったのか?

https://github.com/boppreh/keyboard/commit/7f03a3d567b484ae6c2beebe2fe537be7643f7a9 

よく見ると/dev/consoleがttyグループでアクセスできる前提っぽい。それじゃあ動かねえんだよなあ。一応無駄ではなかったような気がするので良しとする。

2026年5月31日日曜日

godotでandroid向けアプリを作る

godotで遊んでみて、適当に子供向けのプログラムを作った。ただミャクミャクが降ってきて物理演算しているだけだが、4〜5歳児としてはそこそこ暇つぶしにはなるようだ。

外出時にグダグダ言い出したときに与えられるようにandroidで動くようにしたが、godotだと意外と簡単だった。

と言ってもやったのは数ヶ月前なので、すでにかなりの部分を忘れてしまったが思い出した部分を書いておく。

操作

そのままandroidに持っていくと動かせなくて何も面白くない。CanvasLayerを作ってボタンを置いて、Actionを紐付ける。アイコンはgimpで文字書いたpngを作っただけ。これが一番面倒だった。

加速度センサーを使いたかったがうまく行かなくて断念。断念したあとに[プロジェクト設定] ->[入力デバイス]のところに加速度センサーとかを有効にする設定を見つけた。多分これチェックしておけば動いたんじゃなかろうか。まあもう面倒だからまた今度でいいや。 

android-sdk

android-sdkを入れる。javaも必要だった気がするが、自動的に入ったかもしれない。

apt install android-sdk

インストールしたらエディタの設定で指定する。linuxだとaptで入れるだけで便利。

debianでは/usr/lib/android-sdkに入るようだ。javaもdefault-javaのjvmを指定する。(/usr/lib/jvm/default-java)

export-templates

godotのサイトのダウンロードからexport-templatesとやらをダウンロードする。

ダウンロードしたら[エディター] -> [エクスポートテンプレートの管理]からインストールする。

多分これがgodotのメインアプリになって、あとはこの上でgdscriptが動くだけなんだろう。

c#を使っている場合はどうするんだろうと思ったが、export-templatesのc#版がありそうなのでそれで動くんだろうか。

export

[プロジェクト] -> [エクスポート]でエクスポートする。


できたapkファイルをandroidに持っていってインストールする。

実行

動いた。

いくつかandroidに入れてみて大体はなんの問題もなく動いたが、AQUOS sense 10だけ何か動きがおかしかった。弾を撃っても弾が動かない。

FPSの問題っぽい気がしたので、「なめらかハイスピード表示」とやらをgodotのアプリだけOFFにしたら問題が解決した。なめらかハイスピード表示とやらで 120FPSとかになっているのかもしれない。それによって時間の計算がうまく行かず弾が進まない?よくわからないがとりあえずOFFにして解決したので良しとする。

まあそもそもミャクミャクを弾で撃つなと言う話でもある。 

2026年5月29日金曜日

linuxのfirefoxでPasskeyを使いたい

linuxのfirefoxでパスキーを使いたい。現状では何か物理キーを繋げてないと動かなそうな感じ。なんとかしたいと思って調べたところ、keepass-xcがpasskeyに対応しているらしい。  

とりあえずやってみる。

拡張機能を入れる

KeePassXC-Browserをインストール 

拡張機能の設定で「パスキーを有効にする」 をやっておく。

keepass-xcインストール

なんとなくfullで入れておく。

apt install keepassxc-full

入れたらデータベースを作って動かす。

設定で「ブラウザー統合を有効にする」 とし、KeePassXC-Browserと接続する。

テスト

とりあえずパスキーを使っているサイトでパスキーを作ってみると、KeePassXCに登録できたっぽい。中銀証券ではログインもできた。

楽天証券はすでに作成済みと言って作らせてもらえなかったが、一回消したら登録できた。ログインも問題なし。だが、今度はlinuxのfirefoxからしかログインできなくなってしまう。

googleのパスワードマネージャーで登録しておいて、そこからエクスポートできれば良いんだが。まだできない?ようだ。 

とりあえずパスキーは使えるようになったが、本命の楽天証券が一箇所からしかログインできなくて不便だ。なんで一つしか登録できねーんだよアホか。 

と思ったら一旦電話認証で一時的にパスワード認証に変更してログインして パスキー追加できた。解決。アホとか言ってすいませんでした。

他の方法

firefoxでやろうとすると結構大変だが、 他のやり方もありそう。

chromeを使う 

chromeだとlinuxでもパスキーが使える。何も難しいことはない。androidともパスキーの同期が取れる。

が、chromeは使いたくない。スマホで使うときに広告ブロックできないからだ。ブックマークをスマホとPCで同期したいので必然的にPCでもfirefoxを使っている。

物理的なキーを使う

Yubikey?とやらを買えばできそうな気はするが、何か1万円くらいしそう。chrome使えばいいだけのものに1万円は払いたくない。

拡張機能? 

もう少しどうにかならんかと思って探していたら、そういうfirefox拡張機能(linux passkey manager?)がありそう?

が、どう考えてもユーザ数が少ないし、レビュー0件。
これを脳死で使うのはさすがに恐ろしすぎる。


2026年5月23日土曜日

428クリア

428の続編を作るプロジェクトが始まったようだ。楽しみではあるが何か内輪の同窓会みたいにならないか心配でもある。フレッシュな人も居てほしいなあ。金八先生のゲームも面白かったので期待はしたい。 

とりあえず久しぶりに思い出したので、結構前に買ったsteamで428のトロフィーコンプリートした。

昔は428のためだけにwiiを買ったのだが、今はsteamでもできて非常にありがたい。街も出してほしいが何か難しいんだろうか?PS版もあったような気がするんだけど。 


 前に買ったときはwindowsで遊んでいたが、今はlinux上のsteamでも問題なく動く。昔のwineからは考えられないくらい進化しててすごい。下手したら今のwindowsで動かなくなった昔のソフトとかも動かせるんじゃないだろうか。

一つ不具合っぽいものとしては安物bluetoothコントローラーの接続が切れたら一回立ち上げ直さないとジョイスティック操作できないくらい。(元からなのかwineのせいなのかはわからない)