Run Android on VirtualBox/VMWare

有人問了,所以這裡大致整理一下,也算是留下一個記憶(之後沒有要繼續…Orz…)。
事實上,網路上可以找到許多人的文章,根據歸納之後,我發現大多都是從 android-porting 裡的這篇:Howto build Android full source for X86 Architecture like EeePC(ASUS)轉貼來的,所以只要耐心爬完這篇,大致上都沒問題。

  1. 第一步當然是要把 source 拉下來,這個步驟,官方描述得很清楚:Get source (Android Open Source Project),這裡不多作描述。如果你用的是 Ubuntu 8.10,會踩到雷的只有 libreadline5-dev,因為並沒有該頁面描述的 lib32readline5-dev。這裡我假設你跟官方教學步驟一樣,建了 mydroid 目錄。
  2. sync 整份 source code 以後,還需要 eee 701 的部份,所以要在 .repo 下新增一個檔案,並命名為 local_manifest.xml:
    <manifest>
    <project name="platform/vendor/asus/eee_701" path="vendor/asus/eee_701"/>
    </manifest>

    ,然後再 sync 一次。

  3. 這次的 sync 會很快,結束以後,要先 build kernel。切到 mydroid/kernel 目錄下,複製 mydroid/vendor/asus/eee_701/kernel.config 為 mydroid/kernel/.config,接著執行 make menuconfig,進入 kernel configuration 選單以後,把這幾個 driver 選為 built-in:
    • Device drivers / Network device support / Ethernet (10 or 100Mbit) / EISA, VLB, PCI and on board controllers / AMD PCnet32 PCI support
    • Device drivers / Graphics support / Support for frame buffer devices / VESA VGA graphics support
    • Device drivers / Graphics support / Console display driver support / Framebuffer Console support
    • Device drivers / Graphics support / Console display driver support / Select Compiled-in fonts (VGA 8×8 font, VGA 8×16 font)

    ,再把這些取消:

    • Device drivers / Real Time Clock / Android alarm driver
    • Device drivers / Misc devices / Android pmem allocator

    ,然後重新建置 kernel:make bzImage。

  4. 編譯好之後,把 arch/x86/boot/bzImage 複製為 mydroid/vendor/asus/eee_701/kernel。
  5. 我稍稍更動了一些設定,這樣我後面就省打一些東西:
    • vendor/asus/eee_701/BoardConfig.mk:在 BOARD_KERNEL_CMDLINE 加上 vga=788
    • vendor/asus/eee_701/init.eee_701.sh:把 netcfg eth0 dhcp 改為 dhcpcd eth0
  6. 接著就是建置 image 了,這裡是我用的 script,把以下內容存為 build.sh,並放在 mydroid 下:
    #!/bin/bash
    cp kernel/arch/x86/boot/bzImage vendor/asus/eee_701/kernel
    mkdir -p out/target/product/eee_701/data/
    cp kernel/arch/x86/boot/bzImage out/target/product/eee_701/kernel
    TARGET_ARCH=x86 TARGET_PRODUCT=eee_701 DISABLE_DEXPREOPT=true make -j2 installer_img
    

    ,執行前別忘了 chmod +x 。

  7. 建置完以後,你會在 out/target/product/eee_701 下找到 installer.img。
  8. 把 installer.img 轉為 VirtualBox/VMWare 可用的 disk image,這邊要利用 VirtualBox 的 vboxmanage 來轉:vboxmanage convertfromraw -format vdi installer.img installer.vdi,如果你用 VMWare,則是:vboxmanage convertfromraw -format vmdk installer.img installer.vmdk
  9. 建置新的 VM,設置為 Linux kernel 2.6,256M 的 RAM,以及一個超過 2G 的硬碟。然後把上個步驟轉好的 disk image 加為第二個儲存裝置。
  10. 接下來,我只以 VirtualBox 為例,因為我沒試過 VMWare,不過原理一樣。將這個 VM 開機,一開機馬上按 F12,選擇從第二個儲存裝置開機。一開機,你會看到 grub 的開機選單,趕緊按下任意鍵,因為這邊要修改一下,預設 Loader 的開機磁碟是 hd(0,0),你要按 e 進行修改,把 hd(0,0) 改為 hd(1,0),再按 b 繼續開機(如果你不熟 grub,麻煩熟悉,這邊我不多說)。開機以後,就會開始進行安裝的動作,Android 會安裝到第一個磁碟上去,這個步驟要等一陣子,如果有錯誤,再重複一次即可,根據我的經驗,有時候會因為切割磁碟失敗而停止安裝,但再從第二個磁碟開機安裝一次,通常即可解決。安裝完成不會有什麼訊息,但看一下畫面上訊息,你應該可以知道已經完成,輸入 reboot 重新開機。
  11. 最後就大功告成啦~接著你可以移除第二個磁碟,因為再也用不到啦~

最後的最後,希望我沒有遺漏~
android

Seed(3) – Glade

純手工寫 UI 實在是很苦,還好有 Glade,你可以用 Glade 設計出介面以後,再用 Seed 把事件指派一下就可以完成一個程式了。這裡假設你已經用 Glade 設計出畫面,把主要的視窗命名為 window1,並且存為 glade-1.glade。存好以後,要使用 gtk-builder-convert 把 .glade 轉為 .xml。

gtk-builder-convert glade-1.glade glade-1.xml

接著就可以寫 code 了:

#!/usr/bin/env seed
// First, you need to use gtk-builder-convert to convert glade to xml.
// gtk-builder-convert glade-1.glade glade-1.xml
// Import libraries that are used by the program
Seed.import_namespace("Gtk");
// Initialize GTK+
Gtk.init(null, null);
var ui = new Gtk.Builder();
ui.add_from_file("glade-1.xml");
var window = ui.get_object("window1");
window.signal.hide.connect(Gtk.main_quit);
// Start the main GTK+ loop and initiate the program
Gtk.main();

參考資料:Desktop Linux Applications with Javascript

Seed(2)

GObject、Gio、Gtk、Glib、Clutter 等在範例裡看到的 library,在 Seed 原始碼裡是看不到的,Seed 是利用 GObject Introspection 來跟這些 library 互動。
Cairo、sqlite、readline 的話,因為並沒有使用 GObject 這個 library,所以 Seed 另外寫 Module 來跟這些 library 互動,你可以在 Seed 原始碼的 modules 目錄下看到~
Cairo 實際上是在 Canvas 這個 module 裡,Canvas 裡共有四個主要的類別:CairoCanvas、PDFCanvas、SVGCanvas、ImageCanvas,創建這些 Canvas 以後,基本上都是使用 Cairo 來在這些 Canvas 上繪圖。裡面沒有封裝 cairo_pattern_xxxx、cairo_text_xxxx、cairo_mask…等函數,所以不能用 Cairo 來繪圖或是繪字。

