ValaでTwitterアカウントのOAuth認証
Valaとは
Vala は GObjectを利用したC言語のソースコードを生成する、セルフホスティングコンパイラを持つオブジェクト指向言語である。 C#に似た構文を持ち、無名関数やシグナル、プロパティ、ジェネリクス、メモリ管理、例外処理、型推論、および、for-eachなど、C言語にはない言語仕様を持つ。
Vala – ウィキペディア
https://ja.wikipedia.org/wiki/Vala
C#との大きな違いは
- ValaはC言語のソースを出力するので、ネイティブで動作する
- メモリ管理がガーベジコレクションではなく参照カウント
- C#と同等の標準ライブラリは存在せず、Glibの機能を使う
- C言語のライブラリを簡単に利用可能
きっかけ
OSC広島2015で展示してたx68k上でTwitter動かすプログラムはValaで書いたらしい、すげえ!
GUI
GUIはGTK+使うのが1番簡単です。
Gladeで適当にデザインして、XMLファイルを出力するだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
<?xml version="1.0" encoding="UTF-8"?> <!-- Generated with glade 3.18.3 --> <interface> <requires lib="gtk+" version="3.0"/> <object class="GtkWindow" id="main_window"> <property name="can_focus">False</property> <signal name="destroy" handler="on_destroy" swapped="no"/> <child> <object class="GtkBox" id="box1"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="orientation">vertical</property> <child> <object class="GtkFrame" id="frame1"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="label_xalign">0</property> <property name="shadow_type">none</property> <child> <object class="GtkAlignment" id="alignment1"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="left_padding">12</property> <child> <object class="GtkGrid" id="grid1"> <property name="visible">True</property> <property name="can_focus">False</property> <child> <object class="GtkLabel" id="label2"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="label" translatable="yes">Consumer Key</property> </object> <packing> <property name="left_attach">0</property> <property name="top_attach">0</property> </packing> </child> <child> <object class="GtkLabel" id="label3"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="label" translatable="yes">Consumer Secret</property> </object> <packing> <property name="left_attach">0</property> <property name="top_attach">1</property> </packing> </child> <child> <object class="GtkEntry" id="consumer_key_entry"> <property name="visible">True</property> <property name="can_focus">True</property> </object> <packing> <property name="left_attach">1</property> <property name="top_attach">0</property> </packing> </child> <child> <object class="GtkEntry" id="consumer_secret_entry"> <property name="visible">True</property> <property name="can_focus">True</property> </object> <packing> <property name="left_attach">1</property> <property name="top_attach">1</property> </packing> </child> </object> </child> </object> </child> <child type="label"> <object class="GtkLabel" id="label1"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="label" translatable="yes">設定</property> </object> </child> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">0</property> </packing> </child> <child> <object class="GtkButton" id="start_auth_button"> <property name="label" translatable="yes">認証ページを開く</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <property name="relief">none</property> <signal name="clicked" handler="on_start_auth_button_clicked" swapped="yes"/> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">1</property> </packing> </child> <child> <object class="GtkFrame" id="frame2"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="label_xalign">0</property> <property name="shadow_type">none</property> <child> <object class="GtkAlignment" id="alignment2"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="left_padding">12</property> <child> <object class="GtkBox" id="box2"> <property name="visible">True</property> <property name="can_focus">False</property> <child> <object class="GtkLabel" id="label5"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="label" translatable="yes">Pin</property> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">0</property> </packing> </child> <child> <object class="GtkEntry" id="pin_code_entry"> <property name="visible">True</property> <property name="can_focus">True</property> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">1</property> </packing> </child> <child> <object class="GtkButton" id="enter_pin_code_button"> <property name="label">gtk-ok</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <property name="use_stock">True</property> <property name="yalign">0.44999998807907104</property> <signal name="clicked" handler="on_enter_pin_code_button_clicked" swapped="yes"/> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">2</property> </packing> </child> </object> </child> </object> </child> <child type="label"> <object class="GtkLabel" id="label4"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="label" translatable="yes">認証</property> </object> </child> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">2</property> </packing> </child> <child> <object class="GtkFrame" id="frame3"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="label_xalign">0</property> <property name="shadow_type">none</property> <child> <object class="GtkAlignment" id="alignment3"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="left_padding">12</property> <child> <object class="GtkGrid" id="grid2"> <property name="visible">True</property> <property name="can_focus">False</property> <child> <object class="GtkLabel" id="label7"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="label" translatable="yes">Access Token</property> </object> <packing> <property name="left_attach">0</property> <property name="top_attach">0</property> </packing> </child> <child> <object class="GtkLabel" id="label8"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="label" translatable="yes">Access Token Secret</property> </object> <packing> <property name="left_attach">0</property> <property name="top_attach">1</property> </packing> </child> <child> <object class="GtkEntry" id="access_token_entry"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="editable">False</property> </object> <packing> <property name="left_attach">1</property> <property name="top_attach">0</property> </packing> </child> <child> <object class="GtkEntry" id="access_token_secret_entry"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="editable">False</property> </object> <packing> <property name="left_attach">1</property> <property name="top_attach">1</property> </packing> </child> </object> </child> </object> </child> <child type="label"> <object class="GtkLabel" id="label6"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="label" translatable="yes">トークン</property> </object> </child> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">3</property> </packing> </child> </object> </child> </object> </interface> |
UIを記述したファイルはGtkBuilderを使って読み込んでいきます。
ファイルから読み込むにはpublic uint add_from_file (string filename) throws Error
を使います。
1 2 3 4 5 6 |
var builder = new Builder (); builder.add_from_file (UI_FILE); builder.connect_signals (this); //signalを接続する var window = builder.get_object ("main_window") as Window; //メインウィンドウを取得 window.show_all (); //メインウィンドウを表示 |
シグナルの接続は、Gladeの方であらかじめシグナルを登録しておいてから、
対応する関数を書きます。
1 2 3 4 5 |
[CCode (cname = "G_MODULE_EXPORT on_destroy")] public void on_destroy (Widget window) { Gtk.main_quit(); } |
Resources
UIをファイルに記述すると簡単にできるのですが、バイナリファイルとUIファイルが別々になって便利ではないのでバイナリファイルにUIファイルを突っ込んでおこうと思います。
まず、xmlファイルにリソースの一覧を書きます。
1 2 3 4 5 6 |
<?xml version="1.0" encoding="UTF-8"?> <gresources> <gresource prefix="/ui"> <file compressed="true">example.ui</file> </gresource> </gresources> |
次にglib-compile-resources
コマンドでC言語のソースを出力します。
1 |
glib-compile-resources --generate-source --target resources.c resources.xml |
UIをファイルからではなくリソースから読み込むので、その箇所のコードを修正します。
1 |
builder.add_from_resource (UI_FILE); |
ちなみにUI_FILE
の部分は、xmlファイルのprefixが/ui
、ファイルパスがexample.ui
の場合は/ui/example.ui
となります。
リソースにアクセスするときのパスですが、
1 2 3 4 5 6 7 8 |
<?xml version="1.0" encoding="UTF-8"?> <gresources> <gresource prefix="/example"> <file>file1</file> <file>ui/example.ui</file> <file>img/icon/app.png</file> </gresource> </gresources> |
というようなxmlファイルの場合は、
/example/file1
/example/ui/exapmple.ui
/example/icon/app.png
となります。
OAuth認証
rest-0.7使うので超簡単です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
const string CONSUMER_KEY = "xxxxx"; const string CONSUMER_SECRET = "xxxxx"; const string REST_URL = "https://api.twitter.com/"; const string REQUEST_TOKEN_FUNC_NAME = "oauth/request_token"; const string ACCESS_TOKEN_FUNC_NAME = "oauth/access_token"; var proxy = new Rest.OAuthProxy (CONSUMER_KEY, CONSUMER_SECRET, REST_URL, false); // Request Token取得 try { proxy.request_token (REQUEST_TOKEN_FUNC_NAME, "oob"); } catch (Error e) { stderr.printf ("ERROR: %sn", e.message); } // Twitterの認証ページのURLを出力 stdout.printf ("https://api.twitter.com/oauth/authorize?oauth_token=%s", proxy.get_token ()); // 認証 try { proxy.access_token (ACCESS_TOKEN_FUNC_NAME, pin); } catch (Error e) { stderr.printf ("ERROR: %sn", e.message); } // アクセストークンを出力 stdout.printf ("%sn", proxy.get_token ()); stdout.printf ("%sn", proxy.get_token_secret ()); |
できたコード
https://github.com/khws4v1/vala-oauth
コンパイル
CMakeを使うことで超簡単(大嘘)にコンパイルできます。
ここにCMake用のモジュールがあるのでダウンロードして使いましょう。
Vala_CMakeの準備
find_package
でvalacの有無を確認するにはCMakeLists.txt
にCMAKE_MODULE_PATH
を追加する文を記述する必要があります。
1 |
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/Vala_CMake/vala) |
vala_precompile
を使う場合はUseVala
をinclude
する必要があります。
1 |
include(UseVala) |
リソースファイルのコンパイル
glib-compile-resources
コマンドでxmlファイルからC言語のソースコードを出力しないといけないので、add_custom_command
で実行します。
また、find_program
でglib-compile-resources
の有無も調べます。
ちなみにこんな感じ
1 2 3 4 5 6 7 8 9 10 11 12 |
find_program(GLIB_COMPILE_RESOURCES NAMES glib-compile-resources HINTS ${GLIB_PREFIX}) if (NOT GLIB_COMPILE_RESOURCES) message(FATAL "Command not found: glib-compile-resources") endif() set(RESOURCE_C ${CMAKE_SOURCE_DIR}/src/resource.c) set(RESOURCE_FILE ${CMAKE_SOURCE_DIR}/src/resource.xml) add_custom_command( OUTPUT ${RESOURCE_C} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/src COMMAND ${GLIB_COMPILE_RESOURCES} --generate-source --target ${RESOURCE_C} ${RESOURCE_FILE}) |
Valaのコンパイル
vala_precompile
でコンパイルします。
Vala_CMakeのREADMEに使い方が書いてあります。
vala_precompile(VALA_C
source1.vala
source2.vala
source3.vala
PACKAGES
gtk+-2.0
gio-1.0
posix
OPTIONS
–thread
CUSTOM_VAPIS
some_vapi.vapi
GENERATE_VAPI
myvapi
GENERATE_HEADER
myheader
)
こんな感じになります。
1 2 3 4 |
vala_precompile(VALA_C /src/vala-oauth.vala PACKAGES gtk+-3.0 rest-0.7 posix OPTIONS --gresources ${RESOURCE_FILE}) |
そして、add_executable
でコンパイルしたC言語のソースコードを追加します。
リソースファイルから出力したソースコードもあるので、それも追加しておきます。
1 |
add_executable(vala-oauth ${VALA_C} ${RESOURCE_C}) |
最終的にCmakeLists.txt
はこうなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
cmake_minimum_required(VERSION 2.6) project(vala-oauth C) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/Vala_CMake/vala) include(UseVala) find_program(GLIB_COMPILE_RESOURCES NAMES glib-compile-resources HINTS ${GLIB_PREFIX}) if (NOT GLIB_COMPILE_RESOURCES) message(FATAL "Command not found: glib-compile-resources") endif() set(RESOURCE_C ${CMAKE_SOURCE_DIR}/src/resource.c) set(RESOURCE_FILE ${CMAKE_SOURCE_DIR}/src/resource.xml) add_custom_command( OUTPUT ${RESOURCE_C} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/src COMMAND ${GLIB_COMPILE_RESOURCES} --generate-source --target ${RESOURCE_C} ${RESOURCE_FILE}) find_package(Vala REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED gtk+-3.0) pkg_check_modules(REST REQUIRED rest-0.7) add_definitions(${GTK_CFLAGS} ${GTK_CFLAGS_OTHER}) add_definitions(${REST_CFLAGS} ${REST_CFLAGS_OTHER}) link_libraries(${GTK_LIBRARIES}) link_libraries(${REST_LIBRARIES}) link_directories(${GTK_LIBRARY_DIRS}) link_directories(${REST_LIBRARY_DIRS}) vala_precompile(VALA_C /src/vala-oauth.vala PACKAGES gtk+-3.0 rest-0.7 posix OPTIONS --gresources src/resource.xml) add_executable(vala-oauth ${VALA_C} ${RESOURCE_C}) |
これでcmake . && make
でバイナリが出てくる!すごい!