Skip to content

Commit

Permalink
Per game controller maps (#8)
Browse files Browse the repository at this point in the history
* create + fix up controllers slice

* add backend methods for controller settings

* remap action dropdown successfully saving to redux

* bootstrap current game profile values to store

* persist controller mappings to backend

* start setting up controller remap sync

* enable updating hardware button remappings

* add per-game controller map toggle

* immediately save per-game controller maps to backend + update debounce time

* update labels on components
  • Loading branch information
aarron-lee authored Nov 28, 2023
1 parent 48cf2cd commit 9503ae4
Show file tree
Hide file tree
Showing 10 changed files with 355 additions and 45 deletions.
10 changes: 10 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ async def get_settings(self):
async def save_rgb_per_game_profiles_enabled(self, enabled: bool):
return settings.set_setting('rgbPerGameProfilesEnabled', enabled)

async def save_controller_settings(self, controller, currentGameId):
controllerProfiles = controller.get('controllerProfiles')
controllerPerGameProfilesEnabled = controller.get('perGameProfilesEnabled') or False

settings.set_setting('controllerPerGameProfilesEnabled', controllerPerGameProfilesEnabled)
result = settings.set_all_controller_profiles(controllerProfiles)
if currentGameId:
settings.sync_controller_profile_settings(currentGameId)
return result

async def save_rgb_settings(self, rgbProfiles, currentGameId):
result = settings.set_all_rgb_profiles(rgbProfiles)
if currentGameId:
Expand Down
47 changes: 35 additions & 12 deletions py_modules/controller_settings.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import os
import decky_plugin
import legion_configurator
from settings import SettingsManager
from controller_enums import Controller, RemappableButtons, RemapActions

settings_directory = os.environ["DECKY_PLUGIN_SETTINGS_DIR"]
settings_path = os.path.join(settings_directory, 'settings.json')
Expand Down Expand Up @@ -78,15 +81,35 @@ def set_all_rgb_profiles(rgb_profiles):
)


def set_game_profile_setting(profileName: str, key: str, value):
setting_file.read()
if not setting_file.settings.get('gameProfiles'):
setting_file.settings['gameProfiles'] = {}
game_profiles = setting_file.settings['gameProfiles']
if not game_profiles.get(profileName):
game_profiles[profileName] = {}

setting_file.settings['gameProfiles'][profileName][key] = value

# save to settings file
setting_file.commit()
def set_all_controller_profiles(controller_profiles):
settings = get_settings()

if not settings.get('controller'):
settings['controller'] = {}
profiles = settings['controller']
deep_merge(controller_profiles, profiles)

setting_file.settings['controller'] = profiles
setting_file.commit()

def sync_controller_profile_settings(current_game_id):
settings = get_settings()
controller_profile = settings.get('controller').get(current_game_id, {})

try:
for remappable_button_name, remap_action in controller_profile.items():
controller_code = None
if remappable_button_name in ['Y3', 'M2', 'M3']:
controller_code = Controller['RIGHT'].value
elif remappable_button_name in ['Y1', 'Y2']:
controller_code = Controller['LEFT'].value
if not controller_code:
return

btn_code = RemappableButtons[remappable_button_name].value
action_code = RemapActions[remap_action].value
remap_command = legion_configurator.create_button_remap_command(controller_code, btn_code, action_code)

legion_configurator.send_command(remap_command)
except Exception as e:
decky_plugin.logger.error(f"sync_controller_profile_settings: {e}")
22 changes: 11 additions & 11 deletions src/components/RemapActionDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { useState, FC } from 'react';
import { FC } from 'react';
import { DropdownItem } from 'decky-frontend-lib';
import { RemapActions, RemappableButtons } from '../backend/constants';
import { useRemapAction } from '../hooks/controller';

type PropType = {
label: string;
btn: RemappableButtons;
description?: string;
onChange?: any;
logInfo?: any;
};