#!/usr/bin/env seed
Seed.import_namespace("Gtk");
Seed.import_namespace("Canvas");
Seed.import_namespace("Gdk");
//
// Initialize GTK+
//
Gtk.init(null, null);
// Create the main application window and set the title
var window = new Gtk.Window({title: "Canvas Demo"});
var vbox = new Gtk.VBox();
var drawingArea = new Gtk.DrawingArea();
var status = new Gtk.Statusbar();
var hbox = new Gtk.HBox();
var exposeEvent = function() { return true;};
//
// create Cairo Canvas
//
function createCairoCanvas()
{
var cairo = Gdk.cairo_create( drawingArea.window );
return new Canvas.CairoCanvas( cairo );
}
//
// Demos from http://cairographics.org/tutorial/
//
function strokeDemo()
{
drawingArea.window.clear();
var canvas = createCairoCanvas();
canvas.strokeStyle = "rgb( 0, 0, 255 )";
canvas.strokeRect( 10, 10, 50, 50 );
canvas.stroke();
return true;
}
function fillDemo()
{
drawingArea.window.clear();
var canvas = createCairoCanvas();
canvas.fillStyle = "rgb( 0, 0, 255 )";
canvas.fillRect( 10, 10, 50, 50 );
canvas.fill();
return true;
}
function fourColorDemo() {
drawingArea.window.clear();
var canvas = createCairoCanvas();
canvas.strokeStyle = "rgb( 0, 0, 0 )";
canvas.moveTo( 0, 0 );
canvas.lineTo( 100, 100 );
canvas.moveTo( 100, 0 );
canvas.lineTo( 0, 100 );
canvas.lineWidth = 10;
canvas.stroke();
canvas.fillStyle = "rgb( 255, 0, 0 )";
canvas.globalAlpha = 0.8;
canvas.fillRect( 0, 0, 50, 50 );
canvas.fillStyle = "rgb( 0, 255, 0 )";
canvas.globalAlpha = 0.6;
canvas.fillRect( 0, 50, 50, 50 );
canvas.fillStyle = "rgb( 0, 0, 255 )";
canvas.globalAlpha = 0.4;
canvas.fillRect( 50, 0, 50, 50 );
return true;
}
function pathDemo() {
drawingArea.window.clear();
var canvas = createCairoCanvas();
canvas.strokeStyle = "rgb( 255, 0, 0 )";
canvas.beginPath();
canvas.moveTo( 25, 25 );
canvas.lineTo( 50, 37.5 );
canvas.lineTo( 75, 25 );
canvas.arc( 50, 50, 25*Math.sqrt(2), -0.25*Math.PI, 0.25*Math.PI, false );
canvas.bezierCurveTo( 50, 37.5, 50, 62.5, 25, 75 );
canvas.closePath();
canvas.stroke();
return true;
}
function scaleAndTransformDemo() {
drawingArea.window.clear();
var canvas = createCairoCanvas();
canvas.strokeStyle = "rgb( 255, 0, 0 )";
canvas.lineWidth=10;
canvas.save();
canvas.scale( 0.5, 1 );
canvas.arc( 50, 50, 40, 0, 2*Math.PI, true );
canvas.stroke();
canvas.translate( 100, 0 );
canvas.arc( 50, 50, 40, 0, 2*Math.PI, true );
canvas.restore();
canvas.stroke();
return true;
}
//
// Demos from http://cairographics.org/samples/
//
function arcDemo() {
drawingArea.window.clear();
var canvas = createCairoCanvas();
var xc = 128;
var yc = 128;
var radius = 100;
var angle1 = 45 * (Math.PI/180);
var angle2 = 180 * (Math.PI/180);
canvas.lineWidth = 10;
canvas.arc( xc, yc, radius, angle1, angle2, true );
canvas.stroke();
canvas.fillStyle = "rgb( 255, 51, 51 )";
canvas.globalAlpha = 0.6;
canvas.lineWidth = 6;
canvas.arc( xc, yc, 10, 0, 2*Math.PI, true );
canvas.fill();
canvas.arc( xc, yc, radius, angle1, angle2, true );
canvas.lineTo( xc, yc );
canvas.arc( xc, yc, radius, angle2, angle2, true );
canvas.lineTo( xc, yc );
canvas.stroke();
return true;
}
function clipDemo() {
drawingArea.window.clear();
var canvas = createCairoCanvas();
canvas.arc( 128, 128, 76.8, 0, 2*Math.PI, true );
canvas.clip();
canvas.beginPath();
canvas.fillRect( 0, 0, 256, 256 );
canvas.strokeStyle = "rgb( 0, 255, 0)";
canvas.moveTo( 0, 0 );
canvas.lineTo( 256, 256 );
canvas.moveTo( 256, 0 );
canvas.lineTo( 0, 256 );
canvas.lineWidth = 10;
canvas.closePath();
canvas.stroke();
return true;
}
function curveRectangleDemo() {
drawingArea.window.clear();
var canvas = createCairoCanvas();
var x0 = 25.6;
var y0 = 25.6;
var rect_width = 204.8;
var rect_height = 204.8;
var radius = 102.4;
var x1, y1;
x1 = x0 + rect_width;
y1 = y0 + rect_height;
if( rect_width/2 < radius ) {
if( rect_height/2<radius ) {
canvas.moveTo( x0, (y0+y1)/2 );
canvas.bezierCurveTo( x0, y0, x0, y0, (x0+x1)/2, y0 );
canvas.bezierCurveTo( x1, y0, x1, y0, x1, (y0+y1)/2 );
canvas.bezierCurveTo( x1, y1, x1, y1, (x0+x1)/2, y1 );
canvas.bezierCurveTo( x0, y1, x0, y1, x0, (y0+y1)/2 );
}
else {
canvas.moveTo( x0, y0+raius );
canvas.bezierCurveTo( x0, y0, x0, y0, (x0+x1)/2, y0 );
canvas.bezierCurveTo( x1, y0, x1, y0, x1, y0+radius );
canvas.lineTo( x1, y1-radius );
canvas.bezierCurveTo( x1, y1, x1, y1, (x1+x0)/2, y1 );
canvas.bezierCurveTo( x0, y1, x0, y1, x0, y1-radius );
}
}
else {
if( rect_height/2<radius ) {
canvas.moveTo( x0, (y0+y1)/2 );
canvas.bezierCurveTo( x0, y0, x0, y0, x0+radius, y0 );
canvas.lineTo( x1-radius, y0 );
canvas.bezierCurveTo( x1, y0, x1, y0, x1, (y0+y1)/2 );
canvas.bezierCurveTo( x1, y1, x1, y1, x1-radius, y1 );
canvas.lineTo( x0+radius, y1 );
canvas.bezierCurveTo( x0, y1, x0, y1, x0, (y0+y1)/2 );
}
else {
canvas.moveTo( x0, y0+radius );
canvas.bezierCurveTo( x0, y0, x0, y0, x0+radius, y0 );
canvas.lineTo( x1-radius, y0 );
canvas.bezierCurveTo( x1, y0, x1, y0, x1, y0+radius );
canvas.lineTo( x1, y1-radius );
canvas.bezierCurveTo( x1, y1, x1, y1, x1-radius, y1 );
canvas.lineTo( x0+radius, y1 );
canvas.bezierCurveTo( x0, y1, x0, y1, x0, y1-radius );
}
}
canvas.closePath();
canvas.fillStyle = "rgb( 128, 128, 255 )";
canvas.fill(); // no fill_preserve(), so you won't see the border.
canvas.strokeStyle = "rgb( 255, 0, 0 )";
canvas.globalAlpha = 0.5;
canvas.lineWidth = 10;
canvas.stroke();
return true;
}
function curveToDemo() {
drawingArea.window.clear();
var canvas = createCairoCanvas();
var x=25.6, y=128;
var x1=102.4, y1=230.4, x2=153.6, y2=25.6, x3=230.4, y3=128.0;
canvas.moveTo( x, y );
canvas.bezierCurveTo( x1, y1, x2, y2, x3, y3 );
canvas.lineWidth = 10;
canvas.stroke();
canvas.strokeStyle = "rgb( 255, 51, 51 )";
canvas.globalAlpha = 0.6;
canvas.lineWidth = 6;
canvas.moveTo( x, y ); canvas.lineTo( x1, y1 );
canvas.moveTo( x2, y2 ); canvas.lineTo( x3, y3 );
canvas.stroke();
return true;
}
function rotateDemo() {
drawingArea.window.clear();
var canvas = createCairoCanvas();
canvas.translate( 128, 128 );
canvas.rotate( 45*Math.PI/180 );
canvas.scale( 0.9, 0.9 );
canvas.fillStyle = "rgb(200,0,0)";
canvas.fillRect( 10, 10, 55, 50 );
canvas.strokeStyle = "rgb( 0, 200, 0 )";
canvas.strokeRect( 50, 50, 155, 150 );
canvas.strokeStyle = "rgb( 0, 0, 255 )";
canvas.arc( 137.5, 137.5, 100, 0, Math.PI*2, true );
canvas.stroke();
return true;
}
//
// routines
//
function createButton( label, handler ) {
var button = new Gtk.Button( {label: label} );
button.signal.clicked.connect( handler );
return button;
}
function createButtonGroup()
{
var buttonGroup = new Gtk.VBox();
//var buttonGroup = new Gtk.VButtonBox();
buttonGroup.pack_start( createButton( "Stroke", function() {
exposeEvent = strokeDemo;
return exposeEvent();
}), true, true);
buttonGroup.pack_start( createButton( "Fill", function() {
exposeEvent = fillDemo;
return exposeEvent();
}), true, true);
buttonGroup.pack_start( createButton( "4 color", function() {
exposeEvent = fourColorDemo;
return exposeEvent();
}), true, true);
buttonGroup.pack_start( createButton( "Path", function() {
exposeEvent = pathDemo;
return exposeEvent();
}), true, true);
buttonGroup.pack_start( createButton( "Scale and Transform", function() {
exposeEvent = scaleAndTransformDemo;
return exposeEvent();
}), true, true);
buttonGroup.pack_start( createButton( "Arc", function() {
exposeEvent = arcDemo;
return exposeEvent();
}), true, true);
buttonGroup.pack_start( createButton( "Clip", function() {
exposeEvent = clipDemo;
return exposeEvent();
}), true, true);
buttonGroup.pack_start( createButton( "Curve Rectangle", function() {
exposeEvent = curveRectangleDemo;
return exposeEvent();
}), true, true);
buttonGroup.pack_start( createButton( "Curve To", function() {
exposeEvent = curveToDemo;
return exposeEvent();
}), true, true);
buttonGroup.pack_start( createButton( "Rotate", function() {
exposeEvent = rotateDemo;
return exposeEvent();
} ), true, true);
return buttonGroup;
}
//
// Events
//
function drawingArea_ExposeEvent() {
return exposeEvent();
}
//
// Main
//
// Make the program terminate when the window is closed
window.signal.hide.connect(Gtk.main_quit);
drawingArea.signal.expose_event.connect( drawingArea_ExposeEvent );
hbox.pack_start( createButtonGroup(), false, false);
hbox.pack_start( drawingArea, true, true );
vbox.pack_start( hbox, true, true );
vbox.pack_start( status, false, false, 0);
window.add(vbox);
window.show_all();
window.resize( 640, 480 );
// Start the main GTK+ loop and initiate the program
Gtk.main();

