diff --git a/src/driver-test.c b/src/driver-test.c index 4c6af5c2..40f2dc60 100644 --- a/src/driver-test.c +++ b/src/driver-test.c @@ -122,6 +122,34 @@ test_read_button(struct ratbag_button *button) ratbag_button_enable_action_type(button, RATBAG_BUTTON_ACTION_TYPE_MACRO); } +static void +test_read_led(struct ratbag_led *led) +{ + struct ratbag_device *device = led->profile->device; + struct ratbag_test_device *d = ratbag_get_drv_data(device); + struct ratbag_test_profile *p = &d->profiles[led->profile->index]; + struct ratbag_test_led t_led = p->leds[led->index]; + + switch (t_led.mode) { + case RATBAG_LED_ON: + led->mode = RATBAG_LED_ON; + break; + case RATBAG_LED_CYCLE: + led->mode = RATBAG_LED_CYCLE; + break; + case RATBAG_LED_BREATHING: + led->mode = RATBAG_LED_BREATHING; + break; + default: + led->mode = RATBAG_LED_OFF; + } + led->color.red = t_led.color.red; + led->color.green = t_led.color.green; + led->color.blue = t_led.color.blue; + led->hz = t_led.hz; + led->brightness = t_led.brightness; +} + static int test_write_button(struct ratbag_button *button, const struct ratbag_button_action *action) @@ -131,6 +159,17 @@ test_write_button(struct ratbag_button *button, return 0; } +static int +test_write_led(struct ratbag_led *led, + enum ratbag_led_mode mode, + struct ratbag_color color, unsigned int hz, + unsigned int brightness) +{ + /* check if the device is still valid */ + assert(ratbag_get_drv_data(led->profile->device) != NULL); + return 0; +} + static int test_fake_probe(struct ratbag_device *device) { @@ -176,6 +215,8 @@ struct ratbag_driver test_driver = { .write_profile = test_write_profile, .set_active_profile = test_set_active_profile, .read_button = test_read_button, + .read_led = test_read_led, .write_button = test_write_button, .write_resolution_dpi = NULL, + .write_led = test_write_led, }; diff --git a/src/libratbag-private.h b/src/libratbag-private.h index ab0d8624..0053dfa0 100644 --- a/src/libratbag-private.h +++ b/src/libratbag-private.h @@ -198,6 +198,21 @@ struct ratbag_driver { int (*write_resolution_dpi)(struct ratbag_resolution *resolution, int dpi_x, int dpi_y); + /** + * For the given led, fill in the struct ratbag_led + * with the available information. + */ + void (*read_led)(struct ratbag_led *led); + + /* + * FIXME: This function is deprecated and should be removed. Once + * we've updated all the device drivers to stop using it we'll remove + * it. Look at commit() instead. + */ + int (*write_led)(struct ratbag_led *led, enum ratbag_led_mode mode, + struct ratbag_color color, unsigned int hz, + unsigned int brightness); + /* private */ int (*test_probe)(struct ratbag_device *device, void *data); @@ -216,6 +231,20 @@ struct ratbag_resolution { uint32_t capabilities; }; +struct ratbag_led { + int refcount; + void *userdata; + struct list link; + struct ratbag_profile *profile; + unsigned index; + enum ratbag_led_type type; + enum ratbag_led_mode mode; + struct ratbag_color color; + unsigned int hz; /**< rate of action in hz */ + unsigned int brightness; /**< brightness of the LED */ + bool dirty; +}; + struct ratbag_profile { int refcount; void *userdata; @@ -230,6 +259,7 @@ struct ratbag_profile { struct ratbag_resolution *modes; unsigned int num_modes; } resolution; + struct list leds; bool is_active; /**< profile is the currently active one */ bool is_enabled; diff --git a/src/libratbag-test.h b/src/libratbag-test.h index 67ca3d1a..5be231a1 100644 --- a/src/libratbag-test.h +++ b/src/libratbag-test.h @@ -49,9 +49,24 @@ struct ratbag_test_resolution { uint32_t caps; }; +struct ratbag_test_color { + unsigned short red; + unsigned short green; + unsigned short blue; +}; + +struct ratbag_test_led { + enum ratbag_led_type type; + enum ratbag_led_mode mode; + struct ratbag_test_color color; + unsigned int hz; + unsigned int brightness; +}; + struct ratbag_test_profile { struct ratbag_test_button buttons[RATBAG_TEST_MAX_BUTTONS]; struct ratbag_test_resolution resolutions[RATBAG_TEST_MAX_RESOLUTIONS]; + struct ratbag_test_led leds[RATBAG_TEST_MAX_LEDS]; bool active; bool dflt; }; diff --git a/src/libratbag.c b/src/libratbag.c index b22310a2..fc9cdc9e 100644 --- a/src/libratbag.c +++ b/src/libratbag.c @@ -59,6 +59,8 @@ static void ratbag_profile_destroy(struct ratbag_profile *profile); static void ratbag_button_destroy(struct ratbag_button *button); +static void +ratbag_led_destroy(struct ratbag_led *led); static void ratbag_default_log_func(struct ratbag *ratbag, @@ -582,6 +584,25 @@ ratbag_create_button(struct ratbag_profile *profile, unsigned int index) return button; } +static struct ratbag_led * +ratbag_create_led(struct ratbag_profile *profile, unsigned int index) +{ + struct ratbag_device *device = profile->device; + struct ratbag_led *led; + + led = zalloc(sizeof(*led)); + led->refcount = 0; + led->profile = profile; + led->index = index; + + list_insert(&profile->leds, &led->link); + + if (device->driver->read_led) + device->driver->read_led(led); + + return led; +} + static int ratbag_profile_init_buttons(struct ratbag_profile *profile, unsigned int count) { @@ -598,6 +619,11 @@ ratbag_profile_init_buttons(struct ratbag_profile *profile, unsigned int count) static int ratbag_profile_init_leds(struct ratbag_profile *profile, unsigned int count) { + unsigned int i; + + for (i = 0; i < count; i++) + ratbag_create_led(profile, i); + profile->device->num_leds = count; return 0; @@ -624,6 +650,7 @@ ratbag_create_profile(struct ratbag_device *device, list_insert(&device->profiles, &profile->link); list_init(&profile->buttons); + list_init(&profile->leds); for (i = 0; i < num_resolutions; i++) ratbag_resolution_init(profile, i, 0, 0, 0); @@ -682,14 +709,18 @@ ratbag_profile_ref(struct ratbag_profile *profile) static void ratbag_profile_destroy(struct ratbag_profile *profile) { - struct ratbag_button *button, *next; + struct ratbag_button *button, *b_next; + struct ratbag_led *led, *l_next; /* if we get to the point where the profile is destroyed, buttons, * resolutions , etc. are at a refcount of 0, so we can destroy * everything */ - list_for_each_safe(button, next, &profile->buttons, link) + list_for_each_safe(button, b_next, &profile->buttons, link) ratbag_button_destroy(button); + list_for_each_safe(led, l_next, &profile->leds, link) + ratbag_led_destroy(led); + free(profile->resolution.modes); list_remove(&profile->link); @@ -790,6 +821,12 @@ ratbag_device_has_capability(const struct ratbag_device *device, return !!(device->capabilities & (1UL << cap)); } +static inline enum ratbag_error_code +write_led_helper(struct ratbag_device *device, struct ratbag_led *led) +{ + return device->driver->write_led(led, led->mode, led->color, led->hz, led->brightness); +} + /* FIXME: This is a temporary fix for all of the drivers that have yet to be * converted to the new profile-oriented API. Once all of the drivers have been * converted, this code should be removed. @@ -799,6 +836,7 @@ ratbag_old_write_profile(struct ratbag_device *device) { struct ratbag_profile *profile; struct ratbag_button *button; + struct ratbag_led *led; struct ratbag_resolution *resolution; int rc; unsigned int i; @@ -839,6 +877,17 @@ ratbag_old_write_profile(struct ratbag_device *device) return RATBAG_ERROR_DEVICE; } } + + if (device->driver->write_led) { + list_for_each(led, &profile->leds, link) { + if (!led->dirty) + continue; + + rc = write_led_helper(device, led); + if (rc) + return RATBAG_ERROR_DEVICE; + } + } } return RATBAG_SUCCESS; @@ -1268,6 +1317,16 @@ ratbag_button_ref(struct ratbag_button *button) return button; } +LIBRATBAG_EXPORT struct ratbag_led * +ratbag_led_ref(struct ratbag_led *led) +{ + assert(led->refcount < INT_MAX); + + ratbag_profile_ref(led->profile); + led->refcount++; + return led; +} + static void ratbag_button_destroy(struct ratbag_button *button) { @@ -1280,6 +1339,13 @@ ratbag_button_destroy(struct ratbag_button *button) free(button); } +static void +ratbag_led_destroy(struct ratbag_led *led) +{ + list_remove(&led->link); + free(led); +} + LIBRATBAG_EXPORT struct ratbag_button * ratbag_button_unref(struct ratbag_button *button) { @@ -1294,6 +1360,126 @@ ratbag_button_unref(struct ratbag_button *button) return NULL; } +LIBRATBAG_EXPORT struct ratbag_led * +ratbag_led_unref(struct ratbag_led *led) +{ + if (led == NULL) + return NULL; + + assert(led->refcount > 0); + led->refcount--; + + ratbag_profile_unref(led->profile); + + return NULL; +} + +LIBRATBAG_EXPORT enum ratbag_led_mode +ratbag_led_get_mode(struct ratbag_led *led) +{ + return led->mode; +} + +LIBRATBAG_EXPORT enum ratbag_led_type +ratbag_led_get_type(struct ratbag_led *led) +{ + return led->type; +} + +LIBRATBAG_EXPORT struct ratbag_color +ratbag_led_get_color(struct ratbag_led *led) +{ + return led->color; +} + +LIBRATBAG_EXPORT int +ratbag_led_get_effect_rate(struct ratbag_led *led) +{ + return led->hz; +} + +LIBRATBAG_EXPORT unsigned int +ratbag_led_get_brightness(struct ratbag_led *led) +{ + return led->brightness; +} + +LIBRATBAG_EXPORT enum ratbag_error_code +ratbag_led_set_mode(struct ratbag_led *led, enum ratbag_led_mode mode) +{ + + if (!ratbag_device_has_capability(led->profile->device, + RATBAG_DEVICE_CAP_LED)) + return RATBAG_ERROR_CAPABILITY; + + led->mode = mode; + led->dirty = true; + led->profile->dirty = true; + return RATBAG_SUCCESS; +} + +LIBRATBAG_EXPORT enum ratbag_error_code +ratbag_led_set_color(struct ratbag_led *led, struct ratbag_color color) +{ + if (!ratbag_device_has_capability(led->profile->device, + RATBAG_DEVICE_CAP_LED)) + return RATBAG_ERROR_CAPABILITY; + + led->color = color; + led->dirty = true; + led->profile->dirty = true; + return RATBAG_SUCCESS; +} + +LIBRATBAG_EXPORT enum ratbag_error_code +ratbag_led_set_effect_rate(struct ratbag_led *led, unsigned int hz) +{ + if (!ratbag_device_has_capability(led->profile->device, + RATBAG_DEVICE_CAP_LED)) + return RATBAG_ERROR_CAPABILITY; + + led->hz = hz; + led->dirty = true; + led->profile->dirty = true; + return RATBAG_SUCCESS; +} + +LIBRATBAG_EXPORT enum ratbag_error_code +ratbag_led_set_brightness(struct ratbag_led *led, unsigned int brightness) +{ + if (!ratbag_device_has_capability(led->profile->device, + RATBAG_DEVICE_CAP_LED)) + return RATBAG_ERROR_CAPABILITY; + + led->brightness = brightness; + led->dirty = true; + led->profile->dirty = true; + return RATBAG_SUCCESS; +} + +LIBRATBAG_EXPORT struct ratbag_led * +ratbag_profile_get_led(struct ratbag_profile *profile, + unsigned int index) +{ + struct ratbag_device *device = profile->device; + struct ratbag_led *led; + + if (index >= ratbag_device_get_num_leds(device)) { + log_bug_client(device->ratbag, "Requested invalid led %d\n", index); + return NULL; + } + + list_for_each(led, &profile->leds, link) { + if (led->index == index) + return ratbag_led_ref(led); + } + + log_bug_libratbag(device->ratbag, "Led %d, profile %d not found\n", + index, profile->index); + + return NULL; +} + LIBRATBAG_EXPORT void ratbag_set_user_data(struct ratbag *ratbag, void *userdata) { diff --git a/src/libratbag.h b/src/libratbag.h index 174b9ea6..822eaec2 100644 --- a/src/libratbag.h +++ b/src/libratbag.h @@ -55,6 +55,8 @@ extern "C" { * * @defgroup button Button configuration * + * @defgroup led LED configuration + * * @defgroup resolution Resolution and frequency mappings * * A device's sensor resolution and report rate can be configured per @@ -126,6 +128,28 @@ struct ratbag_button; */ struct ratbag_resolution; +/** + * @ingroup led + * @struct ratbag_color + * + * Represents LED color in RGB format. + * each color component is integer 0 - 255 + */ + +struct ratbag_color { + unsigned int red; + unsigned int green; + unsigned int blue; +}; + +/** + * @ingroup led + * @struct ratbag_led + * + * Represents a led on the device. + */ +struct ratbag_led; + /** * @ingroup button * @struct ratbag_macro @@ -1317,6 +1341,23 @@ ratbag_button_set_button(struct ratbag_button *button, enum ratbag_button_action_special ratbag_button_get_special(struct ratbag_button *button); +/** + * @ingroup led + * + * RATBAG_LED_OFF - led is now off, + * RATBAG_LED_ON - led is on with static color, + * RATBAG_LED_CYCLE - led is cycling between all colors. + * RATBAG_LED_BREATHING - led is pulsating with static color + * + * Each LED mode has different properties, e.g. the brightness and rate are only + * available in modes @ref RATBAG_LED_CYCLE and @ref RATBAG_LED_BREATHING modes + */ +enum ratbag_led_mode { + RATBAG_LED_OFF = 0, + RATBAG_LED_ON, + RATBAG_LED_CYCLE, + RATBAG_LED_BREATHING, +}; /** * @ingroup led * @@ -1327,6 +1368,145 @@ enum ratbag_led_type { RATBAG_LED_TYPE_LOGO = 0, RATBAG_LED_TYPE_SIDE }; + +/** + * @ingroup led + * + * Return a reference to the LED given by the index. The order of the + * LEDs is device-specific though. + * + * The LED is refcounted with an initial value of at least 1. + * Use ratbag_led_unref() to release the LED. + * + * @param profile A previously initialized ratbag profile + * @param index The index of the LED + * + * @return A LED context, or NULL if the LED does not exist. + * + * @see ratbag_device_get_profile + */ +struct ratbag_led * +ratbag_profile_get_led(struct ratbag_profile *profile, unsigned int index); +/** + * @ingroup led + * + * This function returns the type for ratbag_led. + * + * @param led A previously initialized ratbag LED + * @return The LED type @ref enum ratbag_led_type + * + * @see ratbag_led_set_mode + */ +enum ratbag_led_type +ratbag_led_get_type(struct ratbag_led *led); +/** + * @ingroup led + * + * This function returns the mode for ratbag_led. + * + * @param led A previously initialized ratbag LED + * @return The LED mod @ref enum ratbag_led_mode + * + * @see ratbag_led_set_mode + */ +enum ratbag_led_mode +ratbag_led_get_mode(struct ratbag_led *led); +/** + * @ingroup led + * + * This function returns the led color. + * + * @param led A previously initialized ratbag LED + * @return The LED color in @ref enum ratbag_led_mode + * + * @see ratbag_led_set_color + */ +struct ratbag_color +ratbag_led_get_color(struct ratbag_led *led); +/** + * @ingroup led + * + * This function returns the LED effect rate. + * + * @param led A previously initialized ratbag LED + * @return The LED rate in Hz, can be 100 - 20000 + * + * @see ratbag_led_set_effect_rate + */ +int +ratbag_led_get_effect_rate(struct ratbag_led *led); +/** + * @ingroup led + * + * This function returns the LED brightness. + * + * @param led A previously initialized ratbag LED + * @return The LED brightness 0 - 255 + * + * @see ratbag_led_get_brightness + */ +unsigned int +ratbag_led_get_brightness(struct ratbag_led *led); + +/** + * @ingroup led + * + * this function sets the LED mode. + * + * @param led A previously initialized ratbag LED + * @param mode LED mode @ref enum ratbag_led_mode. + * @return 0 on success or an error code otherwise. + * + * @see ratbag_led_get_mode + */ +enum ratbag_error_code +ratbag_led_set_mode(struct ratbag_led *led, enum ratbag_led_mode mode); + +/** + * @ingroup led + * + * If the LED's mode is @ref RATBAG_LED_ON or @ref RATBAG_LED_BREATHING + * then this function sets the LED color, otherwise it has no effect. + * + * @param led A previously initialized ratbag LED + * @param color A LED color. + * @return 0 on success or an error code otherwise. + * + * @see ratbag_led_get_color + */ +enum ratbag_error_code +ratbag_led_set_color(struct ratbag_led *led, struct ratbag_color color); + +/** + * @ingroup led + * + * If the LED's mode is @ref RATBAG_LED_CYCLE or @ref RATBAG_LED_BREATHING + * then this function sets the LED rate in Hz + * + * @param led A previously initialized ratbag LED + * @param rate Effect rate in hz, 100 - 20000 + * @return 0 on success or an error code otherwise. + * + * @see ratbag_led_get_effect_rate + */ +enum ratbag_error_code +ratbag_led_set_effect_rate(struct ratbag_led *led, unsigned int rate); + +/** + * @ingroup led + * + * If the LED's mode is @ref RATBAG_LED_CYCLE or @ref RATBAG_LED_BREATHING + * then this function sets the LED brightness, otherwise it has no effect. + * + * @param led A previously initialized ratbag LED + * @param brightness Effect brightness 0 - 255 + * @return 0 on success or an error code otherwise. + * + * @see ratbag_led_get_brightness + */ +enum ratbag_error_code +ratbag_led_set_brightness(struct ratbag_led *led, unsigned int brightness); + /** * @ingroup button * @@ -1610,6 +1790,31 @@ ratbag_button_ref(struct ratbag_button *button); struct ratbag_button * ratbag_button_unref(struct ratbag_button *button); +/** + * @ingroup led + * + * Add a reference to the led. A led is destroyed whenever the + * reference count reaches 0. See @ref ratbag_led_unref. + * + * @param led A previously initialized valid ratbag led + * @return The passed ratbag led + */ +struct ratbag_led * +ratbag_led_ref(struct ratbag_led *led); + +/** + * @ingroup led + * + * Dereference the ratbag led. When the internal refcount reaches + * zero, all resources associated with this object are released. The object + * must be considered invalid once unref is called. + * + * @param led A previously initialized ratbag led + * @return Always NULL + */ +struct ratbag_led * +ratbag_led_unref(struct ratbag_led *led); + #ifdef __cplusplus } #endif diff --git a/src/libratbag.sym b/src/libratbag.sym index 3d8f38ee..15aab48f 100644 --- a/src/libratbag.sym +++ b/src/libratbag.sym @@ -45,6 +45,18 @@ global: ratbag_log_get_priority; ratbag_log_set_handler; ratbag_log_set_priority; + ratbag_profile_get_led; + ratbag_led_get_mode; + ratbag_led_get_type; + ratbag_led_get_color; + ratbag_led_get_effect_rate; + ratbag_led_get_brightness; + ratbag_led_ref; + ratbag_led_set_mode; + ratbag_led_set_color; + ratbag_led_set_effect_rate; + ratbag_led_set_brightness; + ratbag_led_unref; ratbag_device_get_num_leds; ratbag_profile_get_button; ratbag_profile_get_num_resolutions; diff --git a/test/test-device.c b/test/test-device.c index 4c425f20..24d7a1df 100644 --- a/test/test-device.c +++ b/test/test-device.c @@ -79,6 +79,20 @@ const struct ratbag_test_device sane_device = { { .xres = 2200, .yres = 2300, .hz = 3000 }, { .xres = 2300, .yres = 2400, .hz = 3000 }, }, + .leds = { + { + .mode = RATBAG_LED_ON, + .color = { .red = 255, .green = 0, .blue = 0 }, + .hz = 1, + .brightness = 20 + }, + { + .mode = RATBAG_LED_CYCLE, + .color = { .red = 255, .green = 255, .blue = 0 }, + .hz = 3, + .brightness = 40 + } + }, .active = false, .dflt = false, }, @@ -754,6 +768,119 @@ START_TEST(device_buttons_set) } END_TEST +static void +assert_led_equals(struct ratbag_led *l, struct ratbag_test_led e_l) +{ + enum ratbag_led_mode mode; + struct ratbag_color color; + int brightness, hz; + + ck_assert(l != NULL); + mode = ratbag_led_get_mode(l); + color = ratbag_led_get_color(l); + hz = ratbag_led_get_effect_rate(l); + brightness = ratbag_led_get_brightness(l); + ck_assert_int_eq(mode, e_l.mode); + ck_assert_int_eq(color.red, e_l.color.red); + ck_assert_int_eq(color.green, e_l.color.green); + ck_assert_int_eq(color.blue, e_l.color.blue); + ck_assert_int_eq(hz, e_l.hz); + ck_assert_int_eq(brightness, e_l.brightness); +} + +START_TEST(device_leds) +{ + struct ratbag *r; + struct ratbag_device *d; + struct ratbag_profile *p; + struct ratbag_led *led_logo, *led_side; + int nprofiles; + int device_freed_count = 0; + + struct ratbag_test_device td = sane_device; + + td.destroyed_data = &device_freed_count; + + r = ratbag_create_context(&abort_iface, NULL); + d = ratbag_device_new_test_device(r, &td); + + nprofiles = ratbag_device_get_num_profiles(d); + ck_assert_int_eq(nprofiles, 3); + + p = ratbag_device_get_profile(d, 2); + ck_assert(p != NULL); + + led_logo = ratbag_profile_get_led(p, RATBAG_LED_TYPE_LOGO); + assert_led_equals(led_logo, td.profiles[2].leds[RATBAG_LED_TYPE_LOGO]); + led_side = ratbag_profile_get_led(p, RATBAG_LED_TYPE_SIDE); + assert_led_equals(led_side, td.profiles[2].leds[RATBAG_LED_TYPE_SIDE]); + + ratbag_led_unref(led_logo); + ratbag_led_unref(led_side); + ratbag_profile_unref(p); + ratbag_device_unref(d); + ratbag_unref(r); + ck_assert_int_eq(device_freed_count, 1); +} +END_TEST + +START_TEST(device_leds_set) +{ + struct ratbag *r; + struct ratbag_device *d; + struct ratbag_profile *p; + struct ratbag_led *l; + int nprofiles; + int device_freed_count = 0; + + struct ratbag_test_device td = sane_device; + + struct ratbag_color c = { + .red = 0, + .green = 111, + .blue = 222 + }; + + td.destroyed_data = &device_freed_count; + + r = ratbag_create_context(&abort_iface, NULL); + d = ratbag_device_new_test_device(r, &td); + + nprofiles = ratbag_device_get_num_profiles(d); + ck_assert_int_eq(nprofiles, 3); + + p = ratbag_device_get_profile(d, 0); + ck_assert(p != NULL); + + l = ratbag_profile_get_led(p, RATBAG_LED_TYPE_LOGO); + + ratbag_led_set_mode(l, RATBAG_LED_BREATHING); + ratbag_led_set_color(l, c); + ratbag_led_set_effect_rate(l, 11); + ratbag_led_set_brightness(l, 22); + + l = ratbag_profile_get_led(p, RATBAG_LED_TYPE_LOGO); + struct ratbag_test_led e_l = { + .mode = RATBAG_LED_BREATHING, + .color = { + .red = c.red, + .green = c.green, + .blue = c.blue + }, + .hz = 11, + .brightness = 22 + }; + assert_led_equals(l, e_l); + + ratbag_led_unref(l); + ratbag_led_unref(l); + ratbag_profile_unref(p); + ratbag_device_unref(d); + ratbag_unref(r); + ck_assert_int_eq(device_freed_count, 1); +} +END_TEST + static Suite * test_context_suite(void) { @@ -792,6 +919,11 @@ test_context_suite(void) tcase_add_test(tc, device_buttons_set); suite_add_tcase(s, tc); + tc = tcase_create("led"); + tcase_add_test(tc, device_leds); + tcase_add_test(tc, device_leds_set); + suite_add_tcase(s, tc); + return s; }