Skip to content

Add customisable shortcut keybinds #1705

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 10 additions & 66 deletions src/Backends/SDLBackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ namespace gamescope

virtual glm::uvec2 CursorSurfaceSize( glm::uvec2 uvecSize ) const override;

virtual void ToggleFullscreen() override;

////////////////////////
// INestedHints Compat
///////////////////////
Expand Down Expand Up @@ -511,6 +513,12 @@ namespace gamescope
return uvecSize;
}

void CSDLBackend::ToggleFullscreen()
{
g_bFullscreen = !g_bFullscreen;
SDL_SetWindowFullscreen( m_Connector.GetSDLWindow(), g_bFullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0 );
}

///////////////////
// INestedHints
///////////////////
Expand Down Expand Up @@ -724,78 +732,14 @@ namespace gamescope
break;

case SDL_KEYDOWN:
{
// If this keydown event is super + one of the shortcut keys, consume the keydown event, since the corresponding keyup
// event will be consumed by the next case statement when the user releases the key
if ( event.key.keysym.mod & KMOD_LGUI )
{
uint32_t key = SDLScancodeToLinuxKey( event.key.keysym.scancode );
const uint32_t shortcutKeys[] = {KEY_F, KEY_N, KEY_B, KEY_U, KEY_Y, KEY_I, KEY_O, KEY_S, KEY_G};
const bool isShortcutKey = std::find(std::begin(shortcutKeys), std::end(shortcutKeys), key) != std::end(shortcutKeys);
if ( isShortcutKey )
{
break;
}
}
}
[[fallthrough]];
case SDL_KEYUP:
{
uint32_t key = SDLScancodeToLinuxKey( event.key.keysym.scancode );

if ( event.type == SDL_KEYUP && ( event.key.keysym.mod & KMOD_LGUI ) )
{
bool handled = true;
switch ( key )
{
case KEY_F:
g_bFullscreen = !g_bFullscreen;
SDL_SetWindowFullscreen( m_Connector.GetSDLWindow(), g_bFullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0 );
break;
case KEY_N:
g_wantedUpscaleFilter = GamescopeUpscaleFilter::PIXEL;
break;
case KEY_B:
g_wantedUpscaleFilter = GamescopeUpscaleFilter::LINEAR;
break;
case KEY_U:
g_wantedUpscaleFilter = (g_wantedUpscaleFilter == GamescopeUpscaleFilter::FSR) ?
GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::FSR;
break;
case KEY_Y:
g_wantedUpscaleFilter = (g_wantedUpscaleFilter == GamescopeUpscaleFilter::NIS) ?
GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::NIS;
break;
case KEY_I:
g_upscaleFilterSharpness = std::min(20, g_upscaleFilterSharpness + 1);
break;
case KEY_O:
g_upscaleFilterSharpness = std::max(0, g_upscaleFilterSharpness - 1);
break;
case KEY_S:
gamescope::CScreenshotManager::Get().TakeScreenshot( true );
break;
case KEY_G:
g_bGrabbed = !g_bGrabbed;
SDL_SetWindowKeyboardGrab( m_Connector.GetSDLWindow(), g_bGrabbed ? SDL_TRUE : SDL_FALSE );

SDL_Event event;
event.type = GetUserEventIndex( GAMESCOPE_SDL_EVENT_TITLE );
SDL_PushEvent( &event );
break;
default:
handled = false;
}
if ( handled )
{
break;
}
}

// On Wayland, clients handle key repetition
if ( event.key.repeat )
break;

const uint32_t key = SDLScancodeToLinuxKey( event.key.keysym.scancode );

wlserver_lock();
wlserver_key( key, event.type == SDL_KEYDOWN, fake_timestamp );
wlserver_unlock();
Expand Down
89 changes: 6 additions & 83 deletions src/Backends/WaylandBackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,7 @@ namespace gamescope

virtual bool UsesVirtualConnectors() override;
virtual std::shared_ptr<IBackendConnector> CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override;
virtual void ToggleFullscreen() override;
protected:
virtual void OnBackendBlobDestroyed( BackendBlob *pBlob ) override;

Expand Down Expand Up @@ -2208,6 +2209,11 @@ namespace gamescope
return pConnector;
}

