github.com/shurcool/trayhost@v0.0.0-20181020202213-114974ef9e16/trayhost.go (about) 1 package trayhost 2 3 import ( 4 "time" 5 "unsafe" 6 ) 7 8 /* 9 #cgo darwin CFLAGS: -DDARWIN -x objective-c 10 #cgo darwin LDFLAGS: -framework Cocoa 11 12 #cgo linux pkg-config: gtk+-2.0 13 #cgo linux CFLAGS: -DLINUX -I/usr/include/libappindicator-0.1 14 #cgo linux LDFLAGS: -ldl 15 16 #cgo windows CFLAGS: -DWIN32 17 18 #include <stdlib.h> 19 #include "platform/common.h" 20 #include "platform/platform.h" 21 */ 22 import "C" 23 24 var menuItems []MenuItem 25 26 // MenuItem is a menu item. 27 type MenuItem struct { 28 // Title is the title of menu item. 29 // 30 // If empty, it acts as a separator. SeparatorMenuItem can be used 31 // to create such separator menu items. 32 Title string 33 34 // Enabled can optionally control if this menu item is enabled or disabled. 35 // 36 // nil means always enabled. 37 Enabled func() bool 38 39 // Handler is triggered when the item is activated. nil means no handler. 40 Handler func() 41 } 42 43 // Initialize sets up the application properties. 44 // imageData is the icon image in PNG format. 45 func Initialize(title string, imageData []byte, items []MenuItem) { 46 cTitle := C.CString(title) 47 defer C.free(unsafe.Pointer(cTitle)) 48 img, freeImg := create_image(Image{Kind: "png", Bytes: imageData}) 49 defer freeImg() 50 51 // Initialize menu. 52 C.init(cTitle, img) 53 54 menuItems = items 55 for id, item := range menuItems { 56 addItem(id, item) 57 } 58 } 59 60 // EnterLoop enters main loop. 61 func EnterLoop() { 62 C.native_loop() 63 } 64 65 // Exit exits the application. It can be called from a MenuItem handler. 66 func Exit() { 67 C.exit_loop() 68 } 69 70 // SeparatorMenuItem creates a separator MenuItem. 71 func SeparatorMenuItem() MenuItem { return MenuItem{Title: ""} } 72 73 func addItem(id int, item MenuItem) { 74 if item.Title == "" { 75 C.add_separator_item() 76 } else { 77 // ignore errors 78 addMenuItem(id, item) 79 } 80 } 81 82 func cAddMenuItem(id C.int, title *C.char, disabled C.int) { 83 C.add_menu_item(id, title, disabled) 84 } 85 86 func cbool(b bool) C.int { 87 if b { 88 return 1 89 } 90 return 0 91 } 92 93 // --- 94 95 // SetClipboardText sets the system clipboard to the specified UTF-8 encoded 96 // string. 97 // 98 // This function may only be called from the main thread. 99 func SetClipboardText(text string) { 100 cp := C.CString(text) 101 defer C.free(unsafe.Pointer(cp)) 102 103 C.set_clipboard_string(cp) 104 } 105 106 // ImageKind is a file extension in lower case: "png", "jpg", "tiff", etc. Empty string means no image. 107 type ImageKind string 108 109 // Image is an encoded image of certain kind. 110 type Image struct { 111 Kind ImageKind 112 Bytes []byte 113 } 114 115 // ClipboardContent holds the contents of system clipboard. 116 type ClipboardContent struct { 117 Text string 118 Image Image 119 Files []string 120 } 121 122 // GetClipboardContent returns the contents of the system clipboard, if it 123 // contains or is convertible to a UTF-8 encoded string, image, and/or files. 124 // 125 // This function may only be called from the main thread. 126 func GetClipboardContent() (ClipboardContent, error) { 127 var cc ClipboardContent 128 129 ccc := C.get_clipboard_content() 130 if ccc.text != nil { 131 cc.Text = C.GoString(ccc.text) 132 } 133 if ccc.image.kind != nil { 134 cc.Image = Image{ 135 Kind: ImageKind(C.GoString(ccc.image.kind)), 136 Bytes: C.GoBytes(ccc.image.bytes, ccc.image.length), 137 } 138 } 139 if ccc.files.count > 0 { 140 cc.Files = make([]string, int(ccc.files.count)) 141 for i := 0; i < int(ccc.files.count); i++ { 142 var x *C.char 143 p := (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(ccc.files.names)) + uintptr(i)*unsafe.Sizeof(x))) 144 cc.Files[i] = C.GoString(*p) 145 } 146 } 147 148 return cc, nil 149 } 150 151 // --- 152 153 // TODO: Garbage collection. Really only need this until the notification is cleared, so its Handler is accessible. 154 var notifications []Notification 155 156 // Notification represents a user notification. 157 type Notification struct { 158 Title string // Title of user notification. 159 Body string // Body of user notification. 160 Image Image // Image shown in the content of user notification. 161 162 // Timeout specifies time after which the notification is cleared. 163 // 164 // A Timeout of zero means no timeout. 165 Timeout time.Duration 166 167 // Activation (click) handler. 168 // 169 // nil means no handler. 170 Handler func() 171 } 172 173 // Display displays the user notification. 174 func (n Notification) Display() { 175 cTitle := C.CString(n.Title) 176 defer C.free(unsafe.Pointer(cTitle)) 177 cBody := C.CString(n.Body) 178 defer C.free(unsafe.Pointer(cBody)) 179 img, freeImg := create_image(n.Image) 180 defer freeImg() 181 182 // TODO: Move out of Display. 183 notificationId := (C.int)(len(notifications)) 184 notifications = append(notifications, n) 185 186 C.display_notification(notificationId, cTitle, cBody, img, C.double(n.Timeout.Seconds())) 187 } 188 189 // UpdateMenu removes all current menu items, and adds new menu items. 190 func UpdateMenu(newMenu []MenuItem) { 191 C.clear_menu_items() 192 menuItems = newMenu 193 for id, item := range newMenu { 194 addItem(id, item) 195 } 196 }