it only support one controller but thats already good

This commit is contained in:
club 2025-07-01 22:18:52 -04:00
parent 2763543104
commit 4a88270535
14 changed files with 147 additions and 70 deletions

36
app.go
View File

@ -6,12 +6,19 @@ import (
"conjure-os/lib/provider" "conjure-os/lib/provider"
"context" "context"
"fmt" "fmt"
"github.com/wailsapp/wails/v2/pkg/runtime"
"os" "os"
"os/exec" "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 // App struct
type App struct { type App struct {
@ -31,8 +38,27 @@ func (a *App) startup(ctx context.Context) {
provider.Update() provider.Update()
} }
func (a *App) onControllerChange(data any) { func (a *App) onControllerChange(state inputs.ControllerState) {
runtime.EventsEmit(a.ctx, "controller_change", data) 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) { func (a *App) StartGame(id string) {
@ -40,6 +66,7 @@ func (a *App) StartGame(id string) {
if game.Id == id { if game.Id == id {
gamePath := provider.ExtractGame(game) gamePath := provider.ExtractGame(game)
cmd := exec.Command(gamePath) cmd := exec.Command(gamePath)
gameIsOpen = true
// Optional: attach current terminal's std streams // Optional: attach current terminal's std streams
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
@ -60,6 +87,7 @@ func (a *App) StartGame(id string) {
} else { } else {
fmt.Println("Process exited successfully.") 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" /> <OptionsModal v-if="optionsOpen" @close="optionsOpen = false" />
<QrModal v-if="qrLink" :link="qrLink" @close="qrLink = ''" /> <QrModal v-if="qrLink" :link="qrLink" @close="qrLink = ''" />
<LoadingModal v-if="gameIsStarting" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import Sidebar from './components/Sidebar.vue'; import Sidebar from './components/Sidebar.vue';
import GamePreview from './components/GamePreview.vue'; import GamePreview from './components/GamePreview.vue';
import GameCarousel from './components/GameCarousel.vue'; import GameCarousel from './components/GameCarousel.vue';
import OptionsModal from './components/OptionsModal.vue'; import OptionsModal from './components/OptionsModal.vue';
import QrModal from './components/QrModal.vue'; import QrModal from './components/QrModal.vue';
import LoadingModal from './components/LoadingModal.vue';
import { useKeyboardNavigation } from './utils/use-keyboard-navigation'; import { useKeyboardNavigation } from './utils/use-keyboard-navigation';
import { fetchGames } from './services/game-service'; import { fetchGames } from './services/game-service';
import { useAppStore } from "./stores/app-store"; import { useAppStore } from "./stores/app-store";
@ -44,7 +46,7 @@ import { storeToRefs } from "pinia";
import { KeyboardManager } from "./utils/keyboard-manager"; import { KeyboardManager } from "./utils/keyboard-manager";
const store = useAppStore(); 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); 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', transitionDirection: 'down' as 'up' | 'down',
selectedGame: null as Game | null, selectedGame: null as Game | null,
selectedGameIndex: 0, selectedGameIndex: 0,
qrLink: '' as string qrLink: '' as string,
gameIsStarting: false as boolean
}), }),
getters: { getters: {
filteredGames(state): Game[] { filteredGames(state): Game[] {
@ -50,9 +51,11 @@ export const useAppStore = defineStore('app', {
showQr(link: string) { showQr(link: string) {
this.qrLink = link; this.qrLink = link;
}, },
startSelectedGame() { async startSelectedGame() {
if (this.selectedGame) { if (this.selectedGame) {
StartGame(this.selectedGame.Id).then() this.gameIsStarting = true;
await StartGame(this.selectedGame.Id);
this.gameIsStarting = false;
} }
else { else {
console.log("No game selected") console.log("No game selected")

View File

@ -1,3 +1,4 @@
import { ControllerState } from "../../models/controller-state";
import { useAppStore } from "../../stores/app-store"; import { useAppStore } from "../../stores/app-store";
export abstract class KeyContext { 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 { protected onKeyRight(): void {
console.log('onKeyRight'); console.log('onKeyRight');
} }

View File

@ -1,3 +1,4 @@
import { ControllerState } from "../models/controller-state";
import { CarouselKeyContext } from "./key-contexts/carousel-key-context"; import { CarouselKeyContext } from "./key-contexts/carousel-key-context";
import { KeyContext } from "./key-contexts/key-context"; import { KeyContext } from "./key-contexts/key-context";
import { SidebarKeyContext } from "./key-contexts/sidebar-key-context"; import { SidebarKeyContext } from "./key-contexts/sidebar-key-context";
@ -14,4 +15,8 @@ export class KeyboardManager {
static handle(event: KeyboardEvent) { static handle(event: KeyboardEvent) {
this.current?.handleKey(event); 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 { export function useKeyboardNavigation(): void {
onMounted(() => { onMounted(() => {
EventsOn("controller_change", (data) => console.log(data)); EventsOn("controller_change", KeyboardManager.handleState.bind(KeyboardManager));
window.addEventListener('keydown', KeyboardManager.handle.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) // [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
// Sets the width and height of the window. // 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) // [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
// Gets the width and height of the window. // Gets the width and height of the window.

2
go.mod
View File

@ -6,7 +6,7 @@ toolchain go1.23.5
require ( require (
github.com/karalabe/hid v1.0.0 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 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/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 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= 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.10.1 h1:QWHvWMXII2nI/nXz77gpPG8P3ehl6zKe+u4su5BWIns=
github.com/wailsapp/wails/v2 v2.9.2/go.mod h1:uehvlCwJSFcBq7rMCGfk4rxca67QQGsbg5Nm4m9UnBs= 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 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= 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= 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 Device *hid.Device
} }
func (c *Controller) ReadState() error { func (c *Controller) ReadState(buf []byte) (*ControllerState, error) {
buf := make([]byte, 64)
_, err := c.Device.Read(buf) _, err := c.Device.Read(buf)
if err != nil { if err != nil {
return err return nil, err
} }
fmt.Printf("Joystick: X=%d Y=%d Buttons=%08b\n", buf[0], buf[1], buf[6]) x := buf[0] // Horizontal axis (0255)
return nil 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 { type Vec2B struct {
X, Y byte X byte `json:"x"`
Y byte `json:"y"`
} }
type ControllerState struct { type ControllerState struct {
joystick Vec2B Joystick Vec2B `json:"joystick"`
buttons byte Buttons byte `json:"buttons"`
} }
type ConjureControllerButton int type ConjureControllerButton int
const ( const (
ButtonA ConjureControllerButton = iota //0 ButtonPower ConjureControllerButton = iota //0
ButtonB ButtonStart
ButtonC
Button1 Button1
Button2 Button2
Button3 Button3
ButtonStart ButtonA
ButtonPower // 7 ButtonB
ButtonC // 7
) )
var ConjureControllerButtons = []ConjureControllerButton{ var ConjureControllerButtons = []ConjureControllerButton{
ButtonA, ButtonStart,
ButtonB, ButtonPower,
ButtonC,
Button1, Button1,
Button2, Button2,
Button3, Button3,
ButtonStart, ButtonA,
ButtonPower, ButtonB,
ButtonC,
} }
func (s ConjureControllerButton) String() string { 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") fmt.Println("Opening devices")
const vendorID = 0x0079 const vendorID = 0x0079
const productID = 0x0006 const productID = 0x0006
@ -111,50 +119,34 @@ func Start(onStateChange func(any)) {
} }
// Open the first matching device // 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 fmt.Printf("device %d detected\n", i)
for { device, err := deviceDetected.Open()
_, err := device.Read(buf)
if err != nil { if err != nil {
fmt.Printf("Read error: %v", err) fmt.Printf("Failed to open device: %v", err)
return return
} }
defer device.Close()
// For debugging: print raw data controller := Controller{Device: device}
// fmt.Printf("Raw: % X\n", buf[:n])
// Example: decode joystick + button state fmt.Println("Reading data... Press Ctrl+C to exit")
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.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 { if err != nil {
for _, button := range ConjureControllerButtons { fmt.Printf("Read error: %v", err)
if buttons&(1<<button) != 0 { return
fmt.Printf("Button %s pressed\n", (button + 1).String())
}
} }
onStateChange(state)
fmt.Printf("Button was pressed! %d\n", buttons)
}
if x != 127 || y != 127 { if state.Buttons != 0 || state.Joystick.X != 127 || state.Joystick.Y != 127 {
onStateChange(state) fmt.Printf("State changed on device %d\n", i)
fmt.Printf("Joystick moved! %d - %d\n", x, y) onStateChange(*state)
}
} }
} }

View File

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