toaruos/lib/panel_volume.c

233 lines
7.0 KiB
C

/**
* @brief Panel Volume Widget
*
* Shows an icon indicating the mixer's master volume,
* and shows a menu with a volume slider when clicked.
*/
#include <fcntl.h>
#include <sys/ioctl.h>
#include <kernel/mod/sound.h>
#include <toaru/yutani.h>
#include <toaru/graphics.h>
#include <toaru/menu.h>
#include <toaru/text.h>
#include <toaru/panel.h>
#define VOLUME_DEVICE_ID 0
#define VOLUME_KNOB_ID 0
static sprite_t * sprite_volume_mute;
static sprite_t * sprite_volume_low;
static sprite_t * sprite_volume_med;
static sprite_t * sprite_volume_high;
static struct MenuList * volume_menu;
static long volume_level = 0;
static int mixer = -1;
static int widget_update_volume(struct PanelWidget * this, int * force_updates) {
if (mixer == -1) {
mixer = open("/dev/mixer", O_RDONLY);
}
snd_knob_value_t value = {0};
value.device = VOLUME_DEVICE_ID; /* TODO configure this somewhere */
value.id = VOLUME_KNOB_ID; /* TODO this too */
ioctl(mixer, SND_MIXER_READ_KNOB, &value);
volume_level = value.val;
return 0;
}
static void set_volume(void) {
snd_knob_value_t value = {0};
value.device = VOLUME_DEVICE_ID; /* TODO configure this somewhere */
value.id = VOLUME_KNOB_ID; /* TODO this too */
value.val = volume_level;
ioctl(mixer, SND_MIXER_WRITE_KNOB, &value);
redraw();
}
static void volume_raise(void) {
volume_level += 0x10000000;
if (volume_level > 0xF0000000) volume_level = 0xFC000000;
set_volume();
}
static void volume_lower(void) {
volume_level -= 0x10000000;
if (volume_level < 0x0) volume_level = 0x0;
set_volume();
}
#define VOLUME_SLIDER_LEFT_PAD 38
#define VOLUME_SLIDER_RIGHT_PAD 14
#define VOLUME_SLIDER_PAD (VOLUME_SLIDER_LEFT_PAD + VOLUME_SLIDER_RIGHT_PAD)
#define VOLUME_SLIDER_VERT_PAD 10
#define VOLUME_SLIDER_BALL_RADIUS 8
struct SliderStuff {
int level;
uint32_t on;
uint32_t off;
};
uint32_t volume_pattern(int32_t x, int32_t y, double alpha, void * extra) {
struct SliderStuff * stuff = extra;
if (alpha > 1.0) alpha = 1.0;
if (alpha < 0.0) alpha = 0.0;
uint32_t color = stuff->off;
if (x < stuff->level + VOLUME_SLIDER_LEFT_PAD) {
color = stuff->on;
}
color |= rgba(0,0,0,alpha*255);
return premultiply(color);
}
void _menu_draw_MenuEntry_Slider(gfx_context_t * ctx, struct MenuEntry * self, int offset) {
self->offset = offset;
draw_sprite_alpha_paint(ctx, sprite_volume_high, 4, offset, 1.0, rgb(0,0,0));
struct SliderStuff stuff;
stuff.level = (ctx->width - VOLUME_SLIDER_PAD) * (float)volume_level / (float)0xFC000000;
stuff.on = rgba(0,120,220,0);
stuff.off = rgba(140,140,140,0);
draw_rounded_rectangle_pattern(ctx,
/* x */ VOLUME_SLIDER_LEFT_PAD - 4,
/* y */ offset + VOLUME_SLIDER_VERT_PAD - 1,
/* w */ ctx->width - VOLUME_SLIDER_PAD + 8,
/* h */ self->height - 2 * VOLUME_SLIDER_VERT_PAD + 2, 6, volume_pattern, &stuff);
stuff.on = rgba(40,160,255,0);
stuff.off = rgba(200,200,200,0);
draw_rounded_rectangle_pattern(ctx,
/* x */ VOLUME_SLIDER_LEFT_PAD - 3,
/* y */ offset + VOLUME_SLIDER_VERT_PAD,
/* w */ ctx->width - VOLUME_SLIDER_PAD + 6,
/* h */ self->height - 2 * VOLUME_SLIDER_VERT_PAD, 5, volume_pattern, &stuff);
draw_rounded_rectangle(ctx,
/* x */ stuff.level - VOLUME_SLIDER_BALL_RADIUS + VOLUME_SLIDER_LEFT_PAD,
/* y */ offset + 12 - VOLUME_SLIDER_BALL_RADIUS,
/* w */ VOLUME_SLIDER_BALL_RADIUS * 2,
/* h */ VOLUME_SLIDER_BALL_RADIUS * 2, VOLUME_SLIDER_BALL_RADIUS, rgb(140,140,140));
draw_rounded_rectangle(ctx,
/* x */ stuff.level - VOLUME_SLIDER_BALL_RADIUS + 1 + VOLUME_SLIDER_LEFT_PAD,
/* y */ offset + 12 - VOLUME_SLIDER_BALL_RADIUS + 1,
/* w */ VOLUME_SLIDER_BALL_RADIUS * 2 - 2,
/* h */ VOLUME_SLIDER_BALL_RADIUS * 2 - 2, VOLUME_SLIDER_BALL_RADIUS - 1, rgb(220,220,220));
}
int _menu_mouse_MenuEntry_Slider(struct MenuEntry * self, struct yutani_msg_window_mouse_event * event) {
if (event->buttons & YUTANI_MOUSE_BUTTON_LEFT) {
/* Figure out where it is */
float level = (float)(event->new_x - VOLUME_SLIDER_LEFT_PAD) / (float)(self->width - VOLUME_SLIDER_PAD);
if (level >= 1.0) level = 1.0;
if (level <= 0.0) level = 0.0;
if (volume_level != level * 0xFC000000) {
volume_level = level * 0xFC000000;
set_volume();
return 1;
}
}
return 0;
}
static struct MenuEntryVTable slider_vtable = {
.methods = 4,
.renderer = _menu_draw_MenuEntry_Slider,
.mouse_event = _menu_mouse_MenuEntry_Slider,
};
struct MenuEntry * menu_create_slider(void) {
struct MenuEntry * out = menu_create_separator(); /* Steal some defaults */
out->_type = -1; /* Special */
out->height = 24;
out->rwidth = 200;
out->vtable = &slider_vtable;
return out;
}
static int widget_click_volume(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {
if (!volume_menu) {
volume_menu = menu_create();
volume_menu->flags |= MENU_FLAG_BUBBLE_LEFT;
}
/* Clear the menu */
while (volume_menu->entries->length) {
node_t * node = list_pop(volume_menu->entries);
menu_free_entry((struct MenuEntry *)node->value);
free(node);
}
menu_insert(volume_menu, menu_create_slider());
/* TODO Our mixer supports multiple knobs and we could show all of them. */
/* TODO We could also show a nice slider... if we had one... */
if (!volume_menu->window) {
panel_menu_show(this, volume_menu);
}
return 1;
}
static int widget_draw_volume(struct PanelWidget * this, gfx_context_t * ctx) {
uint32_t color = (volume_menu && volume_menu->window) ? HILIGHT_COLOR : ICON_COLOR;
if (volume_level < 10) {
draw_sprite_alpha_paint(ctx, sprite_volume_mute, 0, 1, 1.0, color);
} else if (volume_level < 0x547ae147) {
draw_sprite_alpha_paint(ctx, sprite_volume_low, 0, 1, 1.0, color);
} else if (volume_level < 0xa8f5c28e) {
draw_sprite_alpha_paint(ctx, sprite_volume_med, 0, 1, 1.0, color);
} else {
draw_sprite_alpha_paint(ctx, sprite_volume_high, 0, 1, 1.0, color);
}
return 0;
}
/* For dumb legacy reasons, scroll wheel movement shows up here... */
static int widget_move_volume(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {
int scroll_direction = 0;
if (evt->buttons & YUTANI_MOUSE_SCROLL_UP) scroll_direction = -1;
else if (evt->buttons & YUTANI_MOUSE_SCROLL_DOWN) scroll_direction = 1;
if (scroll_direction == 1) {
volume_lower();
return 1;
} else if (scroll_direction == -1) {
volume_raise();
return 1;
}
return 0;
}
struct PanelWidget * widget_init_volume(void) {
sprite_volume_mute = malloc(sizeof(sprite_t));
sprite_volume_low = malloc(sizeof(sprite_t));
sprite_volume_med = malloc(sizeof(sprite_t));
sprite_volume_high = malloc(sizeof(sprite_t));
load_sprite(sprite_volume_mute, "/usr/share/icons/24/volume-mute.png");
load_sprite(sprite_volume_low, "/usr/share/icons/24/volume-low.png");
load_sprite(sprite_volume_med, "/usr/share/icons/24/volume-medium.png");
load_sprite(sprite_volume_high, "/usr/share/icons/24/volume-full.png");
struct PanelWidget * widget = widget_new();
widget->width = 24;
widget->draw = widget_draw_volume;
widget->click = widget_click_volume;
widget->move = widget_move_volume;
widget->update = widget_update_volume;
list_insert(widgets_enabled, widget);
return widget;
}