關於這個例子,大部分都是從 Cairo網站上的範例搬來的,也幾乎演示了所有的函數,但還是有少數函數與屬性沒有涵蓋到,如 transform、setTransform、clearRect、quadraticCurveTo…等~
有需要再自己去翻 seed-canvas.c 看吧~

xming 與中文輸入

  1. 開終端機以後,要先設置環境變數,告訴兩大 framework 你要使用的輸入法:
    export GTK_IM_MODULE=gcin
    export QT_IM_MODULE=gcin

    ,這裡的 gcin,你可以替換為自己熟悉的輸入法,例如 scim、fcitx…。

  2. Hot key 會打架,所以你用 ctrl-space 的話,是 windows 的輸入法起來,不是 linux 的輸入法起來。因此要把 hot key 換掉,gcin 可以用 ctrl+alt+數字鍵 來切換,要切換為英文,則可以用 caps lock。這邊就看你自己輸入法的設定了。

Seed(1)

Ubuntu裡安裝 Seed 很簡單,參考PPA for Orange Owners裡,把

deb http://ppa.launchpad.net/orange-owners/ppa/ubuntu intrepid main
deb-src http://ppa.launchpad.net/orange-owners/ppa/ubuntu intrepid main

放到 /etc/sources.list 裡,然後用 sudo apt-get update 更新,sudo apt-get install seed 來安裝即可。

執行 script 也很簡單,有兩種方法:

  1. 直接以 seed 執行:seed your_script.js
  2. 把 js 檔的第一行改為 #!/usr/bin/env seed,再以 chmod +x 為 js 檔加上執行權限,就可以用 ./your_script.js 執行。

目前官方沒有文件說明 Seed 內部有哪些類別與方法,這很讓人困擾,這兩天看了 source code 跟 example code 之後,大致上有點了解。

Seed 主要的類別是 Seed,提供了如下方法:

  • include:用來含括其他 js,讓你可以為程式作適當的切割,不至於讓檔案變得太大而難以維護。
    Seed.include("other.js");
  • print:印字串。
    Seed.print("Hello world!");
  • check_syntax:檢查語法,你可以傳 javascript 程式進去檢查,如果有錯,會丟出 exception。
    try {
    Seed.check_syntax("Seed.print(;");
    Seed.print("syntax ok!");
    }
    catch( e ) {
    Seed.print( e.message );
    }
  • spawn:執行外部程式,執行以後會回傳一個 object,這個 object 有兩個屬性:stdout 與 stderr。
    var result = Seed.spawn("ls");
    Seed.print( "=== spawn result(stdout) ===" );
    Seed.print( result.stdout );
    Seed.print( "=== spawn result(stderr) ===" );
    Seed.print( result.stderr );
    
  • fork:這跟 C 的 fork() 一樣,回傳值是 0,表示是子行程,-1 表示失敗,大於 0 的值,表示是父行程。
    var pid = Seed.fork();
    if( pid == 0 ) { // child process
    var result = Seed.spawn( "ls" );
    Seed.print( result.stdout );
    Seed.quit();
    }
    else if( pid == -1 ) {
    Seed.print( "cannot create child process." );
    }
    else { // parent process.
    Seed.print( "I am parent process." );
    }
    
  • quit:離開。
  • introspect:這個函數可以用來探知類別成員函數如何使用,安裝 Seed 以後,/usr/share/doc/seed/examples 下有個 introspect.js,就是一個很好的範例。不過我還不是很懂怎麼去用~
  • import_namespace:含括其他 library 進來使用,不要跟 include 搞混了,include 是含括其他 js 檔。

