summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/chromeos/audio_handler.cc167
-rw-r--r--chrome/browser/chromeos/audio_handler.h89
-rw-r--r--chrome/browser/chromeos/pulse_audio_mixer.cc327
-rw-r--r--chrome/browser/chromeos/pulse_audio_mixer.h114
-rw-r--r--chrome/browser/chromeos/system_key_event_listener.cc51
-rw-r--r--chrome/browser/chromeos/system_key_event_listener.h14
-rw-r--r--chrome/chrome_browser.gypi11
7 files changed, 735 insertions, 38 deletions
diff --git a/chrome/browser/chromeos/audio_handler.cc b/chrome/browser/chromeos/audio_handler.cc
new file mode 100644
index 0000000..d22b737
--- /dev/null
+++ b/chrome/browser/chromeos/audio_handler.cc
@@ -0,0 +1,167 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/audio_handler.h"
+
+#include <math.h>
+
+#include "base/logging.h"
+#include "chrome/browser/chromeos/pulse_audio_mixer.h"
+
+namespace chromeos {
+
+namespace {
+
+const double kSilenceDb = -200.0;
+const double kMinVolumeDb = -90.0;
+const double kMaxVolumeDb = 6.0;
+// A value of less than one adjusts quieter volumes in larger steps (giving
+// finer resolution in the higher volumes).
+const double kVolumeBias = 0.7;
+
+} // namespace
+
+// This class will set volume using PulseAudio to adjust volume and mute, and
+// handles the volume level logic.
+
+// TODO(davej): Serialize volume/mute for next startup?
+// TODO(davej): Should we try to regain a connection if for some reason all was
+// initialized fine, but later IsValid() returned false? Maybe N retries?
+
+double AudioHandler::GetVolumeDb() const {
+ if (!SanityCheck())
+ return kSilenceDb;
+
+ double volume_db = mixer_->GetVolumeDb();
+ if (volume_db <= kSilenceDb)
+ return kSilenceDb;
+ return volume_db;
+}
+
+void AudioHandler::SetVolumeDb(double volume_db) {
+ if (!SanityCheck())
+ return;
+
+ mixer_->SetVolumeDb(volume_db);
+}
+
+double AudioHandler::GetVolumePercent() const {
+ if (!SanityCheck())
+ return 0;
+
+ return VolumeDbToPercent(mixer_->GetVolumeDb());
+}
+
+// Set volume using our internal 0-100% range. Notice 0% is a special case of
+// silence, so we set the mixer volume to kSilenceDb instead of kMinVolumeDb.
+void AudioHandler::SetVolumePercent(double volume_percent) {
+ if (!SanityCheck())
+ return;
+ DCHECK(volume_percent >= 0.0);
+
+ double vol_db;
+ if (volume_percent <= 0)
+ vol_db = kSilenceDb;
+ else
+ vol_db = PercentToVolumeDb(volume_percent);
+
+ mixer_->SetVolumeDb(vol_db);
+}
+
+// Volume range is from kMinVolumeDb at just above 0% to kMaxVolumeDb at 100%
+// with a special case at 0% which maps to kSilenceDb.
+void AudioHandler::AdjustVolumeByPercent(double adjust_by_percent) {
+ if (!SanityCheck())
+ return;
+
+ DLOG(INFO) << "Adjusting Volume by " << adjust_by_percent << " percent";
+
+ double vol = mixer_->GetVolumeDb();
+ double pct = VolumeDbToPercent(vol);
+ if (pct < 0)
+ pct = 0;
+ pct = pct + adjust_by_percent;
+ if (pct > 100.0)
+ pct = 100.0;
+
+ double new_volume;
+
+ if (pct <= 0.1)
+ new_volume = kSilenceDb;
+ else
+ new_volume = PercentToVolumeDb(pct);
+
+ if (new_volume != vol)
+ mixer_->SetVolumeDb(new_volume);
+}
+
+bool AudioHandler::IsMute() const {
+ if (!SanityCheck())
+ return false;
+
+ return mixer_->IsMute();
+}
+
+void AudioHandler::SetMute(bool do_mute) {
+ if (!SanityCheck())
+ return;
+
+ DLOG(INFO) << "Setting Mute to " << do_mute;
+
+ mixer_->SetMute(do_mute ? 1 : 0);
+}
+
+void AudioHandler::ToggleMute() {
+ if (!SanityCheck())
+ return;
+
+ mixer_->ToggleMute();
+}
+
+AudioHandler::AudioHandler()
+ : connected_(false) {
+ mixer_.reset(new PulseAudioMixer());
+ if (mixer_->Init()) {
+ connected_ = true;
+ } else {
+ LOG(ERROR) << "Unable to connect to PulseAudio";
+ }
+}
+
+AudioHandler::~AudioHandler() {
+};
+
+inline bool AudioHandler::SanityCheck() const {
+ if (!mixer_->IsValid()) {
+ if (connected_) {
+ LOG(ERROR) << "Lost connection to PulseAudio";
+ }
+ return false;
+ }
+ return true;
+}
+
+// VolumeDbToPercent() and PercentToVolumeDb() conversion functions allow us
+// complete control over how the 0 to 100% range is mapped to actual loudness.
+//
+// The mapping is confined to these two functions to make it easy to adjust and
+// have everything else just work. The range is biased to give finer resolution
+// in the higher volumes if kVolumeBias is less than 1.0.
+
+// static
+double AudioHandler::VolumeDbToPercent(double volume_db) {
+ if (volume_db < kMinVolumeDb)
+ return 0;
+ return 100.0 * pow((volume_db - kMinVolumeDb) /
+ (kMaxVolumeDb - kMinVolumeDb), 1/kVolumeBias);
+}
+
+// static
+double AudioHandler::PercentToVolumeDb(double volume_percent) {
+ return pow(volume_percent / 100.0, kVolumeBias) *
+ (kMaxVolumeDb - kMinVolumeDb) + kMinVolumeDb;
+}
+
+} // namespace chromeos
+
diff --git a/chrome/browser/chromeos/audio_handler.h b/chrome/browser/chromeos/audio_handler.h
new file mode 100644
index 0000000..54b134d4
--- /dev/null
+++ b/chrome/browser/chromeos/audio_handler.h
@@ -0,0 +1,89 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_AUDIO_HANDLER_H_
+#define CHROME_BROWSER_CHROMEOS_AUDIO_HANDLER_H_
+
+#include "base/scoped_ptr.h"
+#include "base/singleton.h"
+
+namespace chromeos {
+
+class PulseAudioMixer;
+
+class AudioHandler {
+ public:
+ static AudioHandler* instance() {
+ return Singleton<AudioHandler>::get();
+ }
+
+ // Get current volume level in terms of decibels (dB), silence will return
+ // -200 dB. Returns default of -200.0 on error. This function is designed
+ // to block until the volume is retrieved or fails. Blocking call.
+ double GetVolumeDb() const;
+
+ // Blocking call.
+ // TODO(davej): Verify this becomes non-blocking after underlying calls are
+ // made non-blocking.
+ void SetVolumeDb(double volume_db);
+
+ // Get volume level in our internal 0-100% range, 0 being pure silence.
+ // Volume may go above 100% if another process changes PulseAudio's volume.
+ // Returns default of 0 on error. This function will block until the volume
+ // is retrieved or fails. Blocking call.
+ double GetVolumePercent() const;
+
+ // Set volume level from 0-100%. Volumes above 100% are OK and boost volume,
+ // although clipping will occur more at higher volumes. Volume gets quieter
+ // as the percentage gets lower, and then switches to silence at 0%.
+ // Blocking call.
+ // TODO(davej): Verify this becomes non-blocking after underlying calls are
+ // made non-blocking.
+ void SetVolumePercent(double volume_percent);
+
+ // Adust volume up (positive percentage) or down (negative percentage),
+ // capping at 100%. Call GetVolumePercent() afterwards to get the new level.
+ // Blocking call.
+ // TODO(davej): Verify this becomes non-blocking after underlying calls are
+ // made non-blocking.
+ void AdjustVolumeByPercent(double adjust_by_percent);
+
+ // Just returns true if mute, false if not or an error occurred. This call
+ // will block until the mute state is retrieved or fails. Blocking call.
+ bool IsMute() const;
+
+ // Mutes all audio. Non-blocking call.
+ // TODO(davej): Verify this becomes non-blocking after underlying calls are
+ // made non-blocking.
+ void SetMute(bool do_mute);
+
+ // Toggle mute. Use this if you do not need to know the mute state, so it is
+ // possible to operate asynchronously. Blocking call.
+ // TODO(davej): Verify this becomes non-blocking after underlying calls are
+ // made non-blocking.
+ void ToggleMute();
+
+ private:
+ // Defines the delete on exit Singleton traits we like. Best to have this
+ // and constructor/destructor private as recommended for Singletons.
+ friend struct DefaultSingletonTraits<AudioHandler>;
+
+ AudioHandler();
+ virtual ~AudioHandler();
+ inline bool SanityCheck() const;
+
+ // Conversion between our internal scaling (0-100%) and decibels.
+ static double VolumeDbToPercent(double volume_db);
+ static double PercentToVolumeDb(double volume_percent);
+
+ scoped_ptr<PulseAudioMixer> mixer_;
+ bool connected_;
+
+ DISALLOW_COPY_AND_ASSIGN(AudioHandler);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CHROMEOS_AUDIO_HANDLER_H_
+
diff --git a/chrome/browser/chromeos/pulse_audio_mixer.cc b/chrome/browser/chromeos/pulse_audio_mixer.cc
new file mode 100644
index 0000000..b183bf4
--- /dev/null
+++ b/chrome/browser/chromeos/pulse_audio_mixer.cc
@@ -0,0 +1,327 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/pulse_audio_mixer.h"
+
+#include <pulse/pulseaudio.h>
+
+#include "base/logging.h"
+
+namespace chromeos {
+
+// TODO(davej): Do asynchronous versions by using the threaded PulseAudio API
+// so gets, sets, and init sequence do not block calling thread. GetVolume()
+// and IsMute() may still need to be synchronous since caller is asking for the
+// value specifically, which we will not know until the operation completes.
+// So new asynchronous versions of the Get routines could be added with a
+// callback parameter. Currently, all Get, Set and Init calls are blocking on
+// PulseAudio.
+//
+// Set calls can just return without waiting. When a set operation completes,
+// we could proxy to the UI thread to notify there was a volume change update
+// so the new volume can be displayed to the user.
+// TODO(davej): Serialize volume/mute to preserve settings when restarting?
+// TODO(davej): Check if we need some thread safety mechanism (will someone be
+// calling GetVolume while another process is calling SetVolume?)
+
+namespace {
+
+const int kInvalidDeviceId = -1;
+
+} // namespace
+
+// AudioInfo contains all the values we care about when getting info for a
+// Sink (output device) used by GetAudioInfo()
+struct PulseAudioMixer::AudioInfo {
+ pa_cvolume cvolume;
+ bool muted;
+};
+
+// This class will set volume using PulseAudio to adjust volume and mute.
+
+PulseAudioMixer::PulseAudioMixer()
+ : device_id_(kInvalidDeviceId),
+ pa_mainloop_(NULL),
+ pa_context_(NULL),
+ connect_started_(false),
+ last_channels_(0) {
+}
+
+PulseAudioMixer::~PulseAudioMixer() {
+ PulseAudioFree();
+}
+
+bool PulseAudioMixer::Init() {
+ // Find main device for changing 'master' default volume.
+ if (!PulseAudioInit())
+ return false;
+ device_id_ = GetDefaultPlaybackDevice();
+ last_channels_ = 0;
+ LOG(INFO) << "PulseAudioMixer initialized OK";
+ return true;
+}
+
+double PulseAudioMixer::GetVolumeDb() const {
+ AudioInfo data;
+ if (!GetAudioInfo(&data))
+ return pa_sw_volume_to_dB(0); // this returns -inf
+ return pa_sw_volume_to_dB(data.cvolume.values[0]);
+}
+
+void PulseAudioMixer::SetVolumeDb(double vol_db) {
+ if (!PulseAudioValid()) {
+ DLOG(ERROR) << "Called SetVolume when mixer not valid";
+ return;
+ }
+
+ // last_channels_ determines the number of channels on the main output device,
+ // and is used later to set the volume on all channels at once.
+ if (!last_channels_) {
+ AudioInfo data;
+ if (!GetAudioInfo(&data))
+ return;
+ last_channels_ = data.cvolume.channels;
+ }
+
+ pa_operation* pa_op;
+ pa_cvolume cvolume;
+ pa_cvolume_set(&cvolume, last_channels_, pa_sw_volume_from_dB(vol_db));
+ pa_op = pa_context_set_sink_volume_by_index(pa_context_, device_id_,
+ &cvolume, NULL, NULL);
+ CompleteOperationHelper(pa_op);
+}
+
+bool PulseAudioMixer::IsMute() const {
+ AudioInfo data;
+ if (!GetAudioInfo(&data))
+ return false;
+
+ return data.muted;
+}
+
+void PulseAudioMixer::SetMute(bool mute) {
+ if (!PulseAudioValid()) {
+ DLOG(ERROR) << "Called SetMute when mixer not valid";
+ return;
+ }
+
+ pa_operation* pa_op;
+ pa_op = pa_context_set_sink_mute_by_index(pa_context_, device_id_,
+ mute ? 1 : 0, NULL, NULL);
+ CompleteOperationHelper(pa_op);
+}
+
+void PulseAudioMixer::ToggleMute() {
+ SetMute(!IsMute());
+}
+
+bool PulseAudioMixer::IsValid() const {
+ if (device_id_ == kInvalidDeviceId)
+ return false;
+ if (!pa_context_)
+ return false;
+ if (pa_context_get_state(pa_context_) != PA_CONTEXT_READY)
+ return false;
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Private functions that deal with PulseAudio directly
+
+bool PulseAudioMixer::PulseAudioInit() {
+ pa_mainloop_api* pa_mlapi;
+ pa_context_state_t state;
+
+ // It's OK to call Init again to re-initialize. This could be useful if
+ // !IsValid() and we want to try reconnecting.
+ if (pa_mainloop_)
+ PulseAudioFree();
+
+ while (true) {
+ // Create connection to default server.
+ pa_mainloop_ = pa_mainloop_new();
+ if (!pa_mainloop_) {
+ LOG(ERROR) << "Can't create PulseAudio mainloop";
+ break;
+ }
+ pa_mlapi = pa_mainloop_get_api(pa_mainloop_);
+ if (!pa_mlapi) {
+ LOG(ERROR) << "Can't get PulseAudio mainloop api";
+ break;
+ }
+ pa_context_ = pa_context_new(pa_mlapi, "ChromeAudio");
+ if (!pa_context_) {
+ LOG(ERROR) << "Can't create new PulseAudio context";
+ break;
+ }
+
+ // Using simpler method of just checking state after each iterate.
+ // Connect to PulseAudio sound server.
+ if (pa_context_connect(pa_context_, NULL,
+ PA_CONTEXT_NOAUTOSPAWN, NULL) != 0) {
+ LOG(ERROR) << "Can't start connection to PulseAudio sound server";
+ break;
+ }
+
+ connect_started_ = true;
+
+ // Wait until we have a completed connection or fail.
+ // TODO(davej): Remove blocking waits during init as well.
+ do {
+ pa_mainloop_iterate(pa_mainloop_, 1, NULL); // blocking wait
+ state = pa_context_get_state(pa_context_);
+ if (state == PA_CONTEXT_FAILED) {
+ LOG(ERROR) << "PulseAudio context connection failed";
+ break;
+ }
+ if (state == PA_CONTEXT_TERMINATED) {
+ LOG(ERROR) << "PulseAudio connection terminated early";
+ break;
+ }
+ } while (state != PA_CONTEXT_READY);
+
+ if (state != PA_CONTEXT_READY)
+ break;
+
+ return true;
+ }
+
+ // Failed startup sequence, clean up now.
+ PulseAudioFree();
+
+ return false;
+}
+
+void PulseAudioMixer::PulseAudioFree() {
+ if (pa_context_) {
+ if (connect_started_)
+ pa_context_disconnect(pa_context_);
+ pa_context_unref(pa_context_);
+ pa_context_ = NULL;
+ }
+ connect_started_ = false;
+ if (pa_mainloop_) {
+ pa_mainloop_free(pa_mainloop_);
+ pa_mainloop_ = NULL;
+ }
+}
+
+bool PulseAudioMixer::PulseAudioValid() const {
+ if (!pa_context_) {
+ DLOG(ERROR) << "Trying to use PulseAudio when no context";
+ return false;
+ }
+
+ if (pa_context_get_state(pa_context_) != PA_CONTEXT_READY) {
+ LOG(ERROR) << "PulseAudio context not ready";
+ return false;
+ }
+
+ if (device_id_ == kInvalidDeviceId) {
+ DLOG(ERROR) << "Trying to use PulseAudio when no device id";
+ return false;
+ }
+ return true;
+}
+
+bool PulseAudioMixer::CompleteOperationHelper(pa_operation* pa_op) {
+ // After starting any operation, this helper checks if it started OK, then
+ // waits for it to complete by iterating through the mainloop until the
+ // operation is not running anymore.
+ if (!pa_op) {
+ LOG(ERROR) << "Failed to start operation";
+ return false;
+ }
+ while (pa_operation_get_state(pa_op) == PA_OPERATION_RUNNING) {
+ pa_mainloop_iterate(pa_mainloop_, 1, NULL);
+ }
+ pa_operation_unref(pa_op);
+ return true;
+}
+
+int PulseAudioMixer::GetDefaultPlaybackDevice() {
+ int device = kInvalidDeviceId;
+ pa_operation* pa_op;
+
+ if (!pa_context_) {
+ DLOG(ERROR) << "Trying to use PulseAudio when no context";
+ return kInvalidDeviceId;
+ }
+
+ if (pa_context_get_state(pa_context_) != PA_CONTEXT_READY) {
+ LOG(ERROR) << "PulseAudio context not ready";
+ return kInvalidDeviceId;
+ }
+
+ pa_op = pa_context_get_sink_info_list(pa_context_,
+ EnumerateDevicesCallback,
+ &device);
+ CompleteOperationHelper(pa_op);
+ return device;
+}
+
+// static
+void PulseAudioMixer::EnumerateDevicesCallback(pa_context* unused,
+ const pa_sink_info* sink_info,
+ int eol,
+ void* userdata) {
+ int* pa_device = static_cast<int*>(userdata);
+
+ // If eol is set to a positive number, you're at the end of the list.
+ if (eol > 0)
+ return;
+
+ // TODO(davej): Should we handle cases of more than one output sink device?
+ if (*pa_device == kInvalidDeviceId)
+ *pa_device = sink_info->index;
+}
+
+bool PulseAudioMixer::GetAudioInfo(AudioInfo* info) const {
+ DCHECK(info);
+ if (!PulseAudioValid()) {
+ DLOG(ERROR) << "Called GetAudioInfo when mixer not valid";
+ return false;
+ }
+ if (!info)
+ return false;
+
+ pa_operation* pa_op;
+ pa_op = pa_context_get_sink_info_by_index(pa_context_,
+ device_id_,
+ GetAudioInfoCallback,
+ info);
+
+ // Duplicating some code in CompleteOperationHelper because this function
+ // needs to stay 'const', and this isn't allowed if that is called.
+ if (!pa_op) {
+ LOG(ERROR) << "Failed to start operation";
+ return false;
+ }
+ while (pa_operation_get_state(pa_op) == PA_OPERATION_RUNNING) {
+ pa_mainloop_iterate(pa_mainloop_, 1, NULL);
+ }
+ pa_operation_unref(pa_op);
+ return true;
+}
+
+// static
+void PulseAudioMixer::GetAudioInfoCallback(pa_context* unused,
+ const pa_sink_info* sink_info,
+ int eol,
+ void* userdata) {
+ if (!userdata) {
+ DLOG(ERROR) << "userdata NULL";
+ return;
+ }
+ AudioInfo* data = static_cast<AudioInfo*>(userdata);
+
+ // Copy just the information we care about.
+ if (eol == 0) {
+ data->cvolume = sink_info->volume;
+ data->muted = sink_info->mute ? true : false;
+ }
+}
+
+} // namespace chromeos
+
diff --git a/chrome/browser/chromeos/pulse_audio_mixer.h b/chrome/browser/chromeos/pulse_audio_mixer.h
new file mode 100644
index 0000000..7bc213f
--- /dev/null
+++ b/chrome/browser/chromeos/pulse_audio_mixer.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_PULSE_AUDIO_MIXER_H_
+#define CHROME_BROWSER_CHROMEOS_PULSE_AUDIO_MIXER_H_
+
+#include "base/basictypes.h"
+
+struct pa_context;
+struct pa_cvolume;
+struct pa_mainloop;
+struct pa_operation;
+struct pa_sink_info;
+
+namespace chromeos {
+
+class PulseAudioMixer {
+ public:
+ PulseAudioMixer();
+ ~PulseAudioMixer();
+
+ // These public functions all return true if the operation completed
+ // successfully, or false if not set up to talk with the default audio device.
+
+ // Connect to PulseAudio and find a default device. Blocking call.
+ // TODO(davej): Remove blocking waits in PA.
+ bool Init();
+
+ // Blocking call. Returns a default of -inf on error.
+ double GetVolumeDb() const;
+
+ // Blocking call.
+ // TODO(davej): Remove blocking waits in PA.
+ void SetVolumeDb(double vol_db);
+
+ // Gets the mute state of the default device (true == mute). Blocking call.
+ // Returns a default of false on error.
+ bool IsMute() const;
+
+ // Blocking call.
+ // TODO(davej): Remove blocking waits in PA.
+ void SetMute(bool mute);
+
+ // Blocking call.
+ // TODO(davej): Remove blocking waits in PA.
+ void ToggleMute();
+
+ // Call any time to see if we have a valid working connection to PulseAudio.
+ // Non-blocking call.
+ bool IsValid() const;
+
+ private:
+ struct AudioInfo;
+
+ // This goes through sequence of connecting to the default PulseAudio server.
+ // We will block until we either have a valid connection or something failed.
+ // It's safe to call again, in case a connection is lost for some reason
+ // (e.g. if other functions fail, or IsValid() is false).
+ // TODO(davej): Remove blocking waits in PA.
+ bool PulseAudioInit();
+
+ // PulseAudioFree. Disconnect from server.
+ void PulseAudioFree();
+
+ // Check if the PA system is ready for communication, as well as if a default
+ // device is available to talk to. This can return false if we lose the
+ // connection, even after an original successful init.
+ bool PulseAudioValid() const;
+
+ // Iterates the PA mainloop and only returns once an operation has completed
+ // (successfully or unsuccessfully). This blocking call is needed for
+ // synchronous getting of data.
+ // TODO(davej): This function will go away after doing asynchronous versions
+ // so we don't have to block when not necessary.
+ bool CompleteOperationHelper(pa_operation* pa_op);
+
+ // For now, this just gets the first device returned from the enumeration
+ // request. This will be the 'default' or 'master' device that all further
+ // actions are taken on. Blocking call.
+ int GetDefaultPlaybackDevice();
+ static void EnumerateDevicesCallback(pa_context* unused,
+ const pa_sink_info* sink_info,
+ int eol,
+ void* userdata);
+
+ // Get the info we're interested in from the default device. Currently this
+ // is an array of volumes, and the mute state. Blocking call.
+ bool GetAudioInfo(AudioInfo* info) const;
+ static void GetAudioInfoCallback(pa_context* unused,
+ const pa_sink_info* sink_info,
+ int eol,
+ void* userdata);
+
+ // The PulseAudio index of the main device being used.
+ int device_id_;
+
+ // Cached contexts for use in PulseAudio calls.
+ pa_mainloop* pa_mainloop_;
+ pa_context* pa_context_;
+
+ // Set if context connect succeeded so we can safely disconnect later.
+ bool connect_started_;
+
+ // Set to the number of channels on the main device.
+ int last_channels_;
+
+ DISALLOW_COPY_AND_ASSIGN(PulseAudioMixer);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CHROMEOS_PULSE_AUDIO_MIXER_H_
+
diff --git a/chrome/browser/chromeos/system_key_event_listener.cc b/chrome/browser/chromeos/system_key_event_listener.cc
index c693c59..c4ecb52 100644
--- a/chrome/browser/chromeos/system_key_event_listener.cc
+++ b/chrome/browser/chromeos/system_key_event_listener.cc
@@ -4,27 +4,14 @@
#include "chrome/browser/chromeos/system_key_event_listener.h"
+#include "chrome/browser/chromeos/audio_handler.h"
#include "third_party/cros/chromeos_wm_ipc_enums.h"
namespace chromeos {
-// For now, this file contains the SystemKeyEventListener, which listens for key
-// presses from Window Manager, and just calls amixer to adjust volume. Start
-// by just calling instance() to get it going.
-//
-// TODO(davej): Create AudioHandler() class and call its volume up/down/mute
-// functions, getting rid of these constants and RunCommand(). This class will
-// eventually call PulseAudio directly to get current volume and adjust
-// accordingly, giving us more control over Mute/UnMute behavior as well.
-
namespace {
-const char kIncreaseVolumeLevelCommand[] =
- "/usr/bin/amixer -- sset Master unmute 5%+";
-const char kDecreaseVolumeLevelCommand[] =
- "/usr/bin/amixer -- sset Master unmute 5%-";
-const char kMuteAudioCommand[] =
- "/usr/bin/amixer -- sset Master mute";
+const double kStepPercentage = 4.0;
} // namespace
@@ -34,7 +21,8 @@ SystemKeyEventListener* SystemKeyEventListener::instance() {
return instance;
}
-SystemKeyEventListener::SystemKeyEventListener() {
+SystemKeyEventListener::SystemKeyEventListener()
+ : audio_handler_(AudioHandler::instance()) {
WmMessageListener::instance()->AddObserver(this);
}
@@ -47,16 +35,23 @@ void SystemKeyEventListener::ProcessWmMessage(const WmIpc::Message& message,
if (message.type() != WM_IPC_MESSAGE_CHROME_NOTIFY_SYSKEY_PRESSED)
return;
- // TODO(davej): Use WmIpcSystemKey enums when available.
switch (message.param(0)) {
- case 0:
- RunCommand(kMuteAudioCommand);
+ case WM_IPC_SYSTEM_KEY_VOLUME_MUTE:
+ // TODO(davej): Toggle behavior is broken until we can either recieve
+ // notification of key up events without autorepeat, or add a timer to
+ // ignore autorepeated keys. Currently we get notified on key down and
+ // key repeat which would cause us to rapidly cycle mute/unmute/mute as
+ // long as mute key was held.
+ // Refer to http://crosbug.com/3754 and http://crosbug.com/3751
+ audio_handler_->SetMute(true);
break;
- case 1:
- RunCommand(kDecreaseVolumeLevelCommand);
+ case WM_IPC_SYSTEM_KEY_VOLUME_DOWN:
+ audio_handler_->AdjustVolumeByPercent(-kStepPercentage);
+ audio_handler_->SetMute(false);
break;
- case 2:
- RunCommand(kIncreaseVolumeLevelCommand);
+ case WM_IPC_SYSTEM_KEY_VOLUME_UP:
+ audio_handler_->AdjustVolumeByPercent(kStepPercentage);
+ audio_handler_->SetMute(false);
break;
default:
DLOG(ERROR) << "SystemKeyEventListener: Unexpected message "
@@ -65,13 +60,5 @@ void SystemKeyEventListener::ProcessWmMessage(const WmIpc::Message& message,
}
}
-void SystemKeyEventListener::RunCommand(std::string command) {
- if (command.empty())
- return;
- command += " &";
- DLOG(INFO) << "Running command \"" << command << "\"";
- if (system(command.c_str()) < 0)
- LOG(WARNING) << "Got error while running \"" << command << "\"";
-}
-
} // namespace chromeos
+
diff --git a/chrome/browser/chromeos/system_key_event_listener.h b/chrome/browser/chromeos/system_key_event_listener.h
index 9814483..117a3a8 100644
--- a/chrome/browser/chromeos/system_key_event_listener.h
+++ b/chrome/browser/chromeos/system_key_event_listener.h
@@ -5,15 +5,17 @@
#ifndef CHROME_BROWSER_CHROMEOS_SYSTEM_KEY_EVENT_LISTENER_H_
#define CHROME_BROWSER_CHROMEOS_SYSTEM_KEY_EVENT_LISTENER_H_
-#include <gtk/gtk.h>
-
-#include <string>
-
#include "base/singleton.h"
#include "chrome/browser/chromeos/wm_message_listener.h"
namespace chromeos {
+class AudioHandler;
+
+// SystemKeyEventListener listens for volume related key presses from the
+// window manager, then tells the AudioHandler to adjust volume accordingly.
+// Start by just calling instance() to get it going.
+
class SystemKeyEventListener : public WmMessageListener::Observer {
public:
static SystemKeyEventListener* instance();
@@ -30,7 +32,9 @@ class SystemKeyEventListener : public WmMessageListener::Observer {
SystemKeyEventListener();
virtual ~SystemKeyEventListener();
- void RunCommand(std::string command);
+ // AudioHandler is a Singleton class we are just caching a pointer to here,
+ // and we do not own the pointer.
+ AudioHandler* const audio_handler_;
DISALLOW_COPY_AND_ASSIGN(SystemKeyEventListener);
};
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 7196ad4..a92e336 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -339,6 +339,8 @@
'browser/chromeos/notifications/system_notification.cc',
'browser/chromeos/notifications/system_notification_factory.h',
'browser/chromeos/notifications/system_notification_factory.cc',
+ 'browser/chromeos/audio_handler.cc',
+ 'browser/chromeos/audio_handler.h',
'browser/chromeos/boot_times_loader.cc',
'browser/chromeos/boot_times_loader.h',
'browser/chromeos/browser_notification_observers.cc',
@@ -505,6 +507,8 @@
'browser/chromeos/pipe_reader.h',
'browser/chromeos/preferences.cc',
'browser/chromeos/preferences.h',
+ 'browser/chromeos/pulse_audio_mixer.cc',
+ 'browser/chromeos/pulse_audio_mixer.h',
'browser/chromeos/status/browser_status_area_view.cc',
'browser/chromeos/status/browser_status_area_view.h',
'browser/chromeos/status/clock_menu_button.cc',
@@ -2628,7 +2632,12 @@
['chromeos==1', {
'sources!': [
'browser/platform_util_linux.cc',
- ]
+ ],
+ 'link_settings': {
+ 'libraries': [
+ '-lpulse',
+ ],
+ },
}],
['OS=="linux"', {
'dependencies': [