<!--
title: Rofi 簡易入門
summary: 桌面環境下，製作快速選單的好幫手
date: 2022-10-24T13:27:59+08:00
categories: How I Work
-->

目前我使用的 Linux Distro 是基於 Arch 的 [Archcraft]，
它是個相當簡約的系統。
作者 [aditya] 並沒有為常用功能在裡面安裝太多的 GUI 軟體，
例如 APP Launcher, wifi 選單或是休眠/關機選單等。
取而代之的，是許多基於 [Rofi] 的簡短腳本。

既然我都用了這麼久的 Archcraft，自然也要了解一下這個重要的元件如何運作。
研究了一下才發現，這個元件真是不可多得的好工具啊！


## Rofi 的作用

因為常見的 Package Manager 應該都有支援 Rofi 這個套件，
這邊就先略過安裝/編譯步驟不談。

在[官網][Rofi]上，Rofi 是如此定義自己的：

> A window switcher, application launcher and dmenu replacement 

### Window Switcher

就是按下 `Alt + Tab` 會出現的那個東東！
在持續壓下 `Alt` 的情況下，連續按下 `Tab` 可以快速切換已開啟的應用程式。
這個特性在微軟的 Windows 和不少 Linux Distro 都有實作，
可算是個人電腦桌面環境中公認的超實用選項。

### Application Launcher

這個功能則更容易理解！對於一般 Windows 使用者來講，就是「開始」選單嘛。
而在其它 Linux Distro 中，則會顯示 [XDG] （X桌面工作群組）中事先定義的應用程式。
使用者可以輸入關鍵字縮小搜索範圍，並用 tab 或其它按鍵選擇想要啟動的應用程式。
在我的桌面中，它是長這樣的：