把檔案內容放到環境變數

在 bash 下很簡單的一件事情,批次檔似乎沒有比較好的解~
在 bash 下:

VERSION=`cat version.txt`

在批次檔裡,我發現可以用 for 來解:

@For /f "" %%a in (version.txt) do (set VERSION=%%a)

但缺點是,當檔案有多行時,VERSION 會是最後一行的內容。

當 jQuery().ajax() 遇到 ASP

利用 jqGrid 新增中文欄位資料時,到伺服器端時,就變成亂碼了。
FireBug 大神幫忙,發現 request header content-type 的編碼是 utf-8,查過jqGrid的 source code,裡面也只是調用 jQuery 的 ajax 函數而已。
照理來說,應該可以用 $.ajaxSetup() 來修正,但試了好一陣子,發現沒辦法,即使我在 contentType 裡指定了 charset=big5,最後送出時,仍然會是 utf-8…

好吧,山不轉路轉,再拜請Google大神,發現有人利用 escape() 解,也就是先用 javascript escape() 編碼,server 端再解碼,這樣就解了。
大致的代碼是這樣:

//
$("#jqGrid2").jqGrid(
// ... 略 ...
).navGrid( "#pager2", {
// ... 略 ...
add:true,
addfunc: function() {
$("#jqGrid2").editGridRow( "new", {
url: "server.asp",
beforeSubmit: function( postdata, o ) {
var s = postdata[ "your_field_name" ];
var ret=[true, "", ""];
postdata[ "your_field_name" ] = escape( s );
return ret;
}
} );
return false;
}
} );

Xming 的字太小?

Xming 等同是 Windows 上的 X Server,使用的說明可以參考:Xming 簡易使用說明,圖文並茂,寫的非常好。
對我來說,唯一的問題是字太小,該怎麼解決?我找了好久~
最後終於意外發現,只要在 XLaunch 最後一個步驟的畫面的 “Additional parameters for Xming” 裡填上 -dpi 100,就可以解決字太小的問題。

vim auto completion

Linux Today看到這篇文章:Vi and Vim Editor: 5 Awesome Examples For Automatic Word Completion Using Ctrl-X Magic,看完的當下,非常的高興。
原本以為要安裝特定 plugin 才能達到的 auto completion 功能,居然早已內建,而且只需要在編輯時按下 ctrl+x ,再搭配下列按鍵即可:

  • ctrl+p:向前找可以自動完成的字
  • ctrl+n:向後找
  • ctrl+i:找所有單行開頭的,特別適合寫程式,因為通常函數都會是先以單行宣告,根據同事實驗,這還會去找 header 裡的…
  • ctrl+f:檔名的自動完成

能說什麼呢?只能說Vim太威了!