sound.scm.in 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. #!@GUILE@
  2. !#
  3. ;;; sound.scm --- Display OSD with sound volume
  4. ;; Copyright © 2016 Alex Kost <alezost@gmail.com>
  5. ;; This program is free software; you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation, either version 3 of the License, or
  8. ;; (at your option) any later version.
  9. ;;
  10. ;; This program is distributed in the hope that it will be useful,
  11. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ;; GNU General Public License for more details.
  14. ;;
  15. ;; You should have received a copy of the GNU General Public License
  16. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. ;;; Commentary:
  18. ;; This is a simple example showing how Guile-XOSD can be used to
  19. ;; display sound volume by getting info from 'amixer' output.
  20. ;;
  21. ;; This script passes all its arguments to 'amixer' and parses its
  22. ;; output by searching a line like this:
  23. ;;
  24. ;; ... Playback 39118 [60%] [on]
  25. ;;
  26. ;; or this:
  27. ;;
  28. ;; ... Playback 32 [50%] [-32.00dB] [off]
  29. ;;
  30. ;; Examples of using:
  31. ;;
  32. ;; sound.scm sget Master
  33. ;; sound.scm sset Master toggle
  34. ;; sound.scm sset Master 40%
  35. ;;
  36. ;; See 'man amixer' for more info. Note: 'amixer' is a program from
  37. ;; 'alsa-utils' package (or whatever it is called by your distribution):
  38. ;; <http://www.alsa-project.org/>.
  39. ;; Actually, this is not a real world example. It has 2 downsides:
  40. ;;
  41. ;; 1. At first, well, it's not a compiled C program, it's just a Guile
  42. ;; script, so it is not super-fast :-)
  43. ;;
  44. ;; 2. This script exits only after the sound OSD disappears (the timeout
  45. ;; is set to 3 seconds), so, for example, if you bind "sound.scm sset
  46. ;; Master 3%+" to some key in your window manager, you'll not be very
  47. ;; happy if you quickly press this key several times: the sound will be
  48. ;; increased properly (by 3% each time) but you'll see several OSDs
  49. ;; laying on each other, as another "sound.scm" script will be called
  50. ;; when the previous one(s) is(are) still alive.
  51. ;;
  52. ;; In practice I use the following solution, that allows me to overcome
  53. ;; both downsides: I start Guile-Daemon with the config that sets all
  54. ;; this "sound OSD" stuff, and then I just send small Guile expressions
  55. ;; to the Daemon for evaluating. As you can see, I'm still using Guile
  56. ;; to set the sound and to display OSD, but it is *very fast* and it's
  57. ;; the same OSD object every time!
  58. ;;
  59. ;; Guile-Daemon: <https://github.com/alezost/guile-daemon>
  60. ;; My config for Guile-Daemon: <https://github.com/alezost/guile-daemon-config>
  61. ;;; Code:
  62. (use-modules
  63. (ice-9 format)
  64. (ice-9 match)
  65. (ice-9 popen)
  66. (ice-9 rdelim)
  67. (ice-9 regex)
  68. (srfi srfi-11)
  69. (xosd))
  70. (define %color-muted "#E74F35")
  71. (define %color-unmuted "#23B13E")
  72. (define %color-error "orange")
  73. (define %osd
  74. (make-osd #:lines 2
  75. #:timeout 3
  76. #:position 'bottom
  77. #:align 'center
  78. #:font "-*-dejavu sans-bold-r-normal-*-*-300-*-*-p-*-*-1"
  79. #:shadow-offset 2))
  80. (define* (display-sound osd #:key volume mute?)
  81. (set-osd-color! osd (if mute? %color-muted %color-unmuted))
  82. (display-string-in-osd osd (format #f "Volume: ~d%" volume))
  83. (display-percentage-in-osd osd volume 1))
  84. (define (display-error osd)
  85. (set-osd-color! osd %color-error)
  86. (display-string-in-osd osd "")
  87. (display-string-in-osd osd "Oops, can't parse amixer output :-)" 1))
  88. (define (call-amixer . args)
  89. "Call 'amixer' with ARGS and return its output."
  90. (let* ((port (apply open-pipe* OPEN_READ "amixer" args))
  91. (out (read-string port)))
  92. (close-pipe port)
  93. out))
  94. (define (parse-amixer-output output)
  95. "Parse 'amixer' OUTPUT and return 2 values: sound volume (a number
  96. from 0 to 100) and a boolean value showing if the sound is
  97. muted/unmuted. Volume is #f if OUTPUT cannot be parsed."
  98. (let* ((rx (make-regexp
  99. "Playback [0-9]+ \\[([0-9]+)%\\].* \\[(on|off)\\]"))
  100. (res (regexp-exec rx output)))
  101. (if res
  102. (values (string->number (match:substring res 1))
  103. (string=? "off" (match:substring res 2)))
  104. (values #f #f))))
  105. (define (show-help)
  106. (format #t "Usage: ~a AMIXER-ARGS ...
  107. Run 'amixer AMIXER-ARGS ...', parse its output, and display OSD with the
  108. current sound volume.\n"
  109. (car (command-line))))
  110. (define (main args)
  111. (match (cdr args)
  112. (((or "-h" "--help" "help") _ ...)
  113. (show-help))
  114. ((amixer-args ...)
  115. (let-values (((volume mute?)
  116. (parse-amixer-output
  117. (apply call-amixer amixer-args))))
  118. (if volume
  119. (display-sound %osd #:volume volume #:mute? mute?)
  120. (display-error %osd))
  121. (wait-while-osd-displayed %osd)))
  122. (_ (show-help))))
  123. (when (batch-mode?)
  124. (main (command-line)))
  125. ;;; sound.scm ends here