highlight.js

星期一, 5月 10, 2021

『質量』很好, 到底是質好還是量多

很多與我一樣的 5 年級生會不習慣對岸的用詞, 這倒不一定是對岸的用詞一定不好, 而是有些詞用起來就是很混淆。舉例來說, 『質量』一詞就很怪, 因為『質』與『量』是兩件事, 但是對岸口語上講的『質量』通常是指『質』, 例如, 『A 選手這一球質量很高, B 選手雖然接到了球卻無法回擊』, 不如就直接講『A 選手這一球力道很強』。『質』『量』不分, 實在很有問題。

星期五, 9月 11, 2020

在 linux 下複製資料夾內的所有檔案但不複製資料夾

同事問了一個問題, 他想要只把 A 資料夾下包含子資料夾內的檔案通通複製到 B 資料夾下, 但是不要在 B 資料夾下建立各層的子資料夾, 例如:
$ ls -R test
test:
a.txt  t2/

test/t2:
a2.txt
若希望把 test 下的 a.txt 和 test/t2 下的 a2.txt 複製到同一個資料夾下, 例如 dd 下, 但是不要在 dd 內建立 t2 資料夾, 這只靠 cp 指令的 -r 選項是做不到的。根據估狗大神幫我找到了這一篇討論, 找到了使用 find 指令的方式如下:
$ find test -type f -exec cp "{}" dd ";"
$ ls dd
a2.txt  a.txt
find 指令可以使用 -exec 選項在每次找到一個檔案時執行指定的指令, 指令中的 {} 會被取代為找到的檔案名稱, -exec 到 ";" 之間就是要執行的指令。利用這個選項, 就可以為資料夾中的每一個檔案執行 cp 指令, 複製到同一個資料夾中了。上述指令中之所以要將 {} 以及 ; 用 "" 括起來, 是為了避免被 shell 執行 shell expansion 而出錯。

星期二, 5月 26, 2020

Arduino Keyboard 程式庫 println 沒有送出 Enter 的問題

同事有一個程式模擬鍵盤輸入裝置, 使用了 Arduino 的 Keyboard 函式庫, 程式大概是這樣:
  Keyboard.begin();
  delay(2000);
   Keyboard.press(KEY_LEFT_GUI);
  delay(500);
   Keyboard.press('r');
  delay(500);
   Keyboard.releaseAll();
   Keyboard.println("NOTEPAD");                   //輸入文字 NOTEPAD
  delay(500);
