試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

GNU/Linux上の振動対応ジョイパッドデバイスの振動テストを行うGUIツールを作成

PSX-CV02上の PlayStation 2コントローラの振動機能をGNU/Linux上で用いるための試行錯誤(Linux 2.6.31.5時点・第3回)」で振動のテストに用いたツールfftestは端末上で動作するプログラムでGUIの振動テストツールはなかったため、このソースを参考にして作ってみた。fftestは振動以外にも対応しているが、振動以外の種類に対応したデバイスは所有していない(動作が確認できない)ため、振動のみに対応したものとなる。
C言語で作成したものなので、GTK+ 2の開発パッケージ(lib(64)gtk+2.0_0-devel,libgtk2.0-devなどの名前)がインストールされている状態で

$ gcc -O2 $(pkg-config --cflags --libs gtk+-2.0 gmodule-2.0) [下のソースファイル] -o [実行ファイル]

としてコンパイルして実行する。また、Linuxカーネルのヘッダファイルも使用しているため、このパッケージ(linux-userspace-headers,linux-libc-devなどの名前)も入っていなければ必要となる。
GtkBuilderの機能を用いている(UI定義の記述はコード中に埋め込んでいる)ため、少なくともGTK+のバージョンは2.12以上が必要となる。作成した環境のGTK+はバージョン2.18系だが、GtkBuilder関係で問題が起こる可能性もあるため、なるべく新しいバージョンを用いることを推奨する。

コード

動作に大きな問題がないことは確認しているが、細かい部分で改善の余地がある可能性もあるため、バージョンを0.9とした。
[任意]ファイル名: rumbletest-gtk.c ライセンス: GPL-3 or lator

/*
 * rumbletest-gtk (C) 2010 kakurasan
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

/*
 * gcc -O2 -Wall -Wextra $(pkg-config --cflags --libs gtk+-2.0 gmodule-2.0) rumbletest-gtk.c -o rumbletest-gtk
 */

#include <linux/joystick.h>
#include <linux/input.h>
#include <gtk/gtk.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>

enum
{
  COLUMN_COMBO,
  COLUMN_NAME,
  COLUMN_JSDEV,
  COLUMN_EVENTDEV,
  COLUMN_AXES,
  COLUMN_BUTTONS,
  COLUMN_FD_EVENTDEV,
  COLUMN_UPLOADED,
};

#define NAME_LEN             128
#define NUM_JS               32

#define UNUSED_VARIABLE(x)   ((void) (x))

/* FF features check */
#define BITS_PER_LONG        (sizeof (long) * 8)
#define OFF(x)               ((x) % BITS_PER_LONG)
#define LONG(x)              ((x) / BITS_PER_LONG)
#define test_bit(bit, array) ((array[LONG (bit)] >> OFF (bit)) & 1)

