i3blocksの設定

# はじめに
i3window-mangerで使われるi3bar(情報を表示するやつ。日時とか)の標準はi3statusです。i3statusは任意の値を表示できないので、多くの人はi3blocks、polybar、conkyのどれかを使っていると思います。私はシンプルなものが好みなのでi3blocksを使っています。
![2018-09-27_11_41_58.png](https://qiita-image-store.s3.amazonaws.com/0/163210/6007fc59-f90f-ab22-52b7-9a677a017602.png)

# 環境
- Arch Linux (5.4.7-arch1-1)
- i3 version 4.17
- i3blocks 1.5

#i3の設定

``` config:~/.config/i3/config
# 色
set $background #2b303b
set $foreground #c0c5ce
set $secondary #3f4751
set $black #2b303b
set $red #bf616a
set $green #a3be8c
set $yellow #ebcb8b
set $blue #8fa1b3
set $magenta #b48ead
set $cyan #96b5b4
set $white #c0c5ce

bar {
    status_command i3blocks
    font pango:Hack Regular 10
    mode dock
    position top
    separator_symbol " │ "
    workspace_buttons yes
    strip_workspace_numbers yes
    binding_mode_indicator yes
    tray_output none
    tray_padding 0
    colors {
        background $background
        focused_background $background
        statusline $blue
        focused_statusline $blue
        separator $secondary
        focused_separator $secondary
        # 左からborder, bg, fg
        focused_workspace  $blue $blue $background
        active_workspace $background $background $foreground
        inactive_workspace $background $background $foreground
        urgent_workspace   $red $red $background
        binding_mode       $blue $blue $background
    }
}
```

#i3blocksの設定

## 項目
- label : 絵文字や項目名など。任意。
- command : 指定したコマンドの標準出力が表示されます。
- color : 16進数で色を指定。色はcommandでも指定できます。未指定なら白。
- interval : 更新間隔。1で1秒。onceでi3blocksを実行したときだけ。repeatやpersistもある。
- signal : [シグナル](https://www.xmisao.com/2013/11/10/linux-kill-signals.html)を指定します。i3blocksが指定したシグナルを受信したときに更新されます。

## シグナル
音量の表示は、音量を変更したときに更新したいのでシグナルを使います。killやpkillでシグナルを送信します。`signal=1`としたなら`pkill -SIGRTMIN+1 i3blocks`で更新されます。音量を変更するコマンドをどこかのkeyにバインドしていると思うので、その後にpkillを実行するようにします。
`bindsym ... && pkill -SIGRTMIN+1 i3blocks`

## 特殊文字
アイコンというか記号というか特殊文字のようなものを、表示したいのならfont awesomeが必要です。
`sudo pacman -S awesome-terminal-fonts ttf-font-awesome`

## 設定ファイル
```bash:.config/i3blocks/config
[uptime]
label= 
command=uptime | sed 's/.*up \([^,]*\),.*/\1/'
color=#8fa1b3
interval=60

[memory]
label= 
command=~/.config/i3blocks/memory.py
#command=free -h | awk 'match($0, /^Mem/){print $3 " / " $2}'
color=#8fa1b3
interval=1

[load average]
label= 
command=echo "$(uptime | sed 's/.*load average: \(.*\)/\1/' | cut -d, -f1)/$(grep 'processor' /proc/cpuinfo | wc -l)"
color=#8fa1b3
interval=1

[network]
label= 
command=[[ -n $(ip link show up dev wlp4s0) ]] && which wpa_cli &> /dev/null && echo "$({ echo 'status'; echo 'quit'; } | wpa_cli -i wlp4s0 | grep '^ssid=' | cut -d= -f2) : $(cat /proc/net/wireless | tail -1 | tr -s ' ' | cut -d' ' -f4 | sed 's/\./dBm/')"
color=#8fa1b3
interval=1

[lightness]
label=☀ 
command=xbacklight -get | xargs printf '%.0f%%\n'
color=#8fa1b3
interval=once
signal=2

[volume]
label= 
command=~/.config/i3blocks/volume.sh
interval=once
signal=1

[date]
label=
command=date +'%m/%d(%a) %H:%M'
color=#8fa1b3
interval=1

[battery]
command=~/.config/i3blocks/battery.sh
format=json
interval=persist

```

## バッテリー
充電中ならアイコンを順に表示しています。充電中ではないのなら、残量に応じた色とアイコンを表示しています。

```bash:battery.sh
#!/usr/bin/env bash

set -euCo pipefail

function online_icon() {
  [[ $# -eq 0 ]] && return 1
  local -ar icons=('' '' '' '' '')
  local index
  index=$(expr $1 % ${#icons[@]})
  echo ${icons[${index}]}
}

function echo_battery() {
  [[ $# -eq 1 ]] \
    && echo -e "\"full_text\": \"$1 \"" \
    || echo -e "\"full_text\": \"$1 \", \"color\": \"$2\""
}

function get_battery() {
  [[ -e '/sys/class/power_supply/BAT1/capacity' ]] \
    && cat '/sys/class/power_supply/BAT1/capacity' \
    || echo 0
}

function online() {
  [[ $(cat /sys/class/power_supply/ADP1/online) -eq 1 ]] \
    && return 0
  return 1
}

function main() {
  local -Ar \
    high=( ['value']=79 ['icon']='' ['color']='#08d137') \
    middle=( ['icon']='' ['color']='#8fa1b3') \
    low=( ['value']=21 ['icon']='' ['color']='#f73525')

  local online_icon='' cnt=0
  while sleep 1; do
    cnt=$(expr ${cnt} + 1)

    if online; then
      online_icon=$(online_icon ${cnt})
    else
      [[ -z ${online_icon} && $(expr ${cnt} % 60) -ne 1 ]] \
        && continue
      online_icon=''
    fi

    local battery
    battery=$(get_battery)
    if [[ ${battery} -eq 0 ]]; then
      echo_battery ${online_icon}
    elif [[ ${battery} -gt ${high['value']} ]];then
      echo_battery \
        "${online_icon:-${high['icon']}} ${battery}%" ${high['color']}
    elif [[ ${battery} -lt ${low['value']} ]];then
      echo_battery \
        "${online_icon:-${low['icon']}} ${battery}%" ${low['color']}
    else
      echo_battery \
        "${online_icon:-${middle['icon']}} ${battery}%" ${middle['color']}
    fi
  done
}

main
```
## 音量
使用するフォントをHackにしないと■が連結して表示されず、隙間が空いてしまいます。

```bash:volume.sh
#!/usr/bin/env bash

set -euCo pipefail

function get_volume() {
  pactl list sinks \
    | grep 'Volume' | grep -o '[0-9]*%' | head -1 | tr -d '%'
}

function get_muted() {
  pactl list sinks \
    | grep 'Mute' | sed 's/[[:space:]]//g' | cut -d: -f2 | head -1
}

function to_blocks() {
  seq -f '%02g' -s '' 1 5 $1 | sed 's/.\{2\}/■/g'
}

function to_spaces() {
  seq -s ' ' $1 5 100 | tr -d '[:digit:]'
}

function to_meters() {
  echo "[$(to_blocks $1)$(to_spaces $1)]"
}

function print_volume() {
  local -r volume=$(get_volume)
  [[ ${volume} -gt 100 ]] \
    && echo -e "${volume}\n" \
    || echo -e "$(to_meters ${volume})\n"
}

function main() {
  which pactl &> /dev/null || return 1
  LANG=C

  print_volume

  declare -Ar colors=( ['yes']='#434447' ['no']='#8fa1b3' )
  echo "${colors[$(get_muted)]}"
}

main
```

## メモリ
　メモリの情報はfreeコマンドで取得できます。しかし、メモリ使用率は取得できません。メモリ使用率とメモリ消費量を人が見やすいように一緒に表示するのが、シェルスクリプトでは面倒だったのでpythonで書いてしまいました。何故か`free -b`、`free -h`と2回実行したくありませんでした。
　消費量と合計だけを表示するなら、`free -h | awk 'match($0, /^Mem/){print $3 " / " $2}'`、
　使用率だけを表示するなら`free -b | awk 'match($0, /^Mem/){print int($3 * 1000 / $2) / 10}'`で出力可能です。

```python:memory.py
#!/usr/bin/python3

import os

class MemInfo():
    def __init__(self, file_path):
        self.info = self.load(file_path)

    def load(self, file_path):
        if not os.path.exists(file_path):
            return {}
        with open(file_path, 'r') as mem_info:
            return dict(
                    [line.strip().replace(':', '').split()[0:2]
                        for line in mem_info.readlines()]
                    )

    def is_loaded(self):
        if self.info:
            return True
        else:
            return False

    def retrieve(self, target):
        return int(self.info[target])

    def get_mem_total(self):
        return self.retrieve('MemTotal')

    def get_mem_free(self):
        return self.retrieve('MemFree')

    def get_buffers(self):
        return self.retrieve('Buffers')

    def get_cached(self):
        return self.retrieve('Cached')

    def get_mem_used(self):
        return self.get_mem_total() \
                - self.get_mem_free() \
                - self.get_buffers() \
                - self.get_cached()

# size: kB
def convert(size, cnt):
    if size < 1024 ** (cnt+1):
        return f'{size / 1024 ** cnt:.3g}{units[cnt]}'
    return convert(size, cnt+1)

def main():
    mem_info = MemInfo('/proc/meminfo')
    if not mem_info.is_loaded():
        return
    try:
        used = mem_info.get_mem_used()
    except KeyError as err:
        return

    rate = used / mem_info.get_mem_total()
    try:
        print(f'{convert(used, 0)} : {rate:.1%}')
    except IndexError as err:
        return


units = ['kB', 'MB', 'GB', 'TB']

if __name__ == '__main__':
    main()
```