~schnouki/pustule

7c3be332f19d86a30f0d2a5481512a6c0b6aba62 — Thomas Jost 7 years ago b30f80a
Replace pustule-set-handlers with two hooks

Feels more natural to me.
5 files changed, 114 insertions(+), 92 deletions(-)

M README.md
M events.c
M guile.c
M pustule.h
M pustule.scm
M README.md => README.md +12 -8
@@ 56,17 56,21 @@ If you don't add the `--config` option, pustule will try to load
Configuration
-------------

The following Scheme functions are available from the configuration file:
pustule defines two hooks that you can use from the configuration file:

-   **pustule-sink-added-hook**

    Called when a sink is added to PulseAudio (for instance when a new
    application starts). Each function in the hook is called with two arguments:
    the sink index (an integer) and an alist of the properties of the sink.

-   **pustule-set-handlers** *sink-added* *sink-removed*
-   **pustule-sink-removed-hook**

    Register two callbacks in pustule: *sink-added* and *sink-removed*.
    Called when a sink is removed from PulseAudio (playback stops, application
    closed…). Each function is called with a single argument: the sink index.

    When a sink (i.e. a PulseAudio output) is added (for instance when a new
    application starts), *sink-added* is called with 2 arguments: the sink index
    (an integer) and an alist of the properties of the sink. Likewise, when a
    sink is removed (playback stops, application closed…), *sink-removed* is
    called with one argument: the sink index.

The following Scheme functions are available from the configuration file:

-   **pustule-set-volume** *sink-idx* *volume*