/* GtkBuilder UI definitions */
static const gchar *uidef = "<?xml version=\"1.0\"?>"
"<interface>"
"  <object class=\"GtkAdjustment\" id=\"adjustment1\">"
"    <property name=\"upper\">10</property>"
"    <property name=\"lower\">0</property>"
"    <property name=\"page_increment\">10</property>"
"    <property name=\"step_increment\">1</property>"
"    <property name=\"page_size\">0</property>"
"    <property name=\"value\">0</property>"
"  </object>"
"  <object class=\"GtkAdjustment\" id=\"adjustment2\">"
"    <property name=\"upper\">30</property>"
"    <property name=\"lower\">1</property>"
"    <property name=\"page_increment\">10</property>"
"    <property name=\"step_increment\">1</property>"
"    <property name=\"page_size\">0</property>"
"    <property name=\"value\">5</property>"
"  </object>"
"  <object class=\"GtkAdjustment\" id=\"adjustment3\">"
"    <property name=\"upper\">65535</property>"
"    <property name=\"lower\">0</property>"
"    <property name=\"page_increment\">1000</property>"
"    <property name=\"step_increment\">1</property>"
"    <property name=\"page_size\">0</property>"
"    <property name=\"value\">32768</property>"
"  </object>"
"  <object class=\"GtkAdjustment\" id=\"adjustment4\">"
"    <property name=\"upper\">65535</property>"
"    <property name=\"lower\">0</property>"
"    <property name=\"page_increment\">1000</property>"
"    <property name=\"step_increment\">1</property>"
"    <property name=\"page_size\">0</property>"
"    <property name=\"value\">32768</property>"
"  </object>"
"  <object class=\"GtkUIManager\" id=\"uimanager1\">"
"    <child>"
"      <object class=\"GtkActionGroup\" id=\"actiongroup1\">"
"        <child>"
"          <object class=\"GtkAction\" id=\"item_file\">"
"            <property name=\"name\">item_file</property>"
"            <property name=\"label\" translatable=\"yes\">_File</property>"
"          </object>"
"        </child>"
"        <child>"
"          <object class=\"GtkAction\" id=\"item_quit\">"
"            <property name=\"stock_id\">gtk-quit</property>"
"            <property name=\"name\">item_quit</property>"
"            <signal handler=\"on_item_quit_activate\" name=\"activate\"/>"
"          </object>"
"        </child>"
"        <child>"
"          <object class=\"GtkAction\" id=\"item_help\">"
"            <property name=\"name\">item_help</property>"
"            <property name=\"label\" translatable=\"yes\">_Help</property>"
"          </object>"
"        </child>"
"        <child>"
"          <object class=\"GtkAction\" id=\"item_about\">"
"            <property name=\"stock_id\">gtk-about</property>"
"            <property name=\"name\">item_about</property>"
"            <signal handler=\"on_item_about_activate\" name=\"activate\"/>"
"          </object>"
"        </child>"
"      </object>"
"    </child>"
"    <ui>"
"      <menubar name=\"menubar\">"
"        <menu action=\"item_file\">"
"          <menuitem action=\"item_quit\"/>"
"        </menu>"
"        <menu action=\"item_help\">"
"          <menuitem action=\"item_about\"/>"
"        </menu>"
"      </menubar>"
"    </ui>"
"  </object>"
"  <object class=\"GtkWindow\" id=\"mainwindow\">"
"    <property name=\"width_request\">350</property>"
"    <property name=\"height_request\">310</property>"
"    <property name=\"default_width\">350</property>"
"    <property name=\"default_height\">310</property>"
"    <signal handler=\"on_item_quit_activate\" name=\"delete_event\"/>"
"    <child>"
"      <object class=\"GtkVBox\" id=\"vbox\">"
"        <property name=\"visible\">True</property>"
"        <property name=\"orientation\">vertical</property>"
"        <child>"
"          <object class=\"GtkMenuBar\" constructor=\"uimanager1\" id=\"menubar\">"
"            <property name=\"visible\">True</property>"
"          </object>"
"          <packing>"
"            <property name=\"expand\">False</property>"
"            <property name=\"position\">0</property>"
"          </packing>"
"        </child>"
"        <child>"
"          <object class=\"GtkVBox\" id=\"vbox_main\">"
"            <property name=\"visible\">True</property>"
"            <property name=\"orientation\">vertical</property>"
"            <child>"
"              <object class=\"GtkTable\" id=\"table\">"
"                <property name=\"visible\">True</property>"
"                <property name=\"n_rows\">5</property>"
"                <property name=\"n_columns\">2</property>"
"                <child>"
"                  <object class=\"GtkHScale\" id=\"hscale_delay\">"
"                    <property name=\"visible\">True</property>"
"                    <property name=\"can_focus\">True</property>"
"                    <property name=\"adjustment\">adjustment1</property>"
"                    <property name=\"digits\">0</property>"
"                  </object>"
"                  <packing>"
"                    <property name=\"left_attach\">1</property>"
"                    <property name=\"right_attach\">2</property>"
"                    <property name=\"top_attach\">4</property>"
"                    <property name=\"bottom_attach\">5</property>"
"                  </packing>"
"                </child>"
"                <child>"
"                  <object class=\"GtkHScale\" id=\"hscale_length\">"
"                    <property name=\"visible\">True</property>"
"                    <property name=\"can_focus\">True</property>"
"                    <property name=\"adjustment\">adjustment2</property>"
"                    <property name=\"digits\">0</property>"
"                  </object>"
"                  <packing>"
"                    <property name=\"left_attach\">1</property>"
"                    <property name=\"right_attach\">2</property>"
"                    <property name=\"top_attach\">3</property>"
"                    <property name=\"bottom_attach\">4</property>"
"                  </packing>"
"                </child>"
"                <child>"
"                  <object class=\"GtkHScale\" id=\"hscale_weak_magnitude\">"
"                    <property name=\"visible\">True</property>"
"                    <property name=\"can_focus\">True</property>"
"                    <property name=\"adjustment\">adjustment3</property>"
"                    <property name=\"digits\">0</property>"
"                  </object>"
"                  <packing>"
"                    <property name=\"left_attach\">1</property>"
"                    <property name=\"right_attach\">2</property>"
"                    <property name=\"top_attach\">2</property>"
"                    <property name=\"bottom_attach\">3</property>"
"                  </packing>"
"                </child>"
"                <child>"
"                  <object class=\"GtkHScale\" id=\"hscale_strong_magnitude\">"
"                    <property name=\"visible\">True</property>"
"                    <property name=\"can_focus\">True</property>"
"                    <property name=\"adjustment\">adjustment4</property>"
"                    <property name=\"digits\">0</property>"
"                  </object>"
"                  <packing>"
"                    <property name=\"left_attach\">1</property>"
"                    <property name=\"right_attach\">2</property>"
"                    <property name=\"top_attach\">1</property>"
"                    <property name=\"bottom_attach\">2</property>"
"                  </packing>"
"                </child>"
"                <child>"
"                  <object class=\"GtkLabel\" id=\"label_delay\">"
"                    <property name=\"visible\">True</property>"
"                    <property name=\"xalign\">0</property>"
"                    <property name=\"label\" translatable=\"yes\">D_elay[s]</property>"
"                    <property name=\"use_underline\">True</property>"
"                    <property name=\"mnemonic_widget\">hscale_delay</property>"
"                  </object>"
"                  <packing>"
"                    <property name=\"top_attach\">4</property>"
"                    <property name=\"bottom_attach\">5</property>"
"                    <property name=\"x_options\">GTK_FILL</property>"
"                    <property name=\"x_padding\">8</property>"
"                  </packing>"
"                </child>"
"                <child>"
"                  <object class=\"GtkLabel\" id=\"label_length\">"
"                    <property name=\"visible\">True</property>"
"                    <property name=\"xalign\">0</property>"
"                    <property name=\"label\" translatable=\"yes\">_Length[s]</property>"
"                    <property name=\"use_underline\">True</property>"
"                    <property name=\"mnemonic_widget\">hscale_length</property>"
"                  </object>"
"                  <packing>"
"                    <property name=\"top_attach\">3</property>"
"                    <property name=\"bottom_attach\">4</property>"
"                    <property name=\"x_options\">GTK_FILL</property>"
"                    <property name=\"x_padding\">8</property>"
"                  </packing>"
"                </child>"
"                <child>"
"                  <object class=\"GtkLabel\" id=\"label_weak_magnitude\">"
"                    <property name=\"visible\">True</property>"
"                    <property name=\"xalign\">0</property>"
"                    <property name=\"label\" translatable=\"yes\">_Weak magnitude</property>"
"                    <property name=\"use_underline\">True</property>"
"                    <property name=\"mnemonic_widget\">hscale_weak_magnitude</property>"
"                  </object>"
"                  <packing>"
"                    <property name=\"top_attach\">2</property>"
"                    <property name=\"bottom_attach\">3</property>"
"                    <property name=\"x_options\">GTK_FILL</property>"
"                    <property name=\"x_padding\">8</property>"
"                  </packing>"
"                </child>"
"                <child>"
"                  <object class=\"GtkLabel\" id=\"label_strong_magnitude\">"
"                    <property name=\"visible\">True</property>"
"                    <property name=\"xalign\">0</property>"
"                    <property name=\"label\" translatable=\"yes\">_Strong magnitude</property>"
"                    <property name=\"use_underline\">True</property>"
"                    <property name=\"mnemonic_widget\">hscale_strong_magnitude</property>"
"                  </object>"
"                  <packing>"
"                    <property name=\"top_attach\">1</property>"
"                    <property name=\"bottom_attach\">2</property>"
"                    <property name=\"x_options\">GTK_FILL</property>"
"                    <property name=\"x_padding\">8</property>"
"                  </packing>"
"                </child>"
"                <child>"
"                  <object class=\"GtkLabel\" id=\"label_device\">"
"                    <property name=\"visible\">True</property>"
"                    <property name=\"xalign\">0</property>"
"                    <property name=\"label\" translatable=\"yes\">_Device</property>"
"                    <property name=\"use_underline\">True</property>"
"                    <property name=\"mnemonic_widget\">combo_device</property>"
"                  </object>"
"                  <packing>"
"                    <property name=\"x_options\">GTK_FILL</property>"
"                    <property name=\"x_padding\">8</property>"
"                  </packing>"
"                </child>"
"                <child>"
"                  <object class=\"GtkComboBox\" id=\"combo_device\">"
"                    <property name=\"visible\">True</property>"
"                  </object>"
"                  <packing>"
"                    <property name=\"left_attach\">1</property>"
"                    <property name=\"right_attach\">2</property>"
"                  </packing>"
"                </child>"
"              </object>"
"              <packing>"
"                <property name=\"position\">0</property>"
"              </packing>"
"            </child>"
"            <child>"
"              <object class=\"GtkHBox\" id=\"hbox_rumble\">"
"                <property name=\"visible\">True</property>"
"                <child>"
"                  <object class=\"GtkToggleButton\" id=\"button_rumble\">"
"                    <property name=\"label\" translatable=\"yes\">_Rumble</property>"
"                    <property name=\"visible\">True</property>"
"                    <property name=\"can_focus\">True</property>"
"                    <property name=\"receives_default\">True</property>"
"                    <property name=\"use_underline\">True</property>"
"                    <signal handler=\"on_button_rumble_toggled\" name=\"toggled\"/>"
"                  </object>"
"                  <packing>"
"                    <property name=\"fill\">False</property>"
"                    <property name=\"position\">0</property>"
"                  </packing>"
"                </child>"
"              </object>"
"              <packing>"
"                <property name=\"expand\">False</property>"
"                <property name=\"fill\">False</property>"
"                <property name=\"position\">1</property>"
"              </packing>"
"            </child>"
"          </object>"
"          <packing>"
"            <property name=\"position\">1</property>"
"          </packing>"
"        </child>"
"        <child>"
"          <object class=\"GtkStatusbar\" id=\"statusbar\">"
"            <property name=\"visible\">True</property>"
"            <property name=\"spacing\">2</property>"
"          </object>"
"          <packing>"
"            <property name=\"expand\">False</property>"
"            <property name=\"position\">2</property>"
"          </packing>"
"        </child>"
"      </object>"
"    </child>"
"  </object>"
"  <object class=\"GtkAdjustment\" id=\"adjustment_strong_magnitude\">"
"    <property name=\"value\">32768</property>"
"    <property name=\"upper\">65535</property>"
"    <property name=\"step_increment\">1</property>"
"    <property name=\"page_increment\">1000</property>"
"  </object>"
"  <object class=\"GtkAdjustment\" id=\"adjustment_weak_magnitude\">"
"    <property name=\"value\">32768</property>"
"    <property name=\"upper\">65535</property>"
"    <property name=\"step_increment\">1</property>"
"    <property name=\"page_increment\">1000</property>"
"  </object>"
"  <object class=\"GtkAdjustment\" id=\"adjustment_length\">"
"    <property name=\"value\">1</property>"
"    <property name=\"lower\">1</property>"
"    <property name=\"upper\">99</property>"
"    <property name=\"step_increment\">1</property>"
"    <property name=\"page_increment\">10</property>"
"  </object>"
"  <object class=\"GtkAdjustment\" id=\"adjustment_delay\">"
"    <property name=\"value\">1</property>"
"    <property name=\"upper\">99</property>"
"    <property name=\"step_increment\">1</property>"
"    <property name=\"page_increment\">10</property>"
"  </object>"
"  <object class=\"GtkAccelGroup\" id=\"accelgroup\"/>"
"  <object class=\"GtkAboutDialog\" id=\"aboutdialog\">"
"    <property name=\"border_width\">5</property>"
"    <property name=\"type_hint\">normal</property>"
"    <property name=\"has_separator\">False</property>"
"    <property name=\"program_name\">rumbletest-gtk</property>"
"    <property name=\"version\">0.9</property>"
"    <property name=\"copyright\" translatable=\"yes\">(C) 2010 kakurasan</property>"
"    <property name=\"comments\" translatable=\"yes\">GUI rumble tester for GNU/Linux</property>"
"    <property name=\"license\" translatable=\"yes\">Licensed under GPLv3+</property>"
"    <property name=\"authors\">kakurasan &lt;kakurasan AT gmail DOT com&gt;</property>"
"    <child internal-child=\"vbox\">"
"      <object class=\"GtkVBox\" id=\"dialog-vbox\">"
"        <property name=\"visible\">True</property>"
"        <property name=\"orientation\">vertical</property>"
"        <property name=\"spacing\">2</property>"
"        <child>"
"          <placeholder/>"
"        </child>"
"        <child internal-child=\"action_area\">"
"          <object class=\"GtkHButtonBox\" id=\"dialog-action_area\">"
"            <property name=\"visible\">True</property>"
"            <property name=\"layout_style\">end</property>"
"          </object>"
"          <packing>"
"            <property name=\"expand\">False</property>"
"            <property name=\"pack_type\">end</property>"
"            <property name=\"position\">0</property>"
"          </packing>"
"        </child>"
"      </object>"
"    </child>"
"  </object>"
"</interface>";
static GtkListStore *liststore_devices;
static GtkWidget *combo_device;
static GtkWidget *hscale_strong_magnitude;
static GtkWidget *hscale_weak_magnitude;
static GtkWidget *hscale_length;
static GtkWidget *hscale_delay;
static GtkWidget *button_rumble;
static GtkWidget *statusbar;
static GtkWidget *aboutdialog;
static guint ctxid;

