2015年12月13日日曜日

QEventLoopでイベントループを自在に操る

この記事は Qt Advent Calender 2015 の14日の記事です。
QEventLoopクラスを使って非同期処理を同期的に扱う方法を紹介したいと思います。

QEventLoop はQtのイベントループを扱うクラスです。イベントループについては2日目の記事で分かりやすく解説されているので、詳しくない方はまずはこちらを参考にして下さい。

QEventLoop はQApplication::execやQDialog::execなどexecというメソッドを持つクラスの中で使われており、その中でQEventLoop::execが呼ばれて実際にイベントループが実行されます。QEventLoopはpublicなクラスなのでユーザのコードからも呼ぶことができ、これを使うことで非同期処理を同期な方法で呼ぶことができるようになります。実際に例を見てみましょう。

まずはQEventLoopを使わずにシグナルとスロットを用いて非同期処理をシーケンシャルに行うサンプルです。

#include <QApplication>
#include <QDebug>
#include <QObject>
#include <QThread>

class SleepThread : public QThread {
 protected:
  void run() {
    qDebug() << "doing heavy task";
    sleep(1);
  }
};

int main(int argv, char** args) {
  QApplication app(argv, args);

  SleepThread* thread1 = new SleepThread();
  QObject::connect(thread1, &QThread::finished, [&] {
    SleepThread* thread2 = new SleepThread();
    QObject::connect(thread2, &QThread::finished, [&] {
      SleepThread* thread3 = new SleepThread();
      QObject::connect(thread3, &QThread::finished, [&] {
        qDebug() << "finished all tasks!";
        app.exit();
      });
      thread3->start();
    });
    thread2->start();
  });
  thread1->start();
  app.exec();
}

以下の様な出力になります。’doing heavy task’は1秒毎に出力されます。

doing heavy task
doing heavy task
doing heavy task
finished all tasks!

SleepThreadはrunの中で何か重い処理をしていると仮定して、ここでは単に1秒スリープしています。SleepThreadのfinishedシグナルにC++11のラムダ関数をconnectしてスレッドの処理が終了したらラムダ関数が実行され、次のSleepThreadを生成・実行しています。3つ全ての非同期処理が終わったら”finished all tasks!”を表示して終了します。
ご覧のとおりネストが深くなって少々読みづらいですよね。これをQEventLoopを使って同期的に書き直してみます。

#include <QApplication>
#include <QDebug>
#include <QObject>
#include <QThread>
#include <QEventLoop>

class SleepThread : public QThread {
 protected:
  void run() {
    qDebug() << "doing heavy task";
    sleep(1);
  }
};

int main(int argv, char** args) {
  QApplication app(argv, args);

  SleepThread* thread1 = new SleepThread();
  QEventLoop loop1;
  QObject::connect(thread1, &QThread::finished, &loop1, &QEventLoop::quit);
  thread1->start();
  loop1.exec();

  SleepThread* thread2 = new SleepThread();
  QEventLoop loop2;
  QObject::connect(thread2, &QThread::finished, &loop2, &QEventLoop::quit);
  thread2->start();
  loop2.exec();

  SleepThread* thread3 = new SleepThread();
  QEventLoop loop3;
  QObject::connect(thread3, &QThread::finished, &loop3, &QEventLoop::quit);
  thread3->start();
  loop3.exec();

  qDebug() << "finished all tasks!";
}

出力はもちろんさっきと同じです。
QThead::finishedシグナルをQEventLoop::quitスロットにconnectして、スレッドが終了したらイベントループも終了するようにしています。その後QEventLoop::execを実行してイベントループに入ります。こうすることで非同期処理を行っているにも関わらず同期的に記述でき、プログラムもすっきりしました。さらにこの処理を関数として抽出すればもっと読みやすくできそうです。
QEventLoop::execを実行した後は見かけ上そこで処理が止まったようになりますが、実際はイベントループが回っているのでマウスやキーボードのイベントもその中で通常通り処理され、フリーズしたりはしません。

ネストしたイベントループ

このようにQEventLoopを使うことでいつでもイベントループを実行できることを確認しました。次はイベントループを実行した中でさらにイベントループを実行する例を見てみます。

main.cpp

#include <QApplication>
#include <QDebug>
#include <QObject>
#include "SleepThread.h"