也就是模擬按了 Win + r 開啟 Widnows 的『執行』交談窗, 然後輸入 "notepad" 開啟記事本, 但是卻發現執行後只會停在『執行』交談窗, 少了最後按下 Enter 執行所輸入指令的動作。後來使用了新版的 Arduino IDE 重新編譯執行這個程式卻又正常, 可以開啟記事本, 經過查證, 才發現 Arduino IDE 1.8.5 (含) 之前的 Keyboard 程式庫有問題, 1.8.6 就修正了。這個問題主要在於 Keyboard 是繼承自 Print 類別, Print 的 println 會使用字串版的 write 輸出字串, 程式如下:
size_t Print::write(const uint8_t *buffer, size_t size)
{
  size_t n = 0;
  while (size--) {
    if (write(*buffer++)) n++;
    else break;
  }
  return n;
}
在 while 迴圈中, 如果目前輸出的字元是 0, 就會當成是字串結尾, 跳離迴圈;否則會叫用字元版的 write() 輸出單一字元, 而在 Keyboard 類別中重新定義了這個字元版的 write(), 程式如下:
size_t Keyboard_::write(uint8_t c)
{
	uint8_t p = press(c);  // Keydown
	release(c);            // Keyup
	return p;              // just return the result of press() since release() almost always returns 1
}
這主要就是模擬按下並放開鍵盤上對應該字元按鍵的動作, 不過 press() 中會把 ASCII 碼透過 _asciimap 這個陣列轉換成對應按鍵的掃描碼, 而 '\r' (CR, 0x0D) 會對應到掃描碼 0:
const uint8_t _asciimap[128] =
{
	0x00,             // NUL
	0x00,             // SOH
	0x00,             // STX
	0x00,             // ETX
	0x00,             // EOT
	0x00,             // ENQ
	0x00,             // ACK  
	0x00,             // BEL
	0x2a,			// BS	Backspace
	0x2b,			// TAB	Tab
	0x28,			// LF	Enter
	0x00,             // VT 
	0x00,             // FF 
	0x00,             // CR 
	......
使得字元版的 write() 傳回 0, 導致字串版的 write() 認為字串結束而返回。由於 println() 在送出字串後會再送出 "\r\n", 也就是 CRLF 當成換行, 因此處理到 '\r' 就被當成字串結束而停止, 最後的 '\n' 就沒有機會送出, 因此不會模擬成按下 Enter 了。 為了修正這個問題, 從 Arduino 1.8.6 開始, Keyboard 類別就重新定義了自己的 write() 版本, 如下所示:
size_t Keyboard_::write(const uint8_t *buffer, size_t size) {
	size_t n = 0;
	while (size--) {
		if (*buffer != '\r') {
			if (write(*buffer)) {
			  n++;
			} else {
			  break;
			}
		}
		buffer++;
	}
	return n;
}
你可以看到當遇到 'r' 字元時, 它會當成沒看到, 繼續處理下一個字元, 這樣就可以避免上述問題了。

判斷 Arduino IDE 版本

由於同事遇到一個怪問題, 需要根據 Arduino IDE 版本使用不同的程式, 查了一下才知道, Arduino 在編譯時會定義一個巨集 ARDUINO, 格式為類似 10812, 表示 1.8.12 版, 只要利用 #if 判斷, 即可區別不同版本的 Arduino IDE 了, 例如:
#if ARDUINO<=10805
   Keyboard.press(KEY_RETURN);                  //按下 Enter
   Keyboard.release(KEY_RETURN);
#endif
就可以讓程式只有在 Arduino IDE 版本為 1.8.5(含) 之前的版本才會執行, 教新的版本就不會執行了。

星期日, 4月 12, 2020

在 WSL 1/2 執行 GUI 程式

WSL 很好用, 不過預設的情況下你只有 Terminal 跑 Shell 可以用, 如果想要測試一些圖形化界面的程式, 就要借助額外的程式, 好在已經有許多善心人士提供了免費的 X server 軟體, 可以在 Windows 環境裡執行 GUI 程式。我自己習慣使用的是 mobaXterm 軟體, 預設安裝好並不需要其他設定, 只要執行就會啟動 X server。接著, 就可以在 WSL 裡設定 X server 的位址與螢幕編號:
export DISPLAY=:0
上例中表示 X server 的位址就是 localhost, 或者 127.0.0.1, 螢幕編號為 0, 也就是第一個螢幕。設定好之後就可以執行 GUI 程式, 例如 gvim:

View post on imgur.com


如果使用 WSL2, 它會像是 VirtulaBox 一樣幫你建立一個虛擬網路界面, 你可以透過 Windows 下的 ipconfig 指令看到有一個網路界面名稱裡面有一個有 WSL 字樣, 就會標示這個界面中 Windows 這一端的 IP:
D:\wsl2 ❯❯❯ ipconfig

Windows IP 設定

....

乙太網路卡 vEthernet (WSL):

   連線特定 DNS 尾碼 . . . . . . . . :
   連結-本機 IPv6 位址 . . . . . . . : fe80::2d93:534a:4692:bc2e%53
   IPv4 位址 . . . . . . . . . . . . : 172.31.80.1
   子網路遮罩 . . . . . . . . . . . .: 255.255.240.0
   預設閘道 . . . . . . . . . . . . .:

172.31.80.1 就是 Windows 在這個虛擬網路中的 IP 位址。或者你也可以在 WSL2 中使用 ip route 指令查看, 就可以知道 Windows 那一端的 IP:
$ip  route
default via 172.31.80.1 dev eth0
172.31.80.0/20 dev eth0 proto kernel scope link src 172.31.86.214
上述設定 X server 位址的指令就要改成:
export DISPLAY=172.31.80.1:0
才能正確連到 X server。

WSL2 與 VirtualBox 不相容

WSL2 因為使用了 Hyper-V 虛擬機器, 所以比透過 API 轉換層執行的 WSL1 快得多, 不過也因為這個原因, 和 VirtualBox 並不相容, 即使使用了支援 HyperV 的 VirtualBox 6.X 版也一樣, 如果你在啟用了 WSL2 的系統上使用 VirtualBox 安裝例如 Linux 作業系統, 就會遇到到一些靈異現象, 像是我自己就遇到安裝到最後不成功, 或是好像安裝成功, 但是使用時執行 git clone 老是說什麼 hashcode 不對、或者是下載 .deb 檔但要透過 dpkg 安裝卻解壓縮失敗之類的。目前若要同時使用 WSL 與 VirtualBox, 就必須改用 WSL1, 而且要確認沒有啟用『虛擬機器平台』功能:

View post on imgur.com


另外, 也要確認 WSL2 要求你要安裝的 WSL2 Linux kernel update package 也要移除, 才能正常使用 VirtualBox。

星期六, 4月 11, 2020

VIM 的眾多版本差異

在 Linux 上安裝 vim 如果沒有特別指名套件名稱, 例如:
sudo apt install vim
那麼安裝的會是 vim-common 套件, 這個套件只會安裝文字模式的 vim, 不會安裝圖形化版本的 gvim, 而沒有圖形化版本的套件, 邊一時是不會加上 +xterm_clipboard 模組的, 也就是無法讓 vim 複製資料到系統剪貼區 (clipboard), 也無法從剪貼區貼資料到 vim 中。

因此, 建議在安裝 vim 時, 可以選擇有圖形化版本的套件, 例如:
  • vim-gui-common:通用行的圖形化版本, 如果沒有什麼特別需求, 或是面對以下版本不知道該選那一種, 就可以安裝這個版本。
  • vim-athena:採用 X Athena 圖形元件程式庫的版本, 如果你執行這個套建中的 gvim, 會注意到他的圖形界面長的很不一樣, 那就是 X Athena 程式庫。這個版本體積小一點, 如果你不介意界面長的怪怪的, 或者根本就不會執行 gvim, 那安裝這個版本根本沒差。
  • vim-gtk/vim-gtk3:搭配 gtk 同行界面程式庫的版本, gvim 的界面看起來會正常許多。如果你的環境本來就會用到許多使用 gtk 建制的軟體, 那就可以安裝這一個套件。
如果你根本不會執行 gvim, 也不在乎能不能與系統剪貼簿交換資料, 那麼也可以安裝 vim-tiny 套件, 這個套件是 vim-common 的精簡版, 最主要的差別是 vim-tiny 只能使用 vim 語法撰寫 vim 腳本, 無法使用 Python 或是其他程式語言撰寫 vim 腳本。