diff --git a/oversteer/device.py b/oversteer/device.py index a18e2cb..2033806 100644 --- a/oversteer/device.py +++ b/oversteer/device.py @@ -1,4 +1,4 @@ -from evdev import ecodes, InputDevice +from evdev import ecodes, InputDevice, UInput import grp import logging import os @@ -7,6 +7,7 @@ import select import time from . import wheel_ids as wid +from .pedal_mode import AxisMode, get_modified_value logging.basicConfig(level=logging.DEBUG) @@ -22,6 +23,9 @@ class Device: def __init__(self, device_manager, data): self.device_manager = device_manager self.input_device = None + self.injector_device = None + self.injector_active = False + self.pedal_mode = None self.seat_id = None self.vendor_id = None self.product_id = None @@ -142,6 +146,15 @@ def set_mode(self, emulation_mode): time.sleep(5) return True + def get_pedal_mode(self): + if self.pedal_mode is None: + self.set_pedal_mode(AxisMode.NORMAL.value.id) + return self.pedal_mode + + def set_pedal_mode(self, mode): + self.pedal_mode = list(AxisMode)[mode] + self.set_active_event_device() + def get_range(self): path = self.checked_device_file("range") if not path: @@ -368,6 +381,16 @@ def get_input_device(self): self.input_device = InputDevice(self.dev_name) return self.input_device + def set_active_event_device(self): + if self.pedal_mode != AxisMode.NORMAL and not self.injector_active: + self.injector_device = UInput.from_device(self.input_device, name="Custom pedal mode wheel") + self.input_device.grab() + self.injector_active = True + elif self.pedal_mode == AxisMode.NORMAL and self.input_device is not None and self.injector_active: + self.injector_device = None + self.input_device.ungrab() + self.injector_active = False + def get_capabilities(self): return self.get_input_device().capabilities() @@ -380,6 +403,10 @@ def read_events(self, timeout): event = self.normalize_event(event) if event.type == ecodes.EV_ABS: self.last_axis_value[ecodes.ABS_X] = event.value + + if self.pedal_mode != AxisMode.NORMAL and self.injector_device is not None: + self.injector_device.write_event(event) + yield event def normalize_event(self, event): @@ -408,4 +435,6 @@ def normalize_event(self, event): event.code = ecodes.ABS_RZ elif event.code == ecodes.ABS_THROTTLE: event.code = ecodes.ABS_Y + elif self.pedal_mode != AxisMode.NORMAL and event.code in [ecodes.ABS_RZ, ecodes.ABS_Y, ecodes.ABS_Z]: + event.value = get_modified_value(self.pedal_mode, event.value) return event diff --git a/oversteer/gtk_handlers.py b/oversteer/gtk_handlers.py index 0c4dab6..17b08b6 100644 --- a/oversteer/gtk_handlers.py +++ b/oversteer/gtk_handlers.py @@ -4,6 +4,7 @@ import traceback gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk +from .pedal_mode import CombinedPedals class GtkHandlers: @@ -81,13 +82,16 @@ def on_overlay_incrange_clicked(self, widget): self.ui.overlay_wheel_range.set_label(str(wrange)) def on_combine_none_clicked(self, widget): - self.model.set_combine_pedals(0) + self.model.set_combine_pedals(CombinedPedals.NONE) + self.ui.set_pedal_levels() def on_combine_brakes_clicked(self, widget): - self.model.set_combine_pedals(1) + self.model.set_combine_pedals(CombinedPedals.COMBINE_BRAKES) + self.ui.set_pedal_levels() def on_combine_clutch_clicked(self, widget): - self.model.set_combine_pedals(2) + self.model.set_combine_pedals(CombinedPedals.COMBINE_CLUTCH) + self.ui.set_pedal_levels() def on_ff_gain_value_changed(self, widget): ff_gain = int(widget.get_value()) @@ -286,3 +290,8 @@ def on_start_app_manually_state_set(self, widget, state): def on_start_app_clicked(self, widget): self.controller.start_app() + + def on_pedal_mode_changed(self, widget): + mode = self.ui.pedal_mode_combobox.get_active() + self.model.set_pedal_mode(mode) + self.model.flush_ui() diff --git a/oversteer/gtk_ui.py b/oversteer/gtk_ui.py index c356ed5..ad745fc 100644 --- a/oversteer/gtk_ui.py +++ b/oversteer/gtk_ui.py @@ -5,6 +5,7 @@ from .gtk_handlers import GtkHandlers gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk, GLib +from . import pedal_mode class GtkUi: @@ -47,6 +48,11 @@ def __init__(self, controller, argv): self.emulation_mode_combobox.add_attribute(cell_renderer, 'text', 1) self.emulation_mode_combobox.set_id_column(0) + cell_renderer = Gtk.CellRendererText() + self.pedal_mode_combobox.pack_start(cell_renderer, True) + self.pedal_mode_combobox.add_attribute(cell_renderer, 'text', 1) + self.pedal_mode_combobox.set_id_column(0) + self.set_range_overlay('never') self.disable_save_profile() @@ -256,6 +262,47 @@ def set_mode(self, mode): self.change_emulation_mode_button.set_sensitive(True) self.emulation_mode_combobox.set_active_id(mode) + def set_pedal_modes(self): + model = self.pedal_mode_combobox.get_model() + if model is None: + model = Gtk.ListStore(int, str) + + for mode in pedal_mode.AxisMode: + model.append(mode.value) + self.pedal_mode_combobox.set_model(model) + self.set_pedal_mode(pedal_mode.AxisMode.NORMAL.value.id) + + def set_pedal_mode(self, mode): + if self.pedal_mode_combobox.get_model() is None: + return + + set_mode = list(pedal_mode.AxisMode)[mode] + self.pedal_mode_combobox.set_active(set_mode.value.id) + self.set_pedal_levels() + + def set_pedal_levels(self): + combine_mode = self.controller.device.get_combine_pedals() + axis_mode = self.controller.device.get_pedal_mode() + + acceleration_out = pedal_mode.get_modified_value(axis_mode, 255) + acceleration_in = pedal_mode.get_modified_value(axis_mode, 0) + brakes_out = pedal_mode.get_modified_value(axis_mode, 255) + clutch_out = pedal_mode.get_modified_value(axis_mode, 255) + + accel = acceleration_out + + if combine_mode == pedal_mode.CombinedPedals.COMBINE_BRAKES: + accel = acceleration_in + (acceleration_out - acceleration_in) / 2 + brakes_out = 255 + elif combine_mode == pedal_mode.CombinedPedals.COMBINE_CLUTCH: + accel = acceleration_in + (acceleration_out - acceleration_in) / 2 + clutch_out = 255 + + self.safe_call(self.set_accelerator_input, int(accel)) + self.safe_call(self.set_brakes_input, int(brakes_out)) + self.safe_call(self.set_clutch_input, int(clutch_out)) + + def set_range(self, wrange): if wrange is None: self.wheel_range.set_sensitive(False) @@ -277,9 +324,9 @@ def set_combine_pedals(self, combine_pedals): else: self.combine_brakes.set_sensitive(True) self.combine_clutch.set_sensitive(True) - if combine_pedals == 1: + if combine_pedals == pedal_mode.CombinedPedals.COMBINE_BRAKES: self.combine_brakes.set_active(True) - elif combine_pedals == 2: + elif combine_pedals == pedal_mode.CombinedPedals.COMBINE_CLUTCH: self.combine_clutch.set_active(True) else: self.combine_none.set_active(True) @@ -611,6 +658,7 @@ def _set_builder_objects(self): self.clutch_input = self.builder.get_object('clutch_input') self.accelerator_input = self.builder.get_object('accelerator_input') self.brakes_input = self.builder.get_object('brakes_input') + self.pedal_mode_combobox = self.builder.get_object('pedal_mode') self.hat_up_input = self.builder.get_object('hat_up_input') self.hat_down_input = self.builder.get_object('hat_down_input') self.hat_left_input = self.builder.get_object('hat_left_input') diff --git a/oversteer/gui.py b/oversteer/gui.py index 12ff6e8..8b66545 100644 --- a/oversteer/gui.py +++ b/oversteer/gui.py @@ -162,9 +162,13 @@ def populate_profiles(self): profiles.append(profile_name) self.ui.set_profiles(profiles) + def populate_pedal_modes(self): + self.ui.set_pedal_modes() + def populate_window(self): self.populate_devices() self.populate_profiles() + self.populate_pedal_modes() def change_device(self, device_id): self.device = self.device_manager.get_device(device_id) diff --git a/oversteer/main.ui b/oversteer/main.ui index 8d0c08e..143da6c 100644 --- a/oversteer/main.ui +++ b/oversteer/main.ui @@ -1,5 +1,5 @@ - + @@ -768,6 +768,19 @@ 1 + + + True + False + 0 + vertical + + + True + False + 2 + + True @@ -776,7 +789,7 @@ - + True False @@ -864,6 +877,48 @@ 0 + + + True + False + start + 27 + Pedal mode + + + 0 + 3 + 3 + + + + + True + False + Change pedal axis behavior + + + + 0 + 4 + 3 + + + + + True + False + The settings other than normal are only available by selecting the device called: "Custom pedal mode wheel" in-game. You may therefore need to rebind your keys in-game to use this feature. + +Oversteer must also be open while in-game in order for custom pedal modes to work + start + gtk-dialog-question + + + 0 + 3 + + False @@ -995,6 +1050,19 @@ 1 + + + True + False + 0 + vertical + + + True + False + 2 + + False diff --git a/oversteer/model.py b/oversteer/model.py index a1fafae..04c8406 100644 --- a/oversteer/model.py +++ b/oversteer/model.py @@ -18,6 +18,7 @@ class Model: 'use_buttons': None, 'center_wheel': None, 'start_app_manually': None, + 'pedal_mode': None, } types = { @@ -35,6 +36,7 @@ class Model: 'use_buttons': 'boolean', 'center_wheel': 'boolean', 'start_app_manually': 'boolean', + 'pedal_mode': 'integer', } def __init__(self, device = None, ui = None): @@ -78,6 +80,7 @@ def read_device_settings(self): 'use_buttons': False if self.device.get_range() is not None else None, 'center_wheel': False, 'start_app_manually': False, + 'pedal_mode': self.device.get_pedal_mode().value.id } def update_from_device_settings(self): @@ -165,6 +168,14 @@ def set_mode(self, value): def get_mode(self): return self.data['mode'] + def get_pedal_mode(self): + return self.data['pedal_mode'] + + def set_pedal_mode(self, value): + value = int(value) + if self.set_if_changed('pedal_mode', value): + self.device.set_pedal_mode(value) + def set_range(self, value): value = int(value) if self.set_if_changed('range', value): @@ -278,6 +289,8 @@ def flush_device(self): self.device.set_friction_level(self.data['friction_level']) if self.data['ffb_leds'] is not None: self.device.set_ffb_leds(self.data['ffb_leds']) + if self.data['pedal_mode'] is not None: + self.device.set_pedal_mode(self.data['pedal_mode']) def flush_ui(self, data = None): if data is None: @@ -296,4 +309,6 @@ def flush_ui(self, data = None): self.ui.set_use_buttons(data['use_buttons']) self.ui.set_center_wheel(data['center_wheel']) self.ui.set_start_app_manually(data['start_app_manually']) + self.ui.set_pedal_mode(data['pedal_mode']) + self.ui.set_pedal_levels() diff --git a/oversteer/pedal_mode.py b/oversteer/pedal_mode.py new file mode 100644 index 0000000..189d421 --- /dev/null +++ b/oversteer/pedal_mode.py @@ -0,0 +1,69 @@ +from collections import namedtuple +from enum import Enum + + +ABS_mode = namedtuple("ABS_mode", ["id", "name"]) + + +class AxisMode(Enum): + NORMAL = ABS_mode(0, "Normal") + INVERTED = ABS_mode(1, "Inverted") + HALF = ABS_mode(2, "Half") + HALF_INVERTED = ABS_mode(3, "Half inverted") + CENTERED_HALF = ABS_mode(4, "Centered half") + CENTERED_HALF_INVERTED = ABS_mode(5, "Centered half inverted") + CENTERED_SWITCHING = ABS_mode(6, "Centered switching") + CENTERED_SWITCHING_INVERTED = ABS_mode(7, "Centered switching inverted") + CENTERED_LOOPING = ABS_mode(8, "Centered looping") + CENTERED_LOOPING_INVERTED = ABS_mode(9, "Centered looping inverted") + + +class CombinedPedals: + NONE = 0 + COMBINE_BRAKES = 1 + COMBINE_CLUTCH = 2 + + +def get_modified_value(mode, value): + if mode == AxisMode.NORMAL: + return value + + elif mode == AxisMode.INVERTED: + return 255 - value + + elif mode == AxisMode.HALF: + return value + int((255 - value + 1) / 2) + + elif mode == AxisMode.HALF_INVERTED: + return (int(value / 2) - int(255 / 2)) * -1 + + elif mode == AxisMode.CENTERED_HALF: + return int(value / 2) + + elif mode == AxisMode.CENTERED_HALF_INVERTED: + return 255 - int(value / 2) + + elif mode == AxisMode.CENTERED_SWITCHING: + value = value - int(255 / 2) - 1 + if value < 0: + value = (value * -1) + int(255 / 2) + return value + + elif mode == AxisMode.CENTERED_SWITCHING_INVERTED: + if value > int(255 / 2): + value = int(255 + 255 / 2) - value + 1 + return value + + elif mode == AxisMode.CENTERED_LOOPING: + value = value - int(255 / 2) - 1 + if value < 0: + value += 256 + return value + + elif mode == AxisMode.CENTERED_LOOPING_INVERTED: + value = int(255 / 2) - value + if value < 0: + value += 256 + return value + + return value