Let's write β

プログラミング中にできたことか、思ったこととか

Stumpwm on Linux Laptopでサスペンドするには

昨日、開発環境勉強会に参加していたところXMonadの拡張性が非常に高いという事で、
一番拡張性が高いのはXMonadだ!といわれてしまったので、負けじとStumpWMを機能から利用しています。
とりあえず、contribute以下のモジュールなどのおかげでLaptopで利用するのにも便利な環境が整備できているのですが、一つだけ難点が存在しました。それは、サスペンドが有効にならない事です。
これはLaptopユーザーにとっては非常に面倒な状態です。サスペンドができなければ待機しているだけでバッテリーがゴリゴリけずられていく事にもなりかねません。
そこで、ラップトップでも正常に閉じればサスペンドが、開けば復帰が可能なように設定し、そのためにモジュールをstumpwm向けに記述したので、そのコードなども掲載したいとおもいます。
(私の環境はThinkpad Edge 11, Ubuntu 11.04です)

Lidの状態を取得するモジュールを作成する

とりあえずは、閉じたら、閉じたのだと判断してくれるように設定したいですよね。
そこで、lidスイッチの情報を取得する必要があります。そこでcontributeの中にふくまれているbattery-portable.lispという物を改造しlidスイッチの情報を取得するモジュールを作成しましたので、掲載いたします。

;;; Lid switch information for StumpWM's mode-line.
;;;
;;; Written by Pocket <poketo7878@gmail.com>.
;;;
;;; Copyright (c) 2011 Pocket <poketo7878@gmail.com>
;;;
;;; Permission is hereby granted, free of charge, to any person
;;; obtaining a copy of this software and associated documentation
;;; files (the "Software"), to deal in the Software without
;;; restriction, including without limitation the rights to use, copy,
;;; modify, merge, publish, distribute, sublicense, and/or sell copies
;;; of the Software, and to permit persons to whom the Software is
;;; furnished to do so, subject to the following conditions:
;;;
;;; The above copyright notice and this permission notice shall be
;;; included in all copies or substantial portions of the Software.
;;;
;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
;;; DEALINGS IN THE SOFTWARE.
;;;
;;;
;;; To load this module, place
;;;
;;;     (load-module "lid")
;;;
;;; in your .stumpwmrc.  Lid switch information is then available via %L
;;; in your mode-line config.

(defpackage :stumpwm.contrib.lid
  (:use :common-lisp :stumpwm :cl-ppcre)
  (:export #:*refresh-time*
           #:lid-info-string
           ))
(in-package :stumpwm.contrib.lid)

;;; Configuration

(defvar *refresh-time* 1
  "Time in seconds between updates of lid-switch information.")

(defgeneric state-of (lid)
  (:documentation "Returns either :UNKNOWN, :OPEN, :CLOSE"))