M events.c => events.c +5 -4
@@ 51,16 51,17 @@ void pustule_run() {
            pthread_mutex_unlock(&event_queue_mutex);

            SCM index = scm_from_uint32(event->index);
            if ((event->type == SINK_ADDED) && scm_is_true(sink_added_callback)) {
            if ((event->type == SINK_ADDED) && scm_is_false(scm_hook_empty_p(sink_added_hook))) {
                SCM info = scm_from_sink_event(event);
                scm_call_2(sink_added_callback, index, info);
                SCM args = scm_list_2(index, info);
                scm_run_hook(sink_added_hook, args);
                if (event->proplist)
                    pa_proplist_free(event->proplist);
                free(event->name);
                free(event->driver);
            }
            else if ((event->type == SINK_REMOVED) && scm_is_true(sink_removed_callback)) {
                scm_call_1(sink_removed_callback, index);
            else if ((event->type == SINK_REMOVED) && scm_is_false(scm_hook_empty_p(sink_removed_hook))) {
                scm_run_hook(sink_removed_hook, scm_list_1(index));
            }
            free(event);
            pthread_mutex_lock(&event_queue_mutex);

M guile.c => guile.c +11 -19
@@ 34,30 34,15 @@ struct pustule_exported_func {
    scm_t_subr func;
    const char* doc;
} exported_funcs[] = {
    {"pustule-set-handlers", 2, 0, 0, &pustule_set_handlers,
     "Register two callbacks in pustule: SINK-ADDED and SINK-REMOVED.\n\n"
     "When a sink (i.e. a PulseAudio output) is added (for instance when a\n"
     "new application starts), SINK-ADDED is called with 2 arguments:\n"
     "the sink index (an integer) and an alist of the properties of\n"
     "the sin Likewise, when a sink is removed (playback stops,\n"
     "application closed…), SINK-REMOVED is called with one argument:\n"
     "the sink index."},
    {"pustule-set-volume", 2, 0, 0, &pustule_set_volume,
     "Sets the volume of the sink whose index is SINK-IDX (an integer) to\n"
     "VOLUME (a number between 0 and 1)."},
    {NULL, 0, 0, 0, NULL}
};

/* Global handlers */
SCM sink_added_callback = NULL;
SCM sink_removed_callback = NULL;

/* Set the handlers functions */
SCM pustule_set_handlers(SCM sink_added, SCM sink_removed) {
    sink_added_callback = sink_added;
    sink_removed_callback = sink_removed;
    return SCM_BOOL_T;
}
/* Exported hooks */
SCM sink_added_hook = NULL;
SCM sink_removed_hook = NULL;

/* Set volume for the specified sink */
SCM pustule_set_volume(SCM idx, SCM volume) {


@@ 101,8 86,9 @@ SCM scm_from_sink_event(pustule_sink_event_t* event) {
    return info;
}

/* Register functions with Guile */
/* Register functions and other things with Guile */
void pustule_init_guile() {
    /* Register functions (and their docs) */
    SCM key = scm_from_utf8_symbol("documentation");
    struct pustule_exported_func* func;
    for (func = exported_funcs; func->name != NULL; func++) {


@@ 113,4 99,10 @@ void pustule_init_guile() {
            scm_set_procedure_property_x(proc, key, doc);
        }
    }

    /* Create hooks */
    sink_added_hook = scm_make_hook(scm_from_int(2));
    scm_c_define("pustule-sink-added-hook", sink_added_hook);
    sink_removed_hook = scm_make_hook(scm_from_int(1));
    scm_c_define("pustule-sink-removed-hook", sink_removed_hook);
}

M pustule.h => pustule.h +2 -3
@@ 44,7 44,6 @@ void sink_info_callback(pa_context* ctx, const pa_sink_input_info* i, int eol, v
void state_callback(pa_context* ctx, void* userdata);

/* Guile functions */
SCM pustule_set_handlers(SCM sink_added, SCM sink_removed);
SCM pustule_set_volume(SCM idx, SCM volume);

/* Guile helpers */


@@ 58,7 57,7 @@ void pustule_handle_sink_event(pustule_sink_event_t* event);
/* Global variables */
extern pa_context* pactx;
extern pa_threaded_mainloop* paloop;
extern SCM sink_added_callback;
extern SCM sink_removed_callback;
extern SCM sink_added_hook;
extern SCM sink_removed_hook;

#endif

M pustule.scm => pustule.scm +84 -58
@@ 22,9 22,9 @@
;; Each application that plays sound using PulseAudio creates one or several
;; sinks. The volume of each sink can be set independently.
;;
;; In this script, you can register two functions with `pustule-set-handlers':
;; one which is called when a sink is added to the PulseAudio daemon, and the
;; other one which is called when a sink is removed.
;; pustule provides with two hooks, named `pustule-sink-added-hook' and
;; `pustule-sink-removed-hook', which are called when a sink is added to or
;; removed from the PulseAudio daemon.
;;
;; The volume of each sink (a number between 0 and 1) can be set using the
;; `pustule-set-volume' function.


@@ 43,10 43,18 @@

(use-modules (ice-9 format))

;; Some small helpers first
;; {{{ Helpers
;; Association list of all the active sinks. If the
(define active-sinks '())

(define (set-volume idx vol)
  (format #t "[~d] Setting volume to ~,2f.\n" idx vol)
  (pustule-set-volume idx vol))
  (pustule-set-volume idx vol)
  (set! active-sinks (assoc-set! active-sinks idx #t)))

(define (volume-set? idx)
  "Check if a volume has already been set for a sink."
  (assoc-ref active-sinks idx))

(define (show-sink idx sink)
  (let ((show-prop


@@ 57,63 65,81 @@
(define (prop= sink name val)
  (let ((prop (assoc name sink)))
    (and prop (string= (cdr prop) val))))

;; Detect web browsers and Flash player
(define (browser? sink)
   (or (prop= sink "application.process.binary" "firefox")
       (prop= sink "application.process.binary" "chromium")
       (prop= sink "application.process.binary" "plugin-container")))

;; Simple list of all the active sinks
(define all-sinks '())

;; }}}

;; {{{ Application-specific hooks
(define (added/browser idx sink)
  "Set volume for web browsers and Flash player."
  (when (and (not (volume-set? idx))
             (or (prop= sink "application.process.binary" "firefox")
                 (prop= sink "application.process.binary" "chromium")
                 (prop= sink "application.process.binary" "plugin-container")))
    (set-volume idx 0.8)))
(add-hook! pustule-sink-added-hook added/browser)

(define (added/gajim idx sink)
  "Set volume for Gajim notifications."
  (when (and (not (volume-set? idx))
             (prop= sink "application.name" "gajim"))
    (let* ((fn (assoc-ref sink "media.name"))
           (event (basename fn ".wav")))
      (cond
       ((member event '("connected" "disconnected"))
        (set-volume idx 0.5))
       ((string= event "sent")
        (set-volume idx 0.75))
       ((string= event "message2")
        (set-volume idx 1))))))
(add-hook! pustule-sink-added-hook added/gajim)
;; }}}

;; {{{ Media roles
;; Alist of (idx . role) for sinks that have a media.role
(define roles '())

;; Handler for sink-added events
(define (sink-added idx sink)
(define (added/media-role idx sink)
  "Set volume for various applications based on their media role."
  ;; Add some roles to sinks that should have one but don't
  (when (not (volume-set? idx))
    (cond
     ((or (prop= sink "application.process.binary" "mpg123")
          (prop= sink "application.process.binary" "ogg123"))
      (set! sink (assoc-set! sink "media.role" "music"))))

    ;; Add the sink to roles alist
    (let ((role (assoc-ref sink "media.role")))
      (when role
        (set! roles (assoc-set! roles idx role))))

    ;; Set volume depending on the role
    (cond
     ((prop= sink "media.role" "music") (set-volume idx 0.5))
     ((prop= sink "media.role" "video") (set-volume idx 0.8)))))
(add-hook! pustule-sink-added-hook added/media-role #t)

(define (removed/media-roles idx)
  "Remove sink for the roles alist."
  (set! roles (assoc-remove! roles idx)))
(add-hook! pustule-sink-removed-hook removed/media-roles)
;; }}}

;; {{{ Default rules
(define (added/all idx sink)
  "Base hook applied to all the added sinks."
  (format #t "[~d] Sink added.\n" idx)
  (show-sink idx sink)
  (set! active-sinks (assoc-set! active-sinks idx #f)))
(add-hook! pustule-sink-added-hook added/all)

  ;; Add some roles to sinks that should have one but don't
  (cond
   ((or (prop= sink "application.process.binary" "mpg123")
        (prop= sink "application.process.binary" "ogg123"))
    (assoc-set! sink "media.role" "music")))

  ;; Add the sink to relevant lists
  (set! all-sinks (cons idx all-sinks))
  (let ((role (assoc-ref sink "media.role")))
    (if role
        (set! roles
              (assoc-set! roles idx role))))

  (cond
   ((prop= sink "media.role" "music") (set-volume idx 0.5))
   ((prop= sink "media.role" "video") (set-volume idx 0.8))
   ((prop= sink "application.name" "gajim") (sink-added/gajim idx sink))
   ((browser? sink) (set-volume idx 0.8))
   (else (set-volume idx 0.6))))

;; Handler for sink-removed events
(define (sink-removed idx)
(define (removed/all idx)
  (format #t "[~d] Sink removed.\n" idx)
  (set! all-sinks
        (delete! idx all-sinks))
  (set! roles
        (assoc-remove! roles idx)))

;; Sub-handler to handle new Gajim sinks
(define (sink-added/gajim idx sink)
  (let* ((fn (assoc-ref sink "media.name"))
         (event (basename fn ".wav")))
    (cond
     ((member event '("connected" "disconnected"))
      (set-volume idx 0.5))
     ((string= event "sent")
      (set-volume idx 0.75))
     ((string= event "message2")
      (set-volume idx 1)))))

;; And finally register the handlers with Pustule
(pustule-set-handlers sink-added sink-removed)
  (set! active-sinks (assoc-remove! active-sinks idx)))
(add-hook! pustule-sink-removed-hook removed/all)

(define (added/default-volume idx sink)
  "Set a default value for the volume if it wasn't set by the previous hooks."
  (when (not (volume-set? idx))
    (show-sink idx sink)
    (set-volume idx 0.6)))
(add-hook! pustule-sink-added-hook added/default-volume #t)
;; }}}