├── README.md ├── LICENSE └── appwrapper.go /README.md: -------------------------------------------------------------------------------- 1 | # appwrapper 2 | 3 | Execute a binary on Linux and show a desktop notification in case of errors 4 | 5 | ![](https://user-images.githubusercontent.com/2480569/68097212-9985f080-fead-11e9-9c9c-534b5de06aa7.png) 6 | 7 | ## Usage 8 | 9 | Just prepend `appwrapper` when invoking an application: 10 | 11 | ``` 12 | appwrapper ./Atom-1.5.3-x86_64.AppImage 13 | ``` 14 | 15 | This is mainly inteded for wrapping applications that are launched by graphical user interfaces. 16 | 17 | ## Credits 18 | 19 | Inspired by Haiku OS 20 | 21 | ![](https://user-images.githubusercontent.com/2480569/61177311-aa8ab900-a5c1-11e9-8130-804d01ec37a2.png) 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 probonopd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /appwrapper.go: -------------------------------------------------------------------------------- 1 | // appwrapper executes applications and presents errors to the GUI as notifications 2 | package main 3 | 4 | import ( 5 | "bytes" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "strings" 11 | "syscall" 12 | 13 | "github.com/godbus/dbus" 14 | ) 15 | 16 | func main() { 17 | 18 | if len(os.Args) < 2 { 19 | log.Println("Argument missing") 20 | os.Exit(1) 21 | } 22 | 23 | cmd := exec.Command(os.Args[1], os.Args[2:]...) 24 | 25 | var out bytes.Buffer 26 | cmd.Stderr = &out 27 | 28 | if err := cmd.Start(); err != nil { 29 | log.Fatalf("cmd.Start: %v", err) 30 | } 31 | 32 | if err := cmd.Wait(); err != nil { 33 | if exiterr, ok := err.(*exec.ExitError); ok { 34 | // The program has exited with an exit code != 0 35 | if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { 36 | log.Printf("Exit Status: %d", status.ExitStatus()) 37 | log.Println(out.String()) 38 | 39 | summary := "Error" 40 | body := strings.TrimSpace(out.String()) 41 | 42 | if strings.Contains(out.String(), "cannot open shared object file: No such file or directory") == true { 43 | parts := strings.Split(out.String(), ":") 44 | summary = "Error: Missing library " + strings.TrimSpace(parts[2]) 45 | body = filepath.Base(os.Args[1]) + " could not be started because " + strings.TrimSpace(parts[2]) + " is missing" 46 | } 47 | sendDesktopNotification(summary, body) 48 | 49 | } 50 | } else { 51 | log.Fatalf("cmd.Wait: %v", err) 52 | } 53 | } 54 | } 55 | 56 | // Send desktop notification. See 57 | // https://developer.gnome.org/notification-spec/ 58 | func sendDesktopNotification(title string, body string) { 59 | conn, err := dbus.SessionBus() 60 | defer conn.Close() 61 | if err != nil { 62 | log.Println(os.Stderr, "Failed to connect to session bus:", err) 63 | return 64 | } 65 | obj := conn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications") 66 | call := obj.Call("org.freedesktop.Notifications.Notify", 0, "", uint32(0), 67 | "", title, body, []string{}, 68 | map[string]dbus.Variant{}, 69 | int32(0)) // The timeout time in milliseconds at which the notification should automatically close. 70 | // If -1, the notification's expiration time is dependent on the notification server's settings, 71 | // and may vary for the type of notification. 72 | // If 0, the notification never expires. 73 | 74 | if call.Err != nil { 75 | panic(call.Err) 76 | } 77 | } 78 | --------------------------------------------------------------------------------