![](https://topo.tw/photos/2022-10/24_14h04m29s.png)

### dmenu

[dmenu] 可能就比較小眾了。
有接觸過輕量級 Linux 桌面環境的朋友可能會比較熟悉。
簡單來說，這個指令可以：

1. 將標準輸入中每一行的內容當作可選選項，
1. 在桌面環境顯示選單
1. 使用者可以在輸入欄位鍵入關鍵字縮小搜索範圍，再用 Enter 選擇其中一個選項
1. 最後選中的選項將作為 dmenu 的標準輸出

由文字理解可能不太容易，這邊就來示範一下

```bash
# 印出 1~100，共 100 行內容。經由 STDIN 交給 dmenu 處理
# dmenu 的輸出會被印在螢幕上
seq 100 | dmenu | xargs -i echo Entry {} is selected
```

在 shell 中執行以上指令，會馬上在螢幕頂端看到像這樣的選單：

![](https://topo.tw/photos/2022-10/24_14h25m05s.png)

最左邊是輸入欄位，往右以後就是可選的選項。
可以在上圖看到，一旦進行了輸入，右邊的選項馬上就會縮減匹配的 Entry。
在選擇了數字 `3` 按下 Enter 後，shell 馬上會印出 `Entry 3 is selected`。

可以知道，dmenu 最大的作用就是方便使用者從一堆選項中快速取得目標，
再交由其它應用程式處理該選項。

接著說回 Rofi。我們可以用 `-dmenu` 這個參數來模擬 dmenu 的行為。
現在用上面的例子來啟動 Rofi：

```bash
seq 100 | rofi -dmenu | xargs -i echo Entry {} is selected
```

和之前一樣出現了選單，不過不再像 dmenu 是用單行列出所有選項。
而是視覺元素更加豐富的選單： 

![](https://topo.tw/photos/2022-10/24_14h44m13s.png)

可以看到，Rofi 自稱是 **dmenu replacement** 也不是沒有道理的，
它將原本簡陋的介面大大強化。
另外，它也為不同使用情境提供更多自訂選項。
下面就來一一解說：


## 使用 Rofi 

雖然將標準輸入作為選單乍一看好像是很樸素的功能，
但對於每天接觸 Command Line 的使用者來說，
能夠用如此直覺的方式來與選單互動，dmenu 和 Rofi 其實是相當強力的工具。

想像一下，你有許多已經寫好的腳本，但每次使用時總是在參數的輸入上花費大量時間，
這時這類選單軟體的優勢就出來了。

不過在談到自訂的腳本之前，可以看看 Rofi 預先提供的幾個實用模式(mode)。
Rofi 使用參數 `-show` 來指定要啟動的預設模式。

### 視窗切換

```bash
rofi -show window
```

window mode。使用以上的指令，即可呼叫出視窗選單。
使用者選擇要切換的視窗後，即可將桌面環境的焦點引導到選中的視窗。

![](https://topo.tw/photos/2022-10/24_19h26m16s.png)

### 啟動指令

```bash
rofi -show run
```

run mode。使用以上的指令，即可呼叫出指令選單。
和 shell 中一樣，選單中的指令是從環境變數 `PATH` 來的，
所以會有相當多的 Entry。

![](https://topo.tw/photos/2022-10/24_19h51m26s.png)

### 啟動應用程式

```bash
rofi -show drun
```

drun mode。這就是大家平常熟悉的 APP Launcher 啦！。
選單中的指令是從 XDG 來的。所以幾乎每一個都帶有專用圖標。

![](https://topo.tw/photos/2022-10/24_19h53m07s.png)

### 建立 SSH 連線

```bash
rofi -show ssh
```

是的！還可以在 Rofi 中選擇主機連線。
Rofi 會自動從 `~/.ssh/known_hosts` 和 `~/.ssh/config`裡讀取 SSH 的相關設定。
選擇主機後，即可自動開啟 Terminal Emulator 進行連線。

![](https://topo.tw/photos/2022-10/24_20h07m52s.png)

### 同時使用多個模式

```bash
rofi -show drun -modes window,drun,run,ssh
```

Rofi 可以同時啟動多種模式。
使用 `-modes` 可以指定這些模式，它們彼此間用逗號分隔。
其實在不指定 `-modes` 的情況下，Rofi 預設就會同時啟動以上四種模式。
（注意一下上面的幾個選單圖片，在最底下都有可切換的模式）

`-show` 參數指定啟動時要第一個使用的模式，這邊我們選擇可以啟動應用程式的 `drun`。
若要切換模式，使用 `Ctrl + Tab` 便可以依序切換。

### 其它預設模式

Rofi 還包含 `filebrowser`(檔案選擇器), `keys`(快捷鍵選單) 等其它模式，
詳情可以參考 [man page]。


## 使用自訂的指令新增模式

當然除了標準輸入和以上的幾個預設 mode 之外，
也可以使用自訂的指令來創造選單，並設定選擇之後要執行的動作

```bash
rofi -show foo -modes foo:./bar
```

自訂的模式用參數 `-modes` 指定，名稱和指令以 `:` 分隔。
以上是利用名叫 `bar` 的指令為 Rofi 新增名為 `foo` 的 mode。
然後再用 `-show` 參數來指定這個新的 mode。

在撰寫指令時（也就是上方的 `bar` ），只需要掌握幾點：

- 指令若不接受任何參數，其標準輸出就是最初的選單內容
- 選中選項後，該選項會作為指令的參數，並再次執行指令
- 若指令產生了任何標準輸出，則 Rofi 會再次將它們作為選單內容，供使用者選擇
- 被選中選項又會作為指令的參數，重覆上面的步驟
- 最後，若指令沒有任何標準輸出，則整個選單就算大功告成， Rofi 順利退出

```mermaid
sequenceDiagram
  autonumber
  Note left of bar: 不帶參數執行
  bar ->>Rofi: 未加參數的標準輸出
  Note right of Rofi: 使用者選擇 Entry
  Rofi->>bar: 將選中的 Entry 作為 bar 的參數
  Note left of bar: 以被選中的參數執行
  bar ->>Rofi: 新的標準輸出
  Note over bar, Rofi: 重複2和3，直到標準輸出為空
  Note right of Rofi: 結束執行，將最後選中的 Entry 作為標準輸出。
```

由以上的流程可以了解到，自訂的指令需要考慮有參數和無參數兩種情況。
若是你的使用情境僅僅是想讓使用者在選單選擇一次，
則在有參數時直接退出，不作任何標準輸出即可。

當然如同上面提到的多模式參數，
你也可以用多個指令讓 Rofi 同時執行多個模式：

```bash
rofi -show mode1 -modes mode1:./mode1.sh,mode2:./mode2.sh,mode3:./mode3.sh
```

上面使用 `./mode1.sh`, `./mode2.sh`, `./mode3.sh` 3 個指令，分別新增
 `mode1`, `mode2`, `mode3` 三個模式。並預設使用 `mode1` 的選單。
和上面介紹的一樣，使用者可以用 `Ctrl + Tab` 切換要套用的模式。


## 調整風格

若你是從主流的 Package Manager 安裝 Rofi 的話，
很可能會連帶把一些預設的風格檔案(以 `.rasi` 結尾)和相關的腳本一併安裝。
其中一個好用的腳本就是 `rofi-theme-selector`。
它精巧的地方在於，利用 Rofi 的特性為你羅列可用的風格檔，
在選擇後，馬上為你更改 Rofi 的介面風格，
直至你使用快捷鍵 `Alt-A` 確定更改風格。使用起來像這樣：

<video id="video" controls="" width=100% height="auto">
  <source id="webm" src="/photos/2022-10/27_14h16m53s.webm" type="video/webm">
</video>

對了！影片中我在啟動 `rofi-theme-selector` 前的 APP Launcher 也是 Rofi。
只不過套用的是不同的風格檔案。

風格檔案的撰寫格式這邊就略過不提了，有興趣的話可以參考 [man page][rofi-theme]。

另外若是你有稍為讀過這個腳本，
就會知道 Rofi 預設是使用 `~/.config/rofi/config.rasi` 這個檔案作為預設風格。
除此之外，我們也可以用參數 `-theme` 直接為 Rofi 指定風格檔案：

```bash
rofi -theme /path/to/your/theme
```

## 結語

簡而言之，Rofi, dmenu 和其它類似的工具，
其目的就是讓常用 Command Line 的開發者有個利用 STDIN/STDOUT 迅速製作選單的途徑，
不用費神藉由 GTK/QT 等傳統手段製作 GUI 介面。
只要使用得當，在桌面環境下的工作效率真的會大大提高！

前一陣子我也把 Rofi 和常用的密碼管理工具 [PasswordStore] 相結合，
製作出了屬於自己的選單工具。
之後有時間也寫一篇文章來分享。

[Archcraft]:        https://archcraft.io/
[XDG]:              https://zh.wikipedia.org/zh-tw/Freedesktop.org
[aditya]:           https://github.com/adi1090x
[Rofi]:             https://github.com/davatorium/rofi
[dmenu]:            https://tools.suckless.org/dmenu/
[man page]:         https://man.archlinux.org/man/community/rofi/rofi.1.en#Available_Modes
[rofi-theme]:       https://man.archlinux.org/man/community/rofi/rofi-theme.5.en
[PasswordStore]:    https://www.passwordstore.org/ 
