diff --git a/.gitignore b/.gitignore index 90d605a..f723de2 100755 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ wayland/*.helper.go __debug_bin* term.everything term.everything❗mmulet.com-dont_forget_to_chmod_+x_this_file +result diff --git a/Makefile b/Makefile index daba74b..98ec8f0 100644 --- a/Makefile +++ b/Makefile @@ -8,9 +8,9 @@ protocols_files := $(shell find ./wayland/generate) xml_protocols := $(shell find ./wayland/generate -name "*.xml") -generated_protocols := $(patsubst ./wayland/generate/resources/%,./wayland/protocols/%.go,$(xml_protocols)) +generated_protocols := $(patsubst ./wayland/generate/resources/%, ./wayland/protocols/%.go, $(xml_protocols)) -generated_helpers := $(patsubst ./wayland/generate/resources/%,./wayland/%.helper.go,$(xml_protocols)) +generated_helpers := $(patsubst ./wayland/generate/resources/%, ./wayland/%.helper.go, $(xml_protocols)) build: $(generated_protocols) $(generated_helpers) $(bin_name) @@ -29,4 +29,4 @@ clean: rm __debug_bin* 2>/dev/null || true if [ -z "$$MULTI_PLATFORM" ]; then rm -rf ./dist 2>/dev/null || true; fi rm ./wayland/protocols/*.xml.go 2>/dev/null || true - rm ./wayland/*.helper.go 2>/dev/null || true \ No newline at end of file + rm ./wayland/*.helper.go 2>/dev/null || true diff --git a/distribute.sh b/distribute.sh index 30f84a1..5ab6756 100755 --- a/distribute.sh +++ b/distribute.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env sh # This script builds a distributable AppImage # of the term.everything application using Podman. @@ -15,7 +15,7 @@ get_distro() { else DISTRO="unknown" fi - + case $DISTRO in ubuntu|debian) echo "sudo apt update && sudo apt install -y " @@ -51,7 +51,7 @@ if ! command -v podman >/dev/null 2>&1; then echo "Please install podman to proceed, it's literally all you need. Don't even need attention. Just podman. Just get podman. What are you waiting for? Stop reading this and install podman." exit 1 fi - + fi if [ -z "${PLATFORM+x}" ]; then @@ -66,4 +66,3 @@ $PODMAN run \ --volume .:/home/mount \ --rm alpine:latest /bin/sh /home/mount/resources/alpineCompile.sh && \ echo "Output is ./dist/$PLATFORM/static/$APP_NAME" - diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..677fdac --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1767379071, + "narHash": "sha256-EgE0pxsrW9jp9YFMkHL9JMXxcqi/OoumPJYwf+Okucw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "fb7944c166a3b630f177938e478f0378e64ce108", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..3fd4e88 --- /dev/null +++ b/flake.nix @@ -0,0 +1,55 @@ +{ + description = "Run any GUI app in the terminal"; + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + outputs = + { + self, + nixpkgs, + ... + }: + let + allSystems = [ + "x86_64-linux" + "aarch64-linux" + # "x86_64-darwin" + # "aarch64-darwin" + ]; + forAllSystems = + f: + nixpkgs.lib.genAttrs allSystems ( + system: + f { + pkgs = import nixpkgs { inherit system; }; + } + ); + in + { + packages = forAllSystems ( + { pkgs }: + { + default = pkgs.buildGoModule rec { + pname = "term-everything"; + name = pname; + version = "0.7.8"; + subPackages = [ "." ]; + src = ./.; + vendorHash = null; + nativeBuildInputs = with pkgs; [ + pkg-config + ]; + buildInputs = with pkgs; [ + glib + chafa + ]; + preBuild = '' + go generate ./wayland + ''; + postInstall = '' + # rm $out/bin/generate + mv $out/bin/term.everything $out/bin/${name} + ''; + }; + } + ); + }; +} diff --git a/termeverything/MainLoop.go b/termeverything/MainLoop.go index 6c1e6b2..9036ffc 100644 --- a/termeverything/MainLoop.go +++ b/termeverything/MainLoop.go @@ -11,11 +11,38 @@ import ( func MainLoop() { args := ParseArgs() - SetVirtualMonitorSize(args.VirtualMonitorSize) + var logger = newLogger(args.DebugLog, nil, args.Verbose) + logger.logVerbose(` + arguments: + WaylandDisplayNameArg=%v + SupportOldApps=%v + Xwayland=%v + XwaylandWM=%v + Shell=%v + HideStatusBar=%v + VirtualMonitorSize=%v + DebugLog=%v + ReverseScroll=%v + MaxFrameRate=%v + Positionals=%v + Verbose=%v`, + args.WaylandDisplayNameArg, + args.SupportOldApps, + args.Xwayland, + args.XwaylandWM, + args.Shell, + args.HideStatusBar, + args.VirtualMonitorSize, + args.DebugLog, + args.ReverseScroll, + args.MaxFrameRate, + args.Positionals, + args.Verbose) + logger.checkFatalErr(SetVirtualMonitorSize(args.VirtualMonitorSize)) + listener, err := wayland.MakeSocketListener(&args) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to create socket listener: %v\n", err) - os.Exit(1) + logger.logFatal("Failed to create socket listener: %v", err) } displaySize := wayland.Size{ @@ -56,7 +83,7 @@ func MainLoop() { cmdStr := strings.Join(args.Positionals, " ") shell := args.Shell cmd := exec.Command(shell, "-c", cmdStr) - + logger.logVerbose("command: %v", cmd) baseEnv := os.Environ() filtered := make([]string, 0, len(baseEnv)) for _, e := range baseEnv { @@ -79,13 +106,15 @@ func MainLoop() { // cmd.Stderr = os.Stderr // cmd.Stdin = os.Stdin - if err := cmd.Start(); err != nil { - fmt.Fprintf(os.Stderr, "Failed to start command: %v\n", err) - } else { - go func() { - _ = cmd.Wait() - }() - } + result := make(chan error) + go func(ret chan error) { + output, err := cmd.CombinedOutput() + if err != nil { + ret <- fmt.Errorf("command failed to run %v; returncode: %v\nstdout/err: %v", cmd, err, string(output)) + } + }(result) + err := <-result + logger.checkFatalErr(err) } <-done diff --git a/termeverything/ParseArgs.go b/termeverything/ParseArgs.go index 808f79f..062119a 100644 --- a/termeverything/ParseArgs.go +++ b/termeverything/ParseArgs.go @@ -28,6 +28,7 @@ type CommandLineArgs struct { ReverseScroll bool MaxFrameRate string Positionals []string + Verbose bool } func (args *CommandLineArgs) WaylandDisplayName() string { @@ -41,7 +42,7 @@ func ParseArgs() CommandLineArgs { flag.BoolVar(&args.SupportOldApps, "support-old-apps", false, "") flag.StringVar(&args.Xwayland, "xwayland", "", "") flag.StringVar(&args.XwaylandWM, "xwayland-wm", "", "") - flag.StringVar(&args.Shell, "shell", "/bin/bash", "") + flag.StringVar(&args.Shell, "shell", "/bin/sh", "") flag.BoolVar(&args.HideStatusBar, "hide-status-bar", false, "") flag.StringVar(&args.VirtualMonitorSize, "virtual-monitor-size", "", "") versionFlag := flag.Bool("version", false, "") @@ -51,7 +52,7 @@ func ParseArgs() CommandLineArgs { licensesFlag := flag.Bool("licenses", false, "") flag.BoolVar(&args.ReverseScroll, "reverse-scroll", false, "") flag.StringVar(&args.MaxFrameRate, "max-frame-rate", "", "") - + flag.BoolVar(&args.Verbose, "verbose", false, "") flag.Parse() if *versionFlag { diff --git a/termeverything/SetVirtualMonitorSize.go b/termeverything/SetVirtualMonitorSize.go index 8f54764..87ee204 100644 --- a/termeverything/SetVirtualMonitorSize.go +++ b/termeverything/SetVirtualMonitorSize.go @@ -2,32 +2,42 @@ package termeverything import ( "fmt" - "os" "strconv" "strings" "github.com/mmulet/term.everything/wayland" ) -func SetVirtualMonitorSize(newVirtualMonitorSize string) { +func SetVirtualMonitorSize(newVirtualMonitorSize string) error { + mkError := func(msg string, a ...any) error { + errorMessage := fmt.Sprintf("Invalid virtual monitor size %s, expected x", newVirtualMonitorSize) + return fmt.Errorf("%v; %v", errorMessage, fmt.Sprintf(msg, a...)) + } + if newVirtualMonitorSize == "" { - return + return nil } parts := strings.Split(newVirtualMonitorSize, "x") if len(parts) != 2 { - fmt.Fprintf(os.Stderr, "Invalid virtual monitor size %s, expected x\n", newVirtualMonitorSize) - os.Exit(1) + return mkError("found less than 2 dimensions %v", parts) + } + rawWidth, rawHeight := parts[0], parts[1] + width, widthErr := strconv.Atoi(rawWidth) + height, heightErr := strconv.Atoi(rawHeight) + // using the actual error strings are too verbose so just printing the raw value + if widthErr != nil { + return mkError("invalid width %v; must be an integer", rawWidth) + } + if heightErr != nil { + return mkError("invalid height %v; must be an integer", rawHeight) } - width, err1 := strconv.Atoi(parts[0]) - height, err2 := strconv.Atoi(parts[1]) - if err1 != nil || err2 != nil { - fmt.Fprintf(os.Stderr, "Invalid virtual monitor size %s, expected x\n", newVirtualMonitorSize) - os.Exit(1) + if width <= 0 { + return mkError("invalid width %v; must be greater than zero", width) } - if width <= 0 || height <= 0 { - fmt.Fprintf(os.Stderr, "Invalid virtual monitor size %s, expected x\n", newVirtualMonitorSize) - os.Exit(1) + if height <= 0 { + return mkError("invalid height %v; must be greater than zero", height) } wayland.VirtualMonitorSize.Width = wayland.Pixels(width) wayland.VirtualMonitorSize.Height = wayland.Pixels(height) + return nil } diff --git a/termeverything/error.go b/termeverything/error.go new file mode 100644 index 0000000..76291c8 --- /dev/null +++ b/termeverything/error.go @@ -0,0 +1,96 @@ +// // error logging +package termeverything + +import ( + "fmt" + "os" +) + +const DEFAULT_DEBUG_FILE string = "debug.log" + +type Logger struct { + useDebugFile bool + debugFile *os.File + verbose bool +} + +func newLogger(useDebugFile bool, debugFile *string, verbose bool) Logger { + if useDebugFile { + if debugFile == nil || *debugFile == "" { + var t = DEFAULT_DEBUG_FILE // disgusting + debugFile = &t + } + var file, err = os.Create(*debugFile) + if err != nil { + fmt.Print(formatError("failed to open debug file: %v", err)) + os.Exit(1) + } + return Logger{useDebugFile, file, verbose} + } else { + return Logger{useDebugFile, nil, verbose} + } +} + +// check used for functions that return complete errors +func (l Logger) checkErr(err error) { + if err != nil { + l.log("%v", err.Error()) + } +} + +func (l Logger) checkFatalErr(err error) { + if err != nil { + l.logFatal("%v", err.Error()) + } +} + +// log used for errors which need to be formatted +func (l Logger) logFatal(msg string, a ...any) { + l.log(msg, a...) + l.close() + os.Exit(1) +} +func (l Logger) logVerbose(msg string, a ...any) { + if l.verbose { + l._log(fmt.Sprintf("Verbose: %v\n", fmt.Sprintf(msg, a...))) + } +} + +// log error on stderr or DEBUG_FILE if useDebugFile automatically prepends Error: and appends \n +// unsure if to hard crash on error logging failures, or if stderr is even accessible in term.everything? +func (l Logger) log(msg string, a ...any) { + l._log(formatError(msg, a...)) +} + +func (l Logger) close() { + if l.debugFile != nil { + l.debugFile.Close() + } +} + +func (l Logger) _log(s string) { + if l.useDebugFile { + if l.debugFile == nil { + // fmt.Println(formatError("")) + printFormatError("debug file is \"nil\", this should be impossible") // should be unreachable but who knows! + os.Exit(1) + } else { + var _, err = l.debugFile.WriteString(s) + if err != nil { + printFormatError("failed to write to debug file %v", err) + // os.Exit(1) + } + } + } else { + printStderr("%v", s) + } +} +func formatError(msg string, a ...any) string { + return "Error: " + fmt.Sprintf(msg, a...) + "\n" +} +func printStderr(msg string, a ...any) { + fmt.Fprintf(os.Stderr, msg, a...) +} +func printFormatError(msg string, a ...any) { + printStderr("%v", formatError(msg, a...)) +} diff --git a/termeverything/resources/help.md b/termeverything/resources/help.md index 7b1d47c..fd9365d 100755 --- a/termeverything/resources/help.md +++ b/termeverything/resources/help.md @@ -77,7 +77,7 @@ WAYLAND_DISPLAY= DISPLAY= `--shell ` -The shell used to launch the app. Default is `/bin/bash`. +The shell used to launch the app. Default is `/bin/sh`. `--hide-status-bar` Hides the status bar at the top of the terminal. Default is false. @@ -99,6 +99,9 @@ Limit drawing to the terminal to $N frames per second. Accepts float. `--debug-log` Log most debug statements to debug.log instead of printing to console +`--verbose` +Enable verbose logging + # Environment Variables `TERM_EVERYTHING_PIXEL_MODE` Values: