[Scala][狗食] 在工作列上顯示 CPU 的溫度。

話說今天早上將學姐機的 Linux Kernel 更新到 2.36.7 之後,發現我工作列上原本顯示 CPU 溫度的東西不見了,和我說找不到溫度感應器。查了一下之後才發現,2.36.7 把已經宣告 deprecated 很久的 /proc/acpi 下的一些東西拿掉了,而好死不死我還在用的那個軟體,卻仍然是靠讀取 /proc/acpi 來運作的……也就是說,沒救了!而試著換另一套軟體,也依然無法成功顯示出 CPU 的溫度。

雖然宅色夫說真男人就要慣 C,但我已經幾百年沒有寫 C 的程式碼了,實在沒信心可以成功 patch 的信心,而本著 Eating your own dog food 的精神,我就自己用 Scala / SWT 打造了一個簡單的版本……

如果你也有需要的話,可以下載回來使用,使用方式是在命令列使用以下的指令:

java -jar SThermal.min.jar [你的 sysfs Thermal 位置] [更新的間隔(單位為 ms)]

其中比較麻煩的是找到你的 sysfs Thermal 位置,但他一般來說會是在 /sys/class/thermal/thermal_zone0/,如果你的電腦有好幾個溫度感應器,那可能還會看到 thermal_zone1 之類的目錄,請自己想辦法分辨哪個目錄是哪個設備的溫度,因為我也不知道怎麼分。^_^|||

如果你的系統上有 Notification Area(也就是 Pidgin / GCIN / NetworkManager 這些軟體可以在工作列上正常顯示出圖示)的話,那應該會看到像右圖一樣的藍色數字,就是你的 CPU 溫度囉。

以下則是完整的程式碼,基本上就是一直去讀 sysfs 的檔案而已,供有需要的人參考:

package org.bone

import scala.io.Source
import java.io.File

import org.eclipse.swt.layout._
import org.eclipse.swt.widgets._
import org.eclipse.swt._
import org.eclipse.swt.graphics.Image
import org.eclipse.swt.graphics.GC
import org.eclipse.swt.graphics.Color

/**
 *  Linux ACPI Thermal 資訊物件
 *
 *  此物件會從 sysfs 檔案系統中讀取系統的溫度相關資訊
 *
 *  @param  sysFSDirPath    要讀取的 ACPI Thermal Zone 目錄
 */
class ACPIThermal(sysFSDirPath: String)
{
    private val sysFSDir = 
        new File(if (sysFSDirPath.last == '/') sysFSDirPath.init else sysFSDirPath)
    private val temperatureFile = new File(sysFSDir + "/temp")

    // 檢查使用者指定的路徑是不是 Thermal Zone
    // 檢查項目:
    //  1. 該路徑是目錄
    //  2. 該目錄下有 temp 這個檔案
    require (sysFSDir.exists & sysFSDir.isDirectory, 
             "%s is not directory" format(sysFSDir))
    require (temperatureFile.exists & temperatureFile.isFile, 
             "%s is not exist" format(temperatureFile))

    // 根據 sysfs 的定義,溫度是以攝氏 1/1000 度做單位
    def temperature = Source.fromFile(temperatureFile).
                             getLines().mkString.toInt / 1000
}

/**
 *  主程式
 *
 *  使用 SWT 來達成在 System Tray 下顯示系統溫度
 *
 */
object SThermal
{
    // 產生相關的 SWT 元件
    val display = new Display ()
    val shell = new Shell(display)
    val image = new Image(display, 16, 16)

    // 如果使用者所使用的桌面環境有 System Tray,才建立顯示
    // 溫度的元件
    //
    // Scala 裡的 Option[T] 是個神奇的東西,可以把他想像成一個盒子,
    // 有兩種狀態:
    //
    //  - 裡面沒東西(None)
    //  - 裡面有一個類型是 T 的東西(Some(T))
    //
    val trayItem: Option[TrayItem] =  display.getSystemTray match {
        case null => None
        case systemTray => Some(new TrayItem(systemTray, SWT.NONE))
    }

    /**
     *  更新溫度顯示用的 Thread
     *
     *  @param  thermal         ACPI Thermal 資訊
     *  @param  updateInterval  多久更新一次,單位為 ms
     */
    class UpdateThread(thermal: ACPIThermal, updateInterval: Int) extends Thread 
    {
        def updateTrayImage() 
        {
            // 如果要在其他 Thread 來存取 SWT UI 元件,需要用這種方式
            display.syncExec (new Runnable() {

                // 很不幸的,目前 TrayItem 只能顯示 Image,我們要自己畫
                val image = new Image(display, 16, 16)

                def run() {

                    // 在 Image 上畫上溫度
                    val gc = new GC(image)
                    gc.setForeground(display.
                                     getSystemColor(SWT.COLOR_BLUE))

                    gc.setBackground(display.
                                     getSystemColor(SWT.COLOR_INFO_BACKGROUND))

                    gc.drawText (thermal.temperature.toString, 0, 0)

                    // 如果 trayItem 裡面有東西,就做 {} 裡面的事,將
                    // trayItem 所使用的圖型設成上面的 image
                    trayItem.foreach { _.setImage(image) }
                }
            })
        }

        // 沒什麼特別的,每隔 updateInterval ms 後就更新
        override def run() {
            while (true) {
                updateTrayImage()
                Thread.sleep(updateInterval)
            }
        }
    }

    def main (args: Array[String]) 
    {
        // 檢查使用者所提供的參數是否足夠
        if (args.length != 2) {
            println ("Usage: java -jar [JARFile] [Thermal ACPI sysfs dir] " + 
                     "[Update Interval]")
            exit(0)
        }

        // 設定參數
        val thermalPath = args(0)
        val updateInterval = args(1).toInt

        val thermal = new ACPIThermal(args(0))
        val updateThread = new UpdateThread(thermal, updateInterval)

        updateThread.start()

        // 標準的 SWT 的寫法
        while (!shell.isDisposed ()) {
            if (!display.readAndDispatch ()) display.sleep ();
        }

        display.dispose ();
    }
}

回響