2011/09/07

Anatomy of Android LockScreen


最近因為專案需求研究了一下Android LockScreen相關部分的類別,花了不少時間,但類別圖一畫出來便就豁然開朗。如下圖







整個LockScreen我把它分成5大部分,此處數字請與圖配合食用。

  1. SecuritySettings為在setting app介面的顯示部分。它會用到LockPatternUtils來讀取現在Lock的類型。
  2. LockPatternUtils為紀錄所有Lock相關資訊的類別,包含密碼正確判斷,密碼檔存放,Lock類型等。
  3. 這部分包含幾個類別,這些類別都是負責顯示的部分。其中最底下的3個類別,{PasswordUnlock,AccountUnlock,PatternUnlock}Screen,它們繼承了View類別,做真正顯示的部分。LockPatternKeyguardView以及用它的LockPatternKeyguardViewProperties會根據LockPatternUtils讀取到的Lock類型,來具現{PasswordUnlock,AccountUnlock,PatternUnlock}Screen。
  4. KeyguardViewMediator為大魔王,它將第3部分那些複雜的東西,統一成一個類別方便使用(即Design Pattern裡的Mediator),它主要用KeyguardView{Properties,Manager}
  5. 這部分就不用解說了,這是Android platform的部分。

相關修改LockScreen的APP


Market可看到的LockScreen修改程式有很多,像是WidgetLocker、Flyscreen、Nolock等等。若是反組譯其中之一大概都可以看到這樣的code。

      KeyguardManager localKeyguardManager = (KeyguardManager)getSystemService("keyguard");
      // enable lockscreen
      this.mKeyguardLock.reenableKeyguard();
      // disable lockscreen
      this.mKeyguardLock.disableKeyguard();

一直往下追最後就可以從剛才類別圖的第5部分的最上方類別WindowManagerService內的方法disableKeyguard()等等。

有興趣的人再自己追追看囉~

2011/09/04

Custom gesture support on Ubuntu

恰巧在IBM developer看到了一篇Ubuntu上gesture的教學,但因為那篇是perl及年久失修(?),所以我稍微在Ubuntu 11.04及用python重寫了這個應用。

這篇文章將會給介紹Ubuntu上怎麼來寫筆電的touchpad gesture應用,主要用python、synclient和xdotool。synclient是用來抓touchpad被按下的座標值及指數。xdotool則用來送出相對應的鍵盤行為。

synclient

首先,在Ubuntu 11.04上,若要啟用synclient必需先修改xorg.conf,加入Option "SHMConfig" "on",如下

//file: /usr/share/X11/xorg.conf.d/50-synaptics.conf
Section "InputClass"
        Identifier "touchpad catchall"
        Driver "synaptics"
        MatchIsTouchpad "on"
        MatchDevicePath "/dev/input/event*"
        Option "SHMConfig" "on"
EndSection

接著可在terminal上使用synclient,來看一下他的輸出:

doro@doro-UL80Jt ~/src/touchpad $ synclient -m 10
    time     x    y   z f  w  l r u d m     multi  gl gm gr gdx gdy
   0.000   418  413   0 0  0  0 0 0 0 0  00000000
   0.617   363  359  31 1  0  0 0 0 0 0  00000000
   0.627   362  356  31 1  0  0 0 0 0 0  00000000
   0.637   363  352  31 1  0  0 0 0 0 0  00000000
   0.657   364  349  31 1  0  0 0 0 0 0  00000000
   0.677   368  347  31 1  0  0 0 0 0 0  00000000
   0.688   371  344  31 1  0  0 0 0 0 0  00000000
   0.708   373  340  31 1  0  0 0 0 0 0  00000000
   0.728   375  336  31 1  0  0 0 0 0 0  00000000
   0.738   376  333  31 1  0  0 0 0 0 0  00000000
   0.849   376  333   0 0  0  0 0 0 0 0  00000000
   1.688   232  672  31 2  0  0 0 0 0 0  00000000
   1.718   274  679  31 3  0  0 0 0 0 0  00000000
   1.799   274  679   0 0  0  0 0 0 0 0  00000000