int main(int argv, char** args) {
  QApplication app(argv, args);

  SleepThread* thread = new SleepThread();
  QObject::connect(thread, &QThread::finished, &app, &QApplication::quit);
  thread->moveToThread(thread);
  thread->start();
  QMetaObject::invokeMethod(thread, "sleep", Q_ARG(unsigned long, 3000));
  QMetaObject::invokeMethod(thread, "sleep", Q_ARG(unsigned long, 5000));
  app.exec();
}

main関数はSleepThread(先ほどのSleepThreadとは違います)を作成し、finishedシグナルにQApplication::quitをconnectしています。その後thread->moveToThread(thread) でthreadオブジェクトのthread affinityをSleepThreadに移します。
その後thread->start()でスレッドのイベントループを実行します。(今回QThread::runをオーバーライドしていないので、デフォルト動作のQThread::execが実行されます。)
QMetaObject::invokeMethodでSleepThreadのsleepメソッドを非同期で呼び出します。先ほどthread->moveToThread(thread) でthreadオブジェクトのthread affnityをSleepThreadに移したので、invokeMethodを呼び出した時にQt::QueuedConnectionが使われます。invokeMethodの詳細はQtのドキュメントを確認して下さい。
http://doc.qt.io/qt-5/qmetaobject.html#invokeMethod

invokeMethodでSleepThreadのsleepメソッドを2回非同期で呼び出した後app.exec()でQApplicationのイベントループに入ります。
次はSleepThreadの中身を見てみます。

SleepThread.h

#pragma once

#include <QThread>
#include <QEventLoop>
#include <QTimer>

class SleepThread : public QThread {
  Q_OBJECT
 public slots:
  void sleep(unsigned long msec) {
    QEventLoop loop;
    qDebug() << "start timer with" << msec << "[msec]";
    QTimer::singleShot(msec, &loop, [&loop, msec]{
      qDebug() << "finished waiting" << msec << "[msec]";
      loop.quit();
    });
    loop.exec();
    qDebug() << "event loop of" << msec << "[msec] finished";
    exit();
  }
};

sleepメソッドはQEventLoopでイベントループを実行しています。QTimer::singleShotを使ってmsecミリ秒後にイベントループを終了しています。今回なぜsleep(msec)としなかったかというと、sleep(msec)は現在のスレッドをブロックしてしまうため、SleepThreadで回っているイベントループを止めてしまうためです。
イベントループ終了後QThread::exitを呼び出してスレッドを終了します。

出力は以下のようになります。

start timer with 3000 [msec]
start timer with 5000 [msec]
finished waiting 3000 [msec]
finished waiting 5000 [msec]
event loop of 5000 [msec] finished
event loop of 3000 [msec] finished

イベントループはスレッドごとに存在していて、thread->start()でSleepThreadのイベントループが始まります。
次にQMetaObject::invokeMethod(thread, "sleep", Q_ARG(unsigned long, 3000)); を呼び出すとQt::QueuedConnectionの時に内部ではQMetaCallEventというイベントがSleepThreadのイベントループで発生し、sleep(3000) の形でメソッドが呼ばれます。sleepメソッドの中ではさらにQEventLoop::execでイベントループを実行しているため、イベントループがネストした形になります。
その後QMetaObject::invokeMethod(thread, "sleep", Q_ARG(unsigned long, 5000)); でsleep(5000)が実行され、さらに内部でイベントループが実行されます。

QThread::exec()で作られた親イベントループ
  sleep(3000)の中で作られた子イベントループ
    sleep(5000)の中で作られた孫イベントループ

このように3つイベントループがネストしていますが、実際にイベントを処理するのは常に一番深いイベントループで、他のイベントループは止まった状態になっています。この例ではsleep(3000)で作られた子イベントループの方がタイムアウト時間が短いので先にloop.quit(); が呼ばれますが、QEventLoop::quitを呼んでもこの子イベントループは止まっているので実際には即座には終了しません。その2秒後にsleep(5000)のQTimerのタイムアウトが発生しloop.quit(); が呼ばれ、sleep(5000)で作られた孫イベントループが終了します。
その後sleep(3000)の子イベントループが再開しますが、すでに孫イベントループの中でloop.quit()が呼ばれているので即座に終了します。
そしてようやく親イベントループが再開しますが、こちらもすでにsleepメソッドの中でQThread::exitが呼ばれているので、再開しても即座に終了します。

シグナル・スロットを用いた非同期処理とQEventLoopは相性がいいので、色々な場面で役立つこと間違いなしです!
明日はasobotさんのQtCreatorとqmakeについてです。

参考

2015年11月17日火曜日

アロー関数はコンストラクタとして使えない

util.inheritsでアロー関数を渡すとエラーになるので調査していたところ、アロー関数はコンストラクタとして使えないことが分かった。
原因はアロー関数のprototypeがundefinedになるため。

再現コード

'use strict';
var util = require('util');
var EventEmitter = require('events');

function InputDialog() { }

console.log('InputDialog prototype: ' + InputDialog.prototype)
util.inherits(InputDialog, EventEmitter);

const InputDialog2 = () => { }

console.log('InputDialog2 prototype: ' + InputDialog2.prototype)
util.inherits(InputDialog2, EventEmitter);

出力

InputDialog prototype: [object Object]
InputDialog2 prototype: undefined
util.js:764
  Object.setPrototypeOf(ctor.prototype, superCtor.prototype);
         ^

TypeError: Object.setPrototypeOf called on null or undefined
    at Function.setPrototypeOf (native)
    at Object.exports.inherits (util.js:764:10)
    at Object.<anonymous> (/Users/shinichi/code/silkedit/test.js:13:6)
    at Module._compile (module.js:425:26)
    at Object.Module._extensions..js (module.js:432:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:311:12)
    at Function.Module.runMain (module.js:457:10)
    at startup (node.js:136:18)
    at node.js:972:3

util.inheritsのコード
https://github.com/nodejs/node/blob/v5.1.0/lib/util.js

exports.inherits = function(ctor, superCtor) {

  if (ctor === undefined || ctor === null)
    throw new TypeError('The constructor to `inherits` must not be ' +
                        'null or undefined.');

  if (superCtor === undefined || superCtor === null)
    throw new TypeError('The super constructor to `inherits` must not ' +
                        'be null or undefined.');

  if (superCtor.prototype === undefined)
    throw new TypeError('The super constructor to `inherits` must ' +
                        'have a prototype.');

  ctor.super_ = superCtor;
  Object.setPrototypeOf(ctor.prototype, superCtor.prototype);
};

参考

Arrow functions versus normal functions
An arrow function is different from a normal function in only three ways: First, it always has a bound this. Second, it can’t be used as a constructor: There is no internal method [[Construct]] (that allows a normal function to be invoked via new) and no property prototype. Therefore, new (() => {}) throws an error. Third, as arrow functions are an ECMAScript.next-only construct, they can rely on new-style argument handling (parameter default values, rest parameters, etc.) and don’t support the special variable arguments. Nor do they have to, because the new mechanisms can do everything that arguments can.

Node.jsのバージョン間での挙動の違い

ちなみにv0.12.0はアロー関数をコンストラクタとしてutil.inheritsに渡しても動作する。
v5.1.0ではダメ。

2015年3月27日金曜日

QLocalSocket+QEventLoopを組み合わせた時のreadyReadシグナルの仕様

QLocalSocketとQEventLoopを組み合わせると、非同期なネットワーク呼び出しを同期的に記述できます。(もちろんUIスレッドはブロックしません)。

詳細はここの”Forcing event dispatching”という箇所を参考にして下さい。
http://qt-project.org/wiki/Threads_Events_QObjects

基本的な流れとしてはQLocalSocketにデータを何かwriteした後ローカルのQEventLoopを呼び出し(exec()というメソッド)、readyReadシグナルが届いたらイベントループを終了(quit()メソッド)させるという流れです。

しかしここで大きな落とし穴があり、readyReadシグナルが呼ばれた中で再びイベントループを呼び出すと、readyReadシグナルはそのイベントループでは呼び出されません。Qtのドキュメントにもそのように書かれています。(僕はこの仕様でかなりはまりました^^;)

http://doc.qt.io/qt-5/qiodevice.html#readyRead

readyRead() is not emitted recursively; if you reenter the event loop or call waitForReadyRead() inside a slot connected to the readyRead() signal, the signal will not be reemitted (although waitForReadyRead() may still return true).

実際のソースコードの該当箇所を見てみると以下のようになっています。

qabstractsocket.cpp

// only emit readyRead() when not recursing, and only if there is data available
bool hasData = newBytes > 0
#ifndef QT_NO_UDPSOCKET
    || (!isBuffered && socketType != QAbstractSocket::TcpSocket && socketEngine && socketEngine->hasPendingDatagrams())
#endif
    || (!isBuffered && socketType == QAbstractSocket::TcpSocket && socketEngine)
    ;

