About this Blog

This Blog has English posts and Japanese posts. About Mac, iOS, Objective-C, and so on.

2013年3月28日木曜日

[DIY]Nexus 7の押しにくい電源ボタンを改良

今更の感が強いですが、Nexus 7を買いました。
いろいろな方が言及されているように、電源ボタンが使いにくいです。
手探りで音量ボタンと区別が付くようにするために、ちょこっと手を加えて見ました。

写真だと見にくいですが、ブリスターパックのプラスチックを切って両面テープで貼りつけただけ。



端がチクチクするので黒のビニールテープを貼ったのが以下。


電源ボタンを考慮したカバーなどもあるんだろうけど、しばらくこれで。

明るい所で見ると裏側は焦げ茶というかえび色というか、そういう色なんですね。

2013年3月24日日曜日

mruby on Objective-C -スクリプトの実行を中断-

正しい使い方かどうかは分かりませんが、一応公開します。


前提

mruby-master/include/mrbconf.hでdefine ENABLE_DEBUG.
mruby VMがメインスレッド以外で実行されていること。


方針

ユーザーのアクションで、実行中のmruby VMのcode_fetch_hookに関数を登録。
その関数の中でエラーをraiseしてスクリプトの実行を中断。


viewController.h
#import <Foundation/Foundation.h>
#include "mruby.h"
#include "mruby/string.h"
#include "mruby/compile.h"
#include "mruby/class.h"
#include "mruby/value.h"
#include "mruby/proc.h"
#include "mruby/variable.h"
#include "mruby/array.h"

mrb_value mrb_printstr(mrb_state*, mrb_value);//別エントリで解説しています。
void mrb_interrupt(mrb_state*, mrb_irep*, mrb_code*, mrb_value*);

@interface viewController : UIViewController{
    NSOperationQueue *_queue;
    NSBlockOperation *_operation;
    struct mrb_state *_mrb;
}
-(void)mrbInitPrint;//別エントリで解説しています。
-(void)mrbRun:(NSString *)code;//別エントリで解説しています。
-(IBAction)mrbQuit;
@end


viewController.m
必要なコードのみ書いてあります。
void mrb_interrupt(mrb_state *mrb, mrb_irep *irep, mrb_code *pc, mrb_value *regs){
    //code_fetch_hookにすぐにNULLを代入するので、この関数は1回だけ呼ばれます。
    //エラーをraiseするとスクリプトが中断するので意味はないですが、
    //他の処理を割りこませることもできます。
    mrb->code_fetch_hook = NULL;
    mrb_raise(mrb, E_RUNTIME_ERROR, "interrupted");
}

@implementation viewController
-(IBAction)mrbQuit{
    if(_mrb){ _mrb->code_fetch_hook = mrb_interrupt; }
}
@end
UIButtonのtouch up insideなどにOutletしてください。

mruby on Objective-C - puts,pをUITextViewに出力-

方針

__printstr__を(Objective-)Cで実装し、それを使ってRubyでp,puts,print,printfを定義する。
mrubyのメソッドとして使うCの関数から、AppDelegate経由でUITextViewにアクセス。


参考



Rubyのスクリプトをプロジェクトに追加しておきます。

print.rb
module Kernel
# needs __printstr__
  def print(*args)
    i = 0
    len = args.size
    while i < len
      __printstr__ args[i].to_s
      i += 1
    end
  end
  def puts(*args)
    i = 0
    len = args.size
    while i < len
      s = args[i].to_s
      __printstr__ s
      __printstr__ "\n" if (s[-1] != "\n")
      i += 1
    end
    __printstr__ "\n" if len == 0
    nil
  end
  def p(*args)
    i = 0
    len = args.size
    while i < len
      __printstr__ args[i].inspect
      __printstr__ "\n"
      i += 1
    end
    args[0]
  end
  def printf(*args)
    __printstr__(sprintf(*args))
    nil
  end
end


あとは、Objective-Cを編集します。
DelegateへのUITextViewの登録、実行するRubyスクリプトの取得などは省略してあります。
また、puts,p,print,printfの度にUITextViewにsetText:するので、最後の出力しか残りません。
クラスを拡張してaddText:のようなメソッドを定義したほうが便利なはずです。


viewController.h
#import <Foundation/Foundation.h>
#include "mruby.h"
#include "mruby/string.h"
#include "mruby/compile.h"
#include "mruby/class.h"
#include "mruby/value.h"
#include "mruby/proc.h"
#include "mruby/variable.h"
#include "mruby/array.h"

mrb_value mrb_printstr(mrb_state*, mrb_value);
void mrb_interrupt(mrb_state*, mrb_irep*, mrb_code*, mrb_value*);//別エントリで解説します。

@interface viewController : UIViewController{
    NSOperationQueue *_queue;
    NSBlockOperation *_operation;
    struct mrb_state *_mrb;
}
-(void)mrbInitPrint;
-(void)mrbRun:(NSString *)code;
-(IBAction)mrbQuit;//別エントリで解説します。
@end


