From 37d5ef75306c45ca95550f4af40d9750c5b9cb88 Mon Sep 17 00:00:00 2001 From: Hannes Schulze Date: Mon, 6 May 2024 13:42:55 +0200 Subject: initial commit yup, i definitely wrote all of this in a single commit --- src/services/battery-service.c | 378 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 src/services/battery-service.c (limited to 'src/services/battery-service.c') diff --git a/src/services/battery-service.c b/src/services/battery-service.c new file mode 100644 index 0000000..2f21070 --- /dev/null +++ b/src/services/battery-service.c @@ -0,0 +1,378 @@ +#include "services/battery-service.h" +#include "dbus/upower.h" +#include + +#define DEVICE_STATE_CHARGING 1 +#define DEVICE_STATE_FULLY_CHARGED 4 + +// +// FoobarBatteryState: +// +// Structure storing the current information about the battery. +// + +struct _FoobarBatteryState +{ + gboolean is_charging; + gboolean is_charged; + gint percentage; + gint time_remaining; +}; + +G_DEFINE_BOXED_TYPE( FoobarBatteryState, foobar_battery_state, foobar_battery_state_copy, foobar_battery_state_free ) + +// +// FoobarBatteryService: +// +// Service monitoring the state of the battery. This is implemented using the UPower D-Bus API. +// + +struct _FoobarBatteryService +{ + GObject parent_instance; + FoobarBatteryState* state; + FoobarUPowerDevice* proxy; + gulong changed_handler_id; +}; + +enum +{ + PROP_STATE = 1, + N_PROPS, +}; + +static GParamSpec* props[N_PROPS] = { 0 }; + +static void foobar_battery_service_class_init ( FoobarBatteryServiceClass* klass ); +static void foobar_battery_service_init ( FoobarBatteryService* self ); +static void foobar_battery_service_get_property ( GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec ); +static void foobar_battery_service_finalize ( GObject* object ); +static void foobar_battery_service_handle_connect ( GObject* object, + GAsyncResult* result, + gpointer userdata ); +static void foobar_battery_service_handle_properties_changed( GDBusProxy* proxy, + GVariant* changed_properties, + GStrv invalidated_properties, + gpointer userdata ); +static void foobar_battery_service_update ( FoobarBatteryService* self ); + +G_DEFINE_FINAL_TYPE( FoobarBatteryService, foobar_battery_service, G_TYPE_OBJECT ) + +// --------------------------------------------------------------------------------------------------------------------- +// State +// --------------------------------------------------------------------------------------------------------------------- + +// +// Create a new, default-initialized state structure. +// +FoobarBatteryState* foobar_battery_state_new( void ) +{ + return g_new0( FoobarBatteryState, 1 ); +} + +// +// Create a mutable copy of a state structure. +// +FoobarBatteryState* foobar_battery_state_copy( FoobarBatteryState const* self ) +{ + if ( !self ) { return NULL; } + + FoobarBatteryState* copy = g_new0( FoobarBatteryState, 1 ); + copy->is_charging = self->is_charging; + copy->is_charged = self->is_charged; + copy->percentage = self->percentage; + copy->time_remaining = self->time_remaining; + return copy; +} + +// +// Release resources associated with a state structure (NULL is allowed). +// +void foobar_battery_state_free( FoobarBatteryState* self ) +{ + g_free( self ); +} + +// +// Compare two battery state structures (NULL is allowed)/ +// +gboolean foobar_battery_state_equal( + FoobarBatteryState const* a, + FoobarBatteryState const* b ) +{ + if ( a == NULL && b == NULL ) { return TRUE; } + if ( a == NULL || b == NULL ) { return FALSE; } + + if ( a->is_charging != b->is_charging ) { return FALSE; } + if ( a->is_charged != b->is_charged ) { return FALSE; } + if ( a->percentage != b->percentage ) { return FALSE; } + if ( a->time_remaining != b->time_remaining ) { return FALSE; } + + return TRUE; +} + +// +// Indicates whether the battery is currently being charged. +// +gboolean foobar_battery_state_is_charging( FoobarBatteryState const* self ) +{ + g_return_val_if_fail( self != NULL, FALSE ); + return self->is_charging; +} + +// +// Indicates whether the battery is currently being charged. +// +void foobar_battery_state_set_charging( + FoobarBatteryState* self, + gboolean value ) +{ + g_return_if_fail( self != NULL ); + self->is_charging = !!value; +} + +// +// Indicates whether the battery is fully charged. +// +gboolean foobar_battery_state_is_charged( FoobarBatteryState const* self ) +{ + g_return_val_if_fail( self != NULL, FALSE ); + return self->is_charged; +} + +// +// Indicates whether the battery is fully charged. +// +void foobar_battery_state_set_charged( + FoobarBatteryState* self, + gboolean value ) +{ + g_return_if_fail( self != NULL ); + self->is_charged = !!value; +} + +// +// Current battery level as a percentage value. +// +gint foobar_battery_state_get_percentage( FoobarBatteryState const* self ) +{ + g_return_val_if_fail( self != NULL, 0 ); + return self->percentage; +} + +// +// Current battery level as a percentage value. +// +void foobar_battery_state_set_percentage( + FoobarBatteryState* self, + gint value ) +{ + g_return_if_fail( self != NULL ); + self->percentage = CLAMP( value, 0, 100 ); +} + +// +// Estimated remaining time until fully charged or empty in seconds. +// +gint foobar_battery_state_get_time_remaining( FoobarBatteryState const* self ) +{ + g_return_val_if_fail( self != NULL, 0 ); + return self->time_remaining; +} + +// +// Estimated remaining time until fully charged or empty in seconds. +// +void foobar_battery_state_set_time_remaining( + FoobarBatteryState* self, + gint value ) +{ + g_return_if_fail( self != NULL ); + self->time_remaining = MAX( value, 0 ); +} + +// --------------------------------------------------------------------------------------------------------------------- +// Service Implementation +// --------------------------------------------------------------------------------------------------------------------- + +// +// Static initialization for the battery service. +// +void foobar_battery_service_class_init( FoobarBatteryServiceClass* klass ) +{ + GObjectClass* object_klass = G_OBJECT_CLASS( klass ); + object_klass->get_property = foobar_battery_service_get_property; + object_klass->finalize = foobar_battery_service_finalize; + + props[PROP_STATE] = g_param_spec_boxed( + "state", + "State", + "Current state of the battery, if available.", + FOOBAR_TYPE_BATTERY_STATE, + G_PARAM_READABLE ); + g_object_class_install_properties( object_klass, N_PROPS, props ); +} + +// +// Instance initialization for the battery service. +// +void foobar_battery_service_init( FoobarBatteryService* self ) +{ + foobar_upower_device_proxy_new_for_bus( + G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + "org.freedesktop.UPower", + "/org/freedesktop/UPower/devices/DisplayDevice", + NULL, + foobar_battery_service_handle_connect, + g_object_ref( self ) ); +} + +// +// Property getter implementation, mapping a property id to a method. +// +void foobar_battery_service_get_property( + GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec ) +{ + FoobarBatteryService* self = (FoobarBatteryService*)object; + + switch ( prop_id ) + { + case PROP_STATE: + g_value_set_boxed( value, foobar_battery_service_get_state( self ) ); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec ); + break; + } +} + +// +// Instance cleanup for the battery service. +// +void foobar_battery_service_finalize( GObject* object ) +{ + FoobarBatteryService* self = (FoobarBatteryService*)object; + + g_clear_signal_handler( &self->changed_handler_id, self->proxy ); + g_clear_object( &self->proxy ); + g_clear_pointer( &self->state, foobar_battery_state_free ); + + G_OBJECT_CLASS( foobar_battery_service_parent_class )->finalize( object ); +} + +// --------------------------------------------------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------------------------------------------------- + +// +// Create a new battery service instance. +// +FoobarBatteryService* foobar_battery_service_new( void ) +{ + return g_object_new( FOOBAR_TYPE_BATTERY_SERVICE, NULL ); +} + +// +// Get the current state of the battery, if available. +// +FoobarBatteryState const* foobar_battery_service_get_state( FoobarBatteryService* self ) +{ + g_return_val_if_fail( FOOBAR_IS_BATTERY_SERVICE( self ), NULL ); + + return self->state; +} + +// --------------------------------------------------------------------------------------------------------------------- +// Signal Handlers +// --------------------------------------------------------------------------------------------------------------------- + +// +// Called after asynchronous initialization of the D-Bus proxy. +// +void foobar_battery_service_handle_connect( + GObject* object, + GAsyncResult* result, + gpointer userdata ) +{ + (void)object; + g_autoptr( FoobarBatteryService ) self = (FoobarBatteryService*)userdata; + + g_autoptr( GError ) error = NULL; + self->proxy = foobar_upower_device_proxy_new_for_bus_finish( result, &error ); + if ( self->proxy ) + { + self->changed_handler_id = g_signal_connect( + self->proxy, + "g-properties-changed", + G_CALLBACK( foobar_battery_service_handle_properties_changed ), + self ); + foobar_battery_service_update( self ); + } + else + { + g_warning( "Unable to connect to battery status service: %s", error->message ); + } +} + +// +// Called by the D-Bus server when the battery state has changed. +// +void foobar_battery_service_handle_properties_changed( + GDBusProxy* proxy, + GVariant* changed_properties, + GStrv invalidated_properties, + gpointer userdata ) +{ + (void)proxy; + (void)changed_properties; + (void)invalidated_properties; + FoobarBatteryService* self = (FoobarBatteryService*)userdata; + + foobar_battery_service_update( self ); +} + +// --------------------------------------------------------------------------------------------------------------------- +// Helper Methods +// --------------------------------------------------------------------------------------------------------------------- + +// +// Reload the battery state. +// +void foobar_battery_service_update( FoobarBatteryService* self ) +{ + g_return_if_fail( FOOBAR_IS_BATTERY_SERVICE( self ) ); + + FoobarBatteryState* new_state = NULL; + if ( self->proxy && foobar_upower_device_get_is_present( self->proxy ) ) + { + guint state = foobar_upower_device_get_state( self->proxy ); + gint percent = (gint)round( foobar_upower_device_get_percentage( self->proxy ) ); + gboolean is_charging = state == DEVICE_STATE_CHARGING; + gboolean is_charged = + state == DEVICE_STATE_FULLY_CHARGED || + ( state == DEVICE_STATE_CHARGING && percent == 100 ); + gint time_remaining = is_charging + ? (gint)foobar_upower_device_get_time_to_full( self->proxy ) + : (gint)foobar_upower_device_get_time_to_empty( self->proxy ); + + new_state = foobar_battery_state_new( ); + foobar_battery_state_set_charging( new_state, is_charging ); + foobar_battery_state_set_charged( new_state, is_charged ); + foobar_battery_state_set_percentage( new_state, percent ); + foobar_battery_state_set_time_remaining( new_state, time_remaining ); + } + + if ( !foobar_battery_state_equal( self->state, new_state ) ) + { + g_clear_pointer( &self->state, foobar_battery_state_free ); + self->state = new_state; + g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_STATE] ); + } +} \ No newline at end of file -- cgit v1.3.1