Skip to content

Commit

Permalink
HID: hid-steam: Add gamepad-only mode switched to by holding options
Browse files Browse the repository at this point in the history
This commit adds a hotkey to switch between "gamepad" mode (mouse and keyboard
disabled) and "desktop" mode (gamepad disabled) by holding down the options
button (mapped here as the start button). This mirrors the behavior of the
official Steam client.

This also adds and uses a function for generating haptic pulses, as Steam also
does when engaging this hotkey.

Signed-off-by: Vicki Pfau <vi@endrift.com>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
  • Loading branch information
endrift authored and Jiri Kosina committed Jan 2, 2024
1 parent 43565b6 commit cd438e5
Showing 1 changed file with 103 additions and 10 deletions.
113 changes: 103 additions & 10 deletions drivers/hid/hid-steam.c
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ enum {
TRACKPAD_GESTURE_KEYBOARD,
};

/* Pad identifiers for the deck */
#define STEAM_PAD_LEFT 0
#define STEAM_PAD_RIGHT 1
#define STEAM_PAD_BOTH 2

/* Other random constants */
#define STEAM_SERIAL_LEN 0x15

Expand All @@ -291,6 +296,9 @@ struct steam_device {
struct power_supply __rcu *battery;
u8 battery_charge;
u16 voltage;
struct delayed_work mode_switch;
bool did_mode_switch;
bool gamepad_mode;
struct work_struct rumble_work;
u16 rumble_left;
u16 rumble_right;
Expand Down Expand Up @@ -460,6 +468,37 @@ static inline int steam_request_conn_status(struct steam_device *steam)
return ret;
}

/*
* Send a haptic pulse to the trackpads
* Duration and interval are measured in microseconds, count is the number
* of pulses to send for duration time with interval microseconds between them
* and gain is measured in decibels, ranging from -24 to +6
*/
static inline int steam_haptic_pulse(struct steam_device *steam, u8 pad,
u16 duration, u16 interval, u16 count, u8 gain)
{
int ret;
u8 report[10] = {ID_TRIGGER_HAPTIC_PULSE, 8};

/* Left and right are swapped on this report for legacy reasons */
if (pad < STEAM_PAD_BOTH)
pad ^= 1;

report[2] = pad;
report[3] = duration & 0xFF;
report[4] = duration >> 8;
report[5] = interval & 0xFF;
report[6] = interval >> 8;
report[7] = count & 0xFF;
report[8] = count >> 8;
report[9] = gain;

mutex_lock(&steam->report_mutex);
ret = steam_send_report(steam, report, sizeof(report));
mutex_unlock(&steam->report_mutex);
return ret;
}

static inline int steam_haptic_rumble(struct steam_device *steam,
u16 intensity, u16 left_speed, u16 right_speed,
u8 left_gain, u8 right_gain)
Expand Down Expand Up @@ -505,6 +544,9 @@ static int steam_play_effect(struct input_dev *dev, void *data,

static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
{
if (steam->gamepad_mode)
enable = false;

if (enable) {
mutex_lock(&steam->report_mutex);
/* enable esc, enter, cursors */
Expand Down Expand Up @@ -542,11 +584,18 @@ static int steam_input_open(struct input_dev *dev)
unsigned long flags;
bool set_lizard_mode;

spin_lock_irqsave(&steam->lock, flags);
set_lizard_mode = !steam->client_opened && lizard_mode;
spin_unlock_irqrestore(&steam->lock, flags);
if (set_lizard_mode)
steam_set_lizard_mode(steam, false);
/*
* Disabling lizard mode automatically is only done on the Steam
* Controller. On the Steam Deck, this is toggled manually by holding
* the options button instead, handled by steam_mode_switch_cb.

This comment has been minimized.

Copy link
@eshcheglov

eshcheglov Dec 25, 2024

Why wasn’t the 'lizard_mode' driver option respected on the SteamDeck? I don’t see any obstacles to disabling 'desktop' mode if 'lizard_mode' is set to 0

*/
if (!(steam->quirks & STEAM_QUIRK_DECK)) {
spin_lock_irqsave(&steam->lock, flags);
set_lizard_mode = !steam->client_opened && lizard_mode;
spin_unlock_irqrestore(&steam->lock, flags);
if (set_lizard_mode)
steam_set_lizard_mode(steam, false);
}

return 0;
}
Expand All @@ -557,11 +606,13 @@ static void steam_input_close(struct input_dev *dev)
unsigned long flags;
bool set_lizard_mode;

spin_lock_irqsave(&steam->lock, flags);
set_lizard_mode = !steam->client_opened && lizard_mode;
spin_unlock_irqrestore(&steam->lock, flags);
if (set_lizard_mode)
steam_set_lizard_mode(steam, true);
if (!(steam->quirks & STEAM_QUIRK_DECK)) {
spin_lock_irqsave(&steam->lock, flags);
set_lizard_mode = !steam->client_opened && lizard_mode;
spin_unlock_irqrestore(&steam->lock, flags);
if (set_lizard_mode)
steam_set_lizard_mode(steam, true);
}
}

static enum power_supply_property steam_battery_props[] = {
Expand Down Expand Up @@ -886,6 +937,34 @@ static void steam_work_connect_cb(struct work_struct *work)
}
}

static void steam_mode_switch_cb(struct work_struct *work)
{
struct steam_device *steam = container_of(to_delayed_work(work),
struct steam_device, mode_switch);
unsigned long flags;
bool client_opened;
steam->gamepad_mode = !steam->gamepad_mode;
if (!lizard_mode)
return;

if (steam->gamepad_mode)
steam_set_lizard_mode(steam, false);
else {
spin_lock_irqsave(&steam->lock, flags);
client_opened = steam->client_opened;
spin_unlock_irqrestore(&steam->lock, flags);
if (!client_opened)
steam_set_lizard_mode(steam, lizard_mode);
}

steam_haptic_pulse(steam, STEAM_PAD_RIGHT, 0x190, 0, 1, 0);
if (steam->gamepad_mode) {
steam_haptic_pulse(steam, STEAM_PAD_LEFT, 0x14D, 0x14D, 0x2D, 0);
} else {
steam_haptic_pulse(steam, STEAM_PAD_LEFT, 0x1F4, 0x1F4, 0x1E, 0);
}
}

static bool steam_is_valve_interface(struct hid_device *hdev)
{
struct hid_report_enum *rep_enum;
Expand Down Expand Up @@ -1040,6 +1119,7 @@ static int steam_probe(struct hid_device *hdev,
mutex_init(&steam->report_mutex);
steam->quirks = id->driver_data;
INIT_WORK(&steam->work_connect, steam_work_connect_cb);
INIT_DELAYED_WORK(&steam->mode_switch, steam_mode_switch_cb);
INIT_LIST_HEAD(&steam->list);
INIT_WORK(&steam->rumble_work, steam_haptic_rumble_cb);

Expand Down Expand Up @@ -1097,6 +1177,7 @@ static int steam_probe(struct hid_device *hdev,
hid_hw_open_fail:
hid_hw_start_fail:
cancel_work_sync(&steam->work_connect);
cancel_delayed_work_sync(&steam->mode_switch);
cancel_work_sync(&steam->rumble_work);
steam_alloc_fail:
hid_err(hdev, "%s: failed with error %d\n",
Expand All @@ -1113,6 +1194,7 @@ static void steam_remove(struct hid_device *hdev)
return;
}

cancel_delayed_work_sync(&steam->mode_switch);
cancel_work_sync(&steam->work_connect);
hid_destroy_device(steam->client_hdev);
steam->client_hdev = NULL;
Expand Down Expand Up @@ -1398,6 +1480,17 @@ static void steam_do_deck_input_event(struct steam_device *steam,
b13 = data[13];
b14 = data[14];

if (!(b9 & BIT(6)) && steam->did_mode_switch) {
steam->did_mode_switch = false;
cancel_delayed_work_sync(&steam->mode_switch);
} else if (!steam->client_opened && (b9 & BIT(6)) && !steam->did_mode_switch) {
steam->did_mode_switch = true;
schedule_delayed_work(&steam->mode_switch, 45 * HZ / 100);
}

if (!steam->gamepad_mode)
return;

lpad_touched = b10 & BIT(3);
rpad_touched = b10 & BIT(4);

Expand Down

0 comments on commit cd438e5

Please sign in to comment.