if (!emittedReadyRead && hasData) {
    QScopedValueRollback<bool> r(emittedReadyRead);
    emittedReadyRead = true;
    emit q->readyRead();
}

readyReadシグナルにDirectConnectionを使ってconnectされたスロットが呼び出され(この時emittedReadyRead == trueとなる)、その中でイベントループが作られ再度上記の箇所に到達した場合、emittedReadyReadはすでにtrueなので再度emitされないという仕組みです。イベントループが終了するとQScopedValueRollbackによりemittedReadyReadの値がfalseに戻され、再びemitされるようになります。

この仕様を実験するため以下のコードを書きました。
QLocalServer、QLocalSocketを使ってサーバ、クライアント間でUnix Domain Socketを介してデータを相互に送受信しています。
動作確認した環境はMac (Mavericks) , Qt 5.4です。
readyReadシグナルにconnectされたonReadyReadスロットの中でQEvnetLoopを作成、実行しています。

サーバ側のコード

Server.h

#pragma once

#include <QLocalServer>
#include <QLocalSocket>
#include <QFile>
#include <QEventLoop>
#include <QObject>

class Result : public QObject {
  Q_OBJECT

  int m_result;

 public:
  int result() { return m_result; }
  void setResult(int result) {
    m_result = result;
    emit ready();
  }

signals:
  void ready();
};

class Server : public QObject {
  Q_OBJECT

  Result m_result1;
  Result m_result3;
  QLocalSocket* m_sock;

 public:
  void start() {
    QString sockPath = "/tmp/qeventloop_test";
    QLocalServer* server = new QLocalServer();
    QFile sockFile(sockPath);
    if (sockFile.exists()) {
      sockFile.remove();
    }
    server->listen(sockPath);
    connect(server, &QLocalServer::newConnection, [this, server]() {
      m_sock = server->nextPendingConnection();
      connect(m_sock, &QLocalSocket::disconnected, m_sock, &QLocalSocket::deleteLater);
      QObject::connect(
          m_sock, &QLocalSocket::readyRead, this, &Server::onReadyRead, Qt::QueuedConnection);

      sendData(m_sock, 1);

      QEventLoop loop;
      connect(&m_result1, &Result::ready, &loop, &QEventLoop::quit);
      qDebug("start event loop to wait for 1");
      loop.exec();
      qDebug("end event loop to wait for 1");
    });
  }

  void onReadyRead() {
    qDebug("bytesAvailable: %lld", m_sock->bytesAvailable());
    qint64 bytesAvailable = m_sock->bytesAvailable();
    QByteArray buffer = m_sock->readAll();
    QDataStream ds(buffer);
    while (bytesAvailable > 0) {
      int num;
      ds >> num;
      qDebug("received %d", num);
      bytesAvailable -= 4;
      if (num == 2) {
        sendData(m_sock, 3);

        QEventLoop loop;
        QObject::connect(&m_result3, &Result::ready, &loop, &QEventLoop::quit);
        qDebug("start event loop to wait for 3");
        loop.exec();
        qDebug("end event loop to wait for 3");

      } else if (num == -1) {
        m_result1.setResult(num);
      } else if (num == -3) {
        m_result3.setResult(num);
      }
    }
  }

  void sendData(QLocalSocket* sock, int num) {
    qDebug("send %d", num);
    QByteArray block;
    QDataStream ds(&block, QIODevice::WriteOnly);
    ds << num;
    sock->write(block);
  }
};

main.cpp

#include <QApplication>
#include <QDebug>
#include <QObject>
#include "Server.h"

int main(int argv, char** args) {
  QApplication app(argv, args);

  Server server;
  server.start();

  return app.exec();
}

クライアント側のコード

main.cpp

#include <QDebug>
#include <QLocalSocket>
#include <QApplication>

void sendData(QLocalSocket& sock, int num) {
  qDebug("send %d", num);
  QByteArray block;
  QDataStream ds(&block, QIODevice::WriteOnly);
  ds << num;
  sock.write(block);
}

int main(int argv, char** args) {
  QApplication app(argv, args);

  QLocalSocket sock;
  QObject::connect(&sock, &QLocalSocket::readyRead, [&sock]() {
    qint64 bytesAvailable = sock.bytesAvailable();
    QByteArray buffer = sock.readAll();
    QDataStream ds(buffer);
    while (bytesAvailable > 0) {
      int num;
      ds >> num;

      qDebug("received %d", num);
      bytesAvailable -= 4;

      if (num == 1) {
        sendData(sock, 2);
        sendData(sock, -1);
      } else if (num == 3) {
        sendData(sock, -3);
      }
    }
  });

  sock.connectToServer("/tmp/qeventloop_test");
  return app.exec();
}

