packages feed

tianbar-1.2.0.0: scripts/volume.js

/*
 * A widget to show and control the system volume.
 *
 * Requires 'jquery' to be available through RequireJS.
 */
define(['jquery', './dbus'], function ($, dbus) {
  "use strict";

  const MAX_VOLUME = 0x10000;

  const WIDTH = 25;
  const HEIGHT = 25;

  const WAVES = 5;

  const self = {};

  self.settings_command = 'gnome-control-center sound';

  self.widget = () => $('.widget-volume');

  self.display = function () {
    const widget = self.widget();

    const canvas = $('<canvas />').attr({
      width: WIDTH,
      height: HEIGHT
    });

    widget.empty();
    widget.append(canvas);

    const context = canvas[0].getContext('2d');

    const middle = WIDTH / 2 - 2;

    const speakerBaseH = 5;
    const speakerBaseW = 4;
    const speakerW = 5;
    const speakerH = 13;

    const spacing = 2;

    const crossSize = 6;

    // Draw a speaker
    context.beginPath();
    context.moveTo(0, middle - speakerBaseH / 2);
    context.lineTo(0, middle + speakerBaseH / 2);
    context.lineTo(speakerBaseW, middle + speakerBaseH / 2);
    context.lineTo(speakerBaseW + speakerW, middle + speakerH / 2);
    context.lineTo(speakerBaseW + speakerW, middle - speakerH / 2);
    context.lineTo(speakerBaseW, middle - speakerBaseH / 2);
    context.lineTo(0, middle - speakerBaseH / 2);
    context.fill();

    if (self.mute) {
      // Draw a cross
      const crossLeft = speakerBaseW + speakerW + spacing;

      context.save();
      context.lineWidth = 1.5;

      context.beginPath();
      context.moveTo(crossLeft, middle - crossSize / 2);
      context.lineTo(crossLeft + crossSize, middle + crossSize / 2);
      context.stroke();

      context.beginPath();
      context.moveTo(crossLeft + crossSize, middle - crossSize / 2);
      context.lineTo(crossLeft, middle + crossSize / 2);
      context.stroke();

      context.restore();
    } else {
      // Draw waves
      const waveAngle = Math.PI / 6;
      for (var i = 0; i < self.volume * WAVES; i ++) {
        context.beginPath();
        context.arc(
          0, middle,
          speakerBaseW + speakerW + spacing + 3 * i,
          waveAngle / 2, -waveAngle / 2,
          true);
        context.stroke();
      }
    }

    const percentage = self.volume.toFixed(2) * 100;
    widget.attr('title', percentage + '%');
  };

  $.ajax('tianbar:///execute', {
    data: {
      command: 'pacmd load-module module-dbus-protocol'
    }
  }).then(function () {
    return dbus.session.getProperty(
      'org.PulseAudio1',
      dbus.toObjectPath('/org/pulseaudio/server_lookup1'),
      'org.PulseAudio.ServerLookup1',
      'Address'
    );
  }).then(function (bus_address) {
    return dbus.connect(bus_address);
  }).then(function (bus) {
    const core_path = dbus.toObjectPath('/org/pulseaudio/core1');
    const seenSinks = {};
    function refresh() {
      function get_device_property(sink, name) {
        return bus.getProperty(
          'org.PulseAudio.Core1',
          sink,
          'org.PulseAudio.Core1.Device',
          name
        );
      }

      function subscribe(sink, eventName) {
        bus.call({
          path: core_path,
          iface: 'org.PulseAudio.Core1',
          member: 'ListenForSignal',
          body: [
            'org.PulseAudio.Core1.Device.' + eventName,
            [sink]
          ]
        }).then(function () {
          bus.listen({
            member: eventName,
            direct: true
          }).then(function (evt) {
            evt.add(refresh);
          });
        });
      }

      // TODO: monitor changes to sinks
      bus.getProperty(
        'org.PulseAudio.Core1',
        core_path,
        'org.PulseAudio.Core1',
        'Sinks'
      ).then(function (sinks) {
        if (sinks.length === 0) {
          // No sound
          return [[0], true];
        } else {
          const sink = sinks[0];

          // Subscribe the updates for this sink
          if (!seenSinks[dbus.fromObjectPath(sink)]) {
            seenSinks[dbus.fromObjectPath(sink)] = true;
            subscribe(sink, 'VolumeUpdated');
            subscribe(sink, 'MuteUpdated');
          }

          return $.when(
            get_device_property(sink, 'Volume'),
            get_device_property(sink, 'Mute')
          );
        }
      }).then(function (volumes, mute) {
        // volume is an array of channels' volumes, average it
        const volume = volumes.reduce((a, b) => a + b, 0) / volumes.length;
        self.volume = volume / MAX_VOLUME;
        self.mute = mute;
        self.display();
      });
    }

    refresh();
  });

  $(document).ready(function () {
    self.widget().click(function () {
      $.ajax('tianbar:///spawn', {
        data: {
          command: self.settings_command
        }
      });
    });
  });

  return self;
});