Enhancement: Fixes #76, Implement sound volume indicator / levels in notification area

This commit is contained in:
Rory Fewell
2023-12-14 23:06:20 +00:00
parent 4780bba27e
commit eef73fe4a0
30 changed files with 2426 additions and 76 deletions

View File

@@ -30,6 +30,7 @@ wintc_resolve_library(wintc-comgtk WINTC_COMGTK)
wintc_resolve_library(wintc-exec WINTC_EXEC)
wintc_resolve_library(wintc-shelldpa WINTC_SHELLDPA)
wintc_resolve_library(wintc-shllang WINTC_SHLLANG)
wintc_resolve_library(wintc-sndapi WINTC_SNDAPI)
wintc_compile_resources()
wintc_create_meta_h()
@@ -57,10 +58,14 @@ add_executable(
src/start/startmenuitem.h
src/start/util.c
src/start/util.h
src/systray/behaviour.c
src/systray/behaviour.h
src/systray/clock.c
src/systray/clock.h
src/systray/notifarea.c
src/systray/notifarea.h
src/systray/volume.c
src/systray/volume.h
src/taskbuttons/taskbuttonbar.c
src/taskbuttons/taskbuttonbar.h
src/taskbuttons/windowmonitor.c
@@ -84,6 +89,7 @@ target_include_directories(
PRIVATE ${WINTC_EXEC_INCLUDE_DIRS}
PRIVATE ${WINTC_SHELLDPA_INCLUDE_DIRS}
PRIVATE ${WINTC_SHLLANG_INCLUDE_DIRS}
PRIVATE ${WINTC_SNDAPI_INCLUDE_DIRS}
)
target_link_directories(
@@ -97,6 +103,7 @@ target_link_directories(
PRIVATE ${WINTC_EXEC_LIBRARY_DIRS}
PRIVATE ${WINTC_SHELLDPA_LIBRARY_DIRS}
PRIVATE ${WINTC_SHLLANG_LIBRARY_DIRS}
PRIVATE ${WINTC_SNDAPI_LIBRARY_DIRS}
)
target_link_libraries(
@@ -110,6 +117,7 @@ target_link_libraries(
PRIVATE ${WINTC_EXEC_LIBRARIES}
PRIVATE ${WINTC_SHELLDPA_LIBRARIES}
PRIVATE ${WINTC_SHLLANG_LIBRARIES}
PRIVATE ${WINTC_SNDAPI_LIBRARIES}
)
add_dependencies(

View File

@@ -7,3 +7,4 @@ bt,rt:wintc-comgtk
bt,rt:wintc-exec
bt,rt:wintc-shelldpa
bt,rt:wintc-shllang
bt,rt:wintc-sndapi

View File

@@ -2,5 +2,6 @@
<gresources>
<gresource prefix="/uk/oddmatics/wintc/taskband">
<file>start-menu.css</file>
<file>volume-popup.css</file>
</gresource>
</gresources>

View File

@@ -0,0 +1,15 @@
.wintc-volmgmt > box > label
{
margin: 10px 20px;
}
.wintc-volmgmt > box > scale
{
margin: 4px 0px;
min-height: 65px;
}
.wintc-volmgmt > box > checkbutton
{
margin: 12px 6px 4px;
}

View File

@@ -0,0 +1,173 @@
#include <glib.h>
#include <gtk/gtk.h>
#include "behaviour.h"
//
// PRIVATE ENUMS
//
enum
{
PROP_WIDGET_NOTIF = 1,
PROP_ICON_NAME
};
enum
{
SIGNAL_ICON_CHANGED = 0,
N_SIGNALS
};
//
// STATIC DATA
//
static gint wintc_notification_behaviour_signals[N_SIGNALS] = { 0 };
//
// FORWARD DECLARATIONS
//
static void wintc_notification_behaviour_get_property(
GObject* object,
guint prop_id,
GValue* value,
GParamSpec* pspec
);
static void wintc_notification_behaviour_set_property(
GObject* object,
guint prop_id,
const GValue* value,
GParamSpec* pspec
);
//
// GTK TYPE DEFINITIONS & CTORS
//
G_DEFINE_TYPE(
WinTCNotificationBehaviour,
wintc_notification_behaviour,
G_TYPE_OBJECT
)
static void wintc_notification_behaviour_class_init(
WinTCNotificationBehaviourClass* klass
)
{
GObjectClass* object_class = G_OBJECT_CLASS(klass);
object_class->get_property = wintc_notification_behaviour_get_property;
object_class->set_property = wintc_notification_behaviour_set_property;
wintc_notification_behaviour_signals[SIGNAL_ICON_CHANGED] =
g_signal_new(
"icon-changed",
G_TYPE_FROM_CLASS(object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0
);
g_object_class_install_property(
object_class,
PROP_WIDGET_NOTIF,
g_param_spec_object(
"widget-notif",
"WidgetNotif",
"The GTK widget that hosts the notification icon.",
GTK_TYPE_WIDGET,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY
)
);
g_object_class_install_property(
object_class,
PROP_ICON_NAME,
g_param_spec_string(
"icon-name",
"IconName",
"The icon name to display in the notification area.",
NULL,
G_PARAM_WRITABLE
)
);
}
static void wintc_notification_behaviour_init(
WinTCNotificationBehaviour* self
)
{
self->icon_name = NULL;
self->widget_notif = NULL;
}
//
// CLASS VIRTUAL METHODS
//
static void wintc_notification_behaviour_get_property(
GObject* object,
guint prop_id,
GValue* value,
GParamSpec* pspec
)
{
WinTCNotificationBehaviour* behaviour =
WINTC_NOTIFICATION_BEHAVIOUR(object);
switch (prop_id)
{
case PROP_ICON_NAME:
g_value_set_string(value, behaviour->icon_name);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void wintc_notification_behaviour_set_property(
GObject* object,
guint prop_id,
const GValue* value,
GParamSpec* pspec
)
{
WinTCNotificationBehaviour* behaviour =
WINTC_NOTIFICATION_BEHAVIOUR(object);
switch (prop_id)
{
case PROP_ICON_NAME:
g_free(behaviour->icon_name);
behaviour->icon_name = g_strdup(g_value_get_string(value));
g_signal_emit(
behaviour,
wintc_notification_behaviour_signals[SIGNAL_ICON_CHANGED],
0
);
break;
case PROP_WIDGET_NOTIF:
behaviour->widget_notif = GTK_WIDGET(g_value_get_object(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
//
// PUBLIC FUNCTIONS
//
const gchar* wintc_notification_behaviour_get_icon_name(
WinTCNotificationBehaviour* behaviour
)
{
return behaviour->icon_name;
}

View File

@@ -0,0 +1,39 @@
#ifndef __BEHAVIOUR_H__
#define __BEHAVIOUR_H__
#include <glib.h>
#include <gtk/gtk.h>
//
// GTK OOP BOILERPLATE
//
typedef struct _WinTCNotificationBehaviourClass
{
GObjectClass __parent__;
} WinTCNotificationBehaviourClass;
typedef struct _WinTCNotificationBehaviour
{
GObject __parent__;
gchar* icon_name;
GtkWidget* widget_notif;
} WinTCNotificationBehaviour;
#define TYPE_WINTC_NOTIFICATION_BEHAVIOUR (wintc_notification_behaviour_get_type())
#define WINTC_NOTIFICATION_BEHAVIOUR(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_WINTC_NOTIFICATION_BEHAVIOUR, WinTCNotificationBehaviour))
#define WINTC_NOTIFICATION_BEHAVIOUR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_WINTC_NOTIFICATION_BEHAVIOUR, WinTCNotificationBehaviourClass))
#define IS_WINTC_NOTIFICATION_BEHAVIOUR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_WINTC_NOTIFICATION_BEHAVIOUR))
#define IS_WINTC_NOTIFICATION_BEHAVIOUR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_WINTC_NOTIFICATION_BEHAVIOUR))
#define WINTC_NOTIFICATION_BEHAVIOUR_GET_CLASS (G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_WINTC_NOTIFICATION_BEHAVIOUR, WinTCNotificationBehaviour))
GType wintc_notification_behaviour_get_type(void) G_GNUC_CONST;
//
// PUBLIC FUNCTIONS
//
const gchar* wintc_notification_behaviour_get_icon_name(
WinTCNotificationBehaviour* behaviour
);
#endif

View File

@@ -3,40 +3,48 @@
#include "clock.h"
//
// PRIVATE ENUMS
//
enum
{
PROP_LABEL_TARGET = 1,
};
//
// GTK OOP CLASS/INSTANCE DEFINITIONS
//
struct _TrayClockPrivate
struct _WinTCClockRunnerClass
{
TrayClock* tray_clock;
guint clock_source_id;
GObjectClass __parent__;
};
struct _TrayClockClass
struct _WinTCClockRunner
{
GtkLabelClass __parent__;
};
GObject __parent__;
struct _TrayClock
{
GtkLabel __parent__;
TrayClockPrivate* priv;
guint clock_source_id;
GtkLabel* label_target;
};
//
// FORWARD DECLARATIONS
//
static void tray_clock_finalize(
static void wintc_clock_runner_finalize(
GObject* object
);
static void tray_clock_launch_time(
TrayClock* tray_clock
static void wintc_clock_runner_set_property(
GObject* object,
guint prop_id,
const GValue* value,
GParamSpec* pspec
);
static void tray_clock_update_time(
TrayClock* tray_clock
static void wintc_clock_runner_launch_time(
WinTCClockRunner* tray_clock
);
static void wintc_clock_runner_update_time(
WinTCClockRunner* tray_clock
);
static gboolean on_clock_timeout_elapsed(
@@ -49,34 +57,38 @@ static gboolean on_sync_timeout_elapsed(
//
// GTK TYPE DEFINITION & CTORS
//
G_DEFINE_TYPE_WITH_CODE(
TrayClock,
tray_clock,
GTK_TYPE_LABEL,
G_ADD_PRIVATE(TrayClock)
G_DEFINE_TYPE(
WinTCClockRunner,
wintc_clock_runner,
G_TYPE_OBJECT
)
static void tray_clock_class_init(
TrayClockClass* klass
static void wintc_clock_runner_class_init(
WinTCClockRunnerClass* klass
)
{
GObjectClass* gclass = G_OBJECT_CLASS(klass);
GObjectClass* object_class = G_OBJECT_CLASS(klass);
gclass->finalize = tray_clock_finalize;
object_class->finalize = wintc_clock_runner_finalize;
object_class->set_property = wintc_clock_runner_set_property;
g_object_class_install_property(
object_class,
PROP_LABEL_TARGET,
g_param_spec_object(
"label-target",
"LabelTarget",
"The target label to manage.",
GTK_TYPE_LABEL,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY
)
);
}
static void tray_clock_init(
TrayClock* self
static void wintc_clock_runner_init(
WinTCClockRunner* self
)
{
self->priv = tray_clock_get_instance_private(self);
// Add style class
//
GtkStyleContext* style = gtk_widget_get_style_context(GTK_WIDGET(self));
gtk_style_context_add_class(style, "clock");
// Establish clock - if we're not dead on the minute then delay launch to sync
// up
//
@@ -85,7 +97,7 @@ static void tray_clock_init(
if (delay > 0)
{
tray_clock_update_time(self);
wintc_clock_runner_update_time(self);
g_timeout_add_seconds_full(
G_PRIORITY_DEFAULT,
@@ -97,66 +109,107 @@ static void tray_clock_init(
}
else
{
tray_clock_launch_time(self);
wintc_clock_runner_launch_time(self);
}
g_date_time_unref(time);
}
//
// FINALIZE
// CLASS VIRTUAL METHODS
//
static void tray_clock_finalize(
static void wintc_clock_runner_finalize(
GObject* object
)
{
TrayClock* tray_clock = TRAY_CLOCK(object);
WinTCClockRunner* clock_runner = WINTC_CLOCK_RUNNER(object);
if (tray_clock->priv->clock_source_id > 0)
if (clock_runner->clock_source_id > 0)
{
g_source_remove(tray_clock->priv->clock_source_id);
g_source_remove(clock_runner->clock_source_id);
}
(*G_OBJECT_CLASS(tray_clock_parent_class)->finalize) (object);
(*G_OBJECT_CLASS(wintc_clock_runner_parent_class)->finalize) (object);
}
static void wintc_clock_runner_set_property(
GObject* object,
guint prop_id,
const GValue* value,
GParamSpec* pspec
)
{
WinTCClockRunner* clock_runner = WINTC_CLOCK_RUNNER(object);
switch (prop_id)
{
case PROP_LABEL_TARGET:
clock_runner->label_target =
GTK_LABEL(g_value_get_object(value));
wintc_clock_runner_update_time(clock_runner);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
//
// PUBLIC FUNCTIONS
//
GtkWidget* tray_clock_new(void)
WinTCClockRunner* wintc_clock_runner_new(
GtkLabel* label_target
)
{
return GTK_WIDGET(
g_object_new(TYPE_TRAY_CLOCK, NULL)
return WINTC_CLOCK_RUNNER(
g_object_new(
TYPE_WINTC_CLOCK_RUNNER,
"label-target", label_target,
NULL
)
);
}
//
// PRIVATE FUNCTIONS
//
static void tray_clock_launch_time(
TrayClock* tray_clock
static void wintc_clock_runner_launch_time(
WinTCClockRunner* clock_runner
)
{
tray_clock_update_time(tray_clock);
wintc_clock_runner_update_time(clock_runner);
tray_clock->priv->clock_source_id =
clock_runner->clock_source_id =
g_timeout_add_seconds_full(
G_PRIORITY_DEFAULT,
60,
on_clock_timeout_elapsed,
tray_clock,
clock_runner,
NULL
);
}
static void tray_clock_update_time(
TrayClock* tray_clock
static void wintc_clock_runner_update_time(
WinTCClockRunner* clock_runner
)
{
if (clock_runner->label_target == NULL)
{
return;
}
// Update target label with time
//
GDateTime* time = g_date_time_new_now_local();
gchar* timestr = g_date_time_format(time, "%H:%M");
gtk_label_set_text(GTK_LABEL(tray_clock), timestr);
gtk_label_set_text(
GTK_LABEL(clock_runner->label_target),
timestr
);
g_date_time_unref(time);
g_free(timestr);
@@ -169,7 +222,7 @@ static gboolean on_clock_timeout_elapsed(
gpointer data
)
{
tray_clock_update_time(TRAY_CLOCK(data));
wintc_clock_runner_update_time(WINTC_CLOCK_RUNNER(data));
return TRUE;
}
@@ -178,7 +231,7 @@ static gboolean on_sync_timeout_elapsed(
gpointer data
)
{
tray_clock_launch_time(TRAY_CLOCK(data));
wintc_clock_runner_launch_time(WINTC_CLOCK_RUNNER(data));
return FALSE;
}

View File

@@ -9,24 +9,25 @@ G_BEGIN_DECLS
//
// GTK OOP BOILERPLATE
//
typedef struct _TrayClockPrivate TrayClockPrivate;
typedef struct _TrayClockClass TrayClockClass;
typedef struct _TrayClock TrayClock;
typedef struct _WinTCClockRunnerClass WinTCClockRunnerClass;
typedef struct _WinTCClockRunner WinTCClockRunner;
#define TYPE_TRAY_CLOCK (tray_clock_get_type())
#define TRAY_CLOCK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_TRAY_CLOCK, TrayClock))
#define TRAY_CLOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_TRAY_CLOCK, TrayClockClass))
#define IS_TRAY_CLOCK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_TRAY_CLOCK))
#define IS_TRAY_CLOCK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_TRAY_CLOCK))
#define TRAY_CLOCK_GET_CLASS (G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_TRAY_CLOCK, TrayClock))
#define TYPE_WINTC_CLOCK_RUNNER (wintc_clock_runner_get_type())
#define WINTC_CLOCK_RUNNER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_WINTC_CLOCK_RUNNER, WinTCClockRunner))
#define WINTC_CLOCK_RUNNER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_WINTC_CLOCK_RUNNER, WinTCClockRunnerClass))
#define IS_WINTC_CLOCK_RUNNER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_WINTC_CLOCK_RUNNER))
#define IS_WINTC_CLOCK_RUNNER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_WINTC_CLOCK_RUNNER))
#define WINTC_CLOCK_RUNNER_GET_CLASS (G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_WINTC_CLOCK_RUNNER, WinTCClockRunner))
GType tray_clock_get_type(void) G_GNUC_CONST;
GType wintc_clock_runner_get_type(void) G_GNUC_CONST;
G_END_DECLS
//
// PUBLIC FUNCTIONS
//
GtkWidget* tray_clock_new(void);
WinTCClockRunner* wintc_clock_runner_new(
GtkLabel* label_target
);
#endif

View File

@@ -1,26 +1,208 @@
#include <glib.h>
#include <gtk/gtk.h>
#include <wintc-comgtk.h>
#include "behaviour.h"
#include "clock.h"
#include "notifarea.h"
#include "volume.h"
//
// GTK OOP CLASS/INSTANCE DEFINITIONS
//
struct _WinTCNotificationAreaClass
{
GtkBinClass __parent__;
};
struct _WinTCNotificationArea
{
GtkBin __parent__;
GtkWidget* box_container;
GtkWidget* label_clock;
WinTCClockRunner* clock_runner;
GHashTable* map_widget_to_behaviour;
};
//
// FORWARD DECLARATIONS
//
static GtkWidget* wintc_notification_area_append_icon(
WinTCNotificationArea* notif_area
);
static void wintc_notification_area_map_widget(
WinTCNotificationArea* notif_area,
GtkWidget* widget_notif,
WinTCNotificationBehaviour* behaviour
);
static void update_notification_icon(
GtkWidget* widget_notif,
WinTCNotificationBehaviour* behaviour
);
static void on_behaviour_icon_changed(
WinTCNotificationBehaviour* behaviour,
gpointer user_data
);
//
// GTK TYPE DEFINITION & CTORS
//
G_DEFINE_TYPE(
WinTCNotificationArea,
wintc_notification_area,
GTK_TYPE_BIN
)
static void wintc_notification_area_class_init(
WINTC_UNUSED(WinTCNotificationAreaClass* klass)
) {}
static void wintc_notification_area_init(
WinTCNotificationArea* self
)
{
// Create map for notification widgets --> behaviours
//
self->map_widget_to_behaviour =
g_hash_table_new(g_direct_hash, g_direct_equal);
// Set up UI
//
self->box_container = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
self->label_clock = gtk_label_new(NULL);
wintc_widget_add_style_class(self->box_container, "wintc-systray");
wintc_widget_add_style_class(self->label_clock, "clock");
gtk_box_pack_end(
GTK_BOX(self->box_container),
self->label_clock,
FALSE,
FALSE,
0
);
gtk_container_add(
GTK_CONTAINER(self),
self->box_container
);
// Hook up clock runner
//
self->clock_runner =
wintc_clock_runner_new(GTK_LABEL(self->label_clock));
// Create volume icon and behaviour for system tray
//
WinTCNotificationVolume* notif_volume;
GtkWidget* widget_volume;
widget_volume = wintc_notification_area_append_icon(self);
notif_volume = wintc_notification_volume_new(widget_volume);
wintc_notification_area_map_widget(
self,
widget_volume,
WINTC_NOTIFICATION_BEHAVIOUR(notif_volume)
);
}
//
// PUBLIC FUNCTIONS
//
GtkWidget* notification_area_new(void)
{
GtkWidget* box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
GtkWidget* clock = tray_clock_new();
return GTK_WIDGET(
g_object_new(
TYPE_WINTC_NOTIFICATION_AREA,
NULL
)
);
}
wintc_widget_add_style_class(box, "wintc-systray");
//
// PRIVATE FUNCTIONS
//
static GtkWidget* wintc_notification_area_append_icon(
WinTCNotificationArea* notif_area
)
{
GtkWidget* event_box = gtk_event_box_new();
GtkWidget* image_icon = gtk_image_new();
gtk_widget_set_events(event_box, GDK_BUTTON_PRESS_MASK);
gtk_container_add(
GTK_CONTAINER(event_box),
image_icon
);
gtk_box_pack_start(
GTK_BOX(box),
clock,
GTK_BOX(notif_area->box_container),
event_box,
FALSE,
FALSE,
0
);
return box;
return event_box;
}
static void wintc_notification_area_map_widget(
WinTCNotificationArea* notif_area,
GtkWidget* widget_notif,
WinTCNotificationBehaviour* behaviour
)
{
g_hash_table_insert(
notif_area->map_widget_to_behaviour,
widget_notif,
behaviour
);
// Connect up widget to behaviour
//
update_notification_icon(
widget_notif,
behaviour
);
g_signal_connect(
behaviour,
"icon-changed",
G_CALLBACK(on_behaviour_icon_changed),
widget_notif
);
}
static void update_notification_icon(
GtkWidget* widget_notif,
WinTCNotificationBehaviour* behaviour
)
{
gtk_image_set_from_icon_name(
GTK_IMAGE(gtk_bin_get_child(GTK_BIN(widget_notif))),
wintc_notification_behaviour_get_icon_name(behaviour),
GTK_ICON_SIZE_SMALL_TOOLBAR
);
}
//
// CALLBACKS
//
static void on_behaviour_icon_changed(
WinTCNotificationBehaviour* behaviour,
gpointer user_data
)
{
GtkWidget* widget_notif = GTK_WIDGET(user_data);
update_notification_icon(
widget_notif,
behaviour
);
}

View File

@@ -1,6 +1,25 @@
#ifndef __NOTIFAREA_H__
#define __NOTIFAREA_H__
#include <glib.h>
#include <gtk/gtk.h>
//
// GTK OOP BOILERPLATE
//
typedef struct _WinTCNotificationAreaClass WinTCNotificationAreaClass;
typedef struct _WinTCNotificationArea WinTCNotificationArea;
#define TYPE_WINTC_NOTIFICATION_AREA (wintc_notification_area_get_type())
#define WINTC_NOTIFICATION_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_WINTC_NOTIFICATION_AREA, WinTCNotificationArea))
#define WINTC_NOTIFICATION_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_WINTC_NOTIFICATION_AREA, WinTCNotificationArea))
#define IS_WINTC_NOTIFICATION_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_WINTC_NOTIFICATION_AREA))
#define IS_WINTC_NOTIFICATION_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_WINTC_NOTIFICATION_AREA))
#define WINTC_NOTIFICATION_AREA_GET_CLASS(obj) (G_TYPE_CHECK_INSTANCE_GET_CLASS((obj), TYPE_WINTC_NOTIFICATION_AREA))
//
// PUBLIC FUNCTIONS
//
GtkWidget* notification_area_new(void);
#endif

View File

@@ -0,0 +1,430 @@
#include <glib.h>
#include <gtk/gtk.h>
#include <wintc-comgtk.h>
#include <wintc-shelldpa.h>
#include <wintc-sndapi.h>
#include "behaviour.h"
#include "volume.h"
//
// GTK OOP CLASS/INSTANCE DEFINITIONS
//
struct _WinTCNotificationVolumeClass
{
WinTCNotificationBehaviourClass __parent__;
};
struct _WinTCNotificationVolume
{
WinTCNotificationBehaviour __parent__;
GtkWidget* popup_volmgmt;
GtkWidget* box_container;
GtkWidget* check_mute;
GtkWidget* scale_volume;
gboolean syncing_state;
// Sound API stuff
//
WinTCSndApiContext* snd_ctx;
WinTCSndApiOutput* snd_output;
};
//
// FORWARD DECLARATIONS
//
static void wintc_notification_volume_constructed(
GObject* object
);
static void wintc_notification_volume_set_have_device(
WinTCNotificationVolume* volume,
gboolean have_device
);
static void on_snd_ctx_connected_changed(
WinTCSndApiContext* ctx,
gboolean connected,
gpointer user_data
);
static void on_snd_ctx_default_output_changed(
WinTCSndApiContext* ctx,
gpointer user_data
);
static void on_snd_output_muted_changed(
WinTCSndApiOutput* output,
gpointer user_data
);
static void on_snd_output_volume_changed(
WinTCSndApiOutput* output,
gpointer user_data
);
static gboolean on_widget_notif_button_press_event(
GtkWidget* self,
GdkEventButton* event,
gpointer user_data
);
static void on_check_mute_toggled(
GtkToggleButton* self,
gpointer user_data
);
static void on_scale_volume_value_changed(
GtkRange* self,
gpointer user_data
);
//
// GTK TYPE DEFINITIONS & CTORS
//
G_DEFINE_TYPE(
WinTCNotificationVolume,
wintc_notification_volume,
TYPE_WINTC_NOTIFICATION_BEHAVIOUR
)
static void wintc_notification_volume_class_init(
WinTCNotificationVolumeClass* klass
)
{
GObjectClass* object_class = G_OBJECT_CLASS(klass);
object_class->constructed = wintc_notification_volume_constructed;
}
static void wintc_notification_volume_init(
WINTC_UNUSED(WinTCNotificationVolume* self)
)
{
static gboolean css_added = FALSE;
// Add CSS once to screen - can't do this in class because GTK may not
// have initialized first
//
if (!css_added)
{
GtkCssProvider* css_volume_popup = gtk_css_provider_new();
gtk_css_provider_load_from_resource(
css_volume_popup,
"/uk/oddmatics/wintc/taskband/volume-popup.css"
);
gtk_style_context_add_provider_for_screen(
gdk_screen_get_default(),
GTK_STYLE_PROVIDER(css_volume_popup),
GTK_STYLE_PROVIDER_PRIORITY_FALLBACK
);
css_added = TRUE;
}
// Other init
//
self->syncing_state = FALSE;
}
//
// CLASS VIRTUAL METHODS
//
static void wintc_notification_volume_constructed(
GObject* object
)
{
WinTCNotificationVolume* volume =
WINTC_NOTIFICATION_VOLUME(object);
WinTCNotificationBehaviour* behaviour =
WINTC_NOTIFICATION_BEHAVIOUR(volume);
// Connect up to the notification icon widget
//
volume->popup_volmgmt =
wintc_dpa_create_popup(behaviour->widget_notif);
volume->box_container = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
volume->check_mute = gtk_check_button_new_with_label("Mute");
volume->scale_volume = gtk_scale_new_with_range(
GTK_ORIENTATION_VERTICAL,
0.0f,
1.0f,
0.05f
);
gtk_scale_set_draw_value(GTK_SCALE(volume->scale_volume), FALSE);
gtk_range_set_inverted(GTK_RANGE(volume->scale_volume), TRUE);
wintc_widget_add_style_class(volume->popup_volmgmt, "wintc-volmgmt");
gtk_box_pack_start(
GTK_BOX(volume->box_container),
gtk_label_new("Volume"),
FALSE,
FALSE,
0
);
gtk_box_pack_start(
GTK_BOX(volume->box_container),
volume->scale_volume,
FALSE,
FALSE,
0
);
gtk_box_pack_start(
GTK_BOX(volume->box_container),
volume->check_mute,
FALSE,
FALSE,
0
);
gtk_container_add(
GTK_CONTAINER(volume->popup_volmgmt),
volume->box_container
);
g_signal_connect(
volume->check_mute,
"toggled",
G_CALLBACK(on_check_mute_toggled),
object
);
g_signal_connect(
volume->scale_volume,
"value-changed",
G_CALLBACK(on_scale_volume_value_changed),
object
);
g_signal_connect(
behaviour->widget_notif,
"button-press-event",
G_CALLBACK(on_widget_notif_button_press_event),
object
);
// Set initial state
//
wintc_notification_volume_set_have_device(volume, FALSE);
// Establish sound API context
//
volume->snd_ctx = wintc_sndapi_context_new();
g_signal_connect(
volume->snd_ctx,
"connected-changed",
G_CALLBACK(on_snd_ctx_connected_changed),
volume
);
g_signal_connect(
volume->snd_ctx,
"default-output-changed",
G_CALLBACK(on_snd_ctx_default_output_changed),
volume
);
wintc_sndapi_context_connect(volume->snd_ctx);
// Chain up
//
(G_OBJECT_CLASS(
wintc_notification_volume_parent_class
))->constructed(object);
}
//
// PUBLIC FUNCTIONS
//
WinTCNotificationVolume* wintc_notification_volume_new(
GtkWidget* widget_notif
)
{
return WINTC_NOTIFICATION_VOLUME(
g_object_new(
TYPE_WINTC_NOTIFICATION_VOLUME,
"widget-notif", widget_notif,
NULL
)
);
}
//
// PRIVATE FUNCTIONS
//
static void wintc_notification_volume_set_have_device(
WinTCNotificationVolume* volume,
gboolean have_device
)
{
gtk_widget_set_sensitive(volume->popup_volmgmt, have_device);
if (!have_device)
{
g_object_set(
volume,
"icon-name", "audio-volume-muted",
NULL
);
}
}
//
// CALLBACKS
//
static void on_snd_ctx_connected_changed(
WINTC_UNUSED(WinTCSndApiContext* ctx),
gboolean connected,
gpointer user_data
)
{
WinTCNotificationVolume* volume =
WINTC_NOTIFICATION_VOLUME(user_data);
if (!connected)
{
wintc_notification_volume_set_have_device(volume, FALSE);
}
}
static void on_snd_ctx_default_output_changed(
WinTCSndApiContext* ctx,
gpointer user_data
)
{
WinTCNotificationVolume* volume =
WINTC_NOTIFICATION_VOLUME(user_data);
WinTCSndApiOutput* output =
wintc_sndapi_context_get_default_output(ctx);
if (volume->snd_output == output)
{
return;
}
volume->snd_output = output;
if (volume->snd_output == NULL)
{
wintc_notification_volume_set_have_device(volume, FALSE);
return;
}
wintc_notification_volume_set_have_device(volume, TRUE);
g_signal_connect(
volume->snd_output,
"muted-changed",
G_CALLBACK(on_snd_output_muted_changed),
volume
);
g_signal_connect(
volume->snd_output,
"volume-changed",
G_CALLBACK(on_snd_output_volume_changed),
volume
);
}
static void on_snd_output_muted_changed(
WinTCSndApiOutput* output,
gpointer user_data
)
{
WinTCNotificationVolume* volume =
WINTC_NOTIFICATION_VOLUME(user_data);
gboolean muted = wintc_sndapi_output_is_muted(output);
gchar* icon_name = muted ? "audio-volume-muted" : "audio-volume-medium";
volume->syncing_state = TRUE;
gtk_toggle_button_set_active(
GTK_TOGGLE_BUTTON(volume->check_mute),
muted
);
volume->syncing_state = FALSE;
g_object_set(
volume,
"icon-name", icon_name,
NULL
);
}
static void on_snd_output_volume_changed(
WinTCSndApiOutput* output,
gpointer user_data
)
{
WinTCNotificationVolume* volume =
WINTC_NOTIFICATION_VOLUME(user_data);
gdouble new_volume = wintc_sndapi_output_get_volume(output);
volume->syncing_state = TRUE;
gtk_range_set_value(
GTK_RANGE(volume->scale_volume),
new_volume
);
volume->syncing_state = FALSE;
}
static gboolean on_widget_notif_button_press_event(
GtkWidget* self,
WINTC_UNUSED(GdkEventButton* event),
gpointer user_data
)
{
WinTCNotificationVolume* volume =
WINTC_NOTIFICATION_VOLUME(user_data);
wintc_dpa_show_popup(
volume->popup_volmgmt,
self
);
return TRUE;
}
static void on_check_mute_toggled(
GtkToggleButton* self,
gpointer user_data
)
{
WinTCNotificationVolume* volume =
WINTC_NOTIFICATION_VOLUME(user_data);
if (volume->syncing_state)
{
return;
}
wintc_sndapi_output_set_muted(
volume->snd_output,
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(self))
);
}
static void on_scale_volume_value_changed(
GtkRange* self,
gpointer user_data
)
{
WinTCNotificationVolume* volume =
WINTC_NOTIFICATION_VOLUME(user_data);
if (volume->syncing_state)
{
return;
}
wintc_sndapi_output_set_volume(
volume->snd_output,
gtk_range_get_value(GTK_RANGE(self))
);
}

View File

@@ -0,0 +1,29 @@
#ifndef __VOLUME_H__
#define __VOLUME_H__
#include <glib.h>
#include <gtk/gtk.h>
//
// GTK OOP BOILERPLATE
//
typedef struct _WinTCNotificationVolumeClass WinTCNotificationVolumeClass;
typedef struct _WinTCNotificationVolume WinTCNotificationVolume;
#define TYPE_WINTC_NOTIFICATION_VOLUME (wintc_notification_volume_get_type())
#define WINTC_NOTIFICATION_VOLUME(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_WINTC_NOTIFICATION_VOLUME, WinTCNotificationVolume))
#define WINTC_NOTIFICATION_VOLUME_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_WINTC_NOTIFICATION_BEHAVIOUR, WinTCNotificationVolumeClass))
#define IS_WINTC_NOTIFICATION_VOLUME(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_WINTC_NOTIFICATION_VOLUME))
#define IS_WINTC_NOTIFICATION_VOLUME_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_WINTC_NOTIFICATION_VOLUME))
#define WINTC_NOTIFICATION_VOLUME_GET_CLASS (G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_WINTC_NOTIFICATION_VOLUME, WinTCNotificationVolume))
GType wintc_notification_volume_get_type(void) G_GNUC_CONST;
//
// PUBLIC FUNCTIONS
//
WinTCNotificationVolume* wintc_notification_volume_new(
GtkWidget* widget_notif
);
#endif