#+ linux
(progn
  (defclass lid ()
    ((path :initarg :path :initform (error ":path missing")
           :reader path-of)
     (info-hash :initform (make-hash-table :test 'equal)
                :reader info-hash-of)))

  (defmethod update-info ((l lid))
    (clrhash (info-hash-of l))
    (loop
       for filename in '("state" "info")
       do (with-open-file (file (merge-pathnames (make-pathname :name filename)
                                                 (path-of l)))
            (loop
               for line = (read-line file nil nil)
               while line
               do (multiple-value-bind (match? matches)
                      (scan-to-strings "^([^:]+):\\s*([^\\s]+)(\\s.*)?$" line)
                    (if (not match?)
                        (format t "Unrecognized line: ~S~%" line)
                        (setf (gethash (aref matches 0) (info-hash-of l))
                              (aref matches 1))))))))

  (define-condition info-value-not-present (error)
    ())

  (defmethod info-value ((l lid) key)
    (multiple-value-bind (val found?)
        (gethash key (info-hash-of l))
        (if found?
            val
            (error 'info-value-not-present))))

  (defun all-lid ()
    (mapcar (lambda (p)
              (make-instance 'lid :path p))
            (list-directory "/proc/acpi/button/lid/")))

  (defmethod state-of ((l lid))
    (handler-case
        (progn
          (update-info l)
          (cond ((string= (info-value l "state") "open")
                 :open)
                ((string= (info-value l "state") "closed")
                 :close)
                (t :unknown)))
      (t () :unknown)))
  )


;;; Interface to the outside world.

(defun lid-info-string ()
  "Compiles a string suitable for StumpWM's mode-line."
  (with-output-to-string (fmt)
    (let ((lids (all-lid)))
      (if (endp lids)
          (format fmt "(no lid-switch)")
          (loop
             for lid in lids
             do (multiple-value-bind (state)
                    (state-of lid)
                  (ecase state
                    (:unknown (format fmt "(no info)"))
                    (:open (format fmt "open"))
                    (:close (format fmt "close")))))))))

(let ((next 0)
      (last-value ""))
  (defun fmt-lid (ml)
    (declare (ignore ml))
    ;; Return the last info again, if we are called too quickly.
    (let ((now (get-universal-time)))
      (when (< now next)
        (return-from fmt-lid last-value))
      (setf next (+ now *refresh-time*)))
    ;; Generate info string.
    (setf last-value (lid-info-string))))

(add-screen-mode-line-formatter #\L #'fmt-lid)

;;; EOF

このモジュールは/proc/acpi/bottom/lid以下からlidスイッチのstateファイルの情報を取得して、現在のlidの状態を報告してくれるモジュールです、contrib以下の他のモジュールと同様にmode-lineに表示するための特殊文字%Lを提供しています。
mode-lineの設定中に%Lを記述してくだされば、lidの状態がopen,close,(no info),(no lid-switch)のそれぞれで取得できます。

このモジュールはそれいがいに、lid-info-stringという物もexportしており、mode-lineではなくstumpwmrcの中からlidの状態文字列が取得可能なようにしてあります。

suspend.shの作成とsudo対策

Lidスイッチの状態が取得できたので、次はサスペンドするためのスクリプトを取得しておきましょう
LinuxノートPCのサスペンドとハイバネーション - SourceForge.JP Magazine : オープンソースの話題満載
このページで公開されているsuspend.shを保存して/usr/local/sbin/suspend.shとして保存しておきましょう。
そして、visudoしてsuspend.shを実行する時にsudoをするのですが、パスワード入力が不要なように
NOPASSWORDしておきましょう

your-username ALL=(ALL) NOPASSWD: /usr/local/sbin/suspend.sh

の行をvisudoして追加しておきましょう(your-usernameは適宜変更してくださいね)
こうする事でsudo suspend.shしてパスワード不要でサスペンドが可能な事を確認したら次のステップです(おそらく復帰はFnあたりで可能だとおもいます、私の環境ではFnで可能でした)

最後にstumpwmでlidの状態を監視しましょう

Lidの状態が取得可能で、コマンドラインからサスペンドも可能になりました。
のこりは、lidの状態を監視して、closeになったらサスペンドする処理をstumpwmで実行してやるだけです。それに最適な関数がすでにstumpwmに用意されていますrun-with-timerです。
この関数は一定の期間ごとに、ある関数を実行してくれます。という事はlidがcloseになっていたらsuspend.shを実行するという関数を定期的にはしらせてやれば良いという事になります。
そこで以下のような設定を記述します

;Setting up suspend timer
(defun suspend-if-closed ()
  (let ((status (stumpwm.contrib.lid:lid-info-string)))
    (when (string= status "close")
      (run-shell-command "sudo suspend.sh"))))

(run-with-timer 1 1 #'suspend-if-closed)

このように設定すると、定期的(おそらく1秒おきに)suspend-if-closedが走ります、すると閉じた後にcloseにlidのstatusが変化しますので、サスペンドにはいります。

まとめ

このように設定する事で私の環境では無事に閉じたらサスペンドが実行できるようになりました、また開くと自動的に復帰するようにもなっております。
以下のページの情報を参考にして、visudoの設定などをおこないました。ありがとうございます。

Stumpwm に suspend と hibernate の導入 - Konbuの技術 && 読書録