it only support one controller but thats already good

This commit is contained in:
club 2025-07-01 22:18:52 -04:00 committed by TristanBrault
parent c20554c4f6
commit 6ca7501e8b
14 changed files with 147 additions and 70 deletions

36
app.go
View File

@ -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
}
}
}

View File

@ -1 +1 @@
0439ac4c00128949ad585ea9e2f68e8e
0d0407303e68412fce4ec212f727db35

View File

@ -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);

View 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>

View File

@ -0,0 +1,7 @@
export interface ControllerState {
joystick: {
x: number,
y: number,
},
buttons: number
}

View File

@ -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")

View File

@ -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');
}

View File

@ -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);
}
}

View File

@ -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));
});

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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 (0255)
y := buf[1] // Vertical axis (0255)
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 (0255)
y := buf[1] // Vertical axis (0255)
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)
}
}
}

View File

@ -17,9 +17,10 @@ func main() {
// Create application with options
err := wails.Run(&options.App{
Title: "conjure-os",
Width: 1024,
Height: 768,
Title: "conjure-os",
Width: 1024,
Height: 768,
Fullscreen: true,
AssetServer: &assetserver.Options{
Assets: assets,
},