/* * Copyright (C) 2021 Kyoken, kyoken@kyoken.ninja * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "config.h" #include #include #include #include #include #include #include #include #include "libratbag-private.h" #include "libratbag-hidraw.h" #include "libratbag-data.h" #include "asus.h" /* ButtonMapping configuration property defaults */ static int ASUS_CONFIG_BUTTON_MAPPING[] = { 0xf0, /* left */ 0xf1, /* right (button 3 in xev) */ 0xf2, /* middle (button 2 in xev) */ 0xe4, /* backward */ 0xe5, /* forward */ 0xe6, /* DPI */ 0xe8, /* wheel up */ 0xe9, /* wheel down */ -1, /* placeholder */ -1, /* placeholder */ -1, /* placeholder */ -1, /* placeholder */ -1, /* placeholder */ -1, /* placeholder */ -1, /* placeholder */ -1, /* placeholder */ -1, /* placeholder */ }; static unsigned int ASUS_LED_MODE[] = { RATBAG_LED_ON, RATBAG_LED_BREATHING, RATBAG_LED_CYCLE, RATBAG_LED_ON, /* rainbow wave */ RATBAG_LED_ON, /* reactive - react to clicks */ RATBAG_LED_ON, /* custom - depends on mouse type */ RATBAG_LED_ON, /* battery - battery indicator */ }; struct asus_data { uint8_t is_ready; int button_mapping[ASUS_MAX_NUM_BUTTON]; int button_indices[ASUS_MAX_NUM_BUTTON]; }; static int asus_driver_load_profile(struct ratbag_device *device, struct ratbag_profile *profile) { int rc; const struct _asus_binding *asus_binding; const struct _asus_led *asus_led; const struct asus_button *asus_button; struct ratbag_button *button; struct ratbag_led *led; struct ratbag_resolution *resolution; union asus_binding_data binding_data; union asus_led_data led_data; union asus_resolution_data resolution_data; unsigned int dpi_count = ratbag_device_get_profile(device, 0)->num_resolutions; struct asus_data *drv_data = ratbag_get_drv_data(device); /* get buttons */ log_debug(device->ratbag, "Loading buttons data\n"); rc = asus_get_binding_data(device, &binding_data); if (rc) return rc; ratbag_profile_for_each_button(profile, button) { int asus_index = drv_data->button_indices[button->index]; if (asus_index == -1) { log_debug(device->ratbag, "No mapping for button %d\n", button->index); continue; } asus_binding = &binding_data.data.binding[asus_index]; /* disabled */ if (asus_binding->action == ASUS_BUTTON_CODE_DISABLED) { button->action.type = RATBAG_BUTTON_ACTION_TYPE_NONE; continue; } /* got action */ switch (asus_binding->type) { case ASUS_BUTTON_ACTION_TYPE_KEY: button->action.type = RATBAG_BUTTON_ACTION_TYPE_KEY; /* ASUS code to Linux code */ button->action.action.key.key = asus_get_linux_key_code(asus_binding->action); break; case ASUS_BUTTON_ACTION_TYPE_BUTTON: asus_button = asus_find_button_by_code(asus_binding->action); if (asus_button != NULL) { /* found button to bind to */ button->action.type = asus_button->type; if (asus_button->type == RATBAG_BUTTON_ACTION_TYPE_BUTTON) button->action.action.button = asus_button->button; else if (asus_button->type == RATBAG_BUTTON_ACTION_TYPE_SPECIAL) button->action.action.special = asus_button->special; } else { log_debug(device->ratbag, "Unknown action code %02x\n", asus_binding->action); } break; default: break; } } /* get DPIs */ log_debug(device->ratbag, "Loading resolutions data\n"); rc = asus_get_resolution_data(device, &resolution_data); if (rc) return rc; switch (dpi_count) { case 2: /* 2 DPI presets */ ratbag_profile_set_report_rate(profile, resolution_data.data2.rate); ratbag_profile_for_each_resolution(profile, resolution) ratbag_resolution_set_resolution( resolution, resolution_data.data2.dpi[resolution->index], resolution_data.data2.dpi[resolution->index]); break; case 4: /* 4 DPI presets */ ratbag_profile_set_report_rate(profile, resolution_data.data4.rate); ratbag_profile_for_each_resolution(profile, resolution) ratbag_resolution_set_resolution( resolution, resolution_data.data4.dpi[resolution->index], resolution_data.data4.dpi[resolution->index]); break; default: break; } /* get LEDs */ log_debug(device->ratbag, "Loading LEDs data\n"); rc = asus_get_led_data(device, &led_data); if (rc) return rc; ratbag_profile_for_each_led(profile, led) { asus_led = &led_data.data.led[led->index]; led->mode = ASUS_LED_MODE[asus_led->mode]; /* convert brightness from 0-4 to 0-256 */ led->brightness = asus_led->brightness * 64; led->color.red = asus_led->r; led->color.green = asus_led->g; led->color.blue = asus_led->b; } return 0; } static int asus_driver_save_profile(struct ratbag_device *device, struct ratbag_profile *profile) { int rc = 0; struct ratbag_button *button; struct ratbag_led *led; struct ratbag_resolution *resolution; struct asus_data *drv_data = ratbag_get_drv_data(device); /* set buttons */ ratbag_profile_for_each_button(profile, button) { if (!button->dirty) continue; int asus_index = drv_data->button_indices[button->index]; if (asus_index == -1) { log_debug(device->ratbag, "No mapping for button %d\n", button->index); continue; } const struct asus_button *asus_button; uint8_t asus_code_src; uint8_t asus_code_dst; rc = drv_data->button_mapping[asus_index]; if (rc == -1) { log_debug(device->ratbag, "No mapping for button %d\n", button->index); continue; } asus_code_src = (uint8_t)rc; log_debug(device->ratbag, "Button %d (%02x) changed\n", button->index, asus_code_src); switch (button->action.type) { case RATBAG_BUTTON_ACTION_TYPE_NONE: rc = asus_set_button_action( device, asus_code_src, ASUS_BUTTON_CODE_DISABLED, ASUS_BUTTON_ACTION_TYPE_BUTTON); break; case RATBAG_BUTTON_ACTION_TYPE_KEY: /* Linux code to ASUS code */ rc = asus_find_key_code(button->action.action.key.key); if (rc != -1) { asus_code_dst = (uint8_t)rc; rc = asus_set_button_action( device, asus_code_src, asus_code_dst, ASUS_BUTTON_ACTION_TYPE_KEY); } break; case RATBAG_BUTTON_ACTION_TYPE_BUTTON: case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: /* ratbag action to ASUS code */ asus_button = asus_find_button_by_action(button->action); if (asus_button != NULL) /* found button to bind to */ rc = asus_set_button_action( device, asus_code_src, asus_button->asus_code, ASUS_BUTTON_ACTION_TYPE_BUTTON); break; default: break; } if (rc) return rc; } /* set extra settings */ if (profile->rate_dirty) { log_debug(device->ratbag, "Polling rate changed to %d Hz\n", profile->hz); rc = asus_set_polling_rate(device, profile->hz); if (rc) return rc; } /* set DPIs */ ratbag_profile_for_each_resolution(profile, resolution) { if (!resolution->dirty) continue; log_debug( device->ratbag, "Resolution %d changed to %d\n", resolution->index, resolution->dpi_x); rc = asus_set_dpi(device, resolution->index, resolution->dpi_x); if (rc) return rc; } /* set LEDs */ ratbag_profile_for_each_led(profile, led) { if (!led->dirty) continue; log_debug(device->ratbag, "LED %d changed\n", led->index); uint8_t led_mode = 0; for (unsigned int i = 0; i < ARRAY_LENGTH(ASUS_LED_MODE); i++) { if (ASUS_LED_MODE[i] == led->mode) { led_mode = i; break; } } /* convert brightness from 0-256 to 0-4 */ uint8_t led_brightness = (uint8_t)round((double)led->brightness / 64.0); rc = asus_set_led(device, led->index, led_mode, led_brightness, led->color); if (rc) return rc; } return 0; } static int asus_driver_load_profiles(struct ratbag_device *device) { int rc; struct asus_profile_data profile_data; struct ratbag_profile *profile; unsigned int current_profile_id; /* get current profile id */ rc = asus_get_profile_data(device, &profile_data); if (rc) return rc; current_profile_id = profile_data.profile_id; log_debug(device->ratbag, "Initial profile is %d\n", current_profile_id); log_debug( device->ratbag, "Primary version %02X.%02X.%02X\n", profile_data.version_primary_major, profile_data.version_primary_minor, profile_data.version_primary_build); log_debug( device->ratbag, "Secondary version %02X.%02X.%02X\n", profile_data.version_secondary_major, profile_data.version_secondary_minor, profile_data.version_secondary_build); /* read ratbag profiles */ ratbag_device_for_each_profile(device, profile) { if (profile->index == current_profile_id) { /* profile is already selected */ profile->is_active = true; } else { /* switch profile */ profile->is_active = false; log_debug(device->ratbag, "Switching to profile %d\n", profile->index); rc = asus_set_profile(device, profile->index); if (rc) return rc; } rc = asus_driver_load_profile(device, profile); if (rc) return rc; } /* back to initial profile */ if (device->num_profiles > 1) { log_debug(device->ratbag, "Switching back to initial profile %d\n", current_profile_id); rc = asus_set_profile(device, current_profile_id); if (rc) return rc; } return 0; } static int asus_driver_save_profiles(struct ratbag_device *device) { int rc; struct asus_profile_data profile_data; struct ratbag_profile *profile; unsigned int current_profile_id; /* get current profile id */ rc = asus_get_profile_data(device, &profile_data); if (rc) return rc; current_profile_id = profile_data.profile_id; log_debug(device->ratbag, "Initial profile is %d\n", current_profile_id); ratbag_device_for_each_profile(device, profile) { if (!profile->dirty) continue; log_debug(device->ratbag, "Profile %d changed\n", profile->index); /* switch profile */ if (profile->index != current_profile_id) { log_debug(device->ratbag, "Switching to profile %d\n", profile->index); rc = asus_set_profile(device, profile->index); if (rc) return rc; } rc = asus_driver_save_profile(device, profile); if (rc) return rc; /* save profile */ log_debug(device->ratbag, "Saving profile\n"); rc = asus_save_profile(device); if (rc) return rc; } /* back to initial profile */ if (device->num_profiles > 1) { log_debug(device->ratbag, "Switching back to initial profile %d\n", current_profile_id); rc = asus_set_profile(device, current_profile_id); if (rc) return rc; } return 0; } static int asus_driver_probe(struct ratbag_device *device) { int rc; unsigned int profile_count, dpi_count, button_count, led_count; const struct asus_button *asus_button; struct asus_data *drv_data; struct ratbag_profile *profile; struct ratbag_button *button; struct ratbag_resolution *resolution; struct ratbag_led *led; rc = ratbag_open_hidraw(device); if (rc) return rc; /* create device state data */ drv_data = zalloc(sizeof(*drv_data)); ratbag_set_drv_data(device, drv_data); drv_data->is_ready = 1; /* get device properties from configuration file */ profile_count = ratbag_device_data_asus_get_profile_count(device->data); dpi_count = ratbag_device_data_asus_get_dpi_count(device->data); button_count = ratbag_device_data_asus_get_button_count(device->data); led_count = ratbag_device_data_asus_get_led_count(device->data); const int *bm = ratbag_device_data_asus_get_button_mapping(device->data); /* merge ButtonMapping configuration property with defaults */ for (unsigned int i = 0; i < ASUS_MAX_NUM_BUTTON; i++) { drv_data->button_mapping[i] = bm[i] != -1 ? bm[i] : ASUS_CONFIG_BUTTON_MAPPING[i]; drv_data->button_indices[i] = -1; } /* setup a lookup table for all defined buttons */ unsigned int button_index = 0; ARRAY_FOR_EACH(ASUS_BUTTON_MAPPING, asus_button) { /* search for this button in the ButtonMapping by it's ASUS code */ for (unsigned int i = 0; i < ASUS_MAX_NUM_BUTTON; i++) { if (drv_data->button_mapping[i] == (int)asus_button->asus_code) { /* add button to indices array */ drv_data->button_indices[button_index] = (int)i; log_debug(device->ratbag, "Button %d is mapped to 0x%02x\n", button_index, (uint8_t)drv_data->button_mapping[i]); button_index++; break; } } } /* init profiles */ ratbag_device_init_profiles( device, max(profile_count, 1), max(dpi_count, 2), max(button_count, 8), max(led_count, 0)); /* setup profiles */ ratbag_device_for_each_profile(device, profile) { if (profile->index == 0) { profile->is_active = true; } asus_setup_profile(device, profile); ratbag_profile_for_each_button(profile, button) asus_setup_button(device, button); ratbag_profile_for_each_resolution(profile, resolution) asus_setup_resolution(device, resolution); ratbag_profile_for_each_led(profile, led) asus_setup_led(device, led); } /* load profiles */ rc = asus_driver_load_profiles(device); if (rc == ASUS_STATUS_ERROR) { /* mouse in invalid state */ drv_data->is_ready = 0; } else if (rc) { /* other errors */ log_error( device->ratbag, "Can't talk to the mouse: '%s' (%d)\n", strerror(-rc), rc); free(drv_data); ratbag_set_drv_data(device, NULL); return -ENODEV; } return 0; } static void asus_driver_remove(struct ratbag_device *device) { ratbag_close_hidraw(device); free(ratbag_get_drv_data(device)); } static int asus_driver_commit(struct ratbag_device *device) { int rc; struct asus_data *drv_data; /* check last device state */ drv_data = ratbag_get_drv_data(device); if (!drv_data->is_ready) { /* device was not ready */ log_error(device->ratbag, "Device was not ready, trying to reload\n"); rc = asus_driver_load_profiles(device); if (rc) { log_error(device->ratbag, "Device reloading failed (%d)\n", rc); if (rc != ASUS_STATUS_ERROR) return rc; } else { drv_data->is_ready = 1; log_error(device->ratbag, "Device was successfully reloaded\n"); } return RATBAG_ERROR_DEVICE; /* fail in any case because we tried to rollback instead of commit */ } /* save profiles */ rc = asus_driver_save_profiles(device); if (rc) { log_error(device->ratbag, "Commit failed (%d)\n", rc); if (rc != ASUS_STATUS_ERROR) return rc; return RATBAG_ERROR_DEVICE; } return 0; } struct ratbag_driver asus_driver = { .name = "ASUS", .id = "asus", .probe = asus_driver_probe, .remove = asus_driver_remove, .commit = asus_driver_commit, .set_active_profile = asus_set_profile, };