這個指令會輸出目前touchpad被按下的點(x,y)以及f欄位標示出指數。因此我們便可利用這3個值來判斷手勢。最後再利用xdotool來執行我們要做的行為。

Example

底下這個例子,將會實作
  • 若3指按下時,送出super+w,進入expo mode 
  • 若3指按下後移動上/下/左/右超過100個單位,送出ctrl+alt+Up/Down/Left/Right,來做work space的切換 

#!/usr/bin/python
# -*- coding: utf-8 -*-

import logging
logging.basicConfig(filename='/tmp/multitouch.log',
        level=logging.DEBUG, 
        format='[%(asctime)s][%(name)s][%(levelname)s] %(message)s')
logger = logging.getLogger('multitouch')

import subprocess
import re

if __name__ == "__main__":
    cmd = 'synclient -m 10'

    p = subprocess.Popen(cmd, stdout = subprocess.PIPE, 
            stderr = subprocess.STDOUT, shell = True)
    skip = False
    try:
        while True:
            line = p.stdout.readline()
            if not line:
                break
            try:
                tokens = [x for x in re.split('([^0-9\.])+', line.strip()) if x.strip()]
                x, y, fingers = int(tokens[1]), int(tokens[2]), int(tokens[4])
                logger.debug('Result: ' + str(tokens))
                if fingers == 3:
                    if skip:
                        continue
                    skip = True
                    start_x, start_y = x, y
                else:
                    if skip:
                        diff_x, diff_y = (x - start_x), (y - start_y)
                        if diff_x > 100:
                            logger.info('send...diff_x > 100')
                            subprocess.Popen("xdotool key ctrl+alt+Right", shell=True)
                        elif diff_x < -100:
                            logger.info('send...diff_x < -100')
                            subprocess.Popen("xdotool key ctrl+alt+Left", shell=True)
                        elif diff_y > 100:
                            logger.info('send...diff_y > 100')
                            subprocess.Popen("xdotool key ctrl+alt+Down", shell=True)
                        elif diff_y < -100:
                            logger.info('send...diff_y < -100')
                            subprocess.Popen("xdotool key ctrl+alt+Up", shell=True)
                        else:
                            logger.info('send...super+w')
                            subprocess.Popen("xdotool key super+w", shell=True)
                    skip = False
            except (IndexError, ValueError):
                pass
    except KeyboardInterrupt:
        pass 
(keyboard is better, but just for fun :D)

2011/08/19

Linux Kernel Initcall

Linux kernel module的module init function 如下,本文將以kernel module為例,說明他們是怎麼被kernel核心呼叫到。
static int __devinit tegra_kbc_init(void) {	
    pr_debug("KBC: tegra_kbc_init\n")
	return platform_driver_register(&tegra_kbc_driver)
}
module_init(tegra_kbc_init)

module_init