viewController.m
//mruby-master/src/print.cからコピペしました。
mrb_value mrb_printstr(mrb_state *mrb, mrb_value self_){
    mrb_value argv;
    mrb_get_args(mrb, "o", &argv);
    struct RString *str;
    char *s;
    int len;
    if(mrb_string_p(argv)){
        str = mrb_str_ptr(argv);
        s = str->ptr;
        len = str->len;
        //以下3行を環境に応じて書き換えてください。
        AppDelegate *appDel = [[UIApplication sharedApplication] delegate];
        UITextView *targetView = appDel.targetTextView;
        [targetView performSelectorOnMainThread:@selector(setText:) withObject:[NSString stringWithFormat:@"%s",s] waitUntilDone:YES];
    }
    return argv;
}
@implementation viewController
-(void)mrbInitPrint{
    //Kernelにp,puts,print,printfの定義に使う__printstr__を登録します。
    struct RClass *krn;
    krn = _mrb->kernel_module;
    mrb_define_method(_mrb, krn, "__printstr__", mrb_printstr, ARGS_REQ(1));

    //プロジェクトにバンドルしたprint.rbを読み込み、mrubyにロードします。
    NSError *error = nil;
    NSString *script_str;
    NSString *path = [[NSBundle mainBundle] pathForResource:[scripts objectAtIndex:i] ofType:@"rb"];
    NSString *print_rb_str = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
    if(!error){
        mrb_load_string(_mrb, [print_rb_str cStringUsingEncoding:NSUTF8StringEncoding]);
    }
}
-(void)mrbRun:(NSString *)c{
    //mruby VMの実行を別スレッドにしている以外は、参考URLの通りです。
    if([_operation isExecuting]){ return; }
    __block NSString *code = [c copy];
    if(!_queue){
        _queue = [[NSOperationQueue alloc] init];
    }
    _operation = [[NSBlockOperation alloc] init];
    [_operation addExecutionBlock:^{
        const char *codeStr = [code cStringUsingEncoding:NSUTF8StringEncoding];
        mrbc_context *ctx;
        _mrb = mrb_open();

        [self mrbInitPrint];

        ctx = mrbc_context_new(_mrb);
        struct mrb_parser_state *st = mrb_parse_string(_mrb, codeStr, ctx);
        int gen_code_num = mrb_generate_code(_mrb, st);
        mrb_pool_close(st->pool);
        mrb_value result = mrb_run(_mrb, mrb_proc_new(_mrb,_mrb->irep[gen_code_num]), mrb_nil_value());
        if(_mrb->exc){//catch exception
            mrb_value r = mrb_funcall(_mrb, result, "inspect", 0);
            if(mrb_string_p(r)){
                struct RString *str = mrb_str_ptr(r);
                NSLog(@"%s",str->ptr);
            }
        }
        mrb_close(_mrb);
        mrbc_context_free(_mrb, ctx);
        _mrb = NULL;
        [_operation release];
        _operation = nil;
        [code release];
    }];
    [_queue addOperation:_operation];
}

mruby on Objective-C -アプリに組み込む-

mrubyを使ったアプリをsubmitしたので、メモをちまちまと放出。
実機でしか動きませんのであしからず。ツッコミ大歓迎。

環境

  • Mac OS X 10.6.8 Snow Leopard
  • Xcode4.2
  • ARC不使用
  • iPad2
  • iOS6.1
  • mruby (2013/02/17にソース取得)


方針

armv7向けにコンパイルしたmrubyの静的ライブラリを作り、それを直接Xcodeプロジェクトにコピーして使う。


build_config.rbを編集

mruby-master/build_config.rbのクロスコンパイル部分を以下のように編集します。
# Define cross build settings
 MRuby::CrossBuild.new('32bit') do |conf|
   isysroot = "/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.1.sdk/usr/include"
   conf.cc.command = "clang"
   conf.cc.flags << "-arch armv7"
   conf.cc.include_paths << isysroot
   conf.cc.include_paths << "#{root}/include"
 end


mrbconf.hを編集

mruby-master/include/mrbconf.hを編集します。
#define DISABLE_STDIO    /* use of stdio */
#define ENABLE_DEBUG     /* hooks for debugger */
STDIOを無効にします。
有効のままでも動きましたが、putsを使ったら落ちました。
DEBUGを有効にします。
これは、スクリプトの実行を中断するのに使っています。


make

コマンドライン上でmruby-masterに移動し、makeします。
途中でRakeが落ちますが、
mruby-master/build/32bit/lib/libmruby.a
が生成されているのでOKとします。(そのうちrake勉強せな)


プロジェクトを新規作成、必要ファイルをコピー

Xcodeで新しいプロジェクト(project_name)を作成し、mrubyのファイルをFinder上でコピーします。
mruby-master/include/をフォルダごとコピーして、そこにlibmruby.aを追加する感じです。
includeのままだと分かりにくいので、mrubyにリネームしました。
project_name/
|--project_name.xcodeproj
|--project_name/
   |--mruby/ 
   |  |--mrbconf.h
   |  |--mruby.h
   |  |--libmruby.a
   |  |--mruby/
   |     |--array.h
   |     |--class.h
   |         .
   |         .
   |     |--value.h
   |     |--variable.h
   |--viewController.h
   |--viewController.m
   |   .
   |   .   
Finder上でコピーしたあと、Xcode上の左ペインにドラッグ・アンド・ドロップして、プロジェクトに追加します。


パスの設定

Build SettingsのHeader Search PathLibrary Search Path
"$(SRCROOT)/project_name/mruby"
を追加します。
Recursiveはなしで。


以上で、iOSアプリでmrubyを使う準備は完了です。