diff --git a/cmd/completion.go b/cmd/completion.go new file mode 100644 index 0000000..c00aa22 --- /dev/null +++ b/cmd/completion.go @@ -0,0 +1,47 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var completionCmd = &cobra.Command{ + Use: "completion [bash|zsh|fish]", + Short: "Get completions for your favorite shell", + RunE: GetCompletion, + ValidArgs: []string{"bash", "zsh", "fish"}, + DisableFlagsInUseLine: true, +} + +func init() { + rootCmd.AddCommand(completionCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // completionCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // completionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +func GetCompletion(cmd *cobra.Command, args []string) error { + if len(args) <= 0 { + return cmd.Help() + } + + switch args[0] { + case "bash": + return rootCmd.GenBashCompletionV2(os.Stdout, true) + case "zsh": + return rootCmd.GenZshCompletion(os.Stdout) + case "fish": + return rootCmd.GenFishCompletion(os.Stdout, true) + default: + return fmt.Errorf("unsupported shell: %s", args[0]) + } +} diff --git a/cmd/hexecute/main.go b/cmd/hexecute/main.go index 2e2f13c..b95b287 100644 --- a/cmd/hexecute/main.go +++ b/cmd/hexecute/main.go @@ -1,214 +1,7 @@ package main -import ( - "encoding/json" - "flag" - "log" - "os" - "runtime" - "time" - - "github.com/ThatOtherAndrew/Hexecute/internal/config" - "github.com/ThatOtherAndrew/Hexecute/internal/draw" - "github.com/ThatOtherAndrew/Hexecute/internal/execute" - gestures "github.com/ThatOtherAndrew/Hexecute/internal/gesture" - "github.com/ThatOtherAndrew/Hexecute/internal/models" - "github.com/ThatOtherAndrew/Hexecute/internal/opengl" - "github.com/ThatOtherAndrew/Hexecute/internal/spawn" - "github.com/ThatOtherAndrew/Hexecute/internal/stroke" - "github.com/ThatOtherAndrew/Hexecute/internal/update" - "github.com/ThatOtherAndrew/Hexecute/pkg/wayland" - "github.com/go-gl/gl/v4.1-core/gl" -) - -func init() { - runtime.LockOSThread() -} - -type App struct { - *models.App -} +import "github.com/ThatOtherAndrew/Hexecute/cmd" func main() { - learnCommand := flag.String("learn", "", "Learn a new gesture for the specified command") - listGestures := flag.Bool("list", false, "List all registered gestures") - removeGesture := flag.String("remove", "", "Remove a gesture by command name") - flag.Parse() - - if flag.NArg() > 0 { - log.Fatalf("Unknown arguments: %v", flag.Args()) - } - - if *listGestures { - gestures, err := gestures.LoadGestures() - if err != nil { - log.Fatal("Failed to load gestures:", err) - } - if len(gestures) == 0 { - println("No gestures registered") - } else { - println("Registered gestures:") - for _, g := range gestures { - println(" ", g.Command) - } - } - return - } - - if *removeGesture != "" { - gestures, err := gestures.LoadGestures() - if err != nil { - log.Fatal("Failed to load gestures:", err) - } - - found := false - for i, g := range gestures { - if g.Command == *removeGesture { - gestures = append(gestures[:i], gestures[i+1:]...) - found = true - break - } - } - - if !found { - log.Fatalf("Gesture not found: %s", *removeGesture) - } - - configFile, err := config.GetPath() - if err != nil { - log.Fatal("Failed to get config path:", err) - } - - data, err := json.Marshal(gestures) - if err != nil { - log.Fatal("Failed to marshal gestures:", err) - } - - if err := os.WriteFile(configFile, data, 0644); err != nil { - log.Fatal("Failed to save gestures:", err) - } - - println("Removed gesture:", *removeGesture) - return - } - - window, err := wayland.NewWaylandWindow() - if err != nil { - log.Fatal("Failed to create Wayland window:", err) - } - defer window.Destroy() - - app := &models.App{StartTime: time.Now()} - - if *learnCommand != "" { - app.LearnMode = true - app.LearnCommand = *learnCommand - log.Printf("Learn mode: Draw the gesture 3 times for command '%s'", *learnCommand) - } else { - gestures, err := gestures.LoadGestures() - if err != nil { - log.Fatal("Failed to load gestures:", err) - } - app.SavedGestures = gestures - log.Printf("Loaded %d gesture(s)", len(gestures)) - } - - opengl := opengl.New(app) - if err := opengl.InitGL(); err != nil { - log.Fatal("Failed to initialize OpenGL:", err) - } - - gl.ClearColor(0, 0, 0, 0) - - for range 5 { - window.PollEvents() - gl.Clear(gl.COLOR_BUFFER_BIT) - window.SwapBuffers() - } - - x, y := window.GetCursorPos() - app.LastCursorX = float32(x) - app.LastCursorY = float32(y) - - lastTime := time.Now() - var wasPressed bool - - for !window.ShouldClose() { - now := time.Now() - dt := float32(now.Sub(lastTime).Seconds()) - lastTime = now - - window.PollEvents() - update := update.New(app) - update.UpdateCursor(window) - - if key, state, hasKey := window.GetLastKey(); hasKey { - if state == 1 && key == 0xff1b { - if !app.IsExiting { - app.IsExiting = true - app.ExitStartTime = time.Now() - window.DisableInput() - x, y := window.GetCursorPos() - spawn := spawn.New(app) - spawn.SpawnExitWisps(float32(x), float32(y)) - } - } - window.ClearLastKey() - } - - if app.IsExiting { - if time.Since(app.ExitStartTime).Seconds() > 0.8 { - break - } - } - isPressed := window.GetMouseButton() - if isPressed && !wasPressed { - app.IsDrawing = true - } else if !isPressed && wasPressed { - app.IsDrawing = false - - if app.LearnMode && len(app.Points) > 0 { - processed := stroke.ProcessStroke(app.Points) - app.LearnGestures = append(app.LearnGestures, processed) - app.LearnCount++ - log.Printf("Captured gesture %d/3", app.LearnCount) - - app.Points = nil - - if app.LearnCount >= 3 { - if err := gestures.SaveGesture(app.LearnCommand, app.LearnGestures); err != nil { - log.Fatal("Failed to save gesture:", err) - } - log.Printf("Gesture saved for command: %s", app.LearnCommand) - - app.IsExiting = true - app.ExitStartTime = time.Now() - window.DisableInput() - x, y := window.GetCursorPos() - spawn := spawn.New(app) - spawn.SpawnExitWisps(float32(x), float32(y)) - } - } else if !app.LearnMode && len(app.Points) > 0 { - x, y := window.GetCursorPos() - exec := execute.New(app) - exec.RecognizeAndExecute(window, float32(x), float32(y)) - app.Points = nil - } - } - wasPressed = isPressed - - if app.IsDrawing { - x, y := window.GetCursorPos() - gesture := gestures.New(app) - gesture.AddPoint(float32(x), float32(y)) - - spawn := spawn.New(app) - spawn.SpawnCursorSparkles(float32(x), float32(y)) - } - - update.UpdateParticles(dt) - drawer := draw.New(app) - drawer.Draw(window) - window.SwapBuffers() - } + cmd.Execute() } diff --git a/cmd/learn.go b/cmd/learn.go new file mode 100644 index 0000000..a443605 --- /dev/null +++ b/cmd/learn.go @@ -0,0 +1,154 @@ +package cmd + +import ( + "log" + "runtime" + "strings" + "time" + + "github.com/ThatOtherAndrew/Hexecute/internal/draw" + gestures "github.com/ThatOtherAndrew/Hexecute/internal/gesture" + "github.com/ThatOtherAndrew/Hexecute/internal/models" + "github.com/ThatOtherAndrew/Hexecute/internal/opengl" + "github.com/ThatOtherAndrew/Hexecute/internal/spawn" + "github.com/ThatOtherAndrew/Hexecute/internal/stroke" + "github.com/ThatOtherAndrew/Hexecute/internal/update" + "github.com/ThatOtherAndrew/Hexecute/pkg/wayland" + "github.com/go-gl/gl/v4.1-core/gl" + "github.com/spf13/cobra" +) + +// learnCmd represents the learn command +var learnCmd = &cobra.Command{ + Use: "learn", + Short: "Learn a new gesture for the specified command", + Run: learnGesture, +} + +func init() { + rootCmd.AddCommand(learnCmd) + runtime.LockOSThread() + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // learnCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // learnCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +func learnGesture(cmd *cobra.Command, args []string) { + window, err := wayland.NewWaylandWindow() + if err != nil { + log.Fatal("Failed to create Wayland window:", err) + } + + defer window.Destroy() + app := &models.App{StartTime: time.Now()} + + if len(args) <= 0 { + log.Fatalf("Please specify a command") + } + + command := strings.Join(args, " ") + + app.LearnCommand = command + log.Printf("Learn mode: Draw the gesture 3 times for command '%s'", command) + + opengl := opengl.New(app) + if err := opengl.InitGL(); err != nil { + log.Fatal("Failed to initialize OpenGL:", err) + } + + gl.ClearColor(0, 0, 0, 0) + + for range 5 { + window.PollEvents() + gl.Clear(gl.COLOR_BUFFER_BIT) + window.SwapBuffers() + } + + x, y := window.GetCursorPos() + app.LastCursorX = float32(x) + app.LastCursorY = float32(y) + + lastTime := time.Now() + var wasPressed bool + + for !window.ShouldClose() { + now := time.Now() + dt := float32(now.Sub(lastTime).Seconds()) + lastTime = now + + window.PollEvents() + update := update.New(app) + update.UpdateCursor(window) + + if key, state, hasKey := window.GetLastKey(); hasKey { + if state == 1 && key == 0xff1b { + if !app.IsExiting { + app.IsExiting = true + app.ExitStartTime = time.Now() + window.DisableInput() + x, y := window.GetCursorPos() + spawn := spawn.New(app) + spawn.SpawnExitWisps(float32(x), float32(y)) + } + } + window.ClearLastKey() + } + + if app.IsExiting { + if time.Since(app.ExitStartTime).Seconds() > 0.8 { + break + } + } + isPressed := window.GetMouseButton() + if isPressed && !wasPressed { + app.IsDrawing = true + } else if !isPressed && wasPressed { + app.IsDrawing = false + + if len(app.Points) > 0 { + processed := stroke.ProcessStroke(app.Points) + app.LearnGestures = append(app.LearnGestures, processed) + app.LearnCount++ + log.Printf("Captured gesture %d/3", app.LearnCount) + + app.Points = nil + + if app.LearnCount >= 3 { + if err := gestures.SaveGesture(app.LearnCommand, app.LearnGestures); err != nil { + log.Fatal("Failed to save gesture:", err) + } + log.Printf("Gesture saved for command: %s", app.LearnCommand) + + app.IsExiting = true + app.ExitStartTime = time.Now() + window.DisableInput() + x, y := window.GetCursorPos() + spawn := spawn.New(app) + spawn.SpawnExitWisps(float32(x), float32(y)) + } + } + } + wasPressed = isPressed + + if app.IsDrawing { + x, y := window.GetCursorPos() + gesture := gestures.New(app) + gesture.AddPoint(float32(x), float32(y)) + + spawn := spawn.New(app) + spawn.SpawnCursorSparkles(float32(x), float32(y)) + } + + update.UpdateParticles(dt) + drawer := draw.New(app) + drawer.Draw(window) + window.SwapBuffers() + } +} diff --git a/cmd/list.go b/cmd/list.go new file mode 100644 index 0000000..61a6e3c --- /dev/null +++ b/cmd/list.go @@ -0,0 +1,45 @@ +package cmd + +import ( + "fmt" + "log" + + gestures "github.com/ThatOtherAndrew/Hexecute/internal/gesture" + "github.com/spf13/cobra" +) + +var listCmd = &cobra.Command{ + Use: "list", + Short: "List all registered gestures", + Run: listGestures, +} + +func init() { + rootCmd.AddCommand(listCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // listCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // listCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +func listGestures(cmd *cobra.Command, args []string) { + + gestures, err := gestures.LoadGestures() + if err != nil { + log.Fatal("Failed to load gestures:", err) + } + if len(gestures) == 0 { + fmt.Println("No gestures registered") + } else { + fmt.Println("Registered gestures:") + for _, g := range gestures { + fmt.Println(" ", g.Command) + } + } +} diff --git a/cmd/remove.go b/cmd/remove.go new file mode 100644 index 0000000..515a794 --- /dev/null +++ b/cmd/remove.go @@ -0,0 +1,73 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "log" + "os" + + "github.com/ThatOtherAndrew/Hexecute/internal/config" + gestures "github.com/ThatOtherAndrew/Hexecute/internal/gesture" + "github.com/spf13/cobra" +) + +var removeCmd = &cobra.Command{ + Use: "remove [gesture]", + Short: "Remove a gesture by command name", + Run: removeGesture, +} + +func init() { + rootCmd.AddCommand(removeCmd) + log.SetFlags(0) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // removeCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // removeCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +func removeGesture(cmd *cobra.Command, args []string) { + + if len(args) <= 0 { + log.Fatalf("Please specify a gesture") + } + gestures, err := gestures.LoadGestures() + if err != nil { + log.Fatal("Failed to load gestures:", err) + } + + found := false + for i, g := range gestures { + if g.Command == args[0] { + gestures = append(gestures[:i], gestures[i+1:]...) + found = true + break + } + } + + if !found { + log.Fatalf("Gesture not found: %s", args[0]) + } + + configFile, err := config.GetPath() + if err != nil { + log.Fatal("Failed to get config path:", err) + } + + data, err := json.Marshal(gestures) + if err != nil { + log.Fatal("Failed to marshal gestures:", err) + } + + if err := os.WriteFile(configFile, data, 0644); err != nil { + log.Fatal("Failed to save gestures:", err) + } + + fmt.Println("Removed gesture:", args[0]) +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..945d89f --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,33 @@ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "hexecute", + Short: "Launch apps by casting spells!", +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.Hexecute.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/run.go b/cmd/run.go new file mode 100644 index 0000000..14d0504 --- /dev/null +++ b/cmd/run.go @@ -0,0 +1,133 @@ +package cmd + +import ( + "log" + "runtime" + "time" + + "github.com/ThatOtherAndrew/Hexecute/internal/draw" + "github.com/ThatOtherAndrew/Hexecute/internal/execute" + gestures "github.com/ThatOtherAndrew/Hexecute/internal/gesture" + "github.com/ThatOtherAndrew/Hexecute/internal/models" + "github.com/ThatOtherAndrew/Hexecute/internal/opengl" + "github.com/ThatOtherAndrew/Hexecute/internal/spawn" + "github.com/ThatOtherAndrew/Hexecute/internal/update" + "github.com/ThatOtherAndrew/Hexecute/pkg/wayland" + "github.com/go-gl/gl/v4.1-core/gl" + "github.com/spf13/cobra" +) + +var runCmd = &cobra.Command{ + Use: "run", + Short: "run the program", + Run: Run, +} + +func init() { + rootCmd.AddCommand(runCmd) + runtime.LockOSThread() + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // runCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // runCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +func Run(cmd *cobra.Command, args []string) { + window, err := wayland.NewWaylandWindow() + if err != nil { + log.Fatal("Failed to create Wayland window:", err) + } + defer window.Destroy() + + app := &models.App{StartTime: time.Now()} + + gesture, err := gestures.LoadGestures() + if err != nil { + log.Fatal("Failed to load gestures:", err) + } + app.SavedGestures = gesture + log.Printf("Loaded %d gesture(s)", len(gesture)) + + opengl := opengl.New(app) + if err := opengl.InitGL(); err != nil { + log.Fatal("Failed to initialize OpenGL:", err) + } + + gl.ClearColor(0, 0, 0, 0) + + for range 5 { + window.PollEvents() + gl.Clear(gl.COLOR_BUFFER_BIT) + window.SwapBuffers() + } + + x, y := window.GetCursorPos() + app.LastCursorX = float32(x) + app.LastCursorY = float32(y) + + lastTime := time.Now() + var wasPressed bool + + for !window.ShouldClose() { + now := time.Now() + dt := float32(now.Sub(lastTime).Seconds()) + lastTime = now + + window.PollEvents() + update := update.New(app) + update.UpdateCursor(window) + + if key, state, hasKey := window.GetLastKey(); hasKey { + if state == 1 && key == 0xff1b { + if !app.IsExiting { + app.IsExiting = true + app.ExitStartTime = time.Now() + window.DisableInput() + x, y := window.GetCursorPos() + spawn := spawn.New(app) + spawn.SpawnExitWisps(float32(x), float32(y)) + } + } + window.ClearLastKey() + } + + if app.IsExiting { + if time.Since(app.ExitStartTime).Seconds() > 0.8 { + break + } + } + isPressed := window.GetMouseButton() + if isPressed && !wasPressed { + app.IsDrawing = true + } else if !isPressed && wasPressed { + app.IsDrawing = false + + x, y := window.GetCursorPos() + exec := execute.New(app) + exec.RecognizeAndExecute(window, float32(x), float32(y)) + app.Points = nil + } + wasPressed = isPressed + + if app.IsDrawing { + x, y := window.GetCursorPos() + gesture := gestures.New(app) + gesture.AddPoint(float32(x), float32(y)) + + spawn := spawn.New(app) + spawn.SpawnCursorSparkles(float32(x), float32(y)) + } + + update.UpdateParticles(dt) + drawer := draw.New(app) + drawer.Draw(window) + window.SwapBuffers() + } + +} diff --git a/go.mod b/go.mod index 8772d7e..57a605b 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,12 @@ module github.com/ThatOtherAndrew/Hexecute go 1.25.1 -require github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 +require ( + github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 + github.com/spf13/cobra v1.10.1 +) + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect +) diff --git a/go.sum b/go.sum index e1d623b..9b8d571 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,13 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA= github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=