static gboolean
to_widgets_set_sensitive (gpointer data)
{
  gtk_widget_set_sensitive (combo_device, TRUE);
  gtk_widget_set_sensitive (hscale_strong_magnitude, TRUE);
  gtk_widget_set_sensitive (hscale_weak_magnitude, TRUE);
  gtk_widget_set_sensitive (hscale_length, TRUE);
  gtk_widget_set_sensitive (hscale_delay, TRUE);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button_rumble), FALSE);
  UNUSED_VARIABLE (data);
  return FALSE;  /* will not be called again */
}

static gboolean
fe_close_eventdev_fd (GtkTreeModel *model,
                      GtkTreePath *path,
                      GtkTreeIter *iter,
                      gpointer data)
{
  GValue *gv_fd_eventdev = g_new0 (GValue, 1);  /* zero-filled/must be freed */
  gtk_tree_model_get_iter (model, iter, path);
  gtk_tree_model_get_value (model, iter, COLUMN_FD_EVENTDEV, gv_fd_eventdev);
  close ((int) g_value_get_int (gv_fd_eventdev));
  g_free (gv_fd_eventdev);
  UNUSED_VARIABLE (data);
  return FALSE;  /* continue iterating */
}

G_MODULE_EXPORT void
on_item_quit_activate ()
{
  /* close fds */
  gtk_tree_model_foreach (GTK_TREE_MODEL (liststore_devices), fe_close_eventdev_fd, NULL);
  /* quit */
  gtk_main_quit ();
}

