diff options
-rw-r--r-- | chrome/browser/chromeos/audio_handler.cc | 167 | ||||
-rw-r--r-- | chrome/browser/chromeos/audio_handler.h | 89 | ||||
-rw-r--r-- | chrome/browser/chromeos/pulse_audio_mixer.cc | 327 | ||||
-rw-r--r-- | chrome/browser/chromeos/pulse_audio_mixer.h | 114 | ||||
-rw-r--r-- | chrome/browser/chromeos/system_key_event_listener.cc | 51 | ||||
-rw-r--r-- | chrome/browser/chromeos/system_key_event_listener.h | 14 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 11 |
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': [ |