Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ if (BRICKMAN_TEST)
test/controller/FakeFileBrowserController.vala
test/controller/FakeNetworkController.vala
test/controller/FakeOpenRobertaController.vala
test/controller/FakeSoundController.vala
test/view_model/FakeMixerElement.vala
)
else (BRICKMAN_TEST)
set (BRICKMAN_SOURCE_FILES
Expand All @@ -58,6 +60,8 @@ else (BRICKMAN_TEST)
src/controller/FileBrowserController.vala
src/controller/NetworkController.vala
src/controller/OpenRobertaController.vala
src/controller/SoundController.vala
src/view_model/AlsaBackedMixerElement.vala
src/GlobalManager.vala
src/main.vala
)
Expand Down Expand Up @@ -86,6 +90,8 @@ set (BRICKMAN_COMMON_SOURCE_FILES
src/view/DeviceBrowserWindow.vala
src/view/FileBrowserWindow.vala
src/view/HomeWindow.vala
src/view/MixerElementSelectorWindow.vala
src/view/MixerElementVolumeWindow.vala
src/view/MotorBrowserWindow.vala
src/view/MotorInfoWindow.vala
src/view/MotorValueDialog.vala
Expand Down Expand Up @@ -114,10 +120,12 @@ set (BRICKMAN_COMMON_SOURCE_FILES
src/view/WifiNetworkWindow.vala
src/view/WifiStatusBarItem.vala
src/view/WifiWindow.vala
src/view_model/IMixerElementViewModel.vala
)

find_package(PkgConfig REQUIRED)
pkg_check_modules(DEPS REQUIRED
alsa
ev3devkit-0.4
glib-2.0
gobject-2.0
Expand Down Expand Up @@ -174,6 +182,7 @@ PACKAGES
curses
posix
linux
alsa
${BRICKMAN_TEST_PACKAGES}
CUSTOM_VAPIS
bindings/*.vapi
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ not in brickstrap shell:

mkdir -p <some-build-dir>
cd <some-build-dir>
cmake <path-to-brickdm-source> -DCMAKE_BUILD_TYPE=string:Debug -DBRICKMAN_TEST=bool:Yes
cmake <path-to-brickman-source> -DCMAKE_BUILD_TYPE=string:Debug -DBRICKMAN_TEST=bool:Yes
make
make run

Expand Down
3 changes: 2 additions & 1 deletion debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ Priority: standard
Maintainer: David Lechner <david@lechnology.com>
Build-Depends: debhelper (>= 9), dh-systemd, cmake, valac (>= 0.24),
libgirepository1.0-dev, libgudev-1.0-dev,
libncurses5-dev, libev3devkit-dev, netpbm
libncurses5-dev, libev3devkit-dev, netpbm,
libasound2-dev
Standards-Version: 3.9.5
Homepage: https://www.ev3dev.org
Vcs-Git: git://github.com/ev3dev/brickman.git
Expand Down
121 changes: 121 additions & 0 deletions src/controller/SoundController.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* brickman -- Brick Manager for LEGO MINDSTORMS EV3/ev3dev
*
* Copyright 2016 Kaelin Laundry <wasabifan@outlook.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/

/* SoundController.vala - Controller for sound volume control */

using Ev3devKit.Devices;
using Ev3devKit.Ui;
using Alsa;

namespace BrickManager {
public class SoundController : Object, IBrickManagerModule {
const int VOLUME_STEP = 10;

Mixer mixer;
MixerElementSelectorWindow mixer_select_window;
MixerElementVolumeWindow volume_window;

public string display_name { get { return "Sound"; } }

private void initialize_mixer () {
mixer = null;

int err = Mixer.open (out mixer);
if (err != 0) {
critical ("Failed to open mixer: %s", Alsa.strerror (err));
return;
}

err = mixer.attach ();
if (err != 0) {
critical ("Failed to attach mixer: %s", Alsa.strerror (err));
return;
}

err = mixer.register ();
if (err != 0) {
critical ("Failed to register mixer: %s", Alsa.strerror (err));
return;
}

err = mixer.load ();
if (err != 0) {
critical ("Failed to load mixer: %s", Alsa.strerror (err));
return;
}
}

void create_main_window () {
mixer_select_window = new MixerElementSelectorWindow ();

mixer_select_window.mixer_element_selected.connect ((selected_element) => {
if (volume_window == null) {
create_volume_window ();
}

volume_window.current_element = selected_element;
volume_window.show_element_details = true;
volume_window.show ();
});
}

void create_volume_window () {
volume_window = new MixerElementVolumeWindow ();

weak MixerElementVolumeWindow weak_volume_window = volume_window;
// Wire up handlers for volume window signals
volume_window.volume_up.connect (() =>
weak_volume_window.current_element.volume += VOLUME_STEP);

volume_window.volume_down.connect (() =>
weak_volume_window.current_element.volume -= VOLUME_STEP);

volume_window.mute.connect (() =>
weak_volume_window.current_element.volume = IMixerElementViewModel.MIN_VOLUME);
}

public void show_main_window () {
if (mixer_select_window == null) {
create_main_window ();
}

// Whenever the sound item is launched from the main menu,
// repopulate the mixer list
mixer_select_window.clear_elements ();
// Re-initializing will return updated data, including volume
initialize_mixer ();
for (MixerElement element = mixer.first_elem (); element != null; element = element.next ()) {
mixer_select_window.add_element (new AlsaBackedMixerElement (element));
}

if (mixer_select_window.has_single_element) {
if (volume_window == null)
create_volume_window ();

volume_window.current_element = mixer_select_window.first_element;
volume_window.show_element_details = false;
volume_window.show ();
} else {
mixer_select_window.show ();
}
}
}
}
2 changes: 2 additions & 0 deletions src/main.vala
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ namespace BrickManager {
var bluetooth_controller = new BluetoothController ();
network_controller.add_controller (bluetooth_controller);
network_controller.add_controller (network_controller.wifi_controller);
var sound_controller = new SoundController ();
home_window.add_controller (sound_controller);
var battery_controller = new BatteryController ();
home_window.add_controller (battery_controller);
var open_roberta_controller = new OpenRobertaController ();
Expand Down
130 changes: 130 additions & 0 deletions src/view/MixerElementSelectorWindow.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* brickman -- Brick Manager for LEGO MINDSTORMS EV3/ev3dev
*
* Copyright (C) 2016 Kaelin Laundry <wasabifan@outlook.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

/*
* MixerElementSelectorWindow.vala - Lists ALSA mixer elements
*/

using Ev3devKit;
using Ev3devKit.Ui;

namespace BrickManager {

public class MixerElementSelectorWindow : BrickManagerWindow {
Ui.Menu element_menu;

public signal void mixer_element_selected (IMixerElementViewModel selected_element);

public MixerElementSelectorWindow () {
title = "Volume Controls";
element_menu = new Ui.Menu ();
content_vbox.add (element_menu);
}

protected string get_element_label_text (IMixerElementViewModel element) {
var builder = new StringBuilder ();
builder.append (element.name);

if (element.index != 0) {
builder.append_printf ("[%u]", element.index);
}

if (element.can_mute && element.is_muted) {
builder.append (" (muted)");
} else {
builder.append_printf (" (%ld%%)", element.volume);
}

return builder.str;
}

protected void sort_element_menu () {
// TODO: we would get much better performance if we just inserted
// the item in the correct place instead of sorting the entire list
// each time an item is inserted.
element_menu.sort_menu_items ((a, b) => {
var element_a = a.represented_object as IMixerElementViewModel;
var element_b = b.represented_object as IMixerElementViewModel;

// Group by name, and sort by index within the same name
if (element_a.name == element_b.name) {
return (int)element_a.index - (int)element_b.index;
} else {
return element_a.name.ascii_casecmp (element_b.name);
}
});
}

public void add_element (IMixerElementViewModel element) {
var menu_item = new Ui.MenuItem (get_element_label_text (element)) {
represented_object = (Object)element
};

weak IMixerElementViewModel weak_element = element;
// Update the menu item whenever the represented element changes
weak_element.notify.connect ((sender, property) => {
menu_item.label.text = get_element_label_text (weak_element);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a reference cycle here. menu_item has a reference to element in represented_object and element has a reference to menu_item in the signal closure.

sort_element_menu ();
});

// Emit a selection signal for this element when its menu item is selected
menu_item.button.pressed.connect (() =>
mixer_element_selected (weak_element));

element_menu.add_menu_item (menu_item);
}

protected void remove_menu_item (Ui.MenuItem menu_item) {
if (menu_item != null) {
element_menu.remove_menu_item (menu_item);
}
}

public void remove_element (IMixerElementViewModel element) {
var item = element_menu.find_menu_item<IMixerElementViewModel> (element, (menu_item, target_element) => {
return target_element == (menu_item.represented_object as IMixerElementViewModel);
});

remove_menu_item (item);
}

public void clear_elements () {
var iter = element_menu.menu_item_iter ();
while (iter.size > 0) {
remove_menu_item (iter[0]);
}
}

public bool has_single_element {
get {
return element_menu.menu_item_iter ().size == 1;
}
}

public IMixerElementViewModel? first_element {
get {
if (element_menu.menu_item_iter ().size <= 0) {
return null;
}

return element_menu.menu_item_iter ().get (0).represented_object as IMixerElementViewModel;
}
}
}
}
Loading