const RemapActionDropdown: FC<PropType> = ({ label, btn, onChange }) => {
const [selected, setSelected] = useState<RemapActions>(RemapActions.DISABLED);
const RemapActionDropdown: FC<PropType> = ({ label, btn }) => {
const { remapAction, setRemapAction } = useRemapAction(btn);
const dropdownOptions = Object.values(RemapActions).map((action) => {
return {
data: action,
label: `${action}`,
label: `${action.split('_').join(' ')}`,
value: action
};
});
Expand All @@ -32,12 +31,13 @@ const RemapActionDropdown: FC<PropType> = ({ label, btn, onChange }) => {
value: o.value
};
})}
selectedOption={dropdownOptions.find((o) => {
return o.data === selected;
})}
selectedOption={
dropdownOptions.find((o) => {
return o.data === remapAction;
})?.data || RemapActions.DISABLED
}
onChange={(value: any) => {
onChange && onChange(btn, value.data);
setSelected(value.data);
setRemapAction(value.data);
}}
/>
</>
Expand Down
44 changes: 27 additions & 17 deletions src/components/RemapButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
import { ServerAPI } from 'decky-frontend-lib';
import { FC } from 'react';
import { RemappableButtons } from '../backend/constants';
import RemapActionDropdown from './RemapActionDropdown';
import { createServerApiHelpers } from '../backend/utils';

const RemapButtons: FC<{ serverAPI: ServerAPI }> = ({ serverAPI }) => {
const { remapButton } = createServerApiHelpers(serverAPI);
import { PanelSection, PanelSectionRow, ToggleField } from 'decky-frontend-lib';
import {
useControllerPerGameEnabled,
useControllerProfileDisplayName
} from '../hooks/controller';

const RemapButtons: FC = () => {
const btns = Object.values(RemappableButtons);
const displayName = useControllerProfileDisplayName();
const { controllerPerGameEnabled, setControllerPerGameEnabled } =
useControllerPerGameEnabled();

const title = controllerPerGameEnabled
? `Remap Buttons -\n${displayName.substring(0, 20)}${
displayName.length > 20 ? '...' : ''
}`
: 'Remap Buttons';

return (
<>
{btns.map((btn, idx) => {
return (
<RemapActionDropdown
label={`${btn}`}
btn={btn}
key={idx}
onChange={remapButton}
/>
);
})}
</>
<PanelSection title={title}>
<PanelSectionRow>
<ToggleField
label={'Enable Per Game Remaps'}
checked={controllerPerGameEnabled}
onChange={setControllerPerGameEnabled}
/>
{btns.map((btn, idx) => {
return <RemapActionDropdown label={`${btn}`} btn={btn} key={idx} />;
})}
</PanelSectionRow>
</PanelSection>
);
};

Expand Down
38 changes: 38 additions & 0 deletions src/hooks/controller.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useDispatch, useSelector } from 'react-redux';
import {
controllerSlice,
selectButtonRemapAction,
selectControllerPerGameProfilesEnabled,
selectControllerProfileDisplayName
} from '../redux-modules/controllerSlice';
import { RemapActions, RemappableButtons } from '../backend/constants';

export const useControllerPerGameEnabled = () => {
const controllerPerGameEnabled = useSelector(
selectControllerPerGameProfilesEnabled
);
const dispatch = useDispatch();

const setControllerPerGameEnabled = (enabled: boolean) => {
return dispatch(controllerSlice.actions.setPerGameProfilesEnabled(enabled));
};

return { controllerPerGameEnabled, setControllerPerGameEnabled };
};

export const useRemapAction = (btn: RemappableButtons) => {
const remapAction = useSelector(selectButtonRemapAction(btn));
const dispatch = useDispatch();

const setRemapAction = (remapAction: RemapActions) => {
return dispatch(
controllerSlice.actions.remapButton({ button: btn, remapAction })
);
};

return { remapAction, setRemapAction };
};

export const useControllerProfileDisplayName = () => {
return useSelector(selectControllerProfileDisplayName);
};
4 changes: 2 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { memo, VFC } from 'react';
import { FaShip } from 'react-icons/fa';

// import { createServerApiHelpers } from './backend/utils';
// import RemapButtons from './components/RemapButtons';
import RemapButtons from './components/RemapButtons';
import ControllerLightingPanel from './components/ControllerLightingPanel';
import { createServerApiHelpers, saveServerApi } from './backend/utils';
import { store } from './redux-modules/store';
Expand All @@ -20,7 +20,7 @@ const Content: VFC<{ serverAPI: ServerAPI }> = memo(({ serverAPI }) => {
return (
<>
<ControllerLightingPanel serverAPI={serverAPI} />
{/* <RemapButtons serverAPI={serverAPI} /> */}
<RemapButtons />
</>
);
});
Expand Down
Loading

0 comments on commit 9503ae4

Please sign in to comment.