実行結果は以下のようになります。

send 1
start event loop to wait for 1
bytesAvailable: 8
received 2
send 3
start event loop to wait for 3
bytesAvailable: 4
received -3
end event loop to wait for 3
received -1
end event loop to wait for 1

次にサーバ側プログラムの以下のQueuedConnectionとなっている箇所を

QObject::connect(m_sock, &QLocalSocket::readyRead, this, &Server::onReadyRead, Qt::QueuedConnection);

以下のようにDirectConnection(デフォルトではsenderとreceiverが同一スレッドであればDirectConnectionとなります)を使うように変更してみます。

      QObject::connect(
          m_sock, &QLocalSocket::readyRead, this, &Server::onReadyRead);

実行結果は以下のようになります。クライアントが-3を送信してもサーバのQLocalSocketはreadyReadシグナルが呼び出されないので、以下のように永遠にイベントループが終了しません。

send 1
start event loop to wait for 1
bytesAvailable: 8
received 2
send 3
start event loop to wait for 3

QLocalSocket, QEventLoopを使う時はreadyReadシグナルの仕様に気をつけましょう!

2015年2月7日土曜日

日本からBank of Americaの口座を閉じる

昨年10月にアメリカから帰国したんですが、まだ一部ペンディングのトランザクションがあって帰国前にアメリカの口座を閉じれず、帰国後に閉じたんですがすごく時間がかかって大変でした。最初に電話で問い合わせてから小切手を受け取るまでに3ヶ月半かかりました。以下は簡単な流れです。今後同様のことを行う方の参考になれば幸いです。

10月頭

帰国

10月中旬

電話でCheckingとSavingsアカウントを閉じたいと伝える。残高は小切手(Cashier’s check)を送るから日本の住所を教えてくれと言われ教える。小切手は2週間くらいで届くとのこと。

11月上旬

2週間たっても小切手が届かないので再度電話で問い合わせ。前回の電話で閉じれたのだと思っていたらどうやらCheckingアカウントは残高がかなりあるため電話では受け付けできず、口座を閉じたいという旨の手紙を送れと言われる。

12月中旬

1か月後ようやく返信が来て小切手が届いたのかと思いきや、口座を閉じるなら所定のフォームに公証人のサインをして返信しろという旨の封書が届く。電話で問い合わせたところ日本の公証人でOKらしかったので、すぐに公証人にサインをもらって返信。

1月中旬

ようやく小切手が届いたので、UFJに外貨預金口座を開設して小切手の取り立てを依頼。入金までに3週間程かかるとのこと。

2月上旬

きっちり3週間後外貨預金口座に入金される。

入金まで約3ヶ月半かかりました。良い子のみんなは帰国前に口座を閉じるか、当面の生活費をワイヤートランスファーなどで帰国前に送金しておきましょう。。

2015年1月27日火曜日

Artistic License 2.0のソフトを自作アプリと一緒に配布するための条件

npmを現在開発中のエディタに埋め込んで一緒にバイナリを配布したいので、npmが使用しているArtistic License 2.0について調べてみました。
主にPerlで使われているようです。
http://ja.wikipedia.org/wiki/Artistic_License

原文
http://www.perlfoundation.org/artistic_license_2_0

埋め込む時のポイントは原文の以下の(7)と(8)です。

Aggregating or Linking the Package

(7) You may aggregate the Package (either the Standard Version or Modified Version) with other packages and Distribute the resulting aggregation provided that you do not charge a licensing fee for the Package. Distributor Fees are permitted, and licensing fees for other components in the aggregation are permitted. The terms of this license apply to the use and Distribution of the Standard or Modified Versions as included in the aggregation.

(8) You are permitted to link Modified and Standard Versions with other works, to embed the Package in a larger work of your own, or to build stand-alone binary or bytecode versions of applications that include the Package, and Distribute the result without restriction, provided the result does not expose a direct interface to the Package.

Aggregateする場合は商用ソフトにも含めることができ(ただしnpmはArtistic Licenseのまま)、Linkする場合はnpmへのdirect interfaceを提供しなければ商用ソフトにもリンクして配布できそうです。

direct interfaceとは何でしょう?Artistic Licenseの補足のSection 8に説明があります。

http://www.perlfoundation.org/artistic_2_0_notes

Section 8:

You’re totally free to embed Perl inside your software in a way that doesn’t allow users to access Perl. On the other hand, if you embed it and users can nevertheless do things like running Perl scripts, then you really are distributing Perl and need make sure that your distribution fits one of the use cases in (1)-(7).

ユーザが直接npmのプログラムを実行できたらそれはdirect interfaceと見なされるみたいです。

AggregateとLinkの違いは何でしょう?
GNU GPLのFAQにaggregateの説明があります。

http://www.gnu.org/licenses/gpl-faq.html#MereAggregation

What is the difference between an “aggregate” and other kinds of “modified versions”?

An “aggregate” consists of a number of separate programs, distributed together on the same CD-ROM or other media. The GPL permits you to create and distribute an aggregate, even when the licenses of the other software are non-free or GPL-incompatible. The only condition is that you cannot release the aggregate under a license that prohibits users from exercising rights that each program’s individual license would grant them.

Where’s the line between two separate programs, and one program with two parts? This is a legal question, which ultimately judges will decide. We believe that a proper criterion depends both on the mechanism of communication (exec, pipes, rpc, function calls within a shared address space, etc.) and the semantics of the communication (what kinds of information are interchanged).

If the modules are included in the same executable file, they are definitely combined in one program. If modules are designed to run linked together in a shared address space, that almost surely means combining them into one program.

By contrast, pipes, sockets and command-line arguments are communication mechanisms normally used between two separate programs. So when they are used for communication, the modules normally are separate programs. But if the semantics of the communication are intimate enough, exchanging complex internal data structures, that too could be a basis to consider the two parts as combined into a larger program.

直接npmを静的or動的リンクせず、パイプやソケットを使ってプロセス間通信を行えばaggregateと見なされるみたいです。

結論

つまりnpmとリンクさせずプロセス間通信を使えばnpmを商用エディタに含めて配布することが可能みたいです。その際npmのバイナリだけでなくライセンスファイルも一緒に含めないといけません。

2014年10月7日火曜日

Macで英語配列のRealforceを使う

環境

  • Mac OS X 10.8.5 (Mountain Lion)
  • Realforce 86u

RealforceはWindows用キーボードなので、デフォルトではWinキーはCommand、AltキーはOptionに割り当てられたり等、Mac用のキーボードとかなりレイアウトが異なります。ここではKarabiner(旧KeyRemap4MacBook)、Seil(旧PCKeyboardHack)を使ってMac用キーボードのレイアウトに近づける方法を紹介します。

まずは以下2つのソフトをインストールして下さい。
- Karabiner
https://pqrs.org/osx/karabiner/index.html.ja
- Seil
https://pqrs.org/osx/karabiner/seil.html.ja

CtrlとCaps Lockの入れ替え

この変更は鉄板ですね。色々方法はありますが、僕はRealforceのDip Switch 1を使用しました(スイッチをONにすると入れ替え)。USBを一旦抜かないと有効にならないので注意。

Option、Commandキーの入れ替え

System Preferencesのキーボード設定でモディファイアキーを以下のように設定します。キーボード選択ボックスでRealforceを選択してから設定して下さい。Caps LockをNo Actionにするのは後でSeilを使ってCaps LockにFnキーを割り当てるためです。

  • Caps Lock -> No Action
  • Option -> Command
  • Command -> Option

Caps LockをFnに

SeilでCaps Lockに未使用のキー(今回はF19を使用)を割り当てます。(Fnキーは63となっていますが、63ではFnキーとして動作しません)

Karabinerのprivate.xmlを編集してF19をFnキーに割り当てます。private.xmlの読み込み方についてはhttps://pqrs.org/osx/karabiner/document.html.jaを参照して下さい。

<item>
  <name>F19 Key to Fn (Change CapsLock to F19(80) in Seil)</name>
  <identifier>remap.f192fn</identifier>
  <autogen>__KeyToKey__ KeyCode::F19, KeyCode::FN</autogen>
</item>

Fn+F1,F2…F12の特殊キーを有効

ディスプレイをスリープ(Ctrl+Shift+Eject)のショートカットを設定

この設定はBetterTouchToolを使って行います。
http://blog.boastr.net/downloads-secondbar-bettertouchtool-2/

ApplicationキーをOptionに割り当て

Karabinerのprivate.xmlに以下を追加。Realforceのキーボードのみで有効になるようにしています。

  <devicevendordef>
    <vendorname>TopreCorporation</vendorname>
    <vendorid>0x853</vendorid>
  </devicevendordef>

  <item>
    <name>App to Option</name>
    <identifier>private.deviceproductdef.topre.app_to_option</identifier>
    <device_only>DeviceVendor::TopreCorporation</device_only>
    <autogen>--KeyToKey-- KeyCode::PC_APPLICATION, KeyCode::OPTION_R</autogen>
  </item>

Home/Endキーの設定

private.xmlに以下を追加。

  <item>
    <name>Home/End</name>
    <identifier>private.deviceproductdef.topre.home_end</identifier>
    <device_only>DeviceVendor::TopreCorporation</device_only>
    <autogen>--KeyToKey-- KeyCode::HOME, KeyCode::CURSOR_LEFT, VK_COMMAND</autogen>
    <autogen>--KeyToKey-- KeyCode::END, KeyCode::CURSOR_RIGHT, VK_COMMAND</autogen>
  </item>

かな・英数の切り替え

Macでの日本語入力モードの切り替え方法は悩ましいですが、僕はワンショットモディファイアを使用しています。Shiftだけを押すとかな、Altだけを押すと英数に切り替わるようになっています。

<item>
  <name>When type Command_L only, send JIS_EISUU(except in Virtual Machine, RDC)</name>
  <identifier>private.cmdL_only_to_eisuu</identifier>
  <not>VIRTUALMACHINE, REMOTEDESKTOPCONNECTION</not>
  <autogen>--KeyOverlaidModifier--
    KeyCode::COMMAND_L, ModifierFlag::COMMAND_L | ModifierFlag::NONE,
    KeyCode::COMMAND_L, KeyCode::JIS_EISUU
  </autogen>
</item>

<item>
  <name>When type Shift_L only, send JIS_KANA(except in Virtual Machine, RDC)</name>
  <identifier>private.shiftL_only_to_kana</identifier>
  <not>VIRTUALMACHINE, REMOTEDESKTOPCONNECTION</not>
  <autogen>--KeyOverlaidModifier--
    KeyCode::SHIFT_L, ModifierFlag::SHIFT_L | ModifierFlag::NONE,
    KeyCode::SHIFT_L, KeyCode::JIS_KANA
  </autogen>
</item>

private.xmlのまとめ

ここまで紹介したprivate.xmlをまとめたものを以下に貼ります。

  <!-- Devices -->
  <devicevendordef>
    <vendorname>TopreCorporation</vendorname>
    <vendorid>0x853</vendorid>
  </devicevendordef>

  <item>
    <name>Topre Realforce</name>

    <item>
      <name>F19 Key to Fn (Change CapsLock to F19(80) in Seil)</name>
      <identifier>remap.f192fn</identifier>
      <autogen>__KeyToKey__ KeyCode::F19, KeyCode::FN</autogen>
    </item>

    <item>
      <name>App to Option</name>
      <identifier>private.deviceproductdef.topre.app_to_option</identifier>
      <device_only>DeviceVendor::TopreCorporation</device_only>
      <autogen>--KeyToKey-- KeyCode::PC_APPLICATION, KeyCode::OPTION_R</autogen>
    </item>

    <item>
      <name>Home/End</name>
      <identifier>private.deviceproductdef.topre.home_end</identifier>
      <device_only>DeviceVendor::TopreCorporation</device_only>
      <autogen>--KeyToKey-- KeyCode::HOME, KeyCode::CURSOR_LEFT, VK_COMMAND</autogen>
      <autogen>--KeyToKey-- KeyCode::END, KeyCode::CURSOR_RIGHT, VK_COMMAND</autogen>
    </item>

    <item>
      <name>When type Command_L only, send JIS_EISUU(except in Virtual Machine, RDC)</name>
      <identifier>private.cmdL_only_to_eisuu</identifier>
      <not>VIRTUALMACHINE, REMOTEDESKTOPCONNECTION</not>
      <autogen>--KeyOverlaidModifier--
        KeyCode::COMMAND_L, ModifierFlag::COMMAND_L | ModifierFlag::NONE,
        KeyCode::COMMAND_L, KeyCode::JIS_EISUU
      </autogen>
    </item>

    <item>
      <name>When type Shift_L only, send JIS_KANA(except in Virtual Machine, RDC)</name>
      <identifier>private.shiftL_only_to_kana</identifier>
      <not>VIRTUALMACHINE, REMOTEDESKTOPCONNECTION</not>
      <autogen>--KeyOverlaidModifier--
        KeyCode::SHIFT_L, ModifierFlag::SHIFT_L | ModifierFlag::NONE,
        KeyCode::SHIFT_L, KeyCode::JIS_KANA
      </autogen>
    </item>
  </item>

2014年9月8日月曜日

Qt CreatorでCMake+Ninjaを使う (Mac)

環境

  • Mac OS X Mountain Lion
  • Qt 5.3
  • cmake 3.0.1

qmakeからCMakeベースのプロジェクトに移行

まずはCMake, NinjaをHomebrewからインストール

    brew install cmake ninja

qmakeベースのプロジェクトからCMakeベースのプロジェクトに移行するため、下記のようなCMakeLists.txtを作成。

cmake_minimum_required(VERSION 2.8.11)

project(hello)

# Find includes in corresponding build directories
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# Instruct CMake to run moc automatically when needed.
set(CMAKE_AUTOMOC ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

set(hello_SOURCES main.cpp)

# Find the QtWidgets library
find_package(Qt5Widgets REQUIRED)

# Tell CMake to create the helloworld executable
add_executable(hello MACOSX_BUNDLE ${hello_SOURCES})

# Use the Widgets module from Qt 5.
target_link_libraries(hello Qt5::Widgets)

ちなみに下記のサイトを参考にした。
http://www.executionunit.com/blog/2014/01/22/moving-from-qmake-to-cmake/

コマンドラインでビルドしてみる

% cmake CMakeLists.txt -G Ninja
% ninja

エラーがなければhello.appが作成される。

Qt CreatorでCMakeのプロジェクトとしてオープンする

  1. Qt Creatorの設定でCMakeのパスを設定
  2. “Prefer Ninja generator”にチェックを入れる
  3. “File” -> “Open File or Project”でCMakeLists.txtを指定する
  4. 色々聞かれた後”Run CMake”をする時にGeneratorで”Ninja Generator”を選択して”Run CMake”をクリック
  5. 以下のようなエラーが出た場合、Qt CreatorがNinjaを認識していないので、代わりに”Unix Generator”を選択して”Run CMake”をする
  6. 上記ステップでエラーが出た場合、Projectの”Build Environment”のPATHにninjaへのパスが通っているか確認
  7. ビルドディレクトリ(xxx-build)を削除
  8. 再度”File” -> “Open File or Project”でCMakeLists.txtを指定し、”Run CMake”をする時にGeneratorで”Ninja Generator”を選択して”Run CMake”をクリックすると今度は成功する
CMake Error: Error required internal CMake variable not set, cmake may be not be built correctly.
Missing variable is:
CMAKE_C_COMPILER_ENV_VAR
CMake Error: Error required internal CMake variable not set, cmake may be not be built correctly.
Missing variable is:
CMAKE_C_COMPILER
CMake Error: Could not find cmake module file: /Users/shinichi/Code/untitled-build/CMakeFiles/3.0.1/CMakeCCompiler.cmake
CMake Error: Error required internal CMake variable not set, cmake may be not be built correctly.
Missing variable is:
CMAKE_CXX_COMPILER_ENV_VAR
CMake Error: Error required internal CMake variable not set, cmake may be not be built correctly.
Missing variable is:
CMAKE_CXX_COMPILER
CMake Error: Could not find cmake module file: /Users/shinichi/Code/untitled-build/CMakeFiles/3.0.1/CMakeCXXCompiler.cmake
CMake Error at CMakeLists.txt:1 (project):
  No CMAKE_C_COMPILER could be found.

  Tell CMake where to find the compiler by setting the CMake cache entry
  CMAKE_C_COMPILER to the full path to the compiler, or to the compiler name
  if it is in the PATH.


CMake Error at CMakeLists.txt:1 (project):
  No CMAKE_CXX_COMPILER could be found.

  Tell CMake where to find the compiler by setting the CMake cache entry
  CMAKE_CXX_COMPILER to the full path to the compiler, or to the compiler
  name if it is in the PATH.


CMake Error: CMake was unable to find a build program corresponding to "Ninja".  CMAKE_MAKE_PROGRAM is not set.  You probably need to select a different build tool.
-- Configuring incomplete, errors occurred!