G_MODULE_EXPORT void
on_item_about_activate ()
{
  gtk_dialog_run (GTK_DIALOG (aboutdialog));
  gtk_widget_hide (aboutdialog);
}

G_MODULE_EXPORT void
on_button_rumble_toggled (GtkToggleButton *button)
{
  GValue *gv_eventdev, *gv_fd_eventdev, *gv_uploaded;
  gint devnum = gtk_combo_box_get_active (GTK_COMBO_BOX (combo_device));
  GtkTreePath *path = gtk_tree_path_new_from_indices (devnum, -1);
  GtkTreeIter treeiter_device;
  int fd_eventdev;
  static struct ff_effect effect;
  static guint id_timeout;
  /*
   * get path to event device (/dev/input/eventXX) and its file descriptor
   * from GtkListStore
   */
  gv_eventdev = g_new0 (GValue, 1);
  gv_fd_eventdev = g_new0 (GValue, 1);
  gv_uploaded = g_new0 (GValue, 1);
  gtk_tree_model_get_iter (GTK_TREE_MODEL (liststore_devices), &treeiter_device, path);
  gtk_tree_model_get_value (GTK_TREE_MODEL (liststore_devices), &treeiter_device, COLUMN_EVENTDEV, gv_eventdev);
  gtk_tree_model_get_value (GTK_TREE_MODEL (liststore_devices), &treeiter_device, COLUMN_FD_EVENTDEV, gv_fd_eventdev);
  gtk_tree_model_get_value (GTK_TREE_MODEL (liststore_devices), &treeiter_device, COLUMN_UPLOADED, gv_uploaded);
  fd_eventdev = (int) g_value_get_int (gv_fd_eventdev);
  if (gtk_toggle_button_get_active (button))  /* OFF -> ON */
  {
    /* upload effect */
    effect.type = FF_RUMBLE;
    if (! g_value_get_boolean (gv_uploaded))
      effect.id = -1;  /* add new effect */
    effect.u.rumble.strong_magnitude = (__u16) gtk_range_get_value (GTK_RANGE (hscale_strong_magnitude));
    effect.u.rumble.weak_magnitude = (__u16) gtk_range_get_value (GTK_RANGE (hscale_weak_magnitude));
    effect.replay.length = (__u16) (gtk_range_get_value (GTK_RANGE (hscale_length)) * 1000);
    effect.replay.delay = (__u16) (gtk_range_get_value (GTK_RANGE (hscale_delay)) * 1000);
    if (ioctl (fd_eventdev, EVIOCSFF, &effect) != -1)
    {
      /* play */
      struct input_event play;
      play.type = EV_FF;
      play.code = effect.id;
      play.value = 1;
      if (write (fd_eventdev, (const void *) &play, sizeof (play)) == -1)
        g_warning ("Play failed");
      else
      {
        gtk_list_store_set (liststore_devices, &treeiter_device,
                            COLUMN_UPLOADED, TRUE,
                            -1);
        gtk_widget_set_sensitive (combo_device, FALSE);
        gtk_widget_set_sensitive (hscale_strong_magnitude, FALSE);
        gtk_widget_set_sensitive (hscale_weak_magnitude, FALSE);
        gtk_widget_set_sensitive (hscale_length, FALSE);
        gtk_widget_set_sensitive (hscale_delay, FALSE);
        gtk_statusbar_push (GTK_STATUSBAR (statusbar), ctxid, "Playing...");
        id_timeout = g_timeout_add_seconds (gtk_range_get_value (GTK_RANGE (hscale_length)) + gtk_range_get_value (GTK_RANGE (hscale_delay)),
                                            (GSourceFunc) to_widgets_set_sensitive,
                                            NULL);
      }
    }
    else
      g_warning ("Uploading effect failed");
  }
  else  /* ON -> OFF */
  {
    /* stop */
    struct input_event stop;
    stop.type = EV_FF;
    stop.code = effect.id;
    stop.value = 0;
    if (write (fd_eventdev, (const void *) &stop, sizeof (stop)) == -1)
      g_warning ("Stop failed");
    g_source_remove (id_timeout);
    to_widgets_set_sensitive (NULL);
    gtk_statusbar_pop (GTK_STATUSBAR (statusbar), ctxid);
  }
  g_free (gv_eventdev);
  g_free (gv_fd_eventdev);
  g_free (gv_uploaded);
}