隨便找到有呼叫module_init的kernel module往上追,可以找到最後是呼叫到__define_inicall(level,fn,id)這個macro,如下
// File: include/linux/init.h
#define module_init(x)	__initcall(x)
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn)		__define_initcall("6",fn,6)
#define __define_initcall(level,fn,id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" level ".init"))) = fn
其中以module_init(tegra_kbc_init)會被展開成
static initcall_t __initcall_tegra_kbc_init6 __used 
__attribute__((__section__(".initcall6.init"))) = tegra_kbc_init 
由__attribute__得知,它最後會被放到initcall6.init這個section裡。而linker連結時會根據vmlinux.lds.S來將它放到指定的section。如下的INIT_CALLS。
// File: arch/arm/kernel/vmlinux.lds.S
SECTIONS {		
    INIT_SETUP(16)
    INIT_CALLS
    CON_INITCALL
    SECURITY_INITCALL
    INIT_RAM_FS
再往下追就看到了.initcall6.init。
// File: include/asm-generic/vmlinux.lds.h
#define INIT_CALLS							\
		VMLINUX_SYMBOL(__initcall_start) = .;			\
		INITCALLS						\
		VMLINUX_SYMBOL(__initcall_end) = .;

#define INITCALLS							\
	*(.initcallearly.init)						\
	VMLINUX_SYMBOL(__early_initcall_end) = .;			\
  	*(.initcall0.init)						\
  	*(.initcall0s.init)						\
  	*(.initcall1.init)						\
  	*(.initcall1s.init)						\
  	*(.initcall2.init)						\
  	*(.initcall2s.init)						\
  	*(.initcall3.init)						\
  	*(.initcall3s.init)						\
  	*(.initcall4.init)						\
  	*(.initcall4s.init)						\
  	*(.initcall5.init)						\
  	*(.initcall5s.init)						\
	*(.initcallrootfs.init)						\
  	*(.initcall6.init)						\
  	*(.initcall6s.init)						\
  	*(.initcall7.init)						\
  	*(.initcall7s.init)

do_initcalls

kernel是從那裡呼叫到init calls,可以從kernel的entry point,init/main.c,找到下面這個函式。
// File: init/main.c
extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];
static void __init do_initcalls(void) {	
    initcall_t *fn;
	for (fn = __early_initcall_end; fn < __initcall_end; fn++)
        do_one_initcall(*fn);
	/* Make sure there is no pending stuff from the initcall sequence */	
    flush_scheduled_work();
}
一路往上追可以知道do_initcalls()在start_kernel()的最後的函式裡面用一個kernel thread間接呼叫,這裡很容易就可以追到了,我就不再列出。

2011/08/17

Android上的LAN device

最近要在Android上加LAN device support,該LAN device 是USB interface的SMSC9514。

ifconfig


adb shell後用以下指令可以看到目前板子上的網路裝置
# busybox-armv6l ifconfig -a

busybox可自行上網下載執行檔

ifconfig 運作方式


追busybox的code可以找到這段程式

// file: busybox-1.18.4/networking/interface.c
#define _PATH_PROCNET_DEV               "/proc/net/dev"

static int if_readlist_proc(char *target)
{
	static smallint proc_read;

	FILE *fh;
	char buf[512];
	struct interface *ife;
	int err, procnetdev_vsn;

	if (proc_read)
		return 0;
	if (!target)
		proc_read = 1;

	fh = fopen_or_warn(_PATH_PROCNET_DEV, "r");
	if (!fh) {
		return if_readconf();
	}

可以看到它open了一個檔案_PATH_PROCNET_DEV,這個檔案為"/proc/net/dev",所以得知ifconfig是從那裡抓network devices name

driver


秀出/proc/net/dev訊息


至於driver如何將這些資訊秀出可以參考 dev_seq_show() 如下

// file: kernel/net/core/dev.c
static void dev_seq_printf_stats(struct seq_file *seq, struct net_device *dev)
{
	struct rtnl_link_stats64 temp;
	const struct rtnl_link_stats64 *stats = dev_get_stats(dev, &temp);

	seq_printf(seq, "%6s: %7llu %7llu %4llu %4llu %4llu %5llu %10llu %9llu "
		   "%8llu %7llu %4llu %4llu %4llu %5llu %7llu %10llu\n",
		   dev->name, stats->rx_bytes, stats->rx_packets,
		   stats->rx_errors,
		   stats->rx_dropped + stats->rx_missed_errors,
		   stats->rx_fifo_errors,
		   stats->rx_length_errors + stats->rx_over_errors +
		    stats->rx_crc_errors + stats->rx_frame_errors,
		   stats->rx_compressed, stats->multicast,
		   stats->tx_bytes, stats->tx_packets,
		   stats->tx_errors, stats->tx_dropped,
		   stats->tx_fifo_errors, stats->collisions,
		   stats->tx_carrier_errors +
		    stats->tx_aborted_errors +
		    stats->tx_window_errors +
		    stats->tx_heartbeat_errors,
		   stats->tx_compressed);
}

/*
 *	Called from the PROCfs module. This now uses the new arbitrary sized
 *	/proc/net interface to create /proc/net/dev
 */
static int dev_seq_show(struct seq_file *seq, void *v)
{
	if (v == SEQ_START_TOKEN)
		seq_puts(seq, "Inter-|   Receive                            "
			      "                    |  Transmit\n"
			      " face |bytes    packets errs drop fifo frame "
			      "compressed multicast|bytes    packets errs "
			      "drop fifo colls carrier compressed\n");
	else
		dev_seq_printf_stats(seq, v);
	return 0;
}

driver註冊network device


driver要跟kernel註冊自己是個network device,註冊時最底層是用到 list_netdevice

// file: kernel/net/core/dev.c
/* Device list insertion */
static int list_netdevice(struct net_device *dev)
{
	struct net *net = dev_net(dev);

	ASSERT_RTNL();

	write_lock_bh(&dev_base_lock);
	list_add_tail_rcu(&dev->dev_list, &net->dev_base_head);
	hlist_add_head_rcu(&dev->name_hlist, dev_name_hash(net, dev->name));
	hlist_add_head_rcu(&dev->index_hlist,
			   dev_index_hash(net, dev->ifindex));
	write_unlock_bh(&dev_base_lock);
	return 0;
}

最外層是用

// file: kernel/drivers/net/usb/usbnet.c
int
usbnet_probe (struct usb_interface *udev, const struct usb_device_id *prod)
{
	status = register_netdev (net);

usbnet_probe又是誰呼叫了,我們就是我們要用的LAN chip SMSC95XX用到了

// file: kernel/drivers/net/usb/smsc95xx.c
static struct usb_driver smsc95xx_driver = {
	.name		= "smsc95xx",
	.id_table	= products,
	.probe		= usbnet_probe,
	.suspend	= usbnet_suspend,
	.resume		= usbnet_resume,
	.disconnect	= usbnet_disconnect,
};

SMSC9514

# $ adb shell
# lsusb
1d6b:0002 (bus 1, device 1)
1d6b:0002 (bus 2, device 1)
# # 插入裝置
# lsusb
1d6b:0002 (bus 1, device 1)
1d6b:0002 (bus 2, device 1)
0424:9514 (bus 1, device 2)
0424:ec00 (bus 1, device 3)
# busybox-armv6l ash
/ # # 設IP跟DNS
/ # busybox-armv6l ifconfig eth0 192.168.1.123
/ # busybox-armv6l route add default gw 192.168.1.1
/ # ping 168.95.1.1
PING 168.95.1.1 (168.95.1.1) 56(84) bytes of data.
64 bytes from 168.95.1.1: icmp_seq=1 ttl=242 time=670 ms
64 bytes from 168.95.1.1: icmp_seq=2 ttl=242 time=989 ms
64 bytes from 168.95.1.1: icmp_seq=3 ttl=242 time=589 ms
#setprop net.dns1 168.95.1.1

UI


這部分比較麻煩,可以自行參考android-x86的實作。

2011/08/15

Android WIFI Service

需控制Wifi的App會透過system service來呼叫到wifi service,像是在packages/apps/Settings裡的wifi設定頁面,它們會用類似這樣的語法來取得wifi system service。

mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
本文將探討由wifi system service到hardware做動的部分。


WifiService 註冊


ServiceManager.addService() 為所有service註冊都要用到的函式。WifiService也是,程式如下。

private ConnectivityService(Context context) {
        for (int netType : mPriorityList) {
            switch (mNetAttributes[netType].mRadio) {
            case ConnectivityManager.TYPE_WIFI:
                if (DBG) log("Starting Wifi Service.");
                WifiStateTracker wst = new WifiStateTracker();
                WifiService wifiService = new WifiService(context);
                ServiceManager.addService(Context.WIFI_SERVICE, wifiService);
                wifiService.checkAndStartWifi();
                mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst;
                wst.startMonitoring(context, mHandler);

                //TODO: as part of WWS refactor, create only when needed
                mWifiWatchdogService = new WifiWatchdogService(context);
                break;
ConnectivityService是在SystemServer建構函式用到,至於SystemServer如何被叫到,自行去找Zygote的介紹

try {
                Slog.i(TAG, "Connectivity Service");
                connectivity = ConnectivityService.getInstance(context);
                ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity);
            } catch (Throwable e) {
                Slog.e(TAG, "Failure starting Connectivity Service", e);
            }

Service


開關Wifi是透過setWifiEnabled(),其Service內的實作程式如下

public synchronized boolean setWifiEnabled(boolean enable) {
        enforceChangePermission();

        if (DBG) {
            Slog.e(TAG, "Invoking mWifiStateMachine.setWifiEnabled\n");
        }

        if (enable) {
            reportStartWorkSource();
        }
        mWifiStateMachine.setWifiEnabled(enable);
最重要的是它用了WifiStateMachine::setWifiEnabled(),它實作如下

public void setWifiEnabled(boolean enable) {
        mLastEnableUid.set(Binder.getCallingUid());
        if (enable) {
            /* Argument is the state that is entered prior to load */
            sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_STATE_ENABLING, 0));
            sendMessage(CMD_START_SUPPLICANT);
        } else {
            sendMessage(CMD_STOP_SUPPLICANT);
            /* Argument is the state that is entered upon success */
            sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_DISABLED, 0));
        }
    }
可以看到它用了很多sendMessage(),這部分用了樹狀的有限狀態機,sendMessage是拿來叫目前所處的State物件,根據sendMessage要求的狀態來呼叫processMessage()處理。WifiStateMachine的樹狀結構如下

addState(mDefaultState);
            addState(mInitialState, mDefaultState);
            addState(mDriverUnloadingState, mDefaultState);
            addState(mDriverUnloadedState, mDefaultState);
                addState(mDriverFailedState, mDriverUnloadedState);
            addState(mDriverLoadingState, mDefaultState);
            addState(mDriverLoadedState, mDefaultState);
            addState(mSupplicantStartingState, mDefaultState);
            addState(mSupplicantStartedState, mDefaultState);
                addState(mDriverStartingState, mSupplicantStartedState);
                addState(mDriverStartedState, mSupplicantStartedState);
                    addState(mScanModeState, mDriverStartedState);
                    addState(mConnectModeState, mDriverStartedState);
                        addState(mConnectingState, mConnectModeState);
                        addState(mConnectedState, mConnectModeState);
                        addState(mDisconnectingState, mConnectModeState);
                        addState(mDisconnectedState, mConnectModeState);
                        addState(mWaitForWpsCompletionState, mConnectModeState);
                addState(mDriverStoppingState, mSupplicantStartedState);
                addState(mDriverStoppedState, mSupplicantStartedState);
            addState(mSupplicantStoppingState, mDefaultState);
            addState(mSoftApStartedState, mDefaultState);

        setInitialState(mInitialState);
要先確定自己目前的狀態才能知道程式會用那個State物件的processMessage()/enter()來處理。以上面的setWifiEnabled(true)為例,它會要求DriverUnloadedState::processMessage()處理CMD_LOAD_DRIVER。

class DriverUnloadedState extends HierarchicalState {
        @Override
        public void enter() {
            if (DBG) Log.d(TAG, getName() + "\n");
            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
        }
        @Override
        public boolean processMessage(Message message) {
            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
            switch (message.what) {
                case CMD_LOAD_DRIVER:
                    transitionTo(mDriverLoadingState);
                    break;
                default:
                    return NOT_HANDLED;
            }
            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
            return HANDLED;
        }
    }
DriverUnloadedState又轉換狀態到DriverLoadingState,轉過去時會先做enter() method如下

class DriverLoadingState extends HierarchicalState {
        @Override
        public void enter() {
            if (DBG) Log.d(TAG, getName() + "\n");
            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());

            final Message message = new Message();
            message.copyFrom(getCurrentMessage());
            /* TODO: add a timeout to fail when driver load is hung.
		        Similarly for driver unload.
             */
            new Thread(new Runnable() {
                public void run() {
                    mWakeLock.acquire();
                    //enabling state
                    switch(message.arg1) {
                        case WIFI_STATE_ENABLING:
                            setWifiState(WIFI_STATE_ENABLING);
                            break;
                        case WIFI_AP_STATE_ENABLING:
                            setWifiApState(WIFI_AP_STATE_ENABLING);
                            break;
                    }

                    if(WifiNative.loadDriver()) {
                        Log.d(TAG, "Driver load successful");
                        sendMessage(CMD_LOAD_DRIVER_SUCCESS);
在此method中呼叫了WifiNative.loadDriver(),它真正會呼叫到底層叫loading kernel module的動作。終於要進入JNI了。

public class WifiNative {

    static final int BLUETOOTH_COEXISTENCE_MODE_ENABLED = 0;
    static final int BLUETOOTH_COEXISTENCE_MODE_DISABLED = 1;
    static final int BLUETOOTH_COEXISTENCE_MODE_SENSE = 2;

    public native static String getErrorString(int errorCode);

    public native static boolean loadDriver();

JNI


以上面的loadDriver()會呼叫到

static jboolean android_net_wifi_loadDriver(JNIEnv* env, jobject clazz)
{
    return (jboolean)(::wifi_load_driver() == 0);
}
wifi_load_driver()則是HAL提供。

HAL


HAL該提供的函式可參考hardware/libhardware_legacy/include/hardware_legacy/wifi.h,wifi_load_driver()的實作如下,可以看到insmod的關鍵system call。

int wifi_load_driver()
{
    char driver_status[PROPERTY_VALUE_MAX];
    int count = 100; /* wait at most 20 seconds for completion */

    if (is_wifi_driver_loaded()) {
        return 0;
    }

    if (insmod(DRIVER_MODULE_PATH, DRIVER_MODULE_ARG) < 0)
        return -1;

    if (strcmp(FIRMWARE_LOADER,"") == 0) {

其它

除了loading kernel driver外,其它有關wifi操作的部分皆是透過wpa_supplicant這套library,這套library很大,所以我不介紹了,有興趣的人可以參考external/wpa_supplicant。


2011/07/25

Synology DSM 3.2 Beta 之夜


上週應以前大學同學之邀再加上抽獎獎項的誘惑,參加了Synology Beta之夜。其間為了抽獎非常認真做了筆記(沒抽到我,完全用不到^^||),今天整理了一下筆記,順道介紹一下當天聽到了什麼新功能。

圖:真是人山人海

先說明一下我大概是半年前買了他們家的DS211j,目的是1)拿來抓迷之片,2)當網芳使用,3)備份我重要的文件。使用到現在以現有功能我是很滿意,但是我還是覺得缺了一些功能,像是dropbox的功能,本來還期待這次發表會會發表,都只能怪主持人一直喊private cloud,讓我抱了一絲絲的期待。

dropbox,不要說用NFS or curlftpfs or sshfs來做類似的應用(它們家的ssh service也沒有技援sshfs,這我也要抱怨一下),但還是有很多dropbox可以達到的方便性是上述方案做不到。此外,現階段已經有很多open source的dropbox相關軟體,其實群暉只要花點時間改一下應該就可以用到他們的產品上 :)。

上面講了那麼多不相關的東西,我還是要稱讚一下當天presenter的功力很棒,真是完全沒有冷場,2個小時根本就在歡笑中渡過。這應該算是我以前到現在所有參加過的宅宅活動裡頭,氣氛我覺得數一數二的好。此外當天是在君品酒店辦的,還有豐富的餐點,有考慮到有人是下班不吃飯直接衝去。


圖:少不了要有豐盛的食物

廢話到此,我還是稍微來看圖說故事以及部分的新功能介紹。請讓我以Ubuntu/Android user and programmer的角度來看這些新功能,由喜好程度不需要->很需要來介紹。

以下是「我不是企業用戶不需要這個功能」的功能
  • LDAP (還好我家只有5個人)
  • syslog aggregative (有經驗的阿宅網管應該會有自己的一套方法 )
  • Google cloud printer (我覺得有點不太實用,他有技援PDF or DOC?不論實用性,我是覺得不錯玩~ 好笑的是當天demo失敗,最後的presenter還補一槍)
  • 監控系統,跨瀏覽器、方便佈署、效能提升 (如果那天我家遭小偷了或許我會買個ip cam試試)
圖:新版支援統一管理LDAP

圖:demo google cloud print結果突搥了 XDDD

以下有了它「世界更美好」的功能
  • mount everything,簡單的說就是支援把別台電腦的samba資料匣mount到NAS上,還有mount iso檔。(很實用的功能,實作起來又非常非常非常簡單)
  • mobile app,也就是iOS or Android app也有支援軟體。(雖然還用不到,但上面可以做的東西大有可為)
  • 全新的相簿介面,又可同步於facebook相簿。(我家頻寬自己都不夠用了,還要給別人用)

圖:報告Mobile App的RD講者,可能原本就想說RD來報告一定很無聊,我想這位RD可以轉行做PM。

圖:PM在講相簿新增的功能

以下是「考試100分」的功能
  • 拖拉檔案上傳功能
  • Youtube下載功能
  • 免空下載功能
這三個功能是個人覺得比較有機會用到的功能,尤其是平常我常常在用bt抓ubuntu iso image(??),如果可以用拖拉方式上傳種子真是非常方便。(我還是想要有個人的dropbox啦)

講到拖拉種子進去網頁,如果Synology可以固定一個資料匣,讓我用網芳(NFS...)的方式拖拉進去,它就會幫我自動啟動Download Station抓資料,那我會覺得更實用。因為我根本就不想開網頁的管理介面。

結論

Synology的管理介面真的是獨樹一格,功能性也是NAS廠商中的佼佼者,我用了半年下來十分的滿意,我也因此慫恿了一些有相關需求的人買它們家的東西。

這次藉由參加這個活動,也開始思考我會希望他們再提供什麼功能,列舉如下
  • dropbox
  • version control file folder (具有版本控制的資料匣)
  • open api
  • 部分功能與nautilus or explorer結合

最後,若有興趣瞭解更多DSM 3.2 Beta功能的人可以參考當天投影片[這裡下載]

2011/06/09

zim plugins: 個人筆記軟體

Why zim?

前陣子開始習慣用zim來當做個人筆記的軟體,在使用zim之前我survey了許多其他筆記軟體,像是Evernote,Nevernote,Tomboy,gnote等。最後我還是選擇了zim,主要有以下原因
  • wiki語法
  • 所見即所得的介面
  • 筆記以文字檔儲存
  • 內建常用格式 (h1, h2, bold, italy, code...)
  • 容易hack (python)
  • 配合dokuwiki使用,可當公司內的文件分享系統
  • 配合dropbox,便成雲端筆記本
  • 配合shutter,螢幕截圖超方便
這篇文章,主要會介紹一個搜尋文件程式及如何將zim的文件發佈要dokuwiki上。


zim search

zim search是一個視窗程式,以wxPython寫成,輸入欲搜尋字串,它會將有此字串的文件列出,並依照權重排序,雙點任何列便可直接在zim中開啟該文件。此工具截圖如下



設定

這隻程式接受2個參數,第一為zim筆記本名,第二為筆記本路徑,zim裡的tools -> custom tools設定如下




源碼find.py可由此抓到 https://github.com/fcwu/zim-plugins

zim wiki deploy

為了分享我的筆記給其他同仁觀看,我架了dokuwiki當做分享平台,dokuwiki的儲存格式(文字檔)及wiki語法跟zim只有些許的不同,為了做語法的轉換及附件(圖片,或附檔)的發佈,因此我寫了這個腳本。

用此腳本發佈在dokuwiki的結果如下圖:



zim上觀看如下




設定

此腳本接受的參數如下
  1. 筆記本名
  2. dokuwiki root path
  3. user name
  4. 附件路徑
  5. 文本路徑
其中user name是用來區別的文件應該存放的位置。舉例來說dokuwiki root path為/var/www/wiki/,user name為doro則文件會發佈放在/var/www/wiki/data/pages/doro,附件會發佈在/var/www/wiki/data/media/doro

使用前請先確定已建立這些路徑,且擁有寫入權限。zim設定如下圖:



源碼deploy_wiki.py可由此抓到 https://github.com/fcwu/zim-plugins