void CWaylandBackend::ToggleFullscreen()
{
static_cast< CWaylandConnector * >( GetCurrentConnector() )->SetFullscreen( !g_bFullscreen );
}

///////////////////
// INestedHints
///////////////////
Expand Down Expand Up @@ -2744,89 +2750,6 @@ namespace gamescope

void CWaylandInputThread::HandleKey( uint32_t uKey, bool bPressed )
{
if ( m_uKeyModifiers & m_uModMask[ GAMESCOPE_WAYLAND_MOD_META ] )
{
switch ( uKey )
{
case KEY_F:
{
if ( !bPressed )
{
static_cast< CWaylandConnector * >( m_pBackend->GetCurrentConnector() )->SetFullscreen( !g_bFullscreen );
}
return;
}

case KEY_N:
{
if ( !bPressed )
{
g_wantedUpscaleFilter = GamescopeUpscaleFilter::PIXEL;
}
return;
}

case KEY_B:
{
if ( !bPressed )
{
g_wantedUpscaleFilter = GamescopeUpscaleFilter::LINEAR;
}
return;
}

case KEY_U:
{
if ( !bPressed )
{
g_wantedUpscaleFilter = ( g_wantedUpscaleFilter == GamescopeUpscaleFilter::FSR ) ?
GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::FSR;
}
return;
}

case KEY_Y:
{
if ( !bPressed )
{
g_wantedUpscaleFilter = ( g_wantedUpscaleFilter == GamescopeUpscaleFilter::NIS ) ?
GamescopeUpscaleFilter::LINEAR : GamescopeUpscaleFilter::NIS;
}
return;
}

case KEY_I:
{
if ( !bPressed )
{
g_upscaleFilterSharpness = std::min( 20, g_upscaleFilterSharpness + 1 );
}
return;
}

case KEY_O:
{
if ( !bPressed )
{
g_upscaleFilterSharpness = std::max( 0, g_upscaleFilterSharpness - 1 );
}
return;
}

case KEY_S:
{
if ( !bPressed )
{
gamescope::CScreenshotManager::Get().TakeScreenshot( true );
}
return;
}

default:
break;
}
}

wlserver_lock();
wlserver_key( uKey, bPressed, ++m_uFakeTimestamp );
wlserver_unlock();
Expand Down
4 changes: 4 additions & 0 deletions src/backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,8 @@ namespace gamescope

virtual void NotifyPhysicalInput( InputType eInputType ) = 0;

virtual void ToggleFullscreen() = 0;

static IBackend *Get();
template <typename T>
static bool Set();
Expand Down Expand Up @@ -381,6 +383,8 @@ namespace gamescope
virtual std::shared_ptr<IBackendConnector> CreateVirtualConnector( uint64_t ulVirtualConnectorKey ) override;

virtual void NotifyPhysicalInput( InputType eInputType ) override {}

virtual void ToggleFullscreen() override {};
};

// This is a blob of data that may be associated with
Expand Down
136 changes: 136 additions & 0 deletions src/hotkey.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#include <linux/input-event-codes.h>

#include <span>

#include "hotkey.h"
#include "convar.h"
#include "steamcompmgr_shared.hpp"
#include "main.hpp"
#include "wlserver.hpp"

// TODO: Consolidate
#define WAYLAND_NULL() []<typename... Args> ( void *pData, Args... args ) { }
#define WAYLAND_USERDATA_TO_THIS(type, name) []<typename... Args> ( void *pData, Args... args ) { type *pThing = (type *)pData; pThing->name( std::forward<Args>(args)... ); }

namespace gamescope
{
HotkeyHandler g_hotkeyHandler = {};

class CHotkeyBinding
{
public:
bool Init( gamescope_action_binding_manager *pManager, std::span<uint32_t> pKeySyms, std::function<void(void)> callback, const char *description )
{
Shutdown();

m_pBinding = gamescope_action_binding_manager_create_action_binding( pManager );
if ( !m_pBinding )
return false;

m_pCallback = callback;

wl_array array;
wl_array_init(&array);
for ( uint32_t uKeySym : pKeySyms )
{
uint32_t *pKeySymPtr = (uint32_t *)wl_array_add(&array, sizeof(uint32_t) );
*pKeySymPtr = uKeySym;
}

gamescope_action_binding_add_listener( m_pBinding, &s_BindingListener, (void *)this );
gamescope_action_binding_add_keyboard_trigger( m_pBinding, &array );
gamescope_action_binding_set_description( m_pBinding, description );
gamescope_action_binding_arm( m_pBinding, 0 );

return true;
}

void Shutdown()
{
if ( m_pBinding )
{
gamescope_action_binding_destroy( m_pBinding );
m_pBinding = nullptr;
}

m_pCallback = nullptr;
}

void Wayland_Triggered( gamescope_action_binding *pBinding, uint32_t uSequence, uint32_t uTriggerFlags, uint32_t uTimeLo, uint32_t uTimeHi )
{
// FIXME: segfault
if ( m_pCallback ) m_pCallback();
}

private:
gamescope_action_binding *m_pBinding = nullptr;
std::function<void(void)> m_pCallback = nullptr;

static const gamescope_action_binding_listener s_BindingListener;
};

const gamescope_action_binding_listener CHotkeyBinding::s_BindingListener =
{
.triggered = WAYLAND_USERDATA_TO_THIS( CHotkeyBinding, Wayland_Triggered ),
};

bool HotkeyHandler::Init()
{
const char *pDisplayName = getenv( "GAMESCOPE_WAYLAND_DISPLAY" );
if ( !pDisplayName || !*pDisplayName )
pDisplayName = "gamescope-0";

if ( !( m_pDisplay = wl_display_connect( pDisplayName ) ) )
{
fprintf( stderr, "Failed to open GAMESCOPE_WAYLAND_DISPLAY.\n" );
return false;
}

wl_registry *pRegistry;
if ( !( pRegistry = wl_display_get_registry( m_pDisplay ) ) )
{
fprintf( stderr, "Failed to get wl_registry.\n" );
return false;
}

wl_registry_add_listener( pRegistry, &s_RegistryListener, (void *)this );
wl_display_roundtrip( m_pDisplay );
wl_display_roundtrip( m_pDisplay );

if ( !m_pActionBindingManager )
{
fprintf( stderr, "Failed to get Gamescope binding manager\n" );
wl_registry_destroy( pRegistry );
return false;
}

wl_registry_destroy( pRegistry );

return true;
}

bool HotkeyHandler::Bind( std::vector<uint32_t> pKeySyms, std::function<void(void)> callback, const char *description )
{
CHotkeyBinding binding;
auto success = binding.Init( m_pActionBindingManager, pKeySyms, callback, description );
if ( success )
{
wl_display_flush( m_pDisplay );
}
return success;
}

void HotkeyHandler::Wayland_Registry_Global( wl_registry *pRegistry, uint32_t uName, const char *pInterface, uint32_t uVersion )
{
if ( !strcmp( pInterface, gamescope_action_binding_manager_interface.name ) )
{
m_pActionBindingManager = (decltype(m_pActionBindingManager)) wl_registry_bind( pRegistry, uName, &gamescope_action_binding_manager_interface, uVersion );
}
}

const wl_registry_listener HotkeyHandler::s_RegistryListener =
{
.global = WAYLAND_USERDATA_TO_THIS( HotkeyHandler, Wayland_Registry_Global ),
.global_remove = WAYLAND_NULL(),
};
}
31 changes: 31 additions & 0 deletions src/hotkey.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include <cstdint>
#include <functional>

#include <gamescope-action-binding-client-protocol.h>

namespace gamescope
{
class CHotkeyBinding;

class HotkeyHandler
{
public:
HotkeyHandler() {}
~HotkeyHandler() {}

bool Init();
bool Bind( std::vector<uint32_t> pKeySyms, std::function<void(void)> callback, const char *description );
void Dispatch() { wl_display_dispatch(m_pDisplay); }

private:
wl_display *m_pDisplay;
gamescope_action_binding_manager *m_pActionBindingManager = nullptr;

void Wayland_Registry_Global( wl_registry *pRegistry, uint32_t uName, const char *pInterface, uint32_t uVersion );
static const wl_registry_listener s_RegistryListener;
};

extern HotkeyHandler g_hotkeyHandler;
}
Loading