int
main (int argc, char **argv)
{
  gint i;
  GtkBuilder *builder;
  GtkCellRenderer *cellrenderer;
  GtkTreeIter treeiter_device;
  gchar *jsdev, *eventdev;
  unsigned char axes, buttons;
  unsigned long features[1 + FF_MAX / sizeof (unsigned long)];
  char name[NAME_LEN] = "(Unknown name)";
  gint devcnt = 0;
  gtk_init (&argc, &argv);
  builder = gtk_builder_new ();
  gtk_builder_add_from_string (builder, uidef, -1, NULL);
  gtk_builder_connect_signals (builder, NULL);
  hscale_strong_magnitude = GTK_WIDGET (gtk_builder_get_object (builder, "hscale_strong_magnitude"));
  hscale_weak_magnitude = GTK_WIDGET (gtk_builder_get_object (builder, "hscale_weak_magnitude"));
  hscale_length = GTK_WIDGET (gtk_builder_get_object (builder, "hscale_length"));
  hscale_delay = GTK_WIDGET (gtk_builder_get_object (builder, "hscale_delay"));
  button_rumble = GTK_WIDGET (gtk_builder_get_object (builder, "button_rumble"));
  statusbar = GTK_WIDGET (gtk_builder_get_object (builder, "statusbar"));
  aboutdialog = GTK_WIDGET (gtk_builder_get_object (builder, "aboutdialog"));
  liststore_devices = gtk_list_store_new (8,
                                          G_TYPE_STRING,    /* combo text   */
                                          G_TYPE_STRING,    /* name         */
                                          G_TYPE_STRING,    /* jsdev        */
                                          G_TYPE_STRING,    /* eventdev     */
                                          G_TYPE_INT,       /* axes         */
                                          G_TYPE_INT,       /* buttons      */
                                          G_TYPE_INT,       /* fd(eventdev) */
                                          G_TYPE_BOOLEAN);  /* uploaded?    */
  for (i = 0; i < NUM_JS; i++)
  {
    int fd_jsdev, fd_eventdev;
    gchar *eventdevname, *sysdir, *combo_text;
    GDir *dir;
    jsdev = g_strdup_printf ("/dev/input/js%d", i);
    fd_jsdev = open (jsdev, O_RDONLY);
    if (fd_jsdev == -1)
      continue;
    ioctl (fd_jsdev, JSIOCGAXES, &axes);
    ioctl (fd_jsdev, JSIOCGBUTTONS, &buttons);
    ioctl (fd_jsdev, JSIOCGNAME (NAME_LEN), name);
    close (fd_jsdev);
    sysdir = g_strdup_printf ("/sys/class/input/js%d/device", i);
    dir = g_dir_open (sysdir, 0, NULL);
    if (dir != NULL)
    {
      eventdevname = NULL;
      for (;;)
      {
        const gchar *item;
        item = g_dir_read_name (dir);
        if (item == NULL)
          break;
        if (g_str_has_prefix (item, "event"))
          eventdevname = g_strdup (item);
      }
      g_dir_close (dir);
      if (eventdevname == NULL)
        continue;
      eventdev = g_strdup_printf ("/dev/input/%s", eventdevname);
      fd_eventdev = open (eventdev, O_RDWR);
      if (fd_eventdev != -1)
      {
        if (ioctl (fd_eventdev, EVIOCGBIT (EV_FF, sizeof (features)), features) != -1 &&
            test_bit (FF_RUMBLE, features))
        {
          combo_text = g_strdup_printf ("%s\njs:%s\nevent:%s\naxes:%u buttons:%u", name, jsdev, eventdev, axes, buttons);
          gtk_list_store_append (liststore_devices, &treeiter_device);
          gtk_list_store_set (liststore_devices, &treeiter_device,
                              COLUMN_COMBO, (gchar *) combo_text,
                              COLUMN_NAME, (gchar *) name,
                              COLUMN_JSDEV, (gchar *) jsdev,
                              COLUMN_EVENTDEV, (gchar *) eventdev,
                              COLUMN_AXES, (gint) axes,
                              COLUMN_BUTTONS, (gint) buttons,
                              COLUMN_FD_EVENTDEV, (gint) fd_eventdev,
                              COLUMN_UPLOADED, FALSE,
                              -1);
          devcnt++;
          g_free (combo_text);
        }
      }  /* if (fd_eventdev != -1) */
      g_free (eventdev);
      g_free (eventdevname);
    }  /* if (dir != NULL) */
    g_free (sysdir);
    g_free (jsdev);
  }  /* for (i = 0; i < NUM_JS; i++) */
  cellrenderer = gtk_cell_renderer_text_new ();
  combo_device = GTK_WIDGET (gtk_builder_get_object (builder, "combo_device"));
  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_device), cellrenderer, TRUE);
  gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo_device), cellrenderer, "text", COLUMN_COMBO);
  if (devcnt > 0)
  {
    gchar *status_text;
    status_text = g_strdup_printf ("%d device(s)", devcnt);
    gtk_statusbar_push (GTK_STATUSBAR (statusbar), ctxid, status_text);
    g_free (status_text);
  }
  else  /* no device with rumble support found */
  {
    gtk_list_store_append (liststore_devices, &treeiter_device);
    gtk_list_store_set (liststore_devices, &treeiter_device,
                        COLUMN_COMBO, "(No device found)",
                        -1);
    gtk_widget_set_sensitive (combo_device, FALSE);
    gtk_widget_set_sensitive (button_rumble, FALSE);
    gtk_statusbar_push (GTK_STATUSBAR (statusbar), ctxid, "No device with rumble support found");
  }
  gtk_combo_box_set_model (GTK_COMBO_BOX (combo_device), GTK_TREE_MODEL (liststore_devices));
  gtk_combo_box_set_active (GTK_COMBO_BOX (combo_device), 0);  /* first item */
  gtk_widget_show_all (GTK_WIDGET (gtk_builder_get_object (builder, "mainwindow")));
  gtk_main ();
  return EXIT_SUCCESS;
}

使い方


振動機能が利用可能なデバイスの一覧が右上のコンボボックスに出るので、この中から使用したいデバイスを選択し、その下の

  • Strong magnitude: 強い作動装置による振動の規模(強さ)
  • Weak magnitude: 弱い作動装置による振動の規模(強さ)
  • Length: 振動の長さ[秒]
  • Delay: 振動開始のボタンを押した後実際に振動が始まるまでの時間[秒]

を調整した後で下部の「Rumble」ボタン(トグルボタン)を押す。
PSX-CV02上の PlayStation 2コントローラの振動機能をGNU/Linux上で用いるための試行錯誤(Linux 2.6.31.5時点・第3回)」でも書いたが、安全のため、振動のテストをするときにはコントローラを手にしっかりと持っておくのがよい。

振動中は「Playing...」とステータスバーに表示される。
ボタンの状態は振動が終わったときに自動的に元に戻るが、振動中にもう一度押して強制的に止めることもできるようにした。
振動の長さと遅延の時間については内部的にはミリ秒単位で指定でき、範囲ももっと広いのだが、テストツールとして適した範囲として、長さは1-30秒,遅延は0-10秒の範囲に制限した。

関連記事:

使用したバージョン: