it only support one controller but thats already good
This commit is contained in:
parent
2763543104
commit
4a88270535
36
app.go
36
app.go
@ -6,12 +6,19 @@ import (
|
||||
"conjure-os/lib/provider"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
var games []models.Game
|
||||
var (
|
||||
games []models.Game
|
||||
lastEmitTimestamp = time.Now().Add(-10 * time.Second)
|
||||
emitInterval = 300 * time.Millisecond
|
||||
gameIsOpen = false
|
||||
)
|
||||
|
||||
// App struct
|
||||
type App struct {
|
||||
@ -31,8 +38,27 @@ func (a *App) startup(ctx context.Context) {
|
||||
provider.Update()
|
||||
}
|
||||
|
||||
func (a *App) onControllerChange(data any) {
|
||||
runtime.EventsEmit(a.ctx, "controller_change", data)
|
||||
func (a *App) onControllerChange(state inputs.ControllerState) {
|
||||
now := time.Now()
|
||||
|
||||
if now.Sub(lastEmitTimestamp) >= emitInterval && !gameIsOpen {
|
||||
if state.Buttons != 0 {
|
||||
for _, button := range inputs.ConjureControllerButtons {
|
||||
if state.Buttons&(1<<button) != 0 {
|
||||
fmt.Printf("Button %s pressed\n", (button).String())
|
||||
}
|
||||
}
|
||||
fmt.Printf("Button was pressed! %d\n", state.Buttons)
|
||||
}
|
||||
|
||||
if state.Joystick.X != 127 || state.Joystick.Y != 127 {
|
||||
fmt.Printf("Joystick moved! %d - %d\n", state.Joystick.X, state.Joystick.Y)
|
||||
}
|
||||
|
||||
fmt.Printf("Joystick: X=%d Y=%d Buttons=%08b\n", state.Joystick.X, state.Joystick.Y, state.Buttons)
|
||||
runtime.EventsEmit(a.ctx, "controller_change", state)
|
||||
lastEmitTimestamp = now
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) StartGame(id string) {
|
||||
@ -40,6 +66,7 @@ func (a *App) StartGame(id string) {
|
||||
if game.Id == id {
|
||||
gamePath := provider.ExtractGame(game)
|
||||
cmd := exec.Command(gamePath)
|
||||
gameIsOpen = true
|
||||
|
||||
// Optional: attach current terminal's std streams
|
||||
cmd.Stdout = os.Stdout
|
||||
@ -60,6 +87,7 @@ func (a *App) StartGame(id string) {
|
||||
} else {
|
||||
fmt.Println("Process exited successfully.")
|
||||
}
|
||||
gameIsOpen = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
0439ac4c00128949ad585ea9e2f68e8e
|
||||
0d0407303e68412fce4ec212f727db35
|
||||
@ -27,16 +27,18 @@
|
||||
|
||||
<OptionsModal v-if="optionsOpen" @close="optionsOpen = false" />
|
||||
<QrModal v-if="qrLink" :link="qrLink" @close="qrLink = ''" />
|
||||
<LoadingModal v-if="gameIsStarting" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
import Sidebar from './components/Sidebar.vue';
|
||||
import GamePreview from './components/GamePreview.vue';
|
||||
import GameCarousel from './components/GameCarousel.vue';
|
||||
import OptionsModal from './components/OptionsModal.vue';
|
||||
import QrModal from './components/QrModal.vue';
|
||||
import LoadingModal from './components/LoadingModal.vue';
|
||||
import { useKeyboardNavigation } from './utils/use-keyboard-navigation';
|
||||
import { fetchGames } from './services/game-service';
|
||||
import { useAppStore } from "./stores/app-store";
|
||||
@ -44,7 +46,7 @@ import { storeToRefs } from "pinia";
|
||||
import { KeyboardManager } from "./utils/keyboard-manager";
|
||||
|
||||
const store = useAppStore();
|
||||
const { selectedTag, selectedGame, tags, games, transitionDirection, qrLink } = storeToRefs(store);
|
||||
const { selectedTag, selectedGame, tags, games, transitionDirection, qrLink, gameIsStarting } = storeToRefs(store);
|
||||
|
||||
const optionsOpen = ref(false);
|
||||
|
||||
|
||||
13
frontend/src/components/LoadingModal.vue
Normal file
13
frontend/src/components/LoadingModal.vue
Normal file
@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div class="fixed inset-0 bg-black bg-opacity-80 flex items-center justify-center z-50">
|
||||
<div class="bg-white text-black p-6 rounded-lg text-center max-w-sm w-full">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
7
frontend/src/models/controller-state.ts
Normal file
7
frontend/src/models/controller-state.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface ControllerState {
|
||||
joystick: {
|
||||
x: number,
|
||||
y: number,
|
||||
},
|
||||
buttons: number
|
||||
}
|
||||
@ -11,7 +11,8 @@ export const useAppStore = defineStore('app', {
|
||||
transitionDirection: 'down' as 'up' | 'down',
|
||||
selectedGame: null as Game | null,
|
||||
selectedGameIndex: 0,
|
||||
qrLink: '' as string
|
||||
qrLink: '' as string,
|
||||
gameIsStarting: false as boolean
|
||||
}),
|
||||
getters: {
|
||||
filteredGames(state): Game[] {
|
||||
@ -50,9 +51,11 @@ export const useAppStore = defineStore('app', {
|
||||
showQr(link: string) {
|
||||
this.qrLink = link;
|
||||
},
|
||||
startSelectedGame() {
|
||||
async startSelectedGame() {
|
||||
if (this.selectedGame) {
|
||||
StartGame(this.selectedGame.Id).then()
|
||||
this.gameIsStarting = true;
|
||||
await StartGame(this.selectedGame.Id);
|
||||
this.gameIsStarting = false;
|
||||
}
|
||||
else {
|
||||
console.log("No game selected")
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { ControllerState } from "../../models/controller-state";
|
||||
import { useAppStore } from "../../stores/app-store";
|
||||
|
||||
export abstract class KeyContext {
|
||||
@ -30,6 +31,31 @@ export abstract class KeyContext {
|
||||
}
|
||||
}
|
||||
|
||||
public handleState(state: ControllerState) {
|
||||
if (state.joystick.x === 0) {
|
||||
this.onKeyLeft()
|
||||
}
|
||||
else if (state.joystick.x === 255) {
|
||||
this.onKeyRight()
|
||||
}
|
||||
|
||||
if (state.joystick.y === 0) {
|
||||
this.onKeyUp()
|
||||
}
|
||||
else if (state.joystick.y === 255) {
|
||||
this.onKeyDown()
|
||||
}
|
||||
|
||||
if ((state.buttons & 0x02) !== 0) {
|
||||
this.onEnter()
|
||||
}
|
||||
|
||||
// TODO should be 0x01 when the power button will work
|
||||
if ((state.buttons & 0x04) !== 0) {
|
||||
this.onEscape()
|
||||
}
|
||||
}
|
||||
|
||||
protected onKeyRight(): void {
|
||||
console.log('onKeyRight');
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { ControllerState } from "../models/controller-state";
|
||||
import { CarouselKeyContext } from "./key-contexts/carousel-key-context";
|
||||
import { KeyContext } from "./key-contexts/key-context";
|
||||
import { SidebarKeyContext } from "./key-contexts/sidebar-key-context";
|
||||
@ -14,4 +15,8 @@ export class KeyboardManager {
|
||||
static handle(event: KeyboardEvent) {
|
||||
this.current?.handleKey(event);
|
||||
}
|
||||
|
||||
static handleState(controllerState: ControllerState) {
|
||||
this.current?.handleState(controllerState);
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@ import { EventsOn } from "../../wailsjs/runtime";
|
||||
|
||||
export function useKeyboardNavigation(): void {
|
||||
onMounted(() => {
|
||||
EventsOn("controller_change", (data) => console.log(data));
|
||||
EventsOn("controller_change", KeyboardManager.handleState.bind(KeyboardManager));
|
||||
window.addEventListener('keydown', KeyboardManager.handle.bind(KeyboardManager));
|
||||
});
|
||||
|
||||
|
||||
2
frontend/wailsjs/runtime/runtime.d.ts
vendored
2
frontend/wailsjs/runtime/runtime.d.ts
vendored
@ -134,7 +134,7 @@ export function WindowIsFullscreen(): Promise<boolean>;
|
||||
|
||||
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
|
||||
// Sets the width and height of the window.
|
||||
export function WindowSetSize(width: number, height: number): Promise<Size>;
|
||||
export function WindowSetSize(width: number, height: number): void;
|
||||
|
||||
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
|
||||
// Gets the width and height of the window.
|
||||
|
||||
2
go.mod
2
go.mod
@ -6,7 +6,7 @@ toolchain go1.23.5
|
||||
|
||||
require (
|
||||
github.com/karalabe/hid v1.0.0
|
||||
github.com/wailsapp/wails/v2 v2.9.2
|
||||
github.com/wailsapp/wails/v2 v2.10.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
|
||||
4
go.sum
4
go.sum
@ -69,8 +69,8 @@ github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyT
|
||||
github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||
github.com/wailsapp/wails/v2 v2.9.2 h1:Xb5YRTos1w5N7DTMyYegWaGukCP2fIaX9WF21kPPF2k=
|
||||
github.com/wailsapp/wails/v2 v2.9.2/go.mod h1:uehvlCwJSFcBq7rMCGfk4rxca67QQGsbg5Nm4m9UnBs=
|
||||
github.com/wailsapp/wails/v2 v2.10.1 h1:QWHvWMXII2nI/nXz77gpPG8P3ehl6zKe+u4su5BWIns=
|
||||
github.com/wailsapp/wails/v2 v2.10.1/go.mod h1:zrebnFV6MQf9kx8HI4iAv63vsR5v67oS7GTEZ7Pz1TY=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
|
||||
@ -23,52 +23,60 @@ type Controller struct {
|
||||
Device *hid.Device
|
||||
}
|
||||
|
||||
func (c *Controller) ReadState() error {
|
||||
buf := make([]byte, 64)
|
||||
func (c *Controller) ReadState(buf []byte) (*ControllerState, error) {
|
||||
_, err := c.Device.Read(buf)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Printf("Joystick: X=%d Y=%d Buttons=%08b\n", buf[0], buf[1], buf[6])
|
||||
return nil
|
||||
x := buf[0] // Horizontal axis (0–255)
|
||||
y := buf[1] // Vertical axis (0–255)
|
||||
buttons := buf[6] // Buttons as bitfield
|
||||
state := ControllerState{
|
||||
Joystick: Vec2B{x, y},
|
||||
Buttons: buttons,
|
||||
}
|
||||
|
||||
return &state, nil
|
||||
}
|
||||
|
||||
type Vec2B struct {
|
||||
X, Y byte
|
||||
X byte `json:"x"`
|
||||
Y byte `json:"y"`
|
||||
}
|
||||
|
||||
type ControllerState struct {
|
||||
joystick Vec2B
|
||||
buttons byte
|
||||
Joystick Vec2B `json:"joystick"`
|
||||
Buttons byte `json:"buttons"`
|
||||
}
|
||||
|
||||
type ConjureControllerButton int
|
||||
|
||||
const (
|
||||
ButtonA ConjureControllerButton = iota //0
|
||||
ButtonB
|
||||
ButtonC
|
||||
ButtonPower ConjureControllerButton = iota //0
|
||||
ButtonStart
|
||||
|
||||
Button1
|
||||
Button2
|
||||
Button3
|
||||
|
||||
ButtonStart
|
||||
ButtonPower // 7
|
||||
ButtonA
|
||||
ButtonB
|
||||
ButtonC // 7
|
||||
)
|
||||
|
||||
var ConjureControllerButtons = []ConjureControllerButton{
|
||||
ButtonA,
|
||||
ButtonB,
|
||||
ButtonC,
|
||||
ButtonStart,
|
||||
ButtonPower,
|
||||
|
||||
Button1,
|
||||
Button2,
|
||||
Button3,
|
||||
|
||||
ButtonStart,
|
||||
ButtonPower,
|
||||
ButtonA,
|
||||
ButtonB,
|
||||
ButtonC,
|
||||
}
|
||||
|
||||
func (s ConjureControllerButton) String() string {
|
||||
@ -94,7 +102,7 @@ func (s ConjureControllerButton) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
func Start(onStateChange func(any)) {
|
||||
func Start(onStateChange func(ControllerState)) {
|
||||
fmt.Println("Opening devices")
|
||||
const vendorID = 0x0079
|
||||
const productID = 0x0006
|
||||
@ -111,50 +119,34 @@ func Start(onStateChange func(any)) {
|
||||
}
|
||||
|
||||
// Open the first matching device
|
||||
device, err := devices[0].Open()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to open device: %v", err)
|
||||
return
|
||||
}
|
||||
defer device.Close()
|
||||
|
||||
fmt.Println("Reading data... Press Ctrl+C to exit")
|
||||
for i, deviceDetected := range devices {
|
||||
|
||||
buf := make([]byte, 32) // Adjust size if needed
|
||||
for {
|
||||
_, err := device.Read(buf)
|
||||
fmt.Printf("device %d detected\n", i)
|
||||
device, err := deviceDetected.Open()
|
||||
if err != nil {
|
||||
fmt.Printf("Read error: %v", err)
|
||||
fmt.Printf("Failed to open device: %v", err)
|
||||
return
|
||||
}
|
||||
defer device.Close()
|
||||
|
||||
// For debugging: print raw data
|
||||
// fmt.Printf("Raw: % X\n", buf[:n])
|
||||
controller := Controller{Device: device}
|
||||
|
||||
// Example: decode joystick + button state
|
||||
x := buf[0] // Horizontal axis (0–255)
|
||||
y := buf[1] // Vertical axis (0–255)
|
||||
buttons := buf[6] // Buttons as bitfield
|
||||
state := ControllerState{
|
||||
joystick: Vec2B{x, y},
|
||||
buttons: buttons,
|
||||
}
|
||||
fmt.Println("Reading data... Press Ctrl+C to exit")
|
||||
|
||||
// fmt.Printf("Joystick X: %d, Y: %d, Buttons: %08b\n", x, y, buttons)
|
||||
buf := make([]byte, 32) // Adjust size if needed
|
||||
for {
|
||||
state, err := controller.ReadState(buf)
|
||||
|
||||
if buttons != 0 {
|
||||
for _, button := range ConjureControllerButtons {
|
||||
if buttons&(1<<button) != 0 {
|
||||
fmt.Printf("Button %s pressed\n", (button + 1).String())
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Printf("Read error: %v", err)
|
||||
return
|
||||
}
|
||||
onStateChange(state)
|
||||
fmt.Printf("Button was pressed! %d\n", buttons)
|
||||
}
|
||||
|
||||
if x != 127 || y != 127 {
|
||||
onStateChange(state)
|
||||
fmt.Printf("Joystick moved! %d - %d\n", x, y)
|
||||
if state.Buttons != 0 || state.Joystick.X != 127 || state.Joystick.Y != 127 {
|
||||
fmt.Printf("State changed on device %d\n", i)
|
||||
onStateChange(*state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user