parent
7a70833ba1
commit
5f2d92f916
|
@ -0,0 +1,263 @@
|
||||||
|
#
|
||||||
|
# Build
|
||||||
|
#
|
||||||
|
|
||||||
|
if {![have_spec linux]} {
|
||||||
|
puts "This run script requires linux!"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
set build_components {
|
||||||
|
core init
|
||||||
|
drivers/timer
|
||||||
|
server/ram_fs
|
||||||
|
drivers/framebuffer
|
||||||
|
server/dynamic_rom
|
||||||
|
server/report_rom
|
||||||
|
server/nitpicker
|
||||||
|
server/fs_rom
|
||||||
|
server/wm
|
||||||
|
app/pointer
|
||||||
|
app/floating_window_layouter
|
||||||
|
app/decorator
|
||||||
|
app/mixer_gui_qt
|
||||||
|
}
|
||||||
|
|
||||||
|
source ${genode_dir}/repos/base/run/platform_drv.inc
|
||||||
|
append_platform_drv_build_components
|
||||||
|
|
||||||
|
build $build_components
|
||||||
|
|
||||||
|
create_boot_directory
|
||||||
|
|
||||||
|
#
|
||||||
|
# Generate config
|
||||||
|
#
|
||||||
|
|
||||||
|
set config {
|
||||||
|
<config verbose="yes">
|
||||||
|
<parent-provides>
|
||||||
|
<service name="ROM"/>
|
||||||
|
<service name="RAM"/>
|
||||||
|
<service name="CAP"/>
|
||||||
|
<service name="IO_MEM"/>
|
||||||
|
<service name="IO_PORT"/>
|
||||||
|
<service name="IRQ"/>
|
||||||
|
<service name="PD"/>
|
||||||
|
<service name="RM"/>
|
||||||
|
<service name="CPU"/>
|
||||||
|
<service name="LOG"/>
|
||||||
|
<service name="SIGNAL"/>
|
||||||
|
</parent-provides>
|
||||||
|
<default-route>
|
||||||
|
<any-service> <parent/> <any-child/> </any-service>
|
||||||
|
</default-route>
|
||||||
|
<start name="timer">
|
||||||
|
<resource name="RAM" quantum="1M"/>
|
||||||
|
<provides> <service name="Timer"/> </provides>
|
||||||
|
</start>}
|
||||||
|
|
||||||
|
append_platform_drv_config
|
||||||
|
|
||||||
|
append_if [have_spec sdl] config {
|
||||||
|
<start name="fb_sdl">
|
||||||
|
<resource name="RAM" quantum="4M"/>
|
||||||
|
<provides>
|
||||||
|
<service name="Input"/>
|
||||||
|
<service name="Framebuffer"/>
|
||||||
|
</provides>
|
||||||
|
</start>}
|
||||||
|
|
||||||
|
append config {
|
||||||
|
<start name="report_rom">
|
||||||
|
<resource name="RAM" quantum="2M"/>
|
||||||
|
<provides>
|
||||||
|
<service name="ROM"/>
|
||||||
|
<service name="Report"/>
|
||||||
|
</provides>
|
||||||
|
<config>
|
||||||
|
<rom>
|
||||||
|
<policy label="layouter -> window_list" report="wm -> window_list"/>
|
||||||
|
<policy label="layouter -> focus_request" report="wm -> focus_request"/>
|
||||||
|
<policy label="decorator -> window_layout" report="layouter -> window_layout"/>
|
||||||
|
<policy label="wm -> resize_request" report="layouter -> resize_request"/>
|
||||||
|
<policy label="decorator -> pointer" report="wm -> pointer"/>
|
||||||
|
<policy label="layouter -> hover" report="decorator -> hover"/>
|
||||||
|
<policy label="wm -> focus" report="layouter -> focus"/>
|
||||||
|
<policy label="mixer_gui_qt -> channel_list" report="mixer -> channel_list"/>
|
||||||
|
<policy label="mixer -> channel_list" report="mixer_gui_qt -> channel_list"/>
|
||||||
|
</rom>
|
||||||
|
</config>
|
||||||
|
</start>
|
||||||
|
<start name="nitpicker">
|
||||||
|
<resource name="RAM" quantum="4M"/>
|
||||||
|
<provides><service name="Nitpicker"/></provides>
|
||||||
|
<config>
|
||||||
|
<report focus="yes" xray="yes" />
|
||||||
|
<domain name="pointer" layer="1" content="client" label="no" origin="pointer" />
|
||||||
|
<domain name="default" layer="2" content="client" label="no" hover="always" focus="click"/>
|
||||||
|
|
||||||
|
<policy label="pointer" domain="pointer"/>
|
||||||
|
<policy label="" domain="default"/>
|
||||||
|
|
||||||
|
<background color="#000000" />
|
||||||
|
</config>
|
||||||
|
<route>
|
||||||
|
<service name="Report"> <child name="report_rom"/> </service>
|
||||||
|
<any-service> <parent/> <any-child/> </any-service>
|
||||||
|
</route>
|
||||||
|
</start>
|
||||||
|
<start name="pointer">
|
||||||
|
<resource name="RAM" quantum="1M"/>
|
||||||
|
<route>
|
||||||
|
<service name="Nitpicker"> <child name="nitpicker"/> </service>
|
||||||
|
<any-service> <parent/> <any-child/> </any-service>
|
||||||
|
</route>
|
||||||
|
</start>
|
||||||
|
<start name="wm" >
|
||||||
|
<resource name="RAM" quantum="16M"/>
|
||||||
|
<provides><service name="Nitpicker"/></provides>
|
||||||
|
<config>
|
||||||
|
<policy label="decorator" role="decorator"/>
|
||||||
|
<policy label="layouter" role="layouter"/>
|
||||||
|
</config>
|
||||||
|
<route>
|
||||||
|
<service name="Nitpicker"> <child name="nitpicker"/> </service>
|
||||||
|
<any-service> <child name="report_rom"/> <parent/> <any-child/> </any-service>
|
||||||
|
</route>
|
||||||
|
</start>
|
||||||
|
<start name="layouter">
|
||||||
|
<binary name="floating_window_layouter"/>
|
||||||
|
<resource name="RAM" quantum="4M"/>
|
||||||
|
<route>
|
||||||
|
<any-service>
|
||||||
|
<child name="wm"/> <child name="report_rom"/> <parent/> <any-child/>
|
||||||
|
</any-service>
|
||||||
|
</route>
|
||||||
|
</start>
|
||||||
|
<start name="decorator">
|
||||||
|
<binary name="decorator"/>
|
||||||
|
<resource name="RAM" quantum="8M"/>
|
||||||
|
<route>
|
||||||
|
<any-service>
|
||||||
|
<child name="wm"/> <child name="report_rom"/> <parent/> <any-child/>
|
||||||
|
</any-service>
|
||||||
|
</route>
|
||||||
|
</start>
|
||||||
|
}
|
||||||
|
|
||||||
|
append config {
|
||||||
|
<start name="dynamic_rom">
|
||||||
|
<resource name="RAM" quantum="4M"/>
|
||||||
|
<provides><service name="ROM"/></provides>
|
||||||
|
<config verbose="yes">
|
||||||
|
<rom name="channel_list">
|
||||||
|
<sleep milliseconds="1000" />
|
||||||
|
<inline description="first config update">
|
||||||
|
<channel_list>
|
||||||
|
<channel type="input" label="test-audio_out0" name="right" number="1" active="1" volume="0" muted="0"/>
|
||||||
|
<channel type="input" label="test-audio_out_click" name="left" number="0" active="1" volume="75" muted="0"/>
|
||||||
|
<channel type="input" label="fancy_init -> test-audio_out1" name="left" number="0" active="1" volume="75" muted="0"/>
|
||||||
|
<channel type="input" label="test-audio_out0" name="left" number="0" active="1" volume="0" muted="0"/>
|
||||||
|
<channel type="input" label="test-audio_out_click" name="right" number="1" active="1" volume="75" muted="0"/>
|
||||||
|
<channel type="input" label="fancy_init -> test-audio_out1" name="right" number="1" active="1" volume="75" muted="0"/>
|
||||||
|
<channel type="output" label="master" name="left" number="0" active="1" volume="100" muted="0"/>
|
||||||
|
<channel type="output" label="master" name="right" number="1" active="1" volume="100" muted="0"/>
|
||||||
|
</channel_list>
|
||||||
|
</inline>
|
||||||
|
<sleep milliseconds="1000" />
|
||||||
|
<inline description="second config update">
|
||||||
|
<channel_list>
|
||||||
|
<channel type="input" label="test-audio_out0" name="right" number="1" active="1" volume="100" muted="0"/>
|
||||||
|
<channel type="input" label="test-audio_out_click" name="left" number="0" active="1" volume="75" muted="1"/>
|
||||||
|
<channel type="input" label="fancy_init -> test-audio_out1" name="left" number="0" active="1" volume="25" muted="0"/>
|
||||||
|
<channel type="input" label="test-audio_out0" name="left" number="0" active="1" volume="100" muted="0"/>
|
||||||
|
<channel type="input" label="test-audio_out_click" name="right" number="1" active="1" volume="15" muted="1"/>
|
||||||
|
<channel type="input" label="fancy_init -> test-audio_out1" name="right" number="1" active="1" volume="25" muted="0"/>
|
||||||
|
<channel type="output" label="master" name="left" number="0" active="1" volume="100" muted="0"/>
|
||||||
|
<channel type="output" label="master" name="right" number="1" active="1" volume="100" muted="0"/>
|
||||||
|
</channel_list>
|
||||||
|
</inline>
|
||||||
|
</rom>
|
||||||
|
</config>
|
||||||
|
</start>
|
||||||
|
|
||||||
|
<start name="mixer_gui_qt">
|
||||||
|
<resource name="RAM" quantum="32M"/>
|
||||||
|
<config ld_verbose="yes">
|
||||||
|
<libc stdout="/dev/log" stderr="/dev/log">
|
||||||
|
<vfs>
|
||||||
|
<dir name="dev"> <log/> </dir>
|
||||||
|
<tar name="qt5_fs_mixer_gui_qt.tar"/>
|
||||||
|
<!--<dir name="config"> <fs label="config"/> </dir>-->
|
||||||
|
</vfs>
|
||||||
|
</libc>
|
||||||
|
</config>
|
||||||
|
<route>
|
||||||
|
<service name="Nitpicker"> <child name="wm"/> </service>
|
||||||
|
<service name="ROM"> <if-arg key="label" value="channel_list" /> <child name="dynamic_rom" /> </service>
|
||||||
|
<any-service> <parent/> <any-child/> </any-service>
|
||||||
|
</route>
|
||||||
|
</start>}
|
||||||
|
|
||||||
|
append config {
|
||||||
|
</config>
|
||||||
|
}
|
||||||
|
|
||||||
|
install_config $config
|
||||||
|
|
||||||
|
#
|
||||||
|
# Prepare resources needed by the application
|
||||||
|
#
|
||||||
|
|
||||||
|
# get fonts
|
||||||
|
exec rm -rf bin/qt5_fs/mixer_gui_qt/qt
|
||||||
|
exec mkdir -p bin/qt5_fs/mixer_gui_qt/qt/lib
|
||||||
|
exec ln -sf [pwd]/bin/qt5_fs/qt/lib/fonts bin/qt5_fs/mixer_gui_qt/qt/lib/fonts
|
||||||
|
|
||||||
|
# create tar archive containg Qt5 resources
|
||||||
|
exec tar chf bin/qt5_fs_mixer_gui_qt.tar -C bin/qt5_fs/mixer_gui_qt .
|
||||||
|
|
||||||
|
#
|
||||||
|
# Boot modules
|
||||||
|
#
|
||||||
|
|
||||||
|
set boot_modules {
|
||||||
|
core init timer
|
||||||
|
ld.lib.so libc.lib.so
|
||||||
|
|
||||||
|
report_rom dynamic_rom ram_fs
|
||||||
|
fs_rom
|
||||||
|
|
||||||
|
qt5_gui.lib.so
|
||||||
|
qt5_widgets.lib.so
|
||||||
|
qt5_xml.lib.so
|
||||||
|
qt5_core.lib.so
|
||||||
|
freetype.lib.so
|
||||||
|
gallium.lib.so
|
||||||
|
icu.lib.so
|
||||||
|
libc_lock_pipe.lib.so
|
||||||
|
libm.lib.so
|
||||||
|
libpng.lib.so
|
||||||
|
jpeg.lib.so
|
||||||
|
zlib.lib.so
|
||||||
|
stdcxx.lib.so
|
||||||
|
pthread.lib.so
|
||||||
|
mixer_gui_qt
|
||||||
|
qt5_fs_mixer_gui_qt.tar
|
||||||
|
nitpicker
|
||||||
|
wm
|
||||||
|
pointer
|
||||||
|
floating_window_layouter
|
||||||
|
decorator
|
||||||
|
}
|
||||||
|
|
||||||
|
append_platform_drv_boot_modules
|
||||||
|
|
||||||
|
lappend_if [have_spec linux] boot_modules fb_sdl
|
||||||
|
|
||||||
|
build_boot_image $boot_modules
|
||||||
|
|
||||||
|
run_genode_until forever
|
||||||
|
|
||||||
|
# vi: set ft=tcl :
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* \brief Mixer frontend
|
||||||
|
* \author Josef Soentgen
|
||||||
|
* \date 2015-10-15
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Genode includes */
|
||||||
|
#include <base/printf.h>
|
||||||
|
#include <base/thread.h>
|
||||||
|
#include <os/attached_rom_dataspace.h>
|
||||||
|
|
||||||
|
/* Qt includes */
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QFile>
|
||||||
|
|
||||||
|
/* application includes */
|
||||||
|
#include "main_window.h"
|
||||||
|
|
||||||
|
|
||||||
|
enum { THREAD_STACK_SIZE = 2 * 1024 * sizeof(long) };
|
||||||
|
|
||||||
|
|
||||||
|
struct Report_thread : Genode::Thread<THREAD_STACK_SIZE>
|
||||||
|
{
|
||||||
|
QMember<Report_proxy> proxy;
|
||||||
|
|
||||||
|
Genode::Attached_rom_dataspace channels_rom { "channel_list" };
|
||||||
|
|
||||||
|
Genode::Signal_receiver sig_rec;
|
||||||
|
Genode::Signal_dispatcher<Report_thread> channels_dispatcher;
|
||||||
|
|
||||||
|
Genode::Lock _report_lock { Genode::Lock::LOCKED };
|
||||||
|
|
||||||
|
void _report(char const *data, size_t size)
|
||||||
|
{
|
||||||
|
Genode::Xml_node node(data, size);
|
||||||
|
proxy->report_changed(&_report_lock, &node);
|
||||||
|
|
||||||
|
/* wait until the report was handled */
|
||||||
|
_report_lock.lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handle_channels(unsigned)
|
||||||
|
{
|
||||||
|
channels_rom.update();
|
||||||
|
_report(channels_rom.local_addr<char>(), channels_rom.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
Report_thread()
|
||||||
|
:
|
||||||
|
Genode::Thread<THREAD_STACK_SIZE>("report_thread"),
|
||||||
|
channels_dispatcher(sig_rec, *this, &Report_thread::_handle_channels)
|
||||||
|
{
|
||||||
|
channels_rom.sigh(channels_dispatcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
void entry() override
|
||||||
|
{
|
||||||
|
using namespace Genode;
|
||||||
|
while (true) {
|
||||||
|
Signal sig = sig_rec.wait_for_signal();
|
||||||
|
int num = sig.num();
|
||||||
|
|
||||||
|
Signal_dispatcher_base *dispatcher;
|
||||||
|
dispatcher = dynamic_cast<Signal_dispatcher_base *>(sig.context());
|
||||||
|
dispatcher->dispatch(num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void connect_window(Main_window *win)
|
||||||
|
{
|
||||||
|
QObject::connect(proxy, SIGNAL(report_changed(void *,void const*)),
|
||||||
|
win, SLOT(report_changed(void *, void const*)),
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static inline void load_stylesheet()
|
||||||
|
{
|
||||||
|
QFile file(":style.qss");
|
||||||
|
if (!file.open(QFile::ReadOnly)) {
|
||||||
|
qWarning() << "Warning:" << file.errorString()
|
||||||
|
<< "opening file" << file.fileName();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qApp->setStyleSheet(QLatin1String(file.readAll()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
Report_thread *report_thread;
|
||||||
|
try { report_thread = new Report_thread(); }
|
||||||
|
catch (...) {
|
||||||
|
PERR("Could not create Report_thread");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
|
load_stylesheet();
|
||||||
|
|
||||||
|
QMember<Main_window> main_window;
|
||||||
|
main_window->show();
|
||||||
|
|
||||||
|
report_thread->connect_window(main_window);
|
||||||
|
report_thread->start();
|
||||||
|
|
||||||
|
app.connect(&app, SIGNAL(lastWindowClosed()), SLOT(quit()));
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
|
@ -0,0 +1,415 @@
|
||||||
|
/*
|
||||||
|
* \brief Main window of the mixer frontend
|
||||||
|
* \author Josef Soentgen
|
||||||
|
* \date 2015-10-15
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Genode Labs GmbH
|
||||||
|
*
|
||||||
|
* This file is part of the Genode OS framework, which is distributed
|
||||||
|
* under the terms of the GNU General Public License version 2.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Genode includes */
|
||||||
|
#include <base/printf.h>
|
||||||
|
#include <mixer/channel.h>
|
||||||
|
#include <os/attached_rom_dataspace.h>
|
||||||
|
#include <os/reporter.h>
|
||||||
|
#include <rom_session/connection.h>
|
||||||
|
|
||||||
|
/* Qt includes */
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFrame>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QSlider>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
/* application includes */
|
||||||
|
#include "main_window.h"
|
||||||
|
|
||||||
|
|
||||||
|
/************
|
||||||
|
** helper **
|
||||||
|
************/
|
||||||
|
|
||||||
|
typedef Mixer::Channel Channel;
|
||||||
|
|
||||||
|
static struct Names {
|
||||||
|
char const *name;
|
||||||
|
Channel::Number number;
|
||||||
|
} names[] = {
|
||||||
|
{ "left", Channel::Number::LEFT },
|
||||||
|
{ "front left", Channel::Number::LEFT },
|
||||||
|
{ "right", Channel::Number::RIGHT },
|
||||||
|
{ "front right", Channel::Number::RIGHT },
|
||||||
|
{ nullptr, Channel::Number::MAX_CHANNELS }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static char const *channel_string_from_number(Channel::Number ch)
|
||||||
|
{
|
||||||
|
for (Names *n = names; n->name; ++n)
|
||||||
|
if (ch == n->number)
|
||||||
|
return n->name;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* keep sorted! */
|
||||||
|
static struct Types {
|
||||||
|
char const *name;
|
||||||
|
Channel::Type type;
|
||||||
|
} types[] = {
|
||||||
|
{ "invalid", Channel::Type::TYPE_INVALID },
|
||||||
|
{ "input", Channel::Type::INPUT },
|
||||||
|
{ "output", Channel::Type::OUTPUT }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static char const *type_to_string(Channel::Type t) { return types[t].name; }
|
||||||
|
|
||||||
|
|
||||||
|
class Channel_widget : public Compound_widget<QFrame, QVBoxLayout>,
|
||||||
|
public Genode::List<Channel_widget>::Element
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Channel::Number _number;
|
||||||
|
Channel::Type _type;
|
||||||
|
|
||||||
|
QCheckBox _muted_checkbox;
|
||||||
|
QSlider _slider { Qt::Vertical };
|
||||||
|
QHBoxLayout _slider_hbox;
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
|
||||||
|
void channel_changed();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Channel_widget(Channel::Type type, Channel::Number number)
|
||||||
|
:
|
||||||
|
_number(number), _type(type),
|
||||||
|
_muted_checkbox("mute")
|
||||||
|
{
|
||||||
|
_slider.setMinimum(Channel::Volume_level::MIN);
|
||||||
|
_slider.setMaximum(Channel::Volume_level::MAX);
|
||||||
|
|
||||||
|
_slider_hbox.addStretch();
|
||||||
|
_slider_hbox.addWidget(&_slider, Qt::AlignCenter);
|
||||||
|
_slider_hbox.addStretch();
|
||||||
|
|
||||||
|
_layout->addLayout(&_slider_hbox);
|
||||||
|
_layout->addWidget(&_muted_checkbox);
|
||||||
|
|
||||||
|
connect(&_slider, SIGNAL(sliderReleased()),
|
||||||
|
this, SIGNAL(channel_changed()));
|
||||||
|
connect(&_muted_checkbox, SIGNAL(stateChanged(int)),
|
||||||
|
this, SIGNAL(channel_changed()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Channel::Number number() const { return _number; }
|
||||||
|
Channel::Type type() const { return _type; }
|
||||||
|
int volume() const { return _slider.value(); }
|
||||||
|
void volume(int v) { _slider.setValue(v); }
|
||||||
|
bool muted() const { return _muted_checkbox.checkState() == Qt::Checked; }
|
||||||
|
void muted(bool v) { _muted_checkbox.setChecked(v); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class Client_widget : public Compound_widget<QFrame, QVBoxLayout>,
|
||||||
|
public Genode::List<Client_widget>::Element
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
bool valid { true };
|
||||||
|
|
||||||
|
void _sorted_insert(Channel_widget *cw)
|
||||||
|
{
|
||||||
|
Channel::Number const nr = cw->number();
|
||||||
|
|
||||||
|
Channel_widget const *last = nullptr;
|
||||||
|
Channel_widget const *w = _list.first();
|
||||||
|
for (; w; w = w->next()) {
|
||||||
|
if (w->number() > nr)
|
||||||
|
break;
|
||||||
|
last = w;
|
||||||
|
}
|
||||||
|
_list.insert(cw, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Genode::List<Channel_widget> _list;
|
||||||
|
Genode::Allocator &_alloc;
|
||||||
|
Channel::Label _label;
|
||||||
|
|
||||||
|
QLabel _name;
|
||||||
|
QHBoxLayout _hlayout;
|
||||||
|
|
||||||
|
static char const *_strip_label(Channel::Label const &label)
|
||||||
|
{
|
||||||
|
char const * str = label.string();
|
||||||
|
int pos = 0;
|
||||||
|
for (int i = 0; str[i]; i++)
|
||||||
|
if (str[i] == '>') pos = i+1;
|
||||||
|
|
||||||
|
return str+pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
|
||||||
|
void client_changed();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Client_widget(Genode::Allocator &alloc, Channel::Label const &label)
|
||||||
|
:
|
||||||
|
_alloc(alloc), _label(label),
|
||||||
|
_name(_strip_label(_label))
|
||||||
|
{
|
||||||
|
setFrameStyle(QFrame::Panel | QFrame::Raised);
|
||||||
|
setLineWidth(4);
|
||||||
|
setToolTip(_label.string());
|
||||||
|
|
||||||
|
_name.setAlignment(Qt::AlignCenter);
|
||||||
|
_name.setContentsMargins(0, 0, 0, 5);
|
||||||
|
|
||||||
|
_layout->addWidget(&_name);
|
||||||
|
_layout->addLayout(&_hlayout);
|
||||||
|
_layout->setContentsMargins(10, 10, 10, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
~Client_widget()
|
||||||
|
{
|
||||||
|
while (Channel_widget *ch = _list.first()) {
|
||||||
|
disconnect(ch, SIGNAL(channel_changed()));
|
||||||
|
_hlayout.removeWidget(ch);
|
||||||
|
_list.remove(ch);
|
||||||
|
Genode::destroy(&_alloc, ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Channel::Label const &label() const { return _label; }
|
||||||
|
|
||||||
|
Channel_widget* lookup_channel(Channel::Number const number)
|
||||||
|
{
|
||||||
|
for (Channel_widget *ch = _list.first(); ch; ch = ch->next())
|
||||||
|
if (number == ch->number())
|
||||||
|
return ch;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Channel_widget* add_channel(Channel::Type const type,
|
||||||
|
Channel::Number const number)
|
||||||
|
{
|
||||||
|
Channel_widget *ch = new (&_alloc) Channel_widget(type, number);
|
||||||
|
connect(ch, SIGNAL(channel_changed()),
|
||||||
|
this, SIGNAL(client_changed()));
|
||||||
|
|
||||||
|
_sorted_insert(ch);
|
||||||
|
_hlayout.addWidget(ch);
|
||||||
|
|
||||||
|
return ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
Channel_widget const* first_channel() const { return _list.first(); }
|
||||||
|
|
||||||
|
void only_show_first()
|
||||||
|
{
|
||||||
|
Channel_widget *cw = _list.first();
|
||||||
|
while ((cw = cw->next())) cw->hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool combined_control() const
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Having a seperate volume control widget for each channel is
|
||||||
|
* nice-to-have but for now it is unnecessary. We therefore disable
|
||||||
|
* it the hardcoded way.
|
||||||
|
*/
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class Client_widget_registry : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Genode::List<Client_widget> _list;
|
||||||
|
Genode::Allocator &_alloc;
|
||||||
|
|
||||||
|
void _remove_destroy(Client_widget *c)
|
||||||
|
{
|
||||||
|
disconnect(c, SIGNAL(client_changed()));
|
||||||
|
_list.remove(c);
|
||||||
|
Genode::destroy(&_alloc, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
|
||||||
|
void registry_changed();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Client_widget_registry(Genode::Allocator &alloc) : QObject(), _alloc(alloc) { }
|
||||||
|
|
||||||
|
Client_widget* first() { return _list.first(); }
|
||||||
|
|
||||||
|
Client_widget* lookup(Channel::Label const &label)
|
||||||
|
{
|
||||||
|
for (Client_widget *c = _list.first(); c; c = c->next()) {
|
||||||
|
if (label == c->label())
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Client_widget* alloc_insert(Channel::Label const &label)
|
||||||
|
{
|
||||||
|
Client_widget *c = lookup(label);
|
||||||
|
if (c == nullptr) {
|
||||||
|
c = new (&_alloc) Client_widget(_alloc, label);
|
||||||
|
connect(c, SIGNAL(client_changed()),
|
||||||
|
this, SIGNAL(registry_changed()));
|
||||||
|
_list.insert(c);
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void invalidate_all()
|
||||||
|
{
|
||||||
|
for (Client_widget *c = _list.first(); c; c = c->next())
|
||||||
|
c->valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_invalid()
|
||||||
|
{
|
||||||
|
for (Client_widget *c = _list.first(); c; c = c->next())
|
||||||
|
if (c->valid == false) _remove_destroy(c);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static Client_widget_registry *client_registry()
|
||||||
|
{
|
||||||
|
static Client_widget_registry inst(*Genode::env()->heap());
|
||||||
|
return &inst;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static Genode::Reporter config_reporter { "mixer.config" };
|
||||||
|
|
||||||
|
|
||||||
|
void Main_window::_update_config()
|
||||||
|
{
|
||||||
|
config_reporter.enabled(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Genode::Reporter::Xml_generator xml(config_reporter, [&] {
|
||||||
|
|
||||||
|
xml.node("channel_list", [&] {
|
||||||
|
for (Client_widget const *c = client_registry()->first(); c; c = c->next()) {
|
||||||
|
bool const combined = c->combined_control();
|
||||||
|
|
||||||
|
static int vol = 0;
|
||||||
|
static bool muted = true;
|
||||||
|
if (combined) {
|
||||||
|
Channel_widget const *w = c->first_channel();
|
||||||
|
vol = w->volume();
|
||||||
|
muted = w->muted();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Channel_widget const *w = c->first_channel(); w; w = w->next()) {
|
||||||
|
Channel::Number const nr = w->number();
|
||||||
|
xml.node("channel", [&] {
|
||||||
|
xml.attribute("type", type_to_string(w->type()));
|
||||||
|
xml.attribute("label", c->label().string());
|
||||||
|
xml.attribute("name", channel_string_from_number(nr));
|
||||||
|
xml.attribute("number", nr);
|
||||||
|
xml.attribute("volume", combined ? vol : w->volume());
|
||||||
|
xml.attribute("muted", combined ? muted : w->muted());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (...) { PWRN("could not report channels"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Main_window::_update_clients(Genode::Xml_node &channels)
|
||||||
|
{
|
||||||
|
for (Client_widget *c = client_registry()->first(); c; c = c->next())
|
||||||
|
_layout->removeWidget(c);
|
||||||
|
|
||||||
|
client_registry()->invalidate_all();
|
||||||
|
|
||||||
|
channels.for_each_sub_node("channel", [&] (Genode::Xml_node const &node) {
|
||||||
|
try {
|
||||||
|
Channel ch(node);
|
||||||
|
|
||||||
|
Client_widget *c = client_registry()->lookup(ch.label);
|
||||||
|
if (c == nullptr)
|
||||||
|
c = client_registry()->alloc_insert(ch.label);
|
||||||
|
|
||||||
|
Channel_widget *w = c->lookup_channel(ch.number);
|
||||||
|
if (w == nullptr)
|
||||||
|
w = c->add_channel(ch.type, ch.number);
|
||||||
|
|
||||||
|
w->volume(ch.volume);
|
||||||
|
w->muted(ch.muted);
|
||||||
|
|
||||||
|
if (c->combined_control()) c->only_show_first();
|
||||||
|
else w->show();
|
||||||
|
|
||||||
|
c->valid = true;
|
||||||
|
|
||||||
|
_layout->addWidget(c);
|
||||||
|
resize(sizeHint());
|
||||||
|
} catch (Channel::Invalid_channel) { PWRN("invalid channel node"); }
|
||||||
|
});
|
||||||
|
|
||||||
|
client_registry()->remove_invalid();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets called from the Genode to Qt proxy object when the report was
|
||||||
|
* updated with a pointer to the XML document.
|
||||||
|
*/
|
||||||
|
void Main_window::report_changed(void *l, void const *p)
|
||||||
|
{
|
||||||
|
Genode::Lock &lock = *reinterpret_cast<Genode::Lock*>(l);
|
||||||
|
Genode::Xml_node &node = *((Genode::Xml_node*)p);
|
||||||
|
|
||||||
|
if (node.has_type("channel_list"))
|
||||||
|
_update_clients(node);
|
||||||
|
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Main_window::Main_window()
|
||||||
|
{
|
||||||
|
connect(client_registry(), SIGNAL(registry_changed()),
|
||||||
|
this, SLOT(_update_config()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Main_window::~Main_window()
|
||||||
|
{
|
||||||
|
disconnect(client_registry(), SIGNAL(registry_changed()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "main_window.moc"
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* \brief Main window of the mixer Qt frontend
|
||||||
|
* \author Josef Soentgen
|
||||||
|
* \date 2015-10-15
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Genode Labs GmbH
|
||||||
|
*
|
||||||
|
* This file is part of the Genode OS framework, which is distributed
|
||||||
|
* under the terms of the GNU General Public License version 2.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _MAIN_WINDOW_H_
|
||||||
|
#define _MAIN_WINDOW_H_
|
||||||
|
|
||||||
|
/* Genode includes */
|
||||||
|
#include <base/printf.h>
|
||||||
|
#include <base/lock.h>
|
||||||
|
#include <util/xml_node.h>
|
||||||
|
|
||||||
|
/* Qt includes */
|
||||||
|
#include <QEvent>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
/* Qoost includes */
|
||||||
|
#include <qoost/compound_widget.h>
|
||||||
|
#include <qoost/qmember.h>
|
||||||
|
|
||||||
|
/* application includes */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class proxies Genode signals to Qt signals
|
||||||
|
*/
|
||||||
|
struct Report_proxy : QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
|
||||||
|
void report_changed(void *, void const *);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class Main_window : public Compound_widget<QWidget, QHBoxLayout>
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void _update_clients(Genode::Xml_node &);
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
|
||||||
|
void _update_config();
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
|
||||||
|
void report_changed(void *, void const *);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Main_window();
|
||||||
|
|
||||||
|
~Main_window();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* _MAIN_WINDOW_H_ */
|
|
@ -0,0 +1,7 @@
|
||||||
|
QT += core gui widgets
|
||||||
|
TEMPLATE = app
|
||||||
|
|
||||||
|
SOURCES += main.cpp \
|
||||||
|
main_window.cpp
|
||||||
|
HEADERS += main_window.h
|
||||||
|
RESOURCES = style.qrc
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!DOCTYPE RCC>
|
||||||
|
<RCC version="1.0">
|
||||||
|
<qresource prefix="/">
|
||||||
|
<file>style.qss</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
|
@ -0,0 +1,5 @@
|
||||||
|
Main_window { min-width: 100; min-height: 100px; }
|
||||||
|
|
||||||
|
Client_widget QFrame {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
# identify the qt5 repository by searching for a file that is unique for qt5
|
||||||
|
QT5_REP_DIR := $(call select_from_repositories,lib/import/import-qt5.inc)
|
||||||
|
QT5_REP_DIR := $(realpath $(dir $(QT5_REP_DIR))../..)
|
||||||
|
|
||||||
|
include $(QT5_REP_DIR)/src/app/qt5/tmpl/target_defaults.inc
|
||||||
|
|
||||||
|
include $(QT5_REP_DIR)/src/app/qt5/tmpl/target_final.inc
|
||||||
|
|
||||||
|
main_window.o: main_window.moc
|
||||||
|
|
||||||
|
LIBS += qoost
|
Loading…
Reference in New Issue