├── .gitattributes ├── .gitignore ├── .travis.yml ├── Godeps ├── Godeps.json └── Readme ├── LICENSE.md ├── README.md ├── app.go ├── callbacks.go ├── cmd └── gallium-bundle │ ├── bindata.go │ ├── bundle.go │ ├── iconset.go │ └── info.plist.tpl ├── cocoa.go ├── dist ├── Gallium.framework │ ├── Frameworks │ ├── Gallium │ ├── Libraries │ ├── Resources │ └── Versions │ │ ├── A │ │ ├── Gallium │ │ ├── Libraries │ │ │ ├── ffmpegsumo.so │ │ │ └── libchromiumcontent.dylib │ │ └── Resources │ │ │ ├── MainMenu.nib │ │ │ ├── WindowController.nib │ │ │ ├── content_shell.pak │ │ │ └── icudtl.dat │ │ └── Current └── include │ └── gallium │ ├── browser.h │ ├── cocoa.h │ ├── core.h │ ├── globalshortcut.h │ └── screen.h ├── examples ├── controller │ └── controller.go ├── desktop-notification │ ├── bindata.go │ ├── gopher.png │ └── notification.go ├── frameless │ └── frameless.go ├── keys │ └── keys.go ├── menu │ └── menu.go ├── native │ └── native.go ├── screens │ └── screens.go ├── simple │ ├── gopher.iconset │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ └── icon_512x512.png │ └── simple.go ├── statusbar │ ├── bindata.go │ ├── icon.png │ └── statusbar.go └── window │ └── window.go ├── globalshortcut.go ├── linkflags.go ├── rect.go ├── redirect.go ├── screen.go └── vendor └── github.com └── alexflint └── go-arg ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── doc.go ├── parse.go ├── scalar.go └── usage.go /.gitattributes: -------------------------------------------------------------------------------- 1 | *.so filter=lfs diff=lfs merge=lfs -text 2 | *.dylib filter=lfs diff=lfs merge=lfs -text 3 | dist/Gallium.framework/Versions/A/Resources/icudtl.dat filter=lfs diff=lfs merge=lfs -text 4 | dist/Gallium.framework/Versions/A/Resources/content_shell.pak filter=lfs diff=lfs merge=lfs -text 5 | dist/Gallium.framework/Versions/A/Gallium filter=lfs diff=lfs merge=lfs -text 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /*.vcxproj* 2 | /*.xcodeproj/ 3 | /lib/build/ 4 | 5 | # Visual Studio 6 | /*.opensdf 7 | /*.sdf 8 | /*.sln 9 | /*.suo 10 | /ipch/ 11 | 12 | # Linux 13 | out/ 14 | 15 | # Vim 16 | /*.swp 17 | *.app 18 | 19 | /build/ 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | os: 3 | - osx 4 | 5 | osx_image: xcode8 6 | 7 | before_install: 8 | - brew install git-lfs 9 | - git lfs install --system 10 | - git lfs pull 11 | 12 | sudo: false 13 | 14 | go: 15 | - 1.7 16 | - tip 17 | 18 | script: 19 | - go test -v ./... 20 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/alexflint/gallium", 3 | "GoVersion": "go1.7", 4 | "GodepVersion": "v74", 5 | "Packages": [ 6 | "./..." 7 | ], 8 | "Deps": [ 9 | { 10 | "ImportPath": "github.com/alexflint/go-arg", 11 | "Rev": "f882700b723834ad1371307b81930cde4b81c0aa" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Alex Flint 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/alexflint/gallium?status.svg)](https://godoc.org/github.com/alexflint/gallium) 2 | [![Build Status](https://travis-ci.org/alexflint/gallium.svg?branch=master)](https://travis-ci.org/alexflint/gallium) 3 | 4 | Write desktop applications in Go, HTML, Javascript, and CSS. 5 | 6 | Gallium is a Go library for managing windows, menus, dock icons, and desktop notifications. Each window contains a webview component, in which you code your UI in HTML. Under the hood, the webview is running Chromium. 7 | 8 | ### Warning 9 | 10 | This is an extremely early version of Gallium. Most APIs will probably change 11 | before the 1.0 release, and much of the functionality that is already implemented 12 | remains unstable. 13 | 14 | ### Platforms 15 | 16 | Only OSX is supported right now. I intend to add support for Windows and Linux 17 | soon. 18 | 19 | ### Discussion 20 | 21 | Join the `#gallium` channel over at the Gophers slack. (You can request an invite to 22 | the Gophers slack team [here](https://gophersinvite.herokuapp.com/).) 23 | 24 | ### Installation 25 | 26 | Requires go >= 1.7 27 | 28 | First install git large file storage, then install Gallium: 29 | ```shell 30 | $ brew install git-lfs 31 | $ git lfs install 32 | $ go get github.com/alexflint/gallium # will not work without git lfs! 33 | ``` 34 | 35 | This will fetch a 92MB framework containing a binary distribution 36 | of the Chromium content module, so it may take a few moments. This 37 | is also why git large file storage must be installed (github has 38 | a limit on file size.) 39 | 40 | ### Quickstart 41 | 42 | ```go 43 | package main 44 | 45 | import ( 46 | "os" 47 | "runtime" 48 | 49 | "github.com/alexflint/gallium" 50 | ) 51 | 52 | func main() { 53 | runtime.LockOSThread() // must be the first statement in main - see below 54 | gallium.Loop(os.Args, onReady) // must be called from main function 55 | } 56 | 57 | func onReady(app *gallium.App) { 58 | app.OpenWindow("http://example.com/", gallium.FramedWindow) 59 | } 60 | ``` 61 | 62 | To run the example as a full-fledged UI application, you need to build 63 | an app bundle: 64 | ```shell 65 | $ go build ./example 66 | $ go install github.com/alexflint/gallium/cmd/gallium-bundle 67 | $ gallium-bundle example 68 | $ open example.app 69 | ``` 70 | 71 | ![Result of the example](https://cloud.githubusercontent.com/assets/640247/18623245/c71c2d26-7def-11e6-9ad3-1a5541d7fc86.png) 72 | 73 | If you run the executable directly without building an app bundle then 74 | many UI elements, such as menus, will not work correctly. 75 | 76 | ```shell 77 | $ go run example.go 78 | ``` 79 | 80 | ### Menus 81 | 82 | ```go 83 | func main() { 84 | runtime.LockOSThread() 85 | gallium.Loop(os.Args, onReady) 86 | } 87 | 88 | func onReady(app *gallium.App) { 89 | app.OpenWindow("http://example.com/", gallium.FramedWindow) 90 | app.SetMenu([]gallium.Menu{ 91 | gallium.Menu{ 92 | Title: "demo", 93 | Entries: []gallium.MenuEntry{ 94 | gallium.MenuItem{ 95 | Title: "About", 96 | OnClick: handleMenuAbout, 97 | }, 98 | gallium.Separator, 99 | gallium.MenuItem{ 100 | Title: "Quit", 101 | Shortcut: "Cmd+q", 102 | OnClick: handleMenuQuit, 103 | }, 104 | }, 105 | }, 106 | }) 107 | } 108 | 109 | func handleMenuAbout() { 110 | log.Println("about clicked") 111 | os.Exit(0) 112 | } 113 | 114 | func handleMenuQuit() { 115 | log.Println("quit clicked") 116 | os.Exit(0) 117 | } 118 | ``` 119 | 120 | ![Menu demo](https://cloud.githubusercontent.com/assets/640247/20243830/17fbaa8e-a91d-11e6-8eca-7ae7c1418a7e.png) 121 | 122 | ### Status Bar 123 | 124 | ```go 125 | func main() { 126 | runtime.LockOSThread() 127 | gallium.Loop(os.Args, onReady) 128 | } 129 | 130 | func onReady(app *gallium.App) { 131 | app.OpenWindow("http://example.com/", gallium.FramedWindow) 132 | app.AddStatusItem( 133 | 20, 134 | "statusbar", 135 | true, 136 | gallium.MenuItem{ 137 | Title: "Do something", 138 | OnClick: handleDoSomething, 139 | }, 140 | gallium.MenuItem{ 141 | Title: "Do something else", 142 | OnClick: handleDoSomethingElse, 143 | }, 144 | ) 145 | } 146 | 147 | func handleDoSomething() { 148 | log.Println("do something") 149 | } 150 | 151 | func handleDoSomethingElse() { 152 | log.Println("do something else") 153 | } 154 | ``` 155 | 156 | ![Statusbar demo](https://cloud.githubusercontent.com/assets/640247/18698431/06e9d88c-7f7f-11e6-9fa5-d6be40a07840.png) 157 | 158 | ### Desktop Notifications 159 | 160 | Note that the OSX Notification Center determines whether or not to show any 161 | given desktop notification, so you may need to open the notification center 162 | and scroll to the bottom in order to see notifications during development. 163 | 164 | ```go 165 | func main() { 166 | runtime.LockOSThread() 167 | gallium.Loop(os.Args, onReady) 168 | } 169 | 170 | func onReady(app *gallium.App) { 171 | img, err := gallium.ImageFromPNG(pngBuffer) 172 | if err != nil { 173 | ... 174 | } 175 | 176 | app.Post(gallium.Notification{ 177 | Title: "Wow this is a notification", 178 | Subtitle: "The subtitle", 179 | Image: img, 180 | }) 181 | } 182 | ``` 183 | 184 | ### Dock icons 185 | 186 | To add a dock icon, create a directory named `myapp.iconset` containing the following files: 187 | ``` 188 | icon_16x16.png # 16 x 16 189 | icon_16x16@2x.png # 32 x 32 190 | icon_32x32.png # 32 x 32 191 | icon_32x32@2x.png # 64 x 64 192 | icon_128x128.png # 128 x 128 193 | icon_128x128@2x.png # 256 x 256 194 | icon_256x256.png # 256 x 256 195 | icon_256x256@2x.png # 512 x 512 196 | icon_512x512.png # 512 x 512 197 | icon_512x512@2x.png # 1024 x 1024 198 | ``` 199 | 200 | Then build you app with 201 | ```shell 202 | gallium-bundle myapp --icon myapp.iconset 203 | ``` 204 | 205 | Alternatively, if you have a `.icns` file: 206 | ```shell 207 | gallium-bundle myapp --icon myapp.icns 208 | ``` 209 | 210 | ### Writing native code 211 | 212 | You can write C or Objective-C code that interfaces directly with native 213 | windowing APIs. The following example uses the macOS native API `[NSWindow 214 | setAlphaValue]` to create a semi-transparent window. 215 | 216 | ```go 217 | package main 218 | 219 | import ( 220 | "log" 221 | "os" 222 | "runtime" 223 | 224 | "github.com/alexflint/gallium" 225 | ) 226 | 227 | /* 228 | #cgo CFLAGS: -x objective-c 229 | #cgo CFLAGS: -framework Cocoa 230 | #cgo LDFLAGS: -framework Cocoa 231 | 232 | #include 233 | #include 234 | 235 | void SetAlpha(void* window, float alpha) { 236 | // Cocoa requires that all UI operations happen on the main thread. Since 237 | // gallium.Loop will have initiated the Cocoa event loop, we can can use 238 | // dispatch_async to run code on the main thread. 239 | dispatch_async(dispatch_get_main_queue(), ^{ 240 | NSWindow* w = (NSWindow*)window; 241 | [w setAlphaValue:alpha]; 242 | }); 243 | } 244 | */ 245 | import "C" 246 | 247 | func onReady(ui *gallium.App) { 248 | window, err := ui.OpenWindow("http://example.com/", gallium.FramedWindow) 249 | if err != nil { 250 | log.Fatal(err) 251 | } 252 | C.SetAlpha(window.NativeWindow(), 0.5) 253 | } 254 | 255 | func main() { 256 | runtime.LockOSThread() 257 | gallium.Loop(os.Args, onReady) 258 | } 259 | ``` 260 | 261 | ### Relationship to other projects 262 | 263 | [Electron](http://electron.atom.io/) is a well-known framework for writing desktop applications in node.js. Electron and Gallium are similar in that the core UI is developed in HTML and javascript, but with Gallium the "outer layer" of logic is written in Go. Both Electron and Gallium use Chromium under the hood, and some of the C components for Gallium were ported from Electron. 264 | 265 | The [Chromium Embedded Framework](https://bitbucket.org/chromiumembedded/cef) is a C framework for embedding Chromium into other applications. I investigated CEF as a basis for Gallium but decided to use [libchromiumcontent](https://github.com/electron/libchromiumcontent) instead. 266 | 267 | [cef2go](https://github.com/cztomczak/cef2go) is a Go wrapper for Chromium based on CEF, but so far it still requires some manual steps to use as a library. 268 | 269 | ### Rationale 270 | 271 | The goal of Gallium is to make it possible to write cross-platform 272 | desktop UI applications in Go. 273 | 274 | ### Troubleshooting 275 | 276 | **"file was built for unsupported file format"** 277 | 278 | If you see the following error: 279 | ``` 280 | ld: warning: ignoring file go/src/github.com/alexflint/gallium/dist/Gallium.framework/Gallium, file was built for unsupported file format ( 0x76 0x65 0x72 0x73 0x69 0x6F 0x6E 0x20 0x68 0x74 0x74 0x70 0x73 0x3A 0x2F 0x2F ) which is not the architecture being linked (x86_64): go/src/github.com/alexflint/gallium/dist/Gallium.framework/Gallium 281 | ``` 282 | then you probably have an issue with `git lfs`. You can confirm that this is 283 | the problem by checking the size of the file in the error message: it should 284 | be over 1 MB, but if you see a much smaller file then this is your problem. 285 | 286 | To fix this, try re-installing `git lfs` as described in the installation 287 | section above, then delete and re-install gallium. 288 | 289 | **No console output** 290 | 291 | When you run an app bundle with `open Foo.app`, OSX launch services discards 292 | standard output and standard error. If you need to see this output for 293 | debugging purposes, use a redirect: 294 | ``` 295 | gallium.RedirectStdoutStderr("output.log") 296 | ``` 297 | 298 | **App does not start** 299 | 300 | When you run an app bundle with `open Foo.app`, OSX launch services will only 301 | start your app if there is not already another instance of the same 302 | application running, so if your app refuses to start then try checking the 303 | activity monitor for an already running instance. 304 | 305 | **Menus not visible** 306 | 307 | If you run the binary directly without building an app 308 | bundle then your menus will not show up, and the window will initially appear 309 | behind other applications. 310 | 311 | ### UI thread issues and runtime.LockOSThread 312 | 313 | It is very important that the first statement in your main function 314 | be `runtime.LockOSThread()`. The reason is that gallium calls 315 | out to various C functions in order to create and manage OSX UI elements, 316 | and many of these are required to be called from the first thread 317 | created by the process. But the Go runtime creates many threads and any 318 | one piece of Go code could end up running on any thread. The solution 319 | is `runtime.LockOSThread`, which tells the Go scheduler to lock the 320 | current goroutine so that it will only ever run on the current thread. 321 | Since the main function always starts off on the main thread, this wil 322 | guarantee that the later call to `gallium.Loop` will also be on the main 323 | thread. At this point gallium takes ownership of this thread for its main 324 | event loop and calls the `OnReady` callback in a separate goroutine. 325 | From this point forward it is safe to call gallium functions from any 326 | goroutine. 327 | 328 | ### Shared libraries and linking issues 329 | 330 | Gallium is based on Chromium, which it accesses via `Gallium.framework`. 331 | That framework in turn contains `libchromiumcontent.dylib`, which is a 332 | shared library containing the chromium content module and is distributed 333 | in binary form by the same folks responsible for the excellent Electron 334 | framework. When you build your Go executable, the directives in 335 | `Gallium.framework` instruct the linker to set up the executable to look for 336 | `Gallium.framework` in two places at runtime: 337 | 1. `/../Frameworks/Gallium.framework`: this 338 | will resolve correctly if you choose to build and run your app as a 339 | bundle (and also means you can distribute the app bundle as a 340 | self-contained unit). 341 | 2. `$GOPATH/src/github.com/alexflint/dist/Gallium.framework`: this will 342 | resolve if you choose to run your executable directly. 343 | 344 | -------------------------------------------------------------------------------- /app.go: -------------------------------------------------------------------------------- 1 | package gallium 2 | 3 | /* 4 | #cgo CFLAGS: -mmacosx-version-min=10.8 5 | #cgo CFLAGS: -DGALLIUM_DIR=${SRCDIR} 6 | #cgo CFLAGS: -Idist/include 7 | 8 | #include 9 | #include "gallium/browser.h" 10 | #include "gallium/cocoa.h" 11 | 12 | // It does not seem that we can import "_cgo_export.h" from here 13 | extern void cgo_onReady(int); 14 | 15 | // This is a wrapper around GalliumLoop that adds the function pointer 16 | // argument, since this does not seem to be possible from Go directly. 17 | static inline void helper_GalliumLoop(int app_id, const char* arg0, struct gallium_error** err) { 18 | GalliumLoop(app_id, arg0, &cgo_onReady, err); 19 | } 20 | */ 21 | import "C" 22 | import ( 23 | "errors" 24 | "fmt" 25 | "log" 26 | "os" 27 | "path/filepath" 28 | "time" 29 | "unsafe" 30 | ) 31 | 32 | var ( 33 | errZeroWidth = errors.New("window width was zero") 34 | errZeroHeight = errors.New("window height was zero") 35 | ) 36 | 37 | // cerr holds a C-allocated error, which must be freed explicitly. 38 | type cerr struct { 39 | c *C.struct_gallium_error 40 | } 41 | 42 | // newCerr allocates a new error struct. It must be freed explicitly. 43 | func newCerr() cerr { 44 | return cerr{ 45 | c: (*C.struct_gallium_error)(C.malloc(C.sizeof_struct_gallium_error)), 46 | } 47 | } 48 | 49 | func (e cerr) free() { 50 | C.free(unsafe.Pointer(e.c)) 51 | } 52 | 53 | func (e *cerr) err() error { 54 | // TODO 55 | return fmt.Errorf("C error") 56 | } 57 | 58 | // Loop starts the browser loop and does not return unless there is an initialization error 59 | func Loop(args []string, onReady func(*App)) error { 60 | log.Println("\n\n=== gallium.Loop ===") 61 | cerr := newCerr() 62 | defer cerr.free() 63 | 64 | app := App{ 65 | ready: make(chan struct{}), 66 | } 67 | 68 | go func() { 69 | select { 70 | case <-app.ready: 71 | onReady(&app) 72 | case <-time.After(3 * time.Second): 73 | log.Fatal("Waited for 3 seconds without ready signal") 74 | } 75 | }() 76 | 77 | appId := apps.add(&app) 78 | C.helper_GalliumLoop(C.int(appId), C.CString(args[0]), &cerr.c) 79 | return cerr.err() 80 | } 81 | 82 | // appManager is the singleton for managing app instances 83 | type appManager []*App 84 | 85 | func (m *appManager) add(app *App) int { 86 | id := len(*m) 87 | *m = append(*m, app) 88 | return id 89 | } 90 | 91 | func (m *appManager) get(id int) *App { 92 | return (*m)[id] 93 | } 94 | 95 | var apps appManager 96 | 97 | // App is the handle that allows you to create windows and menus 98 | type App struct { 99 | // ready is how the cgo onready callback indicates to the Loop goroutine that 100 | // chromium is initialized 101 | ready chan struct{} 102 | } 103 | 104 | // WindowOptions contains options for creating windows 105 | type WindowOptions struct { 106 | Title string // String to display in title bar 107 | Shape Rect // Initial size and position of window 108 | TitleBar bool // Whether the window title bar 109 | Frame bool // Whether the window has a frame 110 | Resizable bool // Whether the window border can be dragged to change its shape 111 | CloseButton bool // Whether the window has a close button 112 | MinButton bool // Whether the window has a miniaturize button 113 | FullScreenButton bool // Whether the window has a full screen button 114 | Menu []MenuEntry 115 | } 116 | 117 | // FramedWindow contains options for an "ordinary" window with title bar, 118 | // frame, and min/max/close buttons. 119 | var FramedWindow = WindowOptions{ 120 | Shape: Rect{ 121 | Width: 800, 122 | Height: 600, 123 | Left: 100, 124 | Bottom: 100, 125 | }, 126 | TitleBar: true, 127 | Frame: true, 128 | Resizable: true, 129 | CloseButton: true, 130 | MinButton: true, 131 | FullScreenButton: true, 132 | Title: defaultWindowTitle(), 133 | } 134 | 135 | func defaultWindowTitle() string { 136 | // try the display name first 137 | if name := BundleInfo("CFBundleDisplayName"); name != "" { 138 | return name 139 | } 140 | // then fall back to the short name 141 | if name := BundleInfo("CFBundleName"); name != "" { 142 | return name 143 | } 144 | // then fall back to the executable name 145 | if len(os.Args) > 0 { 146 | filepath.Base(os.Args[0]) 147 | } 148 | return "" 149 | } 150 | 151 | // FramelessWindow contains options for a window with no frame or border, but that 152 | // is still resizable. 153 | var FramelessWindow = WindowOptions{ 154 | Shape: Rect{ 155 | Width: 800, 156 | Height: 600, 157 | Left: 100, 158 | Bottom: 100, 159 | }, 160 | Resizable: true, 161 | } 162 | 163 | // Window represents a window registered with the native UI toolkit (e.g. NSWindow on macOS) 164 | type Window struct { 165 | c *C.gallium_window_t 166 | } 167 | 168 | // OpenWindow creates a window that will load the given URL. 169 | func (app *App) OpenWindow(url string, opt WindowOptions) (*Window, error) { 170 | if opt.Shape.Width == 0 { 171 | return nil, errZeroWidth 172 | } 173 | if opt.Shape.Height == 0 { 174 | return nil, errZeroHeight 175 | } 176 | // Create the Cocoa window 177 | cwin := C.GalliumOpenWindow( 178 | C.CString(url), 179 | C.CString(opt.Title), 180 | C.int(opt.Shape.Width), 181 | C.int(opt.Shape.Height), 182 | C.int(opt.Shape.Left), 183 | C.int(opt.Shape.Bottom), 184 | C.bool(opt.TitleBar), 185 | C.bool(opt.Frame), 186 | C.bool(opt.Resizable), 187 | C.bool(opt.CloseButton), 188 | C.bool(opt.MinButton), 189 | C.bool(opt.FullScreenButton)) 190 | 191 | // TODO: associate menu 192 | return &Window{ 193 | c: cwin, 194 | }, nil 195 | } 196 | 197 | // Shape gets the current shape of the window. 198 | func (w *Window) Shape() Rect { 199 | return rectFromC(C.GalliumWindowGetShape(w.c)) 200 | } 201 | 202 | // Shape gets the current shape of the window. 203 | func (w *Window) SetShape(r Rect) { 204 | C.GalliumWindowSetShape(w.c, C.int(r.Width), C.int(r.Height), C.int(r.Left), C.int(r.Bottom)) 205 | } 206 | 207 | // URL gets the URL that the window is currently at. 208 | func (w *Window) URL() string { 209 | return C.GoString(C.GalliumWindowGetURL(w.c)) 210 | } 211 | 212 | // LoadURL causes the window to load the given URL 213 | func (w *Window) LoadURL(url string) { 214 | C.GalliumWindowLoadURL(w.c, C.CString(url)) 215 | } 216 | 217 | // Reload reloads the current URL 218 | func (w *Window) Reload() { 219 | C.GalliumWindowReload(w.c) 220 | } 221 | 222 | // Reload reloads the current URL, ignoring cached versions of resources. 223 | func (w *Window) ReloadNoCache() { 224 | C.GalliumWindowReloadNoCache(w.c) 225 | } 226 | 227 | // Open opens the window. This is the default state for a window created 228 | // via OpenWindow, so you only need to call this if you manually closed 229 | // the window. 230 | func (w *Window) Open() { 231 | C.GalliumWindowOpen(w.c) 232 | } 233 | 234 | // Close closes the window, as if the close button had been clicked. 235 | func (w *Window) Close() { 236 | C.GalliumWindowClose(w.c) 237 | } 238 | 239 | // Miniaturize miniaturizes the window, as if the min button had been clicked. 240 | func (w *Window) Miniaturize() { 241 | C.GalliumWindowMiniaturize(w.c) 242 | } 243 | 244 | // Undo undoes the last text editing action 245 | func (w *Window) Undo() { 246 | C.GalliumWindowUndo(w.c) 247 | } 248 | 249 | // Redo redoes the last text editing action 250 | func (w *Window) Redo() { 251 | C.GalliumWindowRedo(w.c) 252 | } 253 | 254 | // Cut cuts the current text selection to the pastboard 255 | func (w *Window) Cut() { 256 | C.GalliumWindowCut(w.c) 257 | } 258 | 259 | // Copy copies the current text selection to the pasteboard 260 | func (w *Window) Copy() { 261 | C.GalliumWindowCopy(w.c) 262 | } 263 | 264 | // Paste pastes from the pasteboard 265 | func (w *Window) Paste() { 266 | C.GalliumWindowPaste(w.c) 267 | } 268 | 269 | // PasteAndMatchStyle pastes from the pasteboard, matching style to the current element 270 | func (w *Window) PasteAndMatchStyle() { 271 | C.GalliumWindowPasteAndMatchStyle(w.c) 272 | } 273 | 274 | // Delete deletes the current text selection 275 | func (w *Window) Delete() { 276 | C.GalliumWindowDelete(w.c) 277 | } 278 | 279 | // SelectAll selects all text in the current element 280 | func (w *Window) SelectAll() { 281 | C.GalliumWindowSelectAll(w.c) 282 | } 283 | 284 | // Unselect unselects any text selection 285 | func (w *Window) Unselect() { 286 | C.GalliumWindowUnselect(w.c) 287 | } 288 | 289 | // OpenDevTools opens the developer tools for this window. 290 | func (w *Window) OpenDevTools() { 291 | C.GalliumWindowOpenDevTools(w.c) 292 | } 293 | 294 | // CloseDevTools closes the developer tools. 295 | func (w *Window) CloseDevTools() { 296 | C.GalliumWindowCloseDevTools(w.c) 297 | } 298 | 299 | // DevToolsVisible returns whether the developer tools are showing 300 | func (w *Window) DevToolsAreOpen() bool { 301 | return bool(C.GalliumWindowDevToolsAreOpen(w.c)) 302 | } 303 | 304 | // NativeWindow gets a operating-system dependent handle for this window. Under macOS 305 | // this is NSWindow*. 306 | func (w *Window) NativeWindow() unsafe.Pointer { 307 | return unsafe.Pointer(C.GalliumWindowNativeWindow(w.c)) 308 | } 309 | 310 | // NativeWindow gets an operating-system dependent handle for the window controller. 311 | // Under macOS this is *NSWindowController. 312 | func (w *Window) NativeController() unsafe.Pointer { 313 | return unsafe.Pointer(C.GalliumWindowNativeController(w.c)) 314 | } 315 | -------------------------------------------------------------------------------- /callbacks.go: -------------------------------------------------------------------------------- 1 | package gallium 2 | 3 | import ( 4 | "log" 5 | "unsafe" 6 | ) 7 | 8 | import "C" 9 | 10 | // This file contains all Go functions that are exported to cgo. They 11 | // are here because the presence of an export means that the C prelude 12 | // gets copied into two locations. 13 | 14 | //export cgo_onReady 15 | func cgo_onReady(appId int) { 16 | // do not actually call the user function from here because that would 17 | // block the UI loop 18 | apps.get(appId).ready <- struct{}{} 19 | } 20 | 21 | //export cgo_onMenuClicked 22 | func cgo_onMenuClicked(data unsafe.Pointer) { 23 | if menuMgr == nil { 24 | log.Println("onMenuClicked called but menu manager was nil") 25 | return 26 | } 27 | 28 | if data == nil { 29 | log.Println("onMenuClicked called but data parameter was nil") 30 | return 31 | } 32 | 33 | id := *(*int)(data) 34 | item, found := menuMgr.items[id] 35 | if !found { 36 | log.Printf("onMenuClicked received non-existent ID %d", id) 37 | return 38 | } 39 | 40 | if item.OnClick == nil { 41 | log.Printf("onMenuClicked found %s but OnClick was nil", item.Title) 42 | return 43 | } 44 | 45 | item.OnClick() 46 | } 47 | -------------------------------------------------------------------------------- /cmd/gallium-bundle/bindata.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-bindata. 2 | // sources: 3 | // info.plist.tpl 4 | // DO NOT EDIT! 5 | 6 | package main 7 | 8 | import ( 9 | "bytes" 10 | "compress/gzip" 11 | "fmt" 12 | "io" 13 | "io/ioutil" 14 | "os" 15 | "path/filepath" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | func bindataRead(data []byte, name string) ([]byte, error) { 21 | gz, err := gzip.NewReader(bytes.NewBuffer(data)) 22 | if err != nil { 23 | return nil, fmt.Errorf("Read %q: %v", name, err) 24 | } 25 | 26 | var buf bytes.Buffer 27 | _, err = io.Copy(&buf, gz) 28 | clErr := gz.Close() 29 | 30 | if err != nil { 31 | return nil, fmt.Errorf("Read %q: %v", name, err) 32 | } 33 | if clErr != nil { 34 | return nil, err 35 | } 36 | 37 | return buf.Bytes(), nil 38 | } 39 | 40 | type asset struct { 41 | bytes []byte 42 | info os.FileInfo 43 | } 44 | 45 | type bindataFileInfo struct { 46 | name string 47 | size int64 48 | mode os.FileMode 49 | modTime time.Time 50 | } 51 | 52 | func (fi bindataFileInfo) Name() string { 53 | return fi.name 54 | } 55 | func (fi bindataFileInfo) Size() int64 { 56 | return fi.size 57 | } 58 | func (fi bindataFileInfo) Mode() os.FileMode { 59 | return fi.mode 60 | } 61 | func (fi bindataFileInfo) ModTime() time.Time { 62 | return fi.modTime 63 | } 64 | func (fi bindataFileInfo) IsDir() bool { 65 | return false 66 | } 67 | func (fi bindataFileInfo) Sys() interface{} { 68 | return nil 69 | } 70 | 71 | var _infoPlistTpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x93\x5f\x6f\x9b\x30\x14\xc5\x9f\xcb\xa7\xf0\x50\x1f\x17\x1b\xba\x56\xdd\x2a\x42\xd5\x40\xd2\x45\xcb\x3f\x0d\x3a\x75\x4f\x93\x67\x5c\x62\xd5\xd8\xc8\x36\xf9\x23\xc4\x77\x9f\x9c\xa4\x69\x9a\x84\x87\xbd\x61\xf9\xfc\x0e\xd7\xf7\x9e\x1b\xdc\xaf\x0a\x0e\x16\x54\x69\x26\x45\xd7\xf5\xa1\xe7\x02\x2a\x88\xcc\x98\xc8\xbb\xee\x53\x3a\xe8\x7c\x75\xef\x43\x27\xf8\x14\x4f\xa3\xf4\xf7\xac\x0f\x4a\xce\xb4\x01\xb3\xa7\xde\x68\x18\x01\xb7\x83\xd0\x43\x59\x72\x8a\x50\x9c\xc6\x60\x36\x1a\x26\x29\xf0\xa1\x87\x50\x7f\xe2\x02\x77\x6e\x4c\x79\x87\xd0\x72\xb9\x84\xd8\xaa\x20\x91\x85\x15\x6a\x34\x53\xb2\xa4\xca\xac\x47\x4c\x9b\x8e\x0f\x3d\x98\x99\xcc\x0d\x9d\x60\xeb\xfe\xa1\x9c\xd0\x09\x32\x46\x4c\xe8\x5c\x04\xaf\x74\x1d\xf6\x2a\xc6\xb3\x31\x26\x73\x26\xe8\x34\xd9\x9c\x02\x64\x2f\x9c\x8b\x40\x1b\xc5\x44\x1e\xfa\xd7\x03\xff\xc6\xfb\x16\xa0\xdd\x79\x47\x46\x83\x5e\x25\x32\x4e\x87\x19\x15\x86\xbd\x30\xaa\x8e\xc1\xba\x86\xc7\x92\xa6\x69\xb3\x99\xe0\x82\xb6\x1a\xd8\xcb\x76\x34\xa9\xca\x52\x2a\x43\xb3\x19\xc7\xe6\x45\xaa\x42\xef\x8d\xb0\x52\xd8\x7e\xec\x2d\xc7\x98\x4c\x93\xe7\x43\x27\xf4\xa6\xd9\x78\xc6\x69\x24\x8b\x92\xf1\xd3\xd7\x10\x59\xbc\xb7\x7d\xa3\xd0\x90\xf3\x45\x01\x09\xc7\x22\x87\xfe\x1f\xef\xb8\xbe\x38\x7d\x2b\xe8\x6c\x5f\x6f\x23\xdf\xf3\xae\xda\xa1\x5f\xdb\xb1\x1d\x63\x8f\xe3\x53\x24\x89\x7f\x9c\x1f\xdd\x4d\x74\xfd\xe5\xac\xfc\x5c\xbb\x0b\x4c\xa4\x5e\xf9\x1e\xf4\xfd\x53\xe6\x99\xc8\xec\x84\xf0\x6e\xaf\xda\xa4\xff\xf1\xe4\x49\xb2\x9b\xa0\x7e\xa8\x8c\x2c\xb0\x61\xe4\x51\xe1\x72\xce\x88\x4e\x96\xcc\xd8\x6c\xe6\x7b\x27\xa3\x2a\x8a\xde\xc9\xef\x2c\x9f\xff\xa4\x5a\xf2\xca\x30\x29\x22\x5c\xe2\xbf\xfc\xa4\xcc\x54\x55\xf4\xe0\xa7\x75\xad\xb0\xc8\x29\xb8\x7c\xa5\xeb\xcf\xe0\x72\x81\x39\xb8\xeb\x02\xd8\x5f\x19\x85\x75\xd3\xd8\xb4\x58\xbe\xae\xad\xc0\xc6\x6e\xeb\x76\x10\x4b\xcb\x7c\xc8\x63\x5d\x53\x91\x35\x8d\x13\xa0\xed\x72\x05\x68\xb3\x7a\xa1\xf3\x2f\x00\x00\xff\xff\xe9\x9e\x25\xe8\x11\x04\x00\x00") 72 | 73 | func infoPlistTplBytes() ([]byte, error) { 74 | return bindataRead( 75 | _infoPlistTpl, 76 | "info.plist.tpl", 77 | ) 78 | } 79 | 80 | func infoPlistTpl() (*asset, error) { 81 | bytes, err := infoPlistTplBytes() 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | info := bindataFileInfo{name: "info.plist.tpl", size: 1041, mode: os.FileMode(420), modTime: time.Unix(1490294366, 0)} 87 | a := &asset{bytes: bytes, info: info} 88 | return a, nil 89 | } 90 | 91 | // Asset loads and returns the asset for the given name. 92 | // It returns an error if the asset could not be found or 93 | // could not be loaded. 94 | func Asset(name string) ([]byte, error) { 95 | cannonicalName := strings.Replace(name, "\\", "/", -1) 96 | if f, ok := _bindata[cannonicalName]; ok { 97 | a, err := f() 98 | if err != nil { 99 | return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) 100 | } 101 | return a.bytes, nil 102 | } 103 | return nil, fmt.Errorf("Asset %s not found", name) 104 | } 105 | 106 | // MustAsset is like Asset but panics when Asset would return an error. 107 | // It simplifies safe initialization of global variables. 108 | func MustAsset(name string) []byte { 109 | a, err := Asset(name) 110 | if err != nil { 111 | panic("asset: Asset(" + name + "): " + err.Error()) 112 | } 113 | 114 | return a 115 | } 116 | 117 | // AssetInfo loads and returns the asset info for the given name. 118 | // It returns an error if the asset could not be found or 119 | // could not be loaded. 120 | func AssetInfo(name string) (os.FileInfo, error) { 121 | cannonicalName := strings.Replace(name, "\\", "/", -1) 122 | if f, ok := _bindata[cannonicalName]; ok { 123 | a, err := f() 124 | if err != nil { 125 | return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) 126 | } 127 | return a.info, nil 128 | } 129 | return nil, fmt.Errorf("AssetInfo %s not found", name) 130 | } 131 | 132 | // AssetNames returns the names of the assets. 133 | func AssetNames() []string { 134 | names := make([]string, 0, len(_bindata)) 135 | for name := range _bindata { 136 | names = append(names, name) 137 | } 138 | return names 139 | } 140 | 141 | // _bindata is a table, holding each asset generator, mapped to its name. 142 | var _bindata = map[string]func() (*asset, error){ 143 | "info.plist.tpl": infoPlistTpl, 144 | } 145 | 146 | // AssetDir returns the file names below a certain 147 | // directory embedded in the file by go-bindata. 148 | // For example if you run go-bindata on data/... and data contains the 149 | // following hierarchy: 150 | // data/ 151 | // foo.txt 152 | // img/ 153 | // a.png 154 | // b.png 155 | // then AssetDir("data") would return []string{"foo.txt", "img"} 156 | // AssetDir("data/img") would return []string{"a.png", "b.png"} 157 | // AssetDir("foo.txt") and AssetDir("notexist") would return an error 158 | // AssetDir("") will return []string{"data"}. 159 | func AssetDir(name string) ([]string, error) { 160 | node := _bintree 161 | if len(name) != 0 { 162 | cannonicalName := strings.Replace(name, "\\", "/", -1) 163 | pathList := strings.Split(cannonicalName, "/") 164 | for _, p := range pathList { 165 | node = node.Children[p] 166 | if node == nil { 167 | return nil, fmt.Errorf("Asset %s not found", name) 168 | } 169 | } 170 | } 171 | if node.Func != nil { 172 | return nil, fmt.Errorf("Asset %s not found", name) 173 | } 174 | rv := make([]string, 0, len(node.Children)) 175 | for childName := range node.Children { 176 | rv = append(rv, childName) 177 | } 178 | return rv, nil 179 | } 180 | 181 | type bintree struct { 182 | Func func() (*asset, error) 183 | Children map[string]*bintree 184 | } 185 | var _bintree = &bintree{nil, map[string]*bintree{ 186 | "info.plist.tpl": &bintree{infoPlistTpl, map[string]*bintree{}}, 187 | }} 188 | 189 | // RestoreAsset restores an asset under the given directory 190 | func RestoreAsset(dir, name string) error { 191 | data, err := Asset(name) 192 | if err != nil { 193 | return err 194 | } 195 | info, err := AssetInfo(name) 196 | if err != nil { 197 | return err 198 | } 199 | err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) 200 | if err != nil { 201 | return err 202 | } 203 | err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) 204 | if err != nil { 205 | return err 206 | } 207 | err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) 208 | if err != nil { 209 | return err 210 | } 211 | return nil 212 | } 213 | 214 | // RestoreAssets restores an asset under the given directory recursively 215 | func RestoreAssets(dir, name string) error { 216 | children, err := AssetDir(name) 217 | // File 218 | if err != nil { 219 | return RestoreAsset(dir, name) 220 | } 221 | // Dir 222 | for _, child := range children { 223 | err = RestoreAssets(dir, filepath.Join(name, child)) 224 | if err != nil { 225 | return err 226 | } 227 | } 228 | return nil 229 | } 230 | 231 | func _filePath(dir, name string) string { 232 | cannonicalName := strings.Replace(name, "\\", "/", -1) 233 | return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) 234 | } 235 | 236 | -------------------------------------------------------------------------------- /cmd/gallium-bundle/bundle.go: -------------------------------------------------------------------------------- 1 | //go:generate go-bindata -o bindata.go info.plist.tpl 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | "strings" 12 | "text/template" 13 | 14 | arg "github.com/alexflint/go-arg" 15 | ) 16 | 17 | func must(err error, info ...interface{}) { 18 | if err != nil { 19 | fmt.Println(append(info, err.Error())...) 20 | os.Exit(1) 21 | } 22 | } 23 | 24 | func copyFile(dst, src string) error { 25 | st, err := os.Stat(src) 26 | if err != nil { 27 | return err 28 | } 29 | buf, err := ioutil.ReadFile(src) 30 | if err != nil { 31 | return err 32 | } 33 | return ioutil.WriteFile(dst, buf, st.Mode()) 34 | } 35 | 36 | func copyTree(dst, src string) error { 37 | return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { 38 | // re-stat the path so that we can tell whether it is a symlink 39 | info, err = os.Lstat(path) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | rel, err := filepath.Rel(src, path) 45 | if err != nil { 46 | return err 47 | } 48 | targ := filepath.Join(dst, rel) 49 | 50 | switch { 51 | case info.IsDir(): 52 | return os.Mkdir(targ, 0777) 53 | case info.Mode()&os.ModeSymlink != 0: 54 | referent, err := os.Readlink(path) 55 | if err != nil { 56 | return err 57 | } 58 | return os.Symlink(referent, targ) 59 | default: 60 | return copyFile(targ, path) 61 | } 62 | }) 63 | } 64 | 65 | func main() { 66 | var args struct { 67 | Executable string `arg:"positional,required"` 68 | Output string `arg:"-o"` 69 | Identifier string `arg:"help:The bundle identifier (CFBundleIdentifier)"` 70 | Name string `arg:"help:The bundle name (CFBundleName)"` 71 | Icon string `arg:"help:Path to a .icns file or a .iconset dir"` 72 | } 73 | arg.MustParse(&args) 74 | 75 | // If output is empty then use the app name if there is one, or the executable otherwise 76 | if args.Output == "" { 77 | if args.Name == "" { 78 | args.Output = filepath.Base(args.Executable) + ".app" 79 | } else { 80 | args.Output = args.Name + ".app" 81 | } 82 | } 83 | 84 | if !strings.HasSuffix(args.Output, ".app") { 85 | fmt.Println("output must end with .app") 86 | os.Exit(1) 87 | } 88 | 89 | // If the bundle name is empty then use the app name 90 | if args.Name == "" { 91 | args.Name = strings.TrimSuffix(filepath.Base(args.Output), ".app") 92 | } 93 | 94 | // If the bundle identifier is empty then use the bundle name 95 | if args.Identifier == "" { 96 | args.Identifier = args.Name 97 | } 98 | 99 | // extras for the Info.plist 100 | extraProps := make(map[string]string) 101 | 102 | // get the path to the gallium package 103 | golistCmd := exec.Command("go", "list", "-f", "{{.Dir}}", "github.com/alexflint/gallium") 104 | golistOut, err := golistCmd.CombinedOutput() 105 | if err != nil { 106 | fmt.Printf("go list github.com/alexflint/gallium failed:\n%s\n", string(golistOut)) 107 | os.Exit(1) 108 | } 109 | 110 | // Find Gallium.framework 111 | galliumDir := strings.TrimSpace(string(golistOut)) 112 | fwSrc := filepath.Join(galliumDir, "dist", "Gallium.framework") 113 | st, err := os.Stat(fwSrc) 114 | if err != nil { 115 | fmt.Printf("framework not found at %s: %v\n", fwSrc, err) 116 | os.Exit(1) 117 | } 118 | if !st.IsDir() { 119 | fmt.Printf("%s is not a directory\n", fwSrc) 120 | os.Exit(1) 121 | } 122 | 123 | // Create the bundle in a temporary dir 124 | tmpBundle, err := ioutil.TempDir("", "") 125 | must(err) 126 | 127 | // Create the bundle.app dir 128 | must(os.MkdirAll(tmpBundle, 0777)) 129 | 130 | // Copy the framework in 131 | fwDst := filepath.Join(tmpBundle, "Contents", "Frameworks", "Gallium.framework") 132 | must(os.MkdirAll(filepath.Dir(fwDst), 0777)) 133 | must(copyTree(fwDst, fwSrc)) 134 | 135 | // Copy the executable in 136 | exeDst := filepath.Join(tmpBundle, "Contents", "MacOS", args.Name) 137 | must(os.MkdirAll(filepath.Dir(exeDst), 0777)) 138 | must(copyFile(exeDst, args.Executable)) 139 | 140 | // Copy the icon in 141 | if args.Icon != "" { 142 | st, err := os.Stat(args.Icon) 143 | must(err) 144 | 145 | iconExt := filepath.Ext(args.Icon) 146 | iconName := strings.TrimSuffix(filepath.Base(args.Icon), iconExt) + ".icns" 147 | iconDst := filepath.Join(tmpBundle, "Contents", "Resources", iconName) 148 | must(os.MkdirAll(filepath.Dir(iconDst), 0777)) 149 | extraProps["CFBundleIconFile"] = iconName 150 | 151 | // There are three kinds of source icons 152 | switch { 153 | case iconExt == ".icns": 154 | if !st.Mode().IsRegular() { 155 | fmt.Println("Icon had extension .icns but was not a regular file") 156 | os.Exit(1) 157 | } 158 | must(copyFile(iconDst, args.Icon)) 159 | case iconExt == ".iconset": 160 | if !st.IsDir() { 161 | fmt.Println("Icon had extension .icns but was not a directory") 162 | os.Exit(1) 163 | } 164 | must(buildIconSet(iconDst, args.Icon), "error building iconset:") 165 | case iconExt == ".png": 166 | fmt.Println("Building icons from raw images not implemented yet") 167 | os.Exit(1) 168 | default: 169 | fmt.Println("Unrecognized icon extension:", iconExt) 170 | os.Exit(1) 171 | } 172 | } 173 | 174 | // Write Info.plist 175 | tpl, err := template.New("info.plist.tpl").Parse(string(MustAsset("info.plist.tpl"))) 176 | must(err) 177 | 178 | plistDst := filepath.Join(tmpBundle, "Contents", "Info.plist") 179 | w, err := os.Create(plistDst) 180 | must(err) 181 | 182 | tpl.Execute(w, map[string]interface{}{ 183 | "BundleName": args.Name, 184 | "BundleIdentifier": args.Identifier, 185 | "Extras": extraProps, 186 | }) 187 | must(w.Close()) 188 | 189 | // Write PkgInfo. I copied this verbatim from another bundle. 190 | pkginfo := []byte{0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f} 191 | pkginfoDst := filepath.Join(tmpBundle, "Contents", "PkgInfo") 192 | must(ioutil.WriteFile(pkginfoDst, pkginfo, 0777)) 193 | 194 | // Delete the bundle.app dir if it already exists 195 | must(os.RemoveAll(args.Output)) 196 | 197 | // Move the temporary dir to the bundle.app location 198 | must(os.Rename(tmpBundle, args.Output)) 199 | } 200 | -------------------------------------------------------------------------------- /cmd/gallium-bundle/iconset.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "os/exec" 6 | ) 7 | 8 | // On macOS, app bundle icons are represents as a .icns file. These files 9 | // are created using the "iconutil" tool. The input to this tool is a 10 | // ".iconset" directory containing some combination of the files: 11 | // icon_16x16[@2x].png 12 | // icon_32x32[@2x].png 13 | // icon_128x128[@2x].png 14 | // icon_256x256[@2x].png 15 | // icon_512x512[@2x].png 16 | 17 | func buildIconSet(dst, src string) error { 18 | cmd := exec.Command("iconutil", "-c", "icns", "-o", dst, src) 19 | output, err := cmd.CombinedOutput() 20 | if err != nil { 21 | return errors.New(string(output)) 22 | } 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /cmd/gallium-bundle/info.plist.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 14F1509 7 | CFBundleIdentifier 8 | {{.BundleIdentifier}} 9 | CFBundleName 10 | {{.BundleName}} 11 | CFBundleSupportedPlatforms 12 | 13 | MacOSX 14 | 15 | DTCompiler 16 | com.apple.compilers.llvm.clang.1_0 17 | DTPlatformBuild 18 | 7C1002 19 | DTPlatformVersion 20 | GM 21 | DTSDKBuild 22 | 15C43 23 | DTSDKName 24 | macosx10.11 25 | DTXcode 26 | 0721 27 | DTXcodeBuild 28 | 7C1002 29 | NSSupportsAutomaticGraphicsSwitching 30 | 31 | NSHighResolutionCapable 32 | True 33 | {{range $key, $val := .Extras}} 34 | {{$key}} 35 | {{$val}} 36 | {{end}} 37 | 38 | 39 | -------------------------------------------------------------------------------- /cocoa.go: -------------------------------------------------------------------------------- 1 | package gallium 2 | 3 | /* 4 | #include 5 | #include "gallium/cocoa.h" 6 | 7 | // It does not seem that we can import "_cgo_export.h" from here 8 | extern void cgo_onMenuClicked(void*); 9 | 10 | // This is a wrapper around NSMenu_AddMenuItem that adds the function pointer 11 | // argument, since this does not seem to be possible from Go directly. 12 | static inline gallium_nsmenuitem_t* helper_NSMenu_AddMenuItem( 13 | gallium_nsmenu_t* menu, 14 | const char* title, 15 | const char* shortcutKey, 16 | gallium_modifier_t shortcutModifier, 17 | void *callbackArg) { 18 | 19 | return NSMenu_AddMenuItem( 20 | menu, 21 | title, 22 | shortcutKey, 23 | shortcutModifier, 24 | &cgo_onMenuClicked, 25 | callbackArg); 26 | } 27 | */ 28 | import "C" 29 | import ( 30 | "errors" 31 | "fmt" 32 | "log" 33 | "strings" 34 | "unsafe" 35 | ) 36 | 37 | // MenuEntry is the interface for menus and menu items. 38 | type MenuEntry interface { 39 | menu() 40 | } 41 | 42 | // Separator displays a horizontal separator within a menu 43 | var Separator MenuEntry 44 | 45 | // A MenuItem has a title and can be clicked on. It is a leaf node in the menu tree. 46 | type MenuItem struct { 47 | Title string 48 | Shortcut KeyCombination 49 | OnClick func() 50 | } 51 | 52 | func (MenuItem) menu() {} 53 | 54 | // A Menu has a title and a list of entries. It is a non-leaf node in the menu tree. 55 | type Menu struct { 56 | Title string 57 | Entries []MenuEntry 58 | } 59 | 60 | func (Menu) menu() {} 61 | 62 | // menuMgr is the singleton that owns the menu 63 | var menuMgr *menuManager 64 | 65 | // menuManager translates Menus and MenuItems to their native equivalent (e.g. NSMenuItem on macOS) 66 | type menuManager struct { 67 | items map[int]MenuItem 68 | } 69 | 70 | func newMenuManager() *menuManager { 71 | return &menuManager{make(map[int]MenuItem)} 72 | } 73 | 74 | func (m *menuManager) add(menu MenuEntry, parent *C.gallium_nsmenu_t) { 75 | switch menu := menu.(type) { 76 | case Menu: 77 | item := C.NSMenu_AddMenuItem(parent, C.CString(menu.Title), nil, 0, nil, nil) 78 | submenu := C.NSMenu_New(C.CString(menu.Title)) 79 | C.NSMenuItem_SetSubmenu(item, submenu) 80 | for _, entry := range menu.Entries { 81 | m.add(entry, submenu) 82 | } 83 | case MenuItem: 84 | id := len(m.items) 85 | m.items[id] = menu 86 | 87 | callbackArg := C.malloc(C.sizeof_int) 88 | *(*C.int)(callbackArg) = C.int(id) 89 | 90 | C.helper_NSMenu_AddMenuItem( 91 | parent, 92 | C.CString(menu.Title), 93 | C.CString(menu.Shortcut.Key), 94 | C.gallium_modifier_t(menu.Shortcut.Modifiers), 95 | callbackArg) 96 | case nil: 97 | // nil means add a separator 98 | C.NSMenu_AddSeparator(parent) 99 | default: 100 | log.Printf("unexpected menu entry: %T", menu) 101 | } 102 | } 103 | 104 | func parseShortcut(s string) (key string, modifiers int, err error) { 105 | parts := strings.Split(s, "+") 106 | if len(parts) == 0 { 107 | return "", 0, fmt.Errorf("empty shortcut") 108 | } 109 | key = parts[len(parts)-1] 110 | if len(key) == 0 { 111 | return "", 0, fmt.Errorf("empty key") 112 | } 113 | for _, part := range parts[:len(parts)-1] { 114 | switch strings.ToLower(part) { 115 | case "cmd": 116 | modifiers |= int(C.GalliumCmdModifier) 117 | case "ctrl": 118 | modifiers |= int(C.GalliumCtrlModifier) 119 | case "cmdctrl": 120 | modifiers |= int(C.GalliumCmdOrCtrlModifier) 121 | case "alt": 122 | modifiers |= int(C.GalliumAltOrOptionModifier) 123 | case "option": 124 | modifiers |= int(C.GalliumAltOrOptionModifier) 125 | case "fn": 126 | modifiers |= int(C.GalliumFnModifier) 127 | case "shift": 128 | modifiers |= int(C.GalliumShiftModifier) 129 | default: 130 | return "", 0, fmt.Errorf("unknown modifier: %s", part) 131 | } 132 | } 133 | return 134 | } 135 | 136 | func (app *App) SetMenu(menus []Menu) { 137 | if menuMgr == nil { 138 | menuMgr = newMenuManager() 139 | } 140 | root := C.NSMenu_New(C.CString("")) 141 | for _, m := range menus { 142 | menuMgr.add(m, root) 143 | } 144 | C.NSApplication_SetMainMenu(root) 145 | } 146 | 147 | type StatusItemOptions struct { 148 | Image *Image // image to show in the status bar, must be non-nil 149 | Width float64 // width of item in pixels (zero means automatic size) 150 | Highlight bool // whether to highlight the item when clicked 151 | Menu []MenuEntry // the menu to display when the item is clicked 152 | } 153 | 154 | func (app *App) AddStatusItem(opts StatusItemOptions) { 155 | if menuMgr == nil { 156 | menuMgr = newMenuManager() 157 | } 158 | if opts.Image == nil { 159 | panic("status item image must not be nil") 160 | } 161 | 162 | menu := C.NSMenu_New(C.CString("")) 163 | for _, m := range opts.Menu { 164 | menuMgr.add(m, menu) 165 | } 166 | C.NSStatusBar_AddItem( 167 | opts.Image.c, 168 | C.float(opts.Width), 169 | C.bool(opts.Highlight), 170 | menu) 171 | } 172 | 173 | // Image holds a handle to a platform-specific image structure (e.g. NSImage on macOS). 174 | type Image struct { 175 | c *C.gallium_nsimage_t 176 | } 177 | 178 | var ( 179 | ErrImageDecodeFailed = errors.New("image could not be decoded") 180 | ) 181 | 182 | // ImageFromPNG creates an image from a buffer containing a PNG-encoded image. 183 | func ImageFromPNG(buf []byte) (*Image, error) { 184 | cbuf := C.CBytes(buf) 185 | defer C.free(cbuf) 186 | cimg := C.NSImage_NewFromPNG(cbuf, C.int(len(buf))) 187 | if cimg == nil { 188 | return nil, ErrImageDecodeFailed 189 | } 190 | return &Image{cimg}, nil 191 | } 192 | 193 | // Notification represents a desktop notification 194 | type Notification struct { 195 | Title string 196 | Subtitle string 197 | InformativeText string 198 | Image *Image 199 | Identifier string 200 | ActionButtonTitle string 201 | OtherButtonTitle string 202 | } 203 | 204 | // Post shows the given desktop notification 205 | func (app *App) Post(n Notification) { 206 | var cimg *C.gallium_nsimage_t 207 | if n.Image != nil { 208 | cimg = n.Image.c 209 | } 210 | cn := C.NSUserNotification_New( 211 | C.CString(n.Title), 212 | C.CString(n.Subtitle), 213 | C.CString(n.InformativeText), 214 | cimg, 215 | C.CString(n.Identifier), 216 | len(n.ActionButtonTitle) > 0, 217 | len(n.OtherButtonTitle) > 0, 218 | C.CString(n.ActionButtonTitle), 219 | C.CString(n.OtherButtonTitle)) 220 | 221 | C.NSUserNotificationCenter_DeliverNotification(cn) 222 | } 223 | 224 | // BundleInfo looks up an entry in the Info.plist for the current bundle. 225 | func BundleInfo(key string) string { 226 | cstr := C.MainBundle_ObjectForKey(C.CString(key)) 227 | if cstr == nil { 228 | return "" 229 | } 230 | defer C.free(unsafe.Pointer(cstr)) 231 | return C.GoString(cstr) 232 | } 233 | 234 | // RunApplication is for debugging only. It allows creation of menus and 235 | // desktop notifications without firing up any parts of chromium. It will 236 | // be removed before the 1.0 release. 237 | func RunApplication() { 238 | C.NSApplication_Run() 239 | } 240 | -------------------------------------------------------------------------------- /dist/Gallium.framework/Frameworks: -------------------------------------------------------------------------------- 1 | Versions/Current/Frameworks -------------------------------------------------------------------------------- /dist/Gallium.framework/Gallium: -------------------------------------------------------------------------------- 1 | Versions/Current/Gallium -------------------------------------------------------------------------------- /dist/Gallium.framework/Libraries: -------------------------------------------------------------------------------- 1 | Versions/Current/Libraries -------------------------------------------------------------------------------- /dist/Gallium.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/Current/Resources -------------------------------------------------------------------------------- /dist/Gallium.framework/Versions/A/Gallium: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f9addc20486e8c137abf9243176ede8abaea034a3964d26498a44902e09d38f9 3 | size 1214020 4 | -------------------------------------------------------------------------------- /dist/Gallium.framework/Versions/A/Libraries/ffmpegsumo.so: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:5aa44f3af22436bb46d2979b595e2beb07ba4a73c70c6b6e397cffe303fab3f2 3 | size 2940288 4 | -------------------------------------------------------------------------------- /dist/Gallium.framework/Versions/A/Libraries/libchromiumcontent.dylib: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:49647ce56bbd10fa01690182e6f6e7ed800421503e5f1d1ce518cbb4533ffb6a 3 | size 75485320 4 | -------------------------------------------------------------------------------- /dist/Gallium.framework/Versions/A/Resources/MainMenu.nib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexflint/gallium/bfa1ecd9241eb3b9f203869fb317c55c3c0c360f/dist/Gallium.framework/Versions/A/Resources/MainMenu.nib -------------------------------------------------------------------------------- /dist/Gallium.framework/Versions/A/Resources/WindowController.nib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexflint/gallium/bfa1ecd9241eb3b9f203869fb317c55c3c0c360f/dist/Gallium.framework/Versions/A/Resources/WindowController.nib -------------------------------------------------------------------------------- /dist/Gallium.framework/Versions/A/Resources/content_shell.pak: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:69170143dacce5bf0c05cb0e82afb92c85f7f4eaf4b1073b8d44fdb539260b71 3 | size 7726596 4 | -------------------------------------------------------------------------------- /dist/Gallium.framework/Versions/A/Resources/icudtl.dat: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:dcff79b4692b3e54d9a4fa8feda1fe109750636de9720cda370561047e059579 3 | size 10456832 4 | -------------------------------------------------------------------------------- /dist/Gallium.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /dist/include/gallium/browser.h: -------------------------------------------------------------------------------- 1 | #ifndef GALLIUM_BROWSER_H_ 2 | #define GALLIUM_BROWSER_H_ 3 | 4 | #include "gallium/core.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | // gallium_window represents a window 11 | typedef struct GALLIUM_EXPORT gallium_window gallium_window_t; 12 | 13 | // GalliumLoop runs the chromium browser loop 14 | GALLIUM_EXPORT int GalliumLoop( 15 | int app_id, 16 | const char* argv0, 17 | void(*on_ready)(int), 18 | struct gallium_error** err); 19 | 20 | // GalliumCreateWindow creates a window pointed at the given url 21 | GALLIUM_EXPORT gallium_window_t* GalliumOpenWindow(const char* url, 22 | const char* title, 23 | int width, 24 | int height, 25 | int x, 26 | int y, 27 | bool titleBar, 28 | bool frame, 29 | bool resizable, 30 | bool closeButton, 31 | bool minButton, 32 | bool fullScreenButton); 33 | 34 | 35 | // GalliumWindowGetWidth gets the width of a window 36 | GALLIUM_EXPORT gallium_rect_t GalliumWindowGetShape(gallium_window_t* window); 37 | 38 | // GalliumWindowGetWidth gets the width of a window 39 | GALLIUM_EXPORT void GalliumWindowSetShape(gallium_window_t* window, 40 | int width, 41 | int height, 42 | int left, 43 | int top); 44 | 45 | // GalliumWindowGetURL gets the URL that the window is currently at 46 | GALLIUM_EXPORT const char* GalliumWindowGetURL(gallium_window_t* window); 47 | 48 | // GalliumWindowLoadURL causes the window to load the given URL 49 | GALLIUM_EXPORT void GalliumWindowLoadURL(gallium_window_t* window, const char* url); 50 | 51 | // GalliumWindowReload reloads the current page 52 | GALLIUM_EXPORT void GalliumWindowReload(gallium_window_t* window); 53 | 54 | // GalliumWindowReload reloads the current page, ignoring any cached resources 55 | GALLIUM_EXPORT void GalliumWindowReloadNoCache(gallium_window_t* window); 56 | 57 | // GalliumWindowOpen opens the window (only for use after GalliumWindowClose) 58 | GALLIUM_EXPORT void GalliumWindowOpen(gallium_window_t* window); 59 | 60 | // GalliumWindowClose closes the window 61 | GALLIUM_EXPORT void GalliumWindowClose(gallium_window_t* window); 62 | 63 | // GalliumWindowClose miniaturizes the window 64 | GALLIUM_EXPORT void GalliumWindowMiniaturize(gallium_window_t* window); 65 | 66 | // GalliumWindowUndo undoes the last text editing action 67 | GALLIUM_EXPORT void GalliumWindowUndo(gallium_window_t* window); 68 | 69 | // GalliumWindowRedo redoes the last text editing action 70 | GALLIUM_EXPORT void GalliumWindowRedo(gallium_window_t* window); 71 | 72 | // GalliumWindow cuts the current text selection to the pastboard 73 | GALLIUM_EXPORT void GalliumWindowCut(gallium_window_t* window); 74 | 75 | // GalliumWindow copies the current text selection to the pasteboard 76 | GALLIUM_EXPORT void GalliumWindowCopy(gallium_window_t* window); 77 | 78 | // GalliumWindow pastes from the pasteboard 79 | GALLIUM_EXPORT void GalliumWindowPaste(gallium_window_t* window); 80 | 81 | // GalliumWindow pastes from the pasteboard, matching style to the current element 82 | GALLIUM_EXPORT void GalliumWindowPasteAndMatchStyle(gallium_window_t* window); 83 | 84 | // GalliumWindow deletes the current text selection 85 | GALLIUM_EXPORT void GalliumWindowDelete(gallium_window_t* window); 86 | 87 | // GalliumWindow selects all text in the current element 88 | GALLIUM_EXPORT void GalliumWindowSelectAll(gallium_window_t* window); 89 | 90 | // GalliumWindowUnselect unselects any text selection 91 | GALLIUM_EXPORT void GalliumWindowUnselect(gallium_window_t* window); 92 | 93 | // GalliumWindowOpenDevTools opens the developer tools for the given window 94 | GALLIUM_EXPORT void GalliumWindowOpenDevTools(gallium_window_t* window); 95 | 96 | // GalliumWindowCloseDevTools closes the developer tools for the given window 97 | GALLIUM_EXPORT void GalliumWindowCloseDevTools(gallium_window_t* window); 98 | 99 | // GalliumWindowDevToolsVisible returns true if the developer tools are currently visible for the given window 100 | GALLIUM_EXPORT bool GalliumWindowDevToolsAreOpen(gallium_window_t* window); 101 | 102 | // GalliumWindowNativeWindow gets a native handle for this window (NSWindow*). 103 | GALLIUM_EXPORT void* GalliumWindowNativeWindow(gallium_window_t* window); 104 | 105 | // GalliumWindowNativeWindow gets a native handle for the window controller (NSWindowController*). 106 | GALLIUM_EXPORT void* GalliumWindowNativeController(gallium_window_t* window); 107 | 108 | // GalliumWindowNativeWindow gets a native handle for the window content (content::WebContent*). 109 | GALLIUM_EXPORT void* GalliumWindowNativeContent(gallium_window_t* window); 110 | 111 | #ifdef __cplusplus 112 | } 113 | #endif 114 | 115 | #endif // ifndef GALLIUM_BROWSER_H_ 116 | -------------------------------------------------------------------------------- /dist/include/gallium/cocoa.h: -------------------------------------------------------------------------------- 1 | #ifndef GALLIUM_COCOA_H_ 2 | #define GALLIUM_COCOA_H_ 3 | 4 | #include "gallium/core.h" 5 | #include "gallium/browser.h" 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | typedef void(*gallium_callback_t)(void*); 12 | 13 | typedef struct GALLIUM_EXPORT gallium_nsmenu gallium_nsmenu_t; 14 | typedef struct GALLIUM_EXPORT gallium_nsmenuitem gallium_nsmenuitem_t; 15 | typedef struct GALLIUM_EXPORT gallium_nsusernotification gallium_nsusernotification_t; 16 | typedef struct GALLIUM_EXPORT gallium_nsimage gallium_nsimage_t; 17 | 18 | GALLIUM_EXPORT gallium_nsmenu_t* NSMenu_New(const char* title); 19 | 20 | GALLIUM_EXPORT gallium_nsmenuitem_t* NSMenu_AddMenuItem( 21 | gallium_nsmenu_t* menu, 22 | const char* title, 23 | const char* shortcutkey, 24 | const gallium_modifier_t shortcutModifier, 25 | gallium_callback_t callback, 26 | void* callbackArg); 27 | 28 | GALLIUM_EXPORT void NSMenu_AddSeparator( 29 | gallium_nsmenu_t* menu); 30 | 31 | GALLIUM_EXPORT void NSMenuItem_SetSubmenu( 32 | gallium_nsmenuitem_t* menuitem, 33 | gallium_nsmenu_t* submenu); 34 | 35 | GALLIUM_EXPORT void NSStatusBar_AddItem( 36 | gallium_nsimage_t* image, 37 | float width, 38 | bool highlightMode, 39 | gallium_nsmenu_t* menu); 40 | 41 | 42 | GALLIUM_EXPORT gallium_nsusernotification_t* NSUserNotification_New( 43 | const char* title, 44 | const char* subtitle, 45 | const char* informativeText, 46 | gallium_nsimage_t* contentImage, 47 | const char* identifier, 48 | bool hasActionButton, 49 | bool hasReplyButton, 50 | const char* actionButtonTitle, 51 | const char* otherButtonTitle); 52 | 53 | GALLIUM_EXPORT void NSUserNotificationCenter_DeliverNotification( 54 | gallium_nsusernotification_t* n); 55 | 56 | GALLIUM_EXPORT gallium_nsimage_t* NSImage_NewFromPNG( 57 | const void* buf, 58 | int size); 59 | 60 | GALLIUM_EXPORT void NSImage_WriteToFile(gallium_nsimage_t* img, const char* path); 61 | 62 | GALLIUM_EXPORT void NSApplication_SetMainMenu( 63 | gallium_nsmenu_t* submenu); 64 | 65 | GALLIUM_EXPORT void NSApplication_Run(); 66 | 67 | GALLIUM_EXPORT char* MainBundle_ObjectForKey(const char* key); 68 | 69 | #ifdef __cplusplus 70 | } 71 | #endif 72 | 73 | #endif // ifndef GALLIUM_COCOA_H_ 74 | -------------------------------------------------------------------------------- /dist/include/gallium/core.h: -------------------------------------------------------------------------------- 1 | #ifndef GALLIUM_EXPORT_H_ 2 | #define GALLIUM_EXPORT_H_ 3 | 4 | #include 5 | #include 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | // GALLIUM_EXPORT is the macro for exporting symbols 12 | #define GALLIUM_EXPORT __attribute__ ((visibility ("default"))) 13 | 14 | // gallium_error represents an error 15 | typedef struct GALLIUM_EXPORT gallium_error { 16 | const char* msg; 17 | } gallium_error_t; 18 | 19 | // gallium_modifier represents a modifier mask for a keyboard shortcut 20 | typedef enum GALLIUM_EXPORT gallium_modifier { 21 | GalliumCmdModifier = 1 << 0, 22 | GalliumCtrlModifier = 1 << 1, 23 | GalliumCmdOrCtrlModifier = 1 << 2, 24 | GalliumAltOrOptionModifier = 1 << 3, 25 | GalliumFnModifier = 1 << 4, 26 | GalliumShiftModifier = 1 << 5, 27 | } gallium_modifier_t; 28 | 29 | // gallium_rect represents a rectangle 30 | typedef struct GALLIUM_EXPORT { 31 | int width; 32 | int height; 33 | int left; 34 | int bottom; 35 | } gallium_rect_t; 36 | 37 | #ifdef __cplusplus 38 | } 39 | #endif 40 | 41 | #endif // ifndef GALLIUM_EXPORT_H_ 42 | 43 | -------------------------------------------------------------------------------- /dist/include/gallium/globalshortcut.h: -------------------------------------------------------------------------------- 1 | #ifndef GALLIUM_GLOBALSHORTCUT_H_ 2 | #define GALLIUM_GLOBALSHORTCUT_H_ 3 | 4 | #include "gallium/core.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | typedef void(*global_shortcut_handler_t)(int64_t); 11 | 12 | void GALLIUM_EXPORT GalliumAddGlobalShortcut(int ID, 13 | const char* key, 14 | gallium_modifier_t mask, 15 | global_shortcut_handler_t handler); 16 | 17 | #ifdef __cplusplus 18 | } 19 | #endif 20 | 21 | #endif // ifndef GALLIUM_GLOBALSHORTCUT_H_ 22 | 23 | -------------------------------------------------------------------------------- /dist/include/gallium/screen.h: -------------------------------------------------------------------------------- 1 | #ifndef GALLIUM_SCREEN_H_ 2 | #define GALLIUM_SCREEN_H_ 3 | 4 | #include "gallium/core.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | typedef struct GALLIUM_EXPORT gallium_screen gallium_screen_t; 11 | 12 | // GalliumScreenCount gets the number of screens 13 | GALLIUM_EXPORT int GalliumScreenCount(); 14 | 15 | // GalliumScreen gets the i-th screen 16 | GALLIUM_EXPORT gallium_screen_t* GalliumScreen(int index); 17 | 18 | // GalliumFocusedScreen gets the screen containing the currently focused window. 19 | GALLIUM_EXPORT gallium_screen_t* GalliumFocusedScreen(); 20 | 21 | // GalliumScreenShape gets the shape of a screen 22 | GALLIUM_EXPORT gallium_rect_t GalliumScreenShape(gallium_screen_t*); 23 | 24 | // GalliumScreenShape gets the usable area of a screen (excludes dock and menubar) 25 | GALLIUM_EXPORT gallium_rect_t GalliumScreenUsable(gallium_screen_t*); 26 | 27 | // GalliumScreenBitsPerPixel gets the bits per pixels for a screen 28 | GALLIUM_EXPORT int GalliumScreenBitsPerPixel(gallium_screen_t*); 29 | 30 | // GalliumScreenID gets the unique ID for a screen 31 | GALLIUM_EXPORT int GalliumScreenID(gallium_screen_t*); 32 | 33 | #ifdef __cplusplus 34 | } 35 | #endif 36 | 37 | #endif // ifndef GALLIUM_SCREEN_H_ 38 | -------------------------------------------------------------------------------- /examples/controller/controller.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "os" 8 | "runtime" 9 | 10 | "github.com/alexflint/gallium" 11 | ) 12 | 13 | var html = ` 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ` 27 | 28 | type app struct { 29 | ui *gallium.App 30 | window *gallium.Window 31 | } 32 | 33 | func (a *app) handleIndex(w http.ResponseWriter, r *http.Request) { 34 | fmt.Fprint(w, html) 35 | } 36 | 37 | func (a *app) handleResize(w http.ResponseWriter, r *http.Request) { 38 | shape := a.window.Shape() 39 | shape.Width += 100 40 | shape.Height += 100 41 | a.window.SetShape(shape) 42 | http.Redirect(w, r, "/", http.StatusTemporaryRedirect) 43 | } 44 | 45 | func (a *app) handleReload(w http.ResponseWriter, r *http.Request) { 46 | a.window.Reload() 47 | } 48 | 49 | func (a *app) handleReloadNoCache(w http.ResponseWriter, r *http.Request) { 50 | a.window.ReloadNoCache() 51 | } 52 | 53 | func (a *app) handleClose(w http.ResponseWriter, r *http.Request) { 54 | a.window.Close() 55 | } 56 | 57 | func (a *app) handleMiniaturize(w http.ResponseWriter, r *http.Request) { 58 | a.window.Miniaturize() 59 | } 60 | 61 | func (a *app) handleWindow(w http.ResponseWriter, r *http.Request) { 62 | a.ui.OpenWindow("http://www.example.com/", gallium.FramedWindow) 63 | http.Redirect(w, r, "/", http.StatusTemporaryRedirect) 64 | } 65 | 66 | func (a *app) handleLoad(w http.ResponseWriter, r *http.Request) { 67 | a.window.LoadURL("http://www.example.com/") 68 | } 69 | 70 | func (a *app) handleOpenDev(w http.ResponseWriter, r *http.Request) { 71 | a.window.OpenDevTools() 72 | http.Redirect(w, r, "/", http.StatusTemporaryRedirect) 73 | } 74 | 75 | func (a *app) handleCloseDev(w http.ResponseWriter, r *http.Request) { 76 | a.window.CloseDevTools() 77 | http.Redirect(w, r, "/", http.StatusTemporaryRedirect) 78 | } 79 | 80 | func onReady(ui *gallium.App) { 81 | opts := gallium.FramedWindow 82 | opts.Shape.Width = 300 83 | opts.Shape.Height = 200 84 | window, err := ui.OpenWindow("http://127.0.0.1:9478/", opts) 85 | if err != nil { 86 | log.Fatal(err) 87 | } 88 | 89 | app := app{ 90 | ui: ui, 91 | window: window, 92 | } 93 | http.HandleFunc("/", app.handleIndex) 94 | http.HandleFunc("/resize", app.handleResize) 95 | http.HandleFunc("/reload", app.handleReload) 96 | http.HandleFunc("/reloadnocache", app.handleReloadNoCache) 97 | http.HandleFunc("/close", app.handleClose) 98 | http.HandleFunc("/miniaturize", app.handleMiniaturize) 99 | http.HandleFunc("/window", app.handleWindow) 100 | http.HandleFunc("/load", app.handleLoad) 101 | http.HandleFunc("/opendev", app.handleOpenDev) 102 | http.HandleFunc("/closedev", app.handleCloseDev) 103 | go http.ListenAndServe(":9478", nil) 104 | } 105 | 106 | func main() { 107 | runtime.LockOSThread() 108 | gallium.RedirectStdoutStderr(os.ExpandEnv("$HOME/Library/Logs/Gallium.log")) 109 | gallium.Loop(os.Args, onReady) 110 | } 111 | -------------------------------------------------------------------------------- /examples/desktop-notification/bindata.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-bindata. 2 | // sources: 3 | // gopher.png 4 | // DO NOT EDIT! 5 | 6 | package main 7 | 8 | import ( 9 | "bytes" 10 | "compress/gzip" 11 | "fmt" 12 | "io" 13 | "io/ioutil" 14 | "os" 15 | "path/filepath" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | func bindataRead(data []byte, name string) ([]byte, error) { 21 | gz, err := gzip.NewReader(bytes.NewBuffer(data)) 22 | if err != nil { 23 | return nil, fmt.Errorf("Read %q: %v", name, err) 24 | } 25 | 26 | var buf bytes.Buffer 27 | _, err = io.Copy(&buf, gz) 28 | clErr := gz.Close() 29 | 30 | if err != nil { 31 | return nil, fmt.Errorf("Read %q: %v", name, err) 32 | } 33 | if clErr != nil { 34 | return nil, err 35 | } 36 | 37 | return buf.Bytes(), nil 38 | } 39 | 40 | type asset struct { 41 | bytes []byte 42 | info os.FileInfo 43 | } 44 | 45 | type bindataFileInfo struct { 46 | name string 47 | size int64 48 | mode os.FileMode 49 | modTime time.Time 50 | } 51 | 52 | func (fi bindataFileInfo) Name() string { 53 | return fi.name 54 | } 55 | func (fi bindataFileInfo) Size() int64 { 56 | return fi.size 57 | } 58 | func (fi bindataFileInfo) Mode() os.FileMode { 59 | return fi.mode 60 | } 61 | func (fi bindataFileInfo) ModTime() time.Time { 62 | return fi.modTime 63 | } 64 | func (fi bindataFileInfo) IsDir() bool { 65 | return false 66 | } 67 | func (fi bindataFileInfo) Sys() interface{} { 68 | return nil 69 | } 70 | 71 | var _gopherPng = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x8c\xb8\x73\x8c\x27\xc0\xbf\x68\xf9\x6d\x7f\xdb\xb6\xad\x69\xbb\xa7\x6d\xdb\x9c\xb6\x6d\xdb\xb6\x6d\xdb\x3d\xad\x69\xdb\xb6\x6d\x73\x73\xdf\xdd\xdc\xcd\xdb\xf7\xdb\x64\x53\xa9\x54\x52\x75\x4e\x52\xf9\xfc\x79\x22\x14\xe4\xc4\xe1\x61\x70\x60\x00\x00\x00\xbc\xa4\x84\x88\x12\x00\x00\x30\x06\x00\x00\xca\x40\x00\x00\x00\x90\x79\xfe\xca\x01\x00\x40\x8f\x2d\x84\x85\x15\x2c\x8c\x8c\x00\x00\xaa\x04\xa3\x3f\x7f\x72\x77\x4f\xf7\x0f\xc7\xc9\xfa\xfa\x14\x14\x54\x94\x0e\x09\x0e\xb1\xb0\x91\xb1\x4b\xfa\x7c\x73\x03\x62\x02\x32\x40\xe0\x50\x92\xa2\x60\xf1\xc0\x40\xbd\x05\x2e\xbb\x3f\xbb\x55\x20\x55\xfa\x46\xfb\x6e\xaf\x70\xc2\x8d\xff\xb4\x57\xc3\x32\x47\x05\xb0\xf6\x65\xc2\x35\x63\x49\x10\x02\x0c\x8b\x92\x17\xf7\x8d\x89\xbe\x20\xc2\x17\x26\xa4\x61\xfc\xf7\xa3\x99\x04\x5a\x25\x94\xd8\xd3\x75\x01\x86\x66\x61\xe0\x5c\x60\x6b\x74\x70\x70\x32\x3c\xe4\x15\xe8\x44\x7f\xe1\xa6\xdf\x20\x52\x44\x33\xce\xb0\xc9\x61\x42\x53\x53\x53\x61\x62\xa9\xdf\x24\x2b\x60\xfa\x53\x86\xc8\x2b\x3a\x86\x60\x72\x3d\xc6\xde\x44\x21\xde\x44\x23\xc8\x2b\xa8\x05\x5a\x14\x7e\x0b\xf8\xaf\x90\xf7\x8a\xf4\xf3\x32\xb1\xc8\xf7\x8f\x0a\xa4\x88\xb8\xa1\x82\xe7\xdb\x07\xd7\x23\xed\x05\x21\x09\xfb\x1f\xce\x17\x32\x03\x61\xab\x25\xfc\x05\x03\xfa\xfd\xfb\x03\x88\xfd\x89\x13\x0a\x72\x0d\x40\xfe\x29\x86\x72\x44\xef\x47\xd3\x47\x97\x44\xd3\x62\xdd\x30\x45\x30\x41\x30\xc9\xfe\xe9\xec\xfb\x35\x89\x37\xd6\x1d\xff\x06\x1f\x45\x2e\x0d\x35\x41\xa7\xc6\xbb\xac\xb6\xec\x65\x9d\x6a\xbd\xb0\xda\xd3\x10\xd6\x74\x73\xac\xb0\x7f\xba\xff\x3a\x90\x31\xea\x11\x30\x91\x60\x05\xdb\x46\xf1\x02\x4a\x40\x9d\x5b\xd1\x2d\xd4\x9d\xf4\x1a\xfc\x56\x7a\x37\x71\xcb\x80\x31\x8b\xbc\x4a\x7c\x4a\xe3\x2e\x4a\xa5\x7a\x1a\x66\x5f\x17\x1e\x39\xaa\x37\x67\x3f\xc7\x60\x98\x34\x18\x33\x58\x27\xa1\x22\x31\x2e\x49\x90\x34\x9e\x96\x9a\xca\x47\xac\x4c\x51\x45\xef\x25\x5d\x2b\xbf\x2d\xfa\x1d\x37\xa0\x36\x24\x44\xce\x5b\xd2\x36\x6e\xa5\x0a\xa3\x22\xab\xe2\x57\x89\xaf\x72\x33\x0f\x5c\x52\x99\xff\xcc\x77\x11\xf0\x4a\x94\xab\xac\x4d\xc9\xcd\xdf\xa6\xfd\x22\x85\x84\x85\xb3\x8a\x31\x8c\xb6\x91\x3d\x94\xcd\x90\xd5\xcf\xd4\x65\xdc\xb1\x68\x18\x74\x0c\x54\xcd\x71\xa9\x95\xcd\xb6\xc9\xcf\xa9\xfa\xdb\x12\xd4\x50\x14\x9b\x94\xd9\x94\x18\x15\x58\xd5\x9d\x15\xd8\xd5\x18\xd6\x59\x56\x9b\x16\xd1\x16\x13\xd7\x65\x57\xaf\x1f\xe8\xe7\x2b\xe0\xb3\x3b\x98\x5f\xd2\xdf\x25\x44\x1f\xda\x03\x67\x16\x19\x1f\xd5\x1c\x19\x19\x5e\x1b\x9e\x81\xdd\x45\x79\x2d\x08\xaa\x5d\x68\xab\xa4\xe1\x28\x3c\x9e\xb9\x98\x75\x86\x87\x8d\x91\x91\xa1\xe9\xd4\xc4\x70\xdc\x78\xb2\x76\xa2\x8e\x66\x4d\xe3\xad\x5e\xad\x9b\xa8\xda\x59\x79\x29\x1b\xe2\x6b\x22\xb9\x54\x8b\x33\xac\x55\xd7\x59\xf1\x84\x88\xfb\x94\x69\x99\x39\xd3\x3a\xde\x82\xde\x4a\x78\x95\x7c\x4d\x73\xce\xb7\x55\xb5\xbe\xb2\xea\xe7\x56\x38\x55\xbc\x96\xf4\x23\x17\x93\xe7\xea\x1e\x3a\x3c\xf9\xad\xb9\x73\x42\x36\xc9\xcb\xbb\xe3\xdd\x3f\x2f\x4a\x12\xa5\x9d\xe6\xad\x9d\xda\xf1\xb9\x3d\x1e\xaf\x5a\xb6\xd2\x5e\xbd\xd5\xb6\xed\xf8\xaf\x55\x96\x22\xab\xcd\x9a\x6d\xad\xd5\x27\x6c\x69\xfa\xab\x4b\xf7\x25\x7b\x0b\xb3\xf5\xa7\xbf\x3f\x5b\x98\x3f\xae\x99\x10\x95\xbf\x67\xbf\xf8\xa9\xc2\xc9\x2c\x71\xe6\x9b\x9b\xff\xac\xf0\xb9\xa4\x48\x1d\xa5\xee\xbb\x8b\xef\xa4\x74\xaa\xa6\xcb\xed\xfc\x86\x7a\x87\xff\x8e\xf1\xa6\x61\x35\xe9\x6b\x86\x59\x3f\xbc\x9b\xe7\x95\xfb\xa7\xe6\xb3\x0b\x96\x30\x93\x2f\xc3\xd7\xf7\xc9\xa5\xff\xe5\x36\x0f\xb4\x57\xee\xcb\xe2\xeb\xe4\xf5\x87\x1f\x00\x0f\x40\x64\xf5\x2b\x1f\x8a\x01\x00\x00\xa0\x86\xd2\xe2\x22\x80\x9f\xb0\xb8\xc9\x5e\x00\x00\x00\x6d\x2f\xa1\xe9\x04\x00\xe8\x88\xfd\xd7\x06\x41\x4b\x16\x16\x04\x00\x00\x50\xce\x92\xb2\xa2\x50\xbb\xd0\xd8\xf0\x58\xa8\x8f\x97\x01\x89\x00\x00\x66\xa3\xa7\x8a\x86\xb3\x92\x81\x1b\x91\xbd\xa3\x9d\xa9\x85\xb5\x09\x91\xb3\x87\xbd\x09\x11\xa7\xa1\x85\x0d\x00\x60\x1e\x50\x92\x25\xe9\x4a\xca\xe5\x16\x72\xa2\x11\x92\xe6\xdf\xa3\x89\xb0\xee\xc4\x73\xfb\x5d\xf6\x4d\x2f\x11\x99\x9c\xac\xea\x97\x92\x53\x29\x93\x10\x0d\x04\xbf\xcd\xdc\x20\xd2\x28\x8d\xfd\xf9\x26\x7c\xd0\xff\x1c\xa3\x36\x41\xb5\x7c\x6c\x18\x32\x29\x92\x7f\xbe\x88\x6b\xfa\x39\x9b\xf9\xe8\x9c\x7d\xda\xe6\xe1\x23\x38\x1a\x01\xe4\x91\x3a\xd4\xbf\x6e\xde\xd3\x33\xe5\x26\x4a\x1d\xf2\xfe\xf0\xc5\xf1\xf9\xb1\x28\x34\x92\x27\xa7\x0b\x3d\xbf\xdd\xb2\xec\x04\x0d\x7a\x39\xc8\x7d\x5e\x26\xde\xbf\xbf\xbd\xad\x7c\xc4\x97\xed\xe9\xd5\xa1\x4e\x7f\x4c\xc3\x6d\x48\xf9\xc0\xa3\xbc\x25\xaf\xd4\xe1\x7e\x19\x94\x72\x31\x29\x6c\xae\x1d\x89\xf6\x19\xca\x54\x94\x9e\xa8\xfc\x5d\xd0\xa9\x99\x4a\xe9\x1d\xd1\xa2\x3b\xfa\x46\xf8\x3c\x5e\xce\x38\x18\xe2\xe7\x7b\xd1\xd2\x3a\x14\x18\xfd\x3c\x12\xa8\x8f\xeb\xe5\xb6\x67\x90\x88\x4b\x72\xf2\xea\x41\x88\x33\xb2\xb0\x27\x00\x0b\x7e\xfe\x9c\xd2\x2f\x82\x25\xa5\x2c\x79\xa2\xc5\x3d\xc8\x19\xdc\xfb\xe1\xc6\x9e\xb6\x8a\x38\x76\x63\x2f\x34\x44\x7f\xfb\xb4\x27\xeb\x96\xf3\x85\x7a\x26\xd8\xe2\x27\x35\x25\x60\x9a\xc9\xad\xff\x82\x2b\x95\xe5\x4b\x43\xd9\x7f\xf7\x09\xde\xe3\x91\x74\x37\x7b\x57\x15\x77\xe3\x9c\xfa\xf5\xb9\x47\x62\x9b\x64\xf6\xb6\x90\x30\x15\x63\x38\xc6\x6d\xe8\x8b\x57\xa8\x75\x1f\xdd\xab\xfc\x55\x9d\xc3\x3f\x95\xb5\x0f\xf4\x48\xd7\xd9\xdf\xfb\x33\xb3\xe7\x86\xd5\xd2\xae\x01\xfc\xbe\xd0\x70\xf7\xcb\x2d\xa2\x0c\xe2\x32\x2d\xcc\xf2\x69\x9e\x34\xdf\xd8\xf1\x33\x54\x84\xe3\x93\xff\x8c\x7a\xd0\x78\x34\x12\x78\xc8\x93\x33\xee\x4b\x2f\x5f\xc4\xed\xbf\x14\x56\xea\xde\xbf\x61\x99\xf2\x32\x96\x7d\xe6\xd7\xbf\xd5\xc2\xe9\x3f\x79\xfd\xf0\x16\x9e\x50\x1b\xb0\xaa\xca\xeb\x9d\xb9\x31\x26\x33\xd1\xca\xf4\xf0\x9a\x76\xeb\x76\xec\xcd\xfa\xbd\xa6\xc3\xaa\xf0\xba\xb6\xc5\x1d\x6f\x0d\xbd\xb3\xe3\x17\xa2\x03\x91\xe0\x48\xbf\x83\x61\xfd\xed\x0a\x92\x19\xf1\x65\x06\xdb\xc3\x04\xca\x1a\x58\xf8\xe3\x10\x00\x08\xd4\x93\xb9\x7a\x38\xac\xe3\x83\xf3\xfa\xcb\x6f\x12\xc2\xa7\xfa\xe0\x3b\x52\xf3\xb6\xfb\xf0\x05\x05\x77\x12\x77\xf7\x9d\xac\xd7\xf9\xb3\x78\xe3\x16\xf5\x7c\x60\xcb\x40\x44\xf7\xe4\xb3\xd9\xb2\x33\xb2\xe5\x1b\xe4\xc7\xb9\x77\x10\x6c\x27\xed\x39\x75\xe7\x52\xcb\xb0\x33\xae\xbf\x7b\x33\x19\xb7\xf0\x35\xd2\x47\x28\x88\x1e\x1d\x8c\x29\x46\x4b\x55\xa7\xe9\x47\xa7\x09\xd6\x11\x17\x35\x31\x32\xd4\x56\x3f\xcd\xff\x69\xf4\x88\xa8\x59\xce\xbf\xaf\xc2\x0f\xf9\xf9\x3d\xd0\x2b\xf0\xc5\x6b\xa0\x6b\x96\x0b\xf2\x39\x29\xf2\xc9\xb3\xe7\xd1\xdd\x5b\x17\x59\xf7\xea\x63\x7a\x0f\x6f\xcd\x3e\x3c\xd2\x98\xe8\x07\x3f\x3e\x14\xd1\x7e\xf8\x75\x18\xff\xfc\xc9\x4b\xa8\x12\x7f\x40\xaa\x46\xec\x3d\x34\xfa\xdd\xc4\xef\xc7\x77\xe3\x61\xf4\xc0\x96\xe4\xc7\x3f\xd8\xc6\x7f\xbc\xe1\xb1\x43\xdf\x1e\xc3\xd3\x2b\xf0\x34\x44\x8a\x59\xc0\xd5\xe8\x14\x8c\xd5\x33\xbb\xf4\x49\xe0\x3f\x0c\xce\x39\x70\xd1\x54\x37\xdb\xab\x95\xde\x4f\x5c\xf9\x69\x84\x7f\x0c\x0f\xe3\xc7\xb7\x57\x48\x26\x1c\x8e\xc6\x17\xeb\x07\x2e\x52\x9c\x52\xf5\xc6\xd4\xf7\xdb\x4c\x13\x0b\x28\x1b\xa8\x46\x6e\x5f\x86\x7d\x82\x29\xd4\xb1\x13\x7c\x27\xe7\xe7\xa9\xff\xca\xf3\x1e\x29\xe5\x41\x58\x50\xf9\x6d\x78\x3c\x9c\xfa\x6b\x18\xc9\x64\xa3\x97\x2f\x06\x29\x8f\xd6\x20\x92\xea\x1b\xaa\x50\x2e\x9f\x39\xbd\xce\xcb\x27\xfb\x95\xc7\x20\x60\x4e\xf2\x2e\x40\x26\xa5\x64\x10\xfb\xda\xb2\x34\x0e\x3c\x59\xad\x5a\x25\xf9\x8d\xdd\x75\x65\xb5\xae\x40\x9f\xd2\x07\xe2\x2f\xf2\xed\x7d\x09\x85\x6f\x72\xc0\xdb\xef\xd4\x2f\x0e\x93\xdb\x00\xf1\x9f\x5e\x66\xc6\x3a\xee\xd7\x09\x66\xa0\x68\x43\xc6\x4e\xc8\xde\x0d\x81\x42\xfd\xcf\x73\x71\xbb\x84\xef\x5b\x05\x63\x6f\x4e\x12\xb1\xac\x0a\xdd\xe4\x74\xd4\x3d\x07\xa5\x6d\xc8\xb7\x83\x30\xf3\xc8\xa6\x11\x69\x77\x36\xee\xca\x92\x5c\x9a\xdb\xd4\x0a\xf9\xd3\xc9\x99\x1f\x8b\xf3\x84\xaa\x8b\xaa\xa7\x33\xc7\x08\xb9\xdd\x43\x51\xfb\xb4\x19\xc8\xa0\x7e\x19\xff\x0c\x8d\xe6\x52\xb2\xaa\x19\xc3\xb1\xea\x72\x66\x31\x28\xe6\xe0\x0b\x84\x1f\x87\xf7\xbb\xcb\xb3\x55\x3e\x7f\xf4\x4c\x5e\xff\x39\x35\x69\x8d\xfe\x83\xe7\xca\xed\x09\x5d\xb6\xd7\xd6\xc0\x26\xb9\xc4\xee\x57\xe7\x21\xad\xdd\x47\xea\xd0\x85\x4f\x17\xc9\x08\x1f\x44\x4b\xfd\x1e\x46\xec\x9b\xf0\xd9\xd3\xe7\xaf\xa9\x3c\xca\x9f\xbd\xcd\x2c\xb6\xe9\x84\xc7\x89\x0b\x42\x1d\xc6\x5c\x3a\xdd\x1f\xcb\xd7\x3a\x8c\x37\x05\x82\xa7\x87\xd8\x99\x1d\xbe\xbe\xa2\x3c\x3f\x3e\xce\x1b\x5a\x2b\x0e\x24\x7d\xcc\xee\xb5\x67\x69\x58\x50\x07\xc2\x6e\x3f\xf5\x1f\x6f\x6d\xc2\x95\xfe\x3a\xb7\x7c\xe5\x73\x16\x28\x5c\x14\x67\xcf\x83\x69\xcd\xa7\x6c\xda\x72\x8a\x08\xb5\x2e\x4f\x25\x3f\x92\xd4\x7d\x07\x6f\xda\xab\x0f\x97\x49\x1e\xce\x07\x2b\x36\x9c\x3f\x3c\x37\xac\x8a\x80\x77\x8b\xcf\x9c\xcf\x8d\x4b\xe7\x69\x84\x52\x96\x6c\xa7\x31\x02\xbe\x9c\xe7\xf6\xc3\x4a\xd8\x61\x08\x3d\x70\x5f\x1a\xca\xa9\x97\x99\x17\xb7\x9f\xb7\x11\xef\x6e\x07\xf3\x96\xfe\x19\xa1\x4d\x3a\xca\x88\x36\xfc\xe0\x82\x9d\xee\xc8\x3a\xf5\x97\x96\x8f\xee\x92\xed\x8c\x19\xc4\xef\x2d\xbc\x6d\xfe\x13\x19\x88\x2c\x90\x73\x52\x1a\xdd\xd6\x21\x8f\xfe\xc6\x13\xe5\x30\x7e\x17\x0e\xcb\x8b\x8c\x95\x69\x72\x64\x03\xd7\x5c\x6c\x99\xe5\xbf\xbe\x93\x0c\x01\x48\xd7\xd4\xb9\xb0\x73\x67\xad\x9b\x7f\x50\xb7\xbd\xb6\xdb\xa4\x7b\xf5\xdf\x12\xd9\x72\x41\x7c\x75\xce\x09\xdc\x62\x32\xd7\xf0\x35\x96\x44\x50\x1d\x79\xee\xc6\x44\x30\x3b\x4e\x36\x3b\x96\x6f\x37\xfe\xa1\x51\x27\x76\x5f\xea\xdc\x4a\xa8\x35\x42\x4b\xb6\xaf\xea\xb8\xfd\x13\xbd\x09\x3a\xb1\xbf\x47\x25\x44\x11\xea\xfb\x56\xed\xc5\x4b\x0d\x44\xb0\xe3\x6b\x98\xbf\x9a\x74\x88\x97\x03\x46\xcf\x7f\xdd\xb2\xed\x56\x1a\xc2\xe6\x93\xbd\x72\xfa\x60\x12\x07\x30\x4c\x94\x35\x53\x8f\x1e\x40\x95\xf0\xff\x0b\x44\x61\x0f\xc4\x49\x53\xd0\xba\xdc\xad\xc5\x6e\x28\x81\x7e\xbd\x76\x37\x94\x27\x9d\x6a\x9e\x97\x13\xbf\x2d\x21\x28\x51\xf6\x22\x78\x3c\x1e\x17\x42\x1d\x2e\x31\x22\xf6\xa8\x6f\x4f\xd1\x13\xcb\x30\x49\xd1\x95\x63\xba\xb6\x20\xc8\x05\x89\x25\x6c\x50\xe2\x51\x92\xb8\x91\x70\x3f\x4f\xed\x89\x8a\x63\x44\xbb\xbf\x00\x35\x7b\x3d\xcf\x64\xa1\xef\xd9\x67\xd4\x35\x3c\x32\x37\x21\x49\xfc\x72\x8d\x14\xf9\x37\x43\x61\xf9\x43\xcf\x4f\x70\xd2\x31\x44\x0b\x5c\x79\xa9\xff\x3c\x42\x87\x4b\x32\x44\xac\xa5\x22\x19\x90\x91\x0c\xbe\xf2\x13\xdf\x47\x0e\xeb\x85\x0d\x7a\xc3\x87\xd2\xff\x46\x83\x1e\x6f\xdc\x11\xfb\x96\xe8\x1c\x28\xdc\xe7\x19\x73\x4b\xd2\xbe\x69\xe1\xd0\x44\x17\x33\xd4\xca\x1f\xbc\xf4\x32\x1e\xcb\x4c\x78\x77\x24\x5d\xca\x08\xc2\xcd\x7a\xa9\xad\x0b\x36\xc4\x34\x7c\xfa\x98\xa0\xf4\xf3\x13\x7d\xb8\x48\xf2\x7b\x00\xa9\xef\x48\x47\x14\x92\xc3\xd8\xf7\x10\x92\xc9\x7a\xec\xec\x9c\xd0\x58\x78\xca\xa8\xda\x21\x6f\xa6\x2e\xf2\x87\xd8\x22\xab\x78\x63\x57\x26\xe4\x7b\x94\x85\x53\xb3\x2b\x8b\x3d\x3e\x8f\xac\x52\x6d\xca\x9c\x88\x5a\x7f\x6e\x6a\xeb\xe8\xaf\x1a\x96\x3d\xaf\x36\x82\x67\x3d\xd5\x23\x7d\xd5\x93\x76\x2c\xaf\xde\xd3\xe2\xc9\xe2\x9c\x94\x7a\x19\x56\x18\x87\xa6\xfb\xcd\x8f\x00\xc2\x38\x51\x87\x21\xcc\x4b\x1d\xbd\x28\x81\x9a\xd6\x0f\x7b\x98\x05\xdd\x19\x3f\x4c\x14\xa0\xd1\x96\x14\xb9\x51\x45\xff\x6d\x75\x6d\xcd\x31\x8d\x60\x42\x9e\xc8\xfa\x91\xee\x6e\xdf\xe4\x45\xea\x4b\xf9\x82\xa2\x7e\xd3\x44\x44\x60\x7a\x61\xc3\x68\x81\x43\x9d\x96\xab\x02\x4a\x1e\x7d\xe4\x2a\x8f\xb3\x90\xc3\x0a\xfa\xd9\xcc\x51\xf5\x8b\xf6\xa5\xc7\xf4\x26\xd4\x03\x22\xb2\x3b\x04\x90\x12\xd7\x1f\x83\xf8\xd4\x2a\xa7\x6d\xa8\x01\xf0\x11\xb2\x28\x76\x3f\xa5\x77\x4a\x18\xf7\x1e\x76\xb0\xda\xa8\x7f\x37\x0e\x6e\x1a\xc0\xa1\xc6\x66\xee\x79\xd6\x2b\xcb\xdd\x7a\x65\x78\x49\x72\xe4\x25\xfd\xb5\x02\x13\x30\x09\xce\x99\xca\x3b\x6e\x80\x5f\xce\x51\x0a\x71\xa9\x6c\x33\xd4\x12\xf2\x1a\xfc\x0b\xda\x94\x80\x73\xd4\x21\xc8\x43\x6e\x27\xd6\x3d\xe6\x68\xc3\x20\x7b\x76\xc3\xb4\x34\x26\x24\xe2\x90\x42\x34\x67\x23\x82\x25\xa6\x5b\x83\x37\x00\xc7\xb6\xcc\x21\x95\x14\x0e\xba\xc2\x64\x1a\x5c\x9a\x76\xe1\x5e\x36\x27\x09\x95\x4c\xa1\x4d\xbc\xff\xad\x71\x41\x8c\xa5\x3d\xb7\xdd\x4e\x86\x32\xba\x9d\xb3\x07\x27\x22\xa7\x4a\x49\xf6\xaf\x42\x59\x0e\xe2\x05\x93\x70\xc0\x6a\x0d\x39\xf8\xf6\x69\x8a\xed\x53\x19\xa9\xee\x67\x6c\xd4\x3d\x41\xcc\x07\x28\x78\x48\xa2\xe9\xfc\xc5\xe5\x40\xc7\xa3\x3a\xe7\x45\xf6\xb2\x2b\x43\xce\x2f\xad\xc0\x9c\xd4\x12\x2e\x09\xa1\x9b\xc3\x87\xe8\x1e\x64\x25\x73\xfe\xc1\x97\xdb\x9a\x4a\x2a\x55\x17\x1b\x38\x98\xda\x94\x01\xa1\xd3\x54\x49\xfe\x0f\x96\xcd\xd1\x23\x99\x1b\xe4\x4b\x52\x46\x81\xc9\xdd\x99\x4a\x0f\xe6\x99\x48\xf9\x93\xc7\xca\xbf\x32\x81\x79\x1b\xe1\x65\x98\xdc\xd5\xd6\x10\xc1\xd1\x9c\x32\x5c\x1d\x07\x10\xa4\x73\x94\x9b\x33\xf2\x2c\x66\x9c\x3e\xf4\x30\xb1\xda\x84\xb2\x21\x83\x41\xbd\xcd\x74\x19\xd1\x7f\x4f\xd8\x4e\x24\x6d\x43\x58\xc4\x36\xb5\x29\x53\xc1\xa8\xd8\x84\x92\x09\xfe\x8a\x1b\x66\x75\x30\x3c\x8d\xd5\x87\x1d\xf7\xdb\x28\xd0\x1f\x46\xe2\xf4\x4f\xcd\x3a\xae\x21\x02\x83\x12\x77\xcc\x44\xcc\x7d\xcb\x11\xe5\x5a\x1e\xe5\xf6\x28\x54\x20\xc3\xfd\x5a\x2b\x9c\xad\x16\x29\x02\xf1\xe1\x20\xae\x67\x07\x82\xf5\x29\xcb\xb6\x05\x66\x9a\x41\x1a\xed\x01\x93\xb3\x15\x19\x32\x20\x2c\x86\x0d\x5e\x6f\x4e\xb2\xaa\xe5\xbf\x8c\x70\x51\x24\xc7\x7e\xc1\x80\xe6\x6b\xc7\x34\x1e\x37\x0b\x08\x16\x8c\x42\xb2\x49\x09\xbe\xd4\xdc\x9b\x6c\x97\x7c\x70\xf8\xda\x0f\x81\x09\x10\xbc\x1a\x34\x2a\x13\x5a\xe7\x64\x95\xba\xfe\x42\x3d\x49\x83\x27\x68\x3e\x56\x23\x43\x19\xc4\xc1\xbe\x89\xab\x1a\xa5\x92\x52\xdf\x28\x30\xb6\x46\x59\xab\x4e\x51\x89\x37\x91\x27\xe5\x12\x15\x9e\xc2\xc3\x9c\x08\x01\xa9\x08\x80\x21\x9d\x07\x49\x08\x46\xd9\xee\x83\x45\x1a\x65\x08\xe4\xd9\xa2\x20\x4f\x36\x9e\x4c\x56\xc1\x73\xee\x51\x97\xbd\x12\x8c\xf6\xee\x32\xbf\x6c\xa6\xc3\xcf\xb7\x4a\x30\x38\x4d\x86\x08\x88\x33\x12\xd2\x61\x0f\x84\x10\xc1\x99\xbd\x05\x46\xc2\xc8\x87\x24\xb0\x2d\xce\x07\x6e\x14\x53\xc8\x92\x76\x27\xc1\x65\x1b\xa0\xe6\x5c\x69\x1a\x64\x39\xd4\x57\x8a\x76\x80\xcb\xe6\x4d\xd6\xff\x12\x52\xc3\x8b\x57\xb3\xa2\xd6\x86\x68\x63\xd7\x90\xe0\x69\x29\x6c\x8f\xf1\xb5\xa1\x90\xad\x17\x94\x57\x7b\xd0\xe0\x48\xd1\xbb\x2a\x4c\x43\x60\x15\x14\xb4\x7d\x87\x82\x80\xc0\xa5\xff\x3c\x1f\x2d\x72\xb9\x5f\xce\x56\x27\x28\x48\x8f\x40\x27\x97\x42\x8a\x48\x3f\x00\xea\xf2\x60\x8b\x1b\xac\x9f\x99\x55\x90\xe8\x8f\x93\xad\xc6\x34\xf3\x24\xb1\xec\xe7\xb7\x7f\x38\xc4\x8d\xce\x6e\x0a\x00\x2f\x74\x87\x95\x55\x22\x70\x13\xc3\x7b\x70\xd3\xa1\xdc\x0b\xb5\x83\x31\xaa\xa3\x41\xaf\xcc\x66\x44\x2f\x62\xba\xc0\xcf\x21\x8b\xaf\x95\x61\x3c\xc7\xaf\xd0\xe6\x04\xf5\xa1\xfe\x80\xa5\xb6\x59\x16\x56\xef\xbd\x64\x47\x0f\xeb\x46\xc0\xcb\xd5\x28\x2a\xa3\xbc\x8a\xc7\x24\xa4\x62\xc8\x9c\x60\xf4\xf9\x86\x08\xde\x20\xc6\xbb\x29\x7a\xfe\x75\x27\x3d\x94\x9a\xbd\x01\xed\xe0\x05\x20\xf0\x38\x40\x58\xa7\x20\x83\x4d\x95\xd6\x7e\xf9\x90\x28\x21\x38\x49\xa4\x2c\x82\xfb\x07\x1e\xe0\x63\xed\x1e\x23\x0d\xdf\x9a\x43\x2a\x9b\x63\xca\x43\x78\x88\x51\x94\x23\xca\xd1\x82\xc4\x12\x03\x17\x6a\x85\xfa\x85\xd1\xc2\x2e\xa7\xa7\x2c\xf7\x48\x11\x77\x60\x83\xd5\x36\x82\x16\xcc\x1d\xd6\x87\x67\xd3\x3f\xdb\x28\xa8\x60\x84\xdb\x30\xb4\x2c\xcf\x63\x59\x05\x2a\xc6\xce\x23\x00\xb3\x5c\xe7\xb9\xde\x3a\x2b\xc8\x4e\x8c\x03\x64\xea\x7f\xc9\x6b\x94\x5d\x77\xfb\x9a\x8d\x0b\xab\x7c\x3a\x35\xc0\x07\xc1\xda\x1e\xb6\x9b\xb1\xd1\x30\x14\x90\xe1\xff\x08\xb6\xdd\x21\x2e\xc1\xef\x05\x08\x4d\xfa\x29\xb0\x86\x23\x7b\x57\x26\xc6\x67\x28\xce\x3a\xc3\x8a\x48\xd7\x4c\xaf\x99\x9e\xfa\xde\x83\x58\xcf\xa7\x80\xec\x62\x12\x80\xc5\x65\x80\x59\x14\x3b\x88\x41\xa1\xba\x05\x45\x96\xd6\xe7\xcc\x76\x0e\x09\x58\x04\x3d\xe5\x10\x19\xf5\x9f\xb0\x48\xc7\x15\x06\x2b\xe7\x97\x56\x50\xf0\x02\x49\xdb\x61\x78\xa5\x12\x50\x75\x0b\x08\x7e\x2d\xa3\x31\x91\xa1\x68\x35\xa7\x87\xbb\x88\x13\xcd\x49\x2e\xaa\x6b\x56\xae\xf8\x32\x97\x81\x16\x9d\xf6\xe0\xed\x8a\xac\xc3\x13\xf0\x4b\x94\x93\x9c\x77\xa1\x9b\x18\xf8\x2b\x9f\xf1\x0f\xe8\xc6\xca\x1f\xb3\x06\xa3\x1b\x14\x39\xe5\x23\x84\x33\x41\x1c\xce\xfa\xeb\x38\x28\x6e\xd8\xf0\xe5\xa6\x75\xf8\x4b\xa2\xb8\xd4\xd4\xe9\x5d\x35\x30\xea\xec\x5b\xcc\x4f\x48\x3f\xce\x6d\xef\x56\x33\x77\xb0\xd1\x49\xb6\x88\x14\xe1\x49\x63\x9a\xe4\xc5\x53\x92\xd8\x77\xe6\xa9\x05\x93\xc1\x6e\xc1\x49\x39\xfa\x3e\xb3\x28\x06\xc1\x1f\xe0\x8b\x31\x11\xf1\xca\xd6\x7d\x60\x08\x78\xe6\x22\x5c\xde\x38\x54\xd4\x32\x59\x29\x5c\x1b\xc3\x92\x1c\xc2\xbd\xd6\xdf\xd6\x97\x98\x09\x39\x8b\xb8\x28\xe1\xdd\xe5\x93\x43\x97\x26\x50\x65\x30\x5e\x82\xda\x06\x38\x60\xe9\xfd\x8c\xf3\x98\x2e\x0d\x62\xe1\x20\x59\x93\x91\x6b\xe7\x8f\xea\x3a\x2b\xaf\x89\x3d\x91\x46\xc4\x25\x84\x13\x2a\x83\x72\x14\xc0\xb1\xd4\x26\x9a\x24\x90\x69\xc4\x65\x08\x87\xa3\x79\x62\xc0\x3a\xf7\xa1\xc2\x0a\x48\x68\x3c\x81\x66\xed\x6d\x35\x0b\x95\x3e\x31\xc3\x96\xb9\x5f\xe4\xc7\x86\xff\xa6\xb9\x4a\x55\xed\x25\x6a\x21\x1b\xf5\x67\xd7\x82\x11\x9f\x41\x7a\x4d\xac\x2e\x34\xb4\x5d\x30\xfd\x26\xaf\x15\x97\xaf\x9a\xe6\x43\x62\xaf\xb8\x67\x49\x17\x15\x75\x0c\x49\xf9\x2c\x38\xa4\x46\xd3\xda\x41\x2c\x90\x39\x29\x26\xb3\xa5\x4d\xd2\x8b\x68\xe2\x56\x0a\xb9\x56\x15\x8d\x23\x12\x28\x48\x05\xc9\x83\xdb\x36\x0d\x5e\x46\xb9\x92\x28\xe0\xa2\x09\x08\x78\x8c\x8f\x42\x54\xb3\xc7\xcd\x1a\xe9\x70\x59\xe7\xec\xe3\x0e\x85\x90\x3e\xfb\x56\xc9\x90\xe2\x6e\x63\xc0\xc6\xdb\x20\xf3\x69\xbe\x55\x29\x6f\xdc\x62\x4f\x0c\x29\x27\x27\xe3\x3e\xf7\x0b\x8a\x12\x8d\xe9\xef\xea\x89\x47\x48\x66\xb5\xb1\x59\x51\x4d\x47\x18\x82\x21\x56\x0a\x57\x05\x25\xaf\xb3\x85\x21\xa0\x89\xf5\x47\xe1\x0f\xbc\x2e\x3f\x23\xa0\xf1\x5c\xe2\x23\x42\xe6\x8c\xc9\x05\x6e\xa5\x68\x98\xe5\x8f\x74\x0a\xa3\xd8\xd2\x1e\x5b\xea\x9e\xd1\xf8\xb3\x4d\xb0\x60\xc2\x21\x63\xc2\xba\x56\x20\x7a\x6b\xcb\xe8\x14\x62\xdc\xa5\x00\xb9\x62\x2c\xca\xdc\x7f\x41\x02\xe1\xef\x47\x7e\x16\x28\xcf\xfd\x70\xca\x03\x6b\x93\x25\x59\x05\x07\x85\x9c\x0c\xb4\x30\xa7\x6c\x34\x3f\xcd\x0d\x40\x3a\x05\x80\x5a\x81\x48\xb5\xd1\x3b\x24\x73\x7d\xe3\x4b\x57\xe5\xa4\x77\xd5\x66\x6a\x02\x9f\xde\xf5\x74\x42\x51\xc3\x81\xd6\xc5\x3e\x9a\xc4\x26\x10\xa3\xba\xa7\x8d\x10\x35\x92\x74\x45\x41\x5f\x35\xab\x62\xca\x0c\xb6\x01\x1c\x7d\xc9\x85\xf2\xb8\xf0\x05\x4a\x45\x51\x45\x67\x24\x10\x8b\x58\xcd\x44\x06\xbb\xfc\xc1\x20\xeb\x38\xea\x05\x94\x2a\x55\x9e\x32\x80\x92\xd8\xd5\xe7\x39\xfa\xb4\x0e\x4c\xae\x3a\x22\x35\x15\x93\x43\xdb\x41\xf8\x60\x93\xc0\x60\x14\x0f\x9d\x28\xe1\xa2\x88\xe7\x54\xe2\x20\x37\xf3\x59\xe6\x45\xd7\x10\x4f\x73\xa5\x81\x66\xdd\xf8\xd1\xd3\xc7\xe5\x2b\x27\xe6\xec\x29\x48\xdc\x7a\xdc\xbb\x00\x50\xc4\x2f\x08\x1b\x05\xd4\xb4\x20\xe8\x4a\xb4\x57\x8d\xb9\x27\x99\x54\x1a\x1b\x50\xb8\x3a\x74\xab\x7f\xf9\x20\x27\x22\xaf\x44\x1d\x32\x90\xcb\xd4\xe0\x9c\x79\xe8\x41\x87\xf7\x9e\xb5\xe5\xbc\xcc\xca\x17\xcd\x99\xc1\xb4\x0f\x06\xec\x5b\x82\x43\xba\x95\x69\xe6\x0d\x86\xf7\xbe\x57\xa2\x18\x8b\x89\x15\x72\x77\x78\x5c\xc4\xb5\xe4\x1f\xc5\xb9\x9d\x44\xfd\xe5\xc4\x1e\x6a\xda\xc2\x61\x5d\x67\x68\x92\xfa\x0a\x92\xdf\x66\xf0\xa6\x47\x05\x8e\x42\x04\xe6\x93\x06\x48\x2e\xb7\x06\xa9\xaf\xbe\x89\x5c\x47\x42\x68\xce\x73\x3c\x73\xa4\x4a\xbb\xac\x7d\x06\x20\x9f\x4a\xab\x87\x6e\xc3\xb1\xef\x16\x53\xd7\x9a\xd8\xf8\x39\x69\x59\xd8\x85\x42\x3b\xf6\xa3\x82\x2a\xbf\xee\xbc\x18\xff\xad\xc2\x2c\x4f\x3f\x66\x7f\xe2\x0c\xc1\x67\x54\xc5\x9f\x4d\x70\x85\x0c\x0d\xed\xd9\x61\x7d\xcd\xd9\xd0\x66\xc2\x37\xf6\x85\x6b\xa8\xab\x00\x2d\x26\x68\x76\x00\xa1\xa0\xdc\xea\xca\x0c\xea\x68\xae\xc7\xc2\xe7\x09\x43\xb6\x1b\x21\x0c\x90\x0b\x82\xb8\xc1\x6e\xdb\xed\x15\xdb\x32\x23\x52\xeb\x51\x62\xab\x2c\x1b\x01\x7f\xaa\xc9\x29\x44\x0e\xb9\xd3\x39\x14\xe9\xce\xd5\x1f\x99\x0d\x89\x76\x7a\xff\xf0\x4d\x4b\x84\xd2\x4f\xa2\x05\x76\x2b\x07\xae\x00\xda\x45\xe9\x91\xb1\xc2\xce\x81\xf9\xf6\xe0\x5d\x82\x79\x38\xf6\x1c\x61\x19\x76\xf6\xb3\x63\xbb\x98\xc5\xc6\x98\x1b\x10\x8a\xf4\x1a\x31\x4c\xc9\xe4\xfe\x26\x70\x34\xa6\xd2\x67\x8d\xdf\x1a\xa3\xf4\xcc\xe4\xe3\x1e\x92\x36\x83\x21\x73\x99\x3d\x52\xea\xbb\xd1\xe1\x62\xe2\x9e\x7d\x68\x56\xfb\xa6\x8c\x1c\xd2\xae\x8e\xd2\x5c\xec\xee\x35\xbb\x2a\x09\x72\xf8\x31\xfe\x08\x7d\x3e\xb4\xdf\xa3\xcc\x86\x7c\xed\x20\x2e\xff\xc6\xeb\x52\x93\xde\xc3\x92\xd0\xaf\xe8\x5f\xd3\x72\x02\x0b\x02\xee\x7c\x65\xca\x2a\x40\x2c\x2a\x91\x6d\x91\xab\x59\x04\x1f\x87\x36\x31\x7b\xcb\x7a\x5f\x45\x22\x45\x60\xda\xb5\x81\x50\xb6\x8f\xf7\x3d\x58\x20\x70\xb4\x86\x78\xbe\xb4\x53\x44\x86\x38\x3d\x58\x2d\xf3\xc1\xaf\xf5\x28\x83\xeb\xbf\xb7\x71\x09\x23\xbc\xf2\x61\xc3\x34\xf0\xf4\x44\xc0\x0a\x85\x21\xdb\x45\xb6\xec\x16\x71\xee\x3b\xa4\xfb\x49\x7d\xfb\x48\x47\xc3\x8e\xd0\x22\x80\x87\x5c\x9a\x96\x81\x81\xb2\xa2\xaf\xca\x48\x71\xca\x24\x24\x07\x0d\x02\xa7\x61\xed\x00\x95\xa4\xbf\x88\x3c\x06\xae\x15\x02\x96\x08\x3c\x42\x57\xf7\xeb\xa0\x31\x93\x66\x63\xdb\xf2\xe7\x41\xb3\xf4\x2a\x96\xaa\x54\xba\xc2\x66\x89\x36\xab\xa1\xa1\xf1\xdd\x21\x39\x0e\x81\x1c\xb9\xac\x86\x29\xde\x1c\xe8\xbb\xf1\x34\x45\x28\x55\x41\xe4\x54\x82\xac\x28\x7f\x9a\x38\x6c\xb3\x34\x25\x0b\x1a\xcc\x1f\x3b\xde\x53\x2a\x54\x65\xa6\xe3\x67\x15\xb5\x15\xdb\x09\x80\x43\xaf\x47\xe4\x5c\xfd\x70\x01\xe7\x36\x94\x69\xc7\x32\x60\x0e\xf8\x10\x0d\x0a\xe9\x93\x0c\x18\xcb\xe0\xfd\x41\xa6\x29\xee\xe1\x17\x47\x89\x43\x93\x9b\x11\x4a\x41\x66\x70\x55\xc5\x7d\x16\x25\x7f\x78\xbd\x3d\x9d\x9d\x76\x93\x82\xa5\xb7\xc6\x0d\x72\xe0\xd9\x03\xd4\xab\x4d\x2e\xc3\xcf\x35\xdb\xee\xc0\x86\x82\x8e\xdc\xf4\x52\x90\x30\xfa\x30\x06\xec\xd3\xbf\x69\x08\x12\x64\x42\x22\x44\x11\x25\x49\x64\xf7\x65\x41\x93\xb2\x57\x48\xfe\x14\x36\xd9\x77\x88\xee\x74\x68\x00\xac\xb5\x9d\x8c\x55\xf8\xae\x81\x77\xc0\x06\x09\x09\x66\x69\x64\x9e\xd6\x12\x89\x3f\xc1\x91\x49\xe0\x91\xa0\xc6\xd0\x39\xcd\x11\xa4\x7f\x92\x45\xc5\x35\xd7\xf5\x3f\x67\xdb\x09\x69\x74\x96\x37\x32\xf1\x80\x0c\x05\x14\x86\x63\xdb\xa6\x67\xe1\x6b\xd1\x3e\x60\x8c\xa7\x20\x9d\x8d\xa0\x85\xa2\x28\xd8\xf9\xfe\xd2\xd4\xee\x52\x6f\xb6\x6e\x0e\x7e\x38\x18\x44\x83\x51\xce\x73\xb8\xaf\x68\x15\x43\x55\x12\x54\xaf\x26\xa8\xde\x0c\x33\x62\x3a\x96\x58\x4b\x73\x18\x4c\x45\xf6\x6c\x4e\xe9\x7f\x76\xe0\xf9\x6c\x01\x03\xe1\x42\x2b\x97\x90\x08\x90\x80\x0f\x68\x3c\x71\x86\xea\x74\xeb\xcc\xf7\x7b\xa8\x2d\x77\x21\x31\x16\x19\x5e\xd6\x43\x50\xce\x26\xa9\x86\x64\xc3\x2c\x76\x85\xe4\x6b\x4c\x17\x08\x69\xc1\x46\x0a\x4c\x28\x7c\x63\x4a\xf3\xd1\x48\xb6\xfc\xa9\x66\xa2\xdb\x82\xf5\xd5\xf5\x39\x14\xd7\x47\xc3\x25\x4f\x52\x52\x1d\x46\xc3\x30\xb7\x84\x46\xd2\x31\x5c\xb6\x79\x24\x69\xc9\x4c\x3c\xaf\x61\xf2\x79\xf0\xf8\xc6\x68\xf2\x66\x5c\x24\x35\x02\xfb\xe0\xea\x21\x9a\x45\x1a\x80\x4e\x41\x01\x17\x81\xf3\x12\xf1\x68\x73\x7a\x34\xf1\x93\xb1\x1e\xa0\x1a\x0a\x28\x3e\x43\x3f\x7b\xef\xd7\x31\xa5\xde\x71\x22\xba\x6e\x72\xf4\xc5\x88\x03\x54\x4f\x09\x6a\x48\x3c\x3e\x15\x84\x09\xfb\x40\x31\x46\xbf\x8d\x30\xce\x04\x13\x40\xb2\xd0\xd1\xab\xa3\x5a\xa2\x11\x86\x60\x45\x5f\x82\x18\x52\xe2\x38\x9f\x52\x14\xfb\x83\xc8\x8d\xbb\x2c\xa4\x3c\xd1\xc2\xdc\xb7\x8d\x80\x98\xe2\x73\x74\x54\x7c\xa4\x09\xc1\x1c\x59\xba\x87\x81\xc6\x6b\x32\x9c\x89\x04\x4f\x15\x7b\x41\xb5\x80\x51\x57\x7a\x10\x33\xe8\xc5\x30\x27\x0f\xbb\x06\x53\x79\xab\xac\x34\x2a\xde\x6a\x6e\x8e\x08\x1d\x14\x5d\x3c\x4c\xc7\x6d\x43\x3b\x96\xfb\x71\x4d\xcb\x0b\x68\x7f\xa3\x8f\x56\xe7\x93\xc1\x74\x74\x12\xc6\xdb\x2e\x81\x34\xa6\x1c\xca\x87\x67\x9a\x90\x52\xa4\x93\xf8\x89\x91\xbc\xfa\x27\xcb\x1b\xdd\xad\x8d\xb1\x1e\x54\xf4\x11\x84\x67\xbd\xa8\x26\x7c\x03\x39\x02\x35\x2b\x9f\x91\x12\x68\x2b\x6c\x1a\x61\x0e\x86\xb6\x36\xeb\xf9\xee\x0b\x9f\x43\xe2\xc0\xd1\xac\xc9\x72\xf2\x6f\x5e\x46\x9d\x08\xaa\x3b\x2a\xf7\xb3\xaf\xba\xc0\x9c\x94\x86\xff\x9e\x09\x24\x9b\x9e\x05\xfc\x24\x9b\x5a\x44\x2f\xb1\x21\xb9\x52\xb4\x06\x52\x1c\xe1\x54\xe9\x16\x7b\x15\x3c\x0d\x81\x28\xa0\x36\xff\x77\x61\xaa\x99\x08\x88\x00\x6b\x70\xe9\x95\x62\x1d\x54\x5a\x73\xea\x24\x15\x5d\x0b\xe3\xb9\xe0\x87\xd4\x20\xcc\xf9\xbf\x3c\xf2\x2b\x08\x25\xde\x79\x0f\xb2\x32\xa8\xab\xdf\xa4\x37\x54\xa3\xf9\xb8\x05\x6f\xb8\x6f\x6e\x88\x1e\xe0\x58\xb3\xd0\xa3\x7f\x51\x98\xe8\x1f\x33\xb2\x34\x84\x9b\xc5\x8e\x20\x57\xa1\xa1\x85\xda\x8d\xb4\x2a\xb1\x46\xf3\x98\x7b\x21\xd5\x89\xdf\x03\x5e\x48\x46\x61\xe5\xc1\x72\xcb\x9b\x15\x87\x0a\x65\xa7\x3d\xc7\xbe\x6d\xde\x8f\x1b\x36\x42\xba\xe7\x82\xbc\x06\xd5\x5b\xc1\x72\x18\x31\xdf\xe8\x0b\x2f\xad\x1c\xc3\x18\x7d\xfa\xdc\x4d\x2a\x41\x9b\x4e\xfd\x50\x3f\x86\x83\x2d\x5c\x35\x4d\x6c\x36\xc7\xfd\x58\x4b\xfb\xc8\x9d\x0d\xb6\x4f\xd1\xca\x72\x86\x2e\xe1\xcd\xd8\x1a\xf3\x9e\x76\xfb\x77\xb6\x2e\x41\x71\x57\x69\x67\x91\xa2\x45\x25\x5a\xe7\xc2\xf2\x2b\x54\x70\xd3\xa2\x96\x1f\xc9\xbb\x14\x13\x8a\x47\xc1\xa1\x38\x93\x0d\xaa\x7b\x94\x23\x22\xc4\x31\x82\x9c\x82\x33\x3d\xc9\x46\x91\x98\xf2\xbc\xac\x41\x3b\xe0\x24\x5b\x43\x86\xe3\x4d\xcd\x41\x95\x8d\xcf\xc0\x0f\x9a\x7f\xd7\x8a\x03\x57\xe9\xc0\xbc\xe7\x1a\xb0\x52\xe8\x69\x91\x53\x7e\xdb\xb4\x29\x56\x09\x84\xc9\x30\xda\x99\xdf\x3e\xb6\xb0\x11\xf2\x2b\x00\xe9\xa3\xcc\x59\xdd\xf1\x27\x4b\x31\x29\xc5\xa0\x67\xf2\xba\x72\x8d\x04\x26\xc0\x64\x1a\xb3\x8f\xf7\xbb\x23\x53\x1a\xfb\x56\x94\x38\x47\x69\x1a\xd1\x5b\x6c\xb4\xe8\x15\x4f\x4e\xaf\x9f\xc3\xf5\x87\x0b\x7e\xb4\x6b\xdd\x20\xd6\x97\x28\xd8\x3f\x07\xfe\x58\x43\x0a\x06\xc6\x75\x21\xd7\xe8\x82\x1b\x87\xbb\x62\x85\xfc\x10\x71\x62\x10\x48\x4a\x53\x2a\x3a\x6d\x30\x21\x21\xdc\xa3\x79\x40\x75\x90\xf1\xc6\xa6\xd8\x6a\xad\xef\x41\xc8\xd6\x50\xac\xb5\xa8\x4a\x15\x29\x48\xa8\x22\xf2\xf7\x61\xd7\xc1\xe3\x99\x62\x8e\x86\xa4\x10\xbe\x80\xc9\x4b\x13\xee\x2b\x71\x3f\xec\xe7\x45\xfc\xf0\x17\xf6\x16\xf6\x88\x08\xcf\xcb\x31\x33\x37\x6b\x87\x6a\x45\x12\x5c\x9a\xe7\xe1\xfc\xd9\x19\x90\xfc\x13\x46\x33\x5c\x82\x7d\x45\x90\xc2\x44\x8f\xd8\x42\xdb\x12\x4e\x65\x09\xde\xd3\xf3\x81\xce\x62\xb1\x23\x3b\x2d\x3b\x81\x13\x08\x4d\x46\x02\x2f\x71\x59\x6b\x44\xb8\x3d\x40\x35\x21\x70\x2f\x2b\x86\x9e\x19\x02\x4e\x26\x72\xc3\xc7\xbb\x29\x74\xf6\x36\x42\x00\x39\x0e\xa3\xb3\xff\xec\xe6\x95\xa6\xf4\x87\x47\xe0\xe8\xda\xbb\x53\xc4\x25\xad\x63\x6a\x10\x31\x38\x80\xe7\x96\x3c\x9c\x08\x32\x35\xe8\x9e\x0e\x05\x09\x9d\x24\x51\x72\x05\xa6\x45\xb1\xb8\xbd\x88\xf0\x09\x5f\xcc\x33\xb4\x84\xfe\x1c\xed\x41\x3a\x90\xa6\x7f\x5b\x0e\xd0\xac\x12\xaa\x3a\x2d\xf0\xfb\xce\x98\x22\x10\xb3\x7f\x4f\xad\x4b\x59\x80\x5a\xa5\x12\x69\x58\xa2\xbd\x37\xe4\x7e\xd1\xa9\x4f\x58\x27\xe7\xdd\x15\xf3\x9f\xe1\x9d\x8c\xc7\x39\xf6\xa4\x52\xbc\x46\xc5\x61\x11\xab\x20\xf7\xcf\xa9\x64\xa2\xb7\x9e\xa1\x18\x1a\x8d\xe2\xa6\x41\x52\x4a\x11\x3c\x96\x0a\xef\x39\x0f\x89\x81\x00\x1d\x1e\xbb\x6f\x44\xf7\x80\xb2\xa0\xf0\x01\x04\x34\xb6\x20\x09\x8c\x2e\x89\xac\x52\xb9\x7c\x10\x50\xad\xa7\x1f\x5e\xb4\x61\xb1\x20\x9d\xbb\x73\x08\x66\x24\x53\xaa\xe0\x42\x3b\x76\xdd\x1e\x40\x7a\x7a\xd2\x65\x11\xd4\x4c\xa0\x01\xb1\xba\x77\x9d\x93\x63\x4b\xa5\x08\x4e\x45\x83\xb1\x38\xc2\x2d\x35\x0a\xea\xe1\xcf\x6f\xd2\x8f\x53\x37\x22\x18\x29\xeb\x2f\xa2\x10\x58\x59\xd6\x5c\x4c\x6d\x4e\xd2\xd7\xf0\x71\xba\x59\xbd\x87\x05\x62\x84\xde\x6a\xd0\x3f\x9b\xbd\x69\xa3\x6b\x06\xca\x4c\xca\x8f\xe7\xd8\x3c\xee\x12\xb6\xdf\x72\x22\x54\xdb\x32\x76\x9c\x94\x6f\xe9\x8f\xac\xad\x13\x1e\x1b\x17\xda\x65\x45\x43\x65\xf4\x18\xee\xca\x55\x30\x74\xfd\xe8\x8c\x18\x0f\xc2\x8c\x6f\x5c\x78\xd5\xd2\x84\xfc\xcf\x13\x43\xb3\x19\x3f\xb8\xb0\x3f\x11\xae\x76\xf1\x59\x8c\x47\x23\x9c\x6e\x44\xeb\x31\x85\x2c\x81\x22\xad\x08\xec\x7e\x72\x7d\x3b\xa8\x93\x60\x4c\x4f\xb5\x97\xf9\x26\x8c\xfd\xe1\x63\x99\xc2\xb3\x50\x38\x9e\xcb\x5d\x8f\x01\x9c\x05\xa8\x50\x08\xfe\x07\x12\xc6\x50\x0b\x60\x47\x4f\x2d\xcf\x6d\xee\xca\xfb\x0d\x20\xac\xe0\xb8\x52\x14\x9e\x06\xd3\xdf\xb2\x04\x5f\xb0\x4e\xd3\x50\x11\x64\x6e\xae\xfa\x57\x4e\x2e\xc6\xb7\x8e\x90\x4e\xae\xb0\xfb\xb5\x27\x98\xd8\xba\xad\xd8\xb0\xe4\x09\x4e\x6e\x82\x0e\x82\x40\x74\x96\x5f\xf0\x27\x78\xd3\x18\x7d\x2b\x58\xda\x3e\xa8\xaf\xbb\x9f\x8d\x6b\x53\x14\x29\x64\x4f\x87\x86\xe0\x04\x6f\x10\x1a\x0e\x47\x94\xb1\x0c\xef\x38\xb6\x6b\x52\x53\x5b\x4f\xf1\x63\x9b\x98\xfb\x78\x19\x1a\x80\x14\x31\x4c\xbd\x4d\x5f\x24\x0e\x26\x37\xc3\x14\xa0\x3f\x84\x45\x42\x15\x83\x3e\x6f\x5a\xbe\x96\xa2\x60\x27\x23\x27\xdf\x5f\xbf\xa0\xfa\x91\x84\x25\x96\x0d\x11\x50\x58\x5d\x0f\x60\xb6\x95\x70\x3a\xe8\xe5\x6d\x7f\xf3\xb4\x7d\xc0\x24\x9c\x85\x7b\x7b\xb0\x18\xad\x92\xec\x66\x1f\x8e\xb2\xa7\x00\x1f\xa3\x24\x22\x84\xd8\x87\x74\x27\x72\x27\x71\x95\x83\x0e\x22\xb1\x5a\x86\x8c\xb5\xd4\x69\xa5\x1f\x5d\xa7\x4b\x80\x61\x6f\x35\xa7\x44\x0a\xb4\xae\xb5\xdc\x79\x92\xc8\x72\x29\x20\x01\x4c\xa7\xa3\x55\x12\x60\x62\x68\xce\x47\x17\xe2\x33\x1b\xb8\xdb\xf2\x00\x76\xc3\x3b\x6b\xec\xb3\xd9\x57\x9f\x58\x23\x3b\x32\xa9\xcd\x5b\x65\x3d\x59\x5d\x5c\xe8\x54\x26\xd8\xa8\x22\x3e\x80\x56\x66\x87\x50\x5a\xef\xbe\x88\xe0\xc8\xcb\x71\x43\xf4\x17\x6b\x68\x03\xc9\xc8\x43\x34\x1e\xfe\x09\xa1\x16\xa7\x82\xf3\x5b\xbc\x0b\x4e\x14\x31\xf4\x75\x7d\x7d\xbf\x64\xf0\xdc\x7b\x4f\x49\xa7\xc4\xba\x0a\xc2\xfa\xe5\x69\x14\x10\x43\x04\x20\xc7\x11\xa9\x0e\x8a\xe9\xb1\x0a\x8e\xe3\xcf\x1a\xaf\x94\x71\x3b\xf5\xb6\x2d\xfe\x4c\x4d\x6c\xcd\x41\x80\xb4\xa8\x56\x7c\x41\xdc\xd6\x2a\x8f\xba\xdb\x46\x85\x09\x00\x5d\xba\x88\x32\x1f\xeb\x08\x23\x77\xcc\x34\x8e\x4c\x6b\x24\x4e\x81\x6e\xbb\x89\x1f\xd3\xde\x7e\x1f\x7e\x7e\x66\xdf\x5b\x21\x93\xe5\x3e\x1f\xf3\x49\x1f\xaf\xa0\xe3\x2b\x69\x1d\x3b\x3b\xb2\x09\x81\xae\x71\x75\xbb\xe7\x7e\x57\xa3\xdf\x77\x29\x9b\x1f\x7e\xa6\xab\xfc\x3f\x75\x84\xc7\x17\xff\x56\xcc\xcf\x76\x10\x0b\x67\x26\xad\x90\x72\xca\xeb\xba\xca\x6e\x5f\x41\x5b\x7a\x7c\x5e\x9f\x74\x25\x5e\xae\x60\xa7\xf3\x76\xb8\xe8\x67\xee\xf2\x76\x6a\xe6\xda\x14\x7e\x30\xfd\xf2\x9d\xef\x95\x02\x62\xa4\x01\x00\x34\xa6\xff\x58\xef\x4d\xdc\x2d\x4c\x01\x00\xf3\x80\x9c\x4c\x33\x47\x72\x2e\xd7\x90\x1f\x23\x74\x2d\xd0\x6e\x4d\x84\x49\xd1\x60\xd7\xfc\xa3\x8b\xdf\xee\xc5\x91\xaa\x5b\x35\xe9\xd8\x8d\x17\x19\xc6\x14\x28\xed\xc7\x58\xc6\xbf\x3f\xf2\x7e\x7a\xae\x7b\x5f\xc7\x7c\x58\xd3\xa4\x2f\x5c\x16\xcf\xdf\xc6\x28\x09\x6f\x0e\x1e\xf3\xf2\xa6\x7b\x8f\x85\xc3\xbb\xcb\xea\x8c\xf6\x2e\x08\xeb\x04\x4f\x52\x6e\x0e\xae\x1f\xf4\x48\xfd\x34\xf8\x7f\x6e\x5d\xbc\xdf\x91\x3e\x08\xc1\x86\x66\xca\x1e\x2f\x58\xec\xf4\xc1\xd8\x20\xa5\xee\x56\x08\x85\xbe\xbb\xe3\x56\xb5\xf1\x33\x2f\x6f\x38\xd2\xbf\x1e\x7c\xf9\x46\x8c\xce\xae\xdb\x50\x39\x6f\xbf\x78\xc3\x32\x2f\x6a\x1a\x52\x47\x59\xad\xea\xda\xee\xfe\x96\xbc\x44\x7c\x1c\xa9\x28\xd1\xcc\xa1\xd0\xce\x94\x41\xaa\xa3\x22\x37\xde\x24\x69\x08\xcb\xf0\x31\x75\xb1\x7a\xc8\xdf\xa4\x14\x70\xce\xbf\xea\x28\x7d\x75\xbf\xdf\xe4\xb1\xf2\x27\x49\xfc\x70\x14\x4d\xcc\x64\x3d\x9a\x79\xf4\x32\x84\xfc\x9e\x91\xcf\xf6\xe5\xcf\x75\xfa\x20\xb8\xf3\xca\x1f\xcf\x9b\x27\x14\xfc\xda\x7c\x89\xbb\xf4\xd6\x3b\xd0\x4f\x3c\xe8\xf9\xbe\x9d\x59\x8b\xb8\x79\xb8\x61\x51\x34\xdc\xbf\x61\x71\xcb\xcb\x5a\xbf\xe5\xf7\xf3\x05\xff\x9a\xb9\x79\xc9\xf5\xad\x53\xb4\xf3\xfd\x91\x92\xe8\xa0\xa7\x44\x2f\x90\x63\x3c\xd8\xf2\xfd\x4a\x68\x39\xba\xb9\xd8\x01\xfc\x2a\xb1\x82\xcc\x52\x5a\xba\xd9\xdd\x39\x76\x79\xc5\x4f\x9f\x23\xe9\xa1\x0d\x56\xf8\x3c\xba\x7d\xe6\x48\x9e\xad\x68\x1d\xec\xd2\xf3\x02\xde\xbc\xae\xdc\xdc\xec\xf8\x7e\x75\xf6\xf2\x7f\x6e\xd8\xdd\x86\x6d\xd0\xe9\x4d\x11\x2e\x5b\x6e\x07\x4a\x2d\x6f\x97\x7d\xbe\x1d\xf6\xf4\x04\xe7\xbd\x7c\xae\x1c\xdf\x32\x4c\x9e\x6e\x7f\x29\xe5\xb7\xaa\xaa\xe8\xc1\x53\x1f\x05\xe7\x14\xa1\x45\xea\x45\xac\xbf\x8d\x6f\x74\xaf\x5c\xc9\x30\xdf\xa8\x87\x46\x22\xf2\xfb\xfa\xfd\x48\xde\xc0\xbd\x0b\xe2\x83\xd1\x50\xad\x8a\xbc\xc0\x22\x46\x7e\xf8\xd1\x7f\x2d\x2a\xbd\x3f\xcc\xdf\xbe\x69\xfb\x92\x57\x07\x2c\x45\x4f\x39\x48\x9d\x08\xdf\xff\x2e\x99\x29\x11\xa4\x34\xde\x26\x11\x50\x64\x1e\xab\x0a\x80\x89\x96\x1d\x8b\xee\x09\xe5\x19\x80\xa3\x3d\xc4\x52\x3e\x9c\x21\xbf\x56\x28\x2a\x62\x72\x8a\xe2\xcd\x8d\x47\x35\x57\x12\xfb\x55\xbd\x38\x00\x9f\xc2\x27\x1b\xcd\x7c\x10\xc9\x3b\x94\xf9\x20\x41\xfe\xa0\xe6\x9a\x5d\x71\xa4\x22\xfc\xbb\x01\xad\xe8\xd8\xd6\x99\xf6\xa9\x7b\x41\xb2\x46\xa3\x93\x7a\x65\x55\x2c\xb7\x23\x2d\x45\x7c\x4d\xa0\xf4\x28\xcb\x4c\xd3\x24\x4d\xb9\x7a\x53\xc9\xb2\xa6\x06\x46\xc5\x4c\xd7\x65\x45\x39\xe6\x5d\x56\xc0\x86\xb6\xa6\x75\xd9\x13\xd7\xa6\x75\xc9\xf5\xf7\xd4\xba\xcd\x1e\x6a\xc1\x2b\xfa\xee\x27\xa9\x5a\xcc\x4a\xe2\x78\x9d\x80\xc8\xf9\x32\x74\x1a\x9f\x83\x39\x19\x27\x99\x5c\x04\x6f\x34\xd2\xbd\x1b\x9e\x2b\xd5\xaf\xcf\x40\xb9\x04\x68\x6d\x05\x22\x31\xb4\xfa\x90\xfb\xd4\x3c\xbb\x70\xe3\x1a\x5f\xf1\x00\xe0\xd2\xe0\xbb\xfc\xa7\x88\x51\xe9\x2c\x5a\x02\x68\x3c\x88\xf7\xe0\xfe\x30\x3f\xd8\x1d\x2d\xdc\xb5\x7e\x9c\xb0\xb5\xc8\x2b\x43\x40\xfe\xaf\x10\x75\x95\xe7\xb8\x62\x5a\x7a\x9a\x69\xed\xfd\x6a\xa3\x69\xbe\x91\x51\x4a\x66\x94\x05\x50\xb2\xe7\x66\x88\xf5\x9c\xce\x71\x1e\x83\x94\x1e\x2b\x98\x89\x57\x82\xd2\xdf\xe4\x26\x6a\xd5\x5c\xe7\x74\xa9\xaf\xdb\xdf\x57\x60\xd7\x6d\xa4\x11\x11\xcc\x79\xf7\x0f\x91\x3a\xd1\x8f\xa1\xb6\x36\x39\xf8\xc7\xb5\xbc\x5a\xd2\x46\xb0\xe1\xa2\xa4\x87\xe6\x83\x0d\xba\x19\xb0\xc3\x8c\x81\xf6\xc7\xed\x3e\x31\xb3\x7b\x1b\x73\x33\xc0\x27\x85\x0d\x87\x99\x06\xf8\x76\x9c\xcd\xa2\x9b\x9e\x69\xcb\x8a\x55\x4d\xea\xe9\x6d\x29\xac\xa7\x6a\xc6\x7b\x17\x31\x22\xd7\x32\x1f\x27\xcb\xab\x52\x45\x61\x9d\x34\x60\xb6\xbb\x98\xdc\x57\x6a\xf3\x38\x91\xac\x8e\x6b\x55\x82\x1d\x05\x96\x92\x66\x14\x78\x69\x96\x9d\x45\xbf\x1c\xff\xc8\xd9\xf2\xca\x40\x67\xea\x3a\xb5\xb1\xc5\x4c\x8b\xf6\x1d\x3b\xd4\x5e\x97\x4b\xab\xf7\xf1\x66\xcd\x1c\xb6\x51\xec\x50\xf6\x59\xd1\xba\xa9\x22\x74\x73\xb3\x5c\xbd\xa6\xa7\x23\x47\x98\xb6\xda\x7d\x55\x46\xc3\xb8\xeb\x79\xb6\xd9\xcc\x58\x33\x0a\x1b\x9f\x12\x8c\x4b\xdd\x50\x0c\xc3\x91\xeb\x75\xbe\xd6\x39\xc4\xec\x8b\x65\x02\x1c\xc3\x96\x09\xe1\x8d\x5c\x8d\xdb\x96\xfd\x64\x19\x0b\x07\xe9\x66\xd2\x34\x8d\x8a\x0a\x97\xe9\xc9\xb3\x55\xd0\xcf\x79\x77\xc9\x73\xd7\x3c\x74\x11\xf2\x88\x94\xbf\x53\xd3\x12\xfd\xc4\x6d\x05\x84\x19\xea\x6a\xd9\xa6\x16\x96\xd9\xb2\xb3\x36\x71\xa6\xcb\x56\x97\x1e\xe3\x4d\x4e\xa9\x7b\x0d\x2a\xd4\xc8\xee\xad\xc1\xa4\x73\x7d\x7d\xc1\x78\x61\xf9\x45\xbb\x07\x66\xdb\xb9\xc1\x1b\xb8\x25\x04\xed\x45\xae\x00\xde\x78\x4d\x3e\x9d\xc6\x7a\x86\x1a\x9e\x65\xb6\x16\x51\x80\x6d\x43\x0a\x5e\x16\xfa\x43\x51\x88\x66\xbb\x0d\x3d\x07\x9d\x4a\x30\xed\x07\x1c\x73\x97\x7c\x59\xa6\x69\x91\xc2\x49\x45\x88\x28\x15\x12\xbb\x58\x43\xe6\xf2\xa5\xb3\x55\x77\x93\x4a\x54\x2a\xe2\xd1\x5f\xc8\x36\x08\xe0\x3a\x4f\x88\x24\xe1\xfc\x0e\x67\xc6\xb7\xd4\xcb\xe6\xef\xd0\x29\xf6\xb1\xc2\xbc\x2a\x3c\x79\xa7\xf8\x20\x2e\xc9\x13\xdd\x05\x9a\xe8\xc8\x6e\x52\x26\x07\x67\xf4\x92\x48\xdb\x6a\xd1\x7e\xe2\x2f\x1c\xd8\x02\x5d\x76\x78\xfc\x1b\x98\xaa\xd6\x2e\xa3\xc9\xe0\x20\x10\xe6\xc4\xfa\x1c\x3d\x8e\x85\x4b\x3e\x05\xf0\x2a\xe9\x14\xe4\x4f\x6e\xb5\x9e\xa6\xb9\x3f\x53\x68\x88\xe2\x52\x42\x8d\x72\xd7\xaf\x7d\x12\x8f\xd0\x1c\xd1\x5e\x37\xce\x27\x7c\x68\x78\x6d\x3d\x99\x30\x11\x50\x7e\x16\xda\xcc\x19\x27\xd5\x66\x37\xda\x03\x92\x6e\x35\xd6\xc7\xce\xa6\x5d\xfe\xf3\x44\x22\x47\x0b\x31\xc6\xcf\x0b\x69\xdc\xf5\x9f\x25\x6e\x72\x10\xcc\xec\x1d\x85\x22\x34\x07\x97\x5a\x07\x81\x0c\x54\x1e\x5d\xb4\x69\xbb\xef\x4a\xa4\x9b\xd7\x16\xb9\xc3\xba\x54\xf0\x44\x3f\x5a\x65\xda\x0f\xa8\xfa\x51\x05\x7d\xf8\x5a\xe9\xb7\x17\x9b\xcc\x50\xa7\x9a\x70\x78\xd2\x98\x7d\x32\x49\x89\x0e\x29\x30\xa6\xa3\xc7\x57\x83\xda\x1d\xad\x25\xd3\xdc\xe1\x38\xb2\x32\xec\x08\xde\xc2\x8c\xd4\x56\xd3\x52\x68\x2a\x95\x26\x8a\x45\x68\x8f\xac\xaa\x0c\x0f\xb6\x0f\xb0\x62\x04\xbe\xf6\x63\xf2\x80\x0a\x41\x9f\x3f\xdc\x6d\xd6\xf1\x4c\x18\xd3\x76\x90\x04\xd1\xc8\x72\x14\x48\x83\x9e\x2b\xf8\x76\xa7\x35\xc2\xf1\xd5\xa0\x97\x64\x46\x59\xb3\x6a\x74\x25\x41\xbf\x5a\xc9\xcb\xcc\xcb\x75\x1f\x0c\x5a\x18\x89\xd8\x66\x0d\x44\x70\x88\x92\x14\x06\xb2\x5e\x01\x80\x21\xe1\x68\x0e\x3a\x21\x39\xaa\x31\x9d\x99\x96\xee\xd3\xcf\xf2\xcc\xc6\x25\xe8\x37\x61\x23\x22\x2c\xfa\xc0\x5a\xd1\x50\x0b\x25\x0c\x70\xbe\x3a\x78\xbb\x37\x38\x0c\x24\x0b\x46\x52\x6e\x34\xd1\xb0\xf2\xf9\x4a\x9c\x73\xf3\x21\x23\xe5\xa4\xa0\x5b\x5b\x14\xfb\x34\x06\x56\x57\xe3\x10\xa3\x93\xa1\x4a\x84\xa5\x44\x56\x2f\x94\x7f\x8d\x40\x4d\xd8\xae\xae\x35\x2f\xab\xd2\xc8\xdf\x90\x58\x28\x0a\x74\xd4\x59\x7a\x31\x92\xa8\xd6\x7d\x49\x4b\xde\xa9\xb2\x25\x6e\x7e\xe6\x25\xb0\xd2\x0a\xf1\x62\x3f\xf9\xf9\x52\x8e\x77\x93\x3a\x6a\xba\x46\x89\x6d\xc8\x12\xb0\xf3\x39\xa9\x35\x09\x54\x82\x64\x86\xb1\xb4\xf6\x11\xc6\x78\x01\x67\x6e\x9b\x35\x51\xde\x15\x89\x87\xf0\x16\x1e\x1c\x66\xed\xdd\xa7\x6b\x1e\xff\x28\x99\x9d\x39\x90\x50\x66\xb8\x12\x32\x69\x36\xe3\x79\x64\x70\x05\x08\x56\x23\xac\x91\x35\xea\x99\x61\x9c\xf5\x98\x85\x2e\x6a\xda\x02\x2f\x6b\xe3\x3d\x6d\x57\xf7\x89\x16\x6c\x4b\xbd\x4f\x32\x17\xe3\xfe\xac\xa9\xa0\x5c\x55\x9b\x7b\xb1\xe0\x70\x90\xb3\x93\x7f\x08\xca\x2e\xcd\x53\x69\xbc\x17\xe2\xd1\xad\xce\x75\xc2\xd7\xf3\x5b\xcc\x7d\xc0\x72\xf2\x2f\x6e\x17\x5b\x0b\xd4\x82\x7e\x10\xc4\xe3\x82\x31\xf9\x53\xd6\xfa\x57\xd3\x18\xfe\x79\x65\x66\x79\x94\x16\x58\x37\x81\x29\xe1\xf0\xa4\x0b\x40\xcb\x06\xd8\x9f\x5e\x33\x0e\x47\x86\x87\x8a\xb5\x89\xd0\x50\x31\x33\x0a\x7e\xd9\x6b\x49\x2b\xc0\x31\xad\x7e\xb3\xc2\x10\xe4\x2a\xb8\xb6\x35\x68\x89\xab\x8b\xbe\xea\x11\x39\x87\xd2\x4d\x17\x8d\x2c\xb1\x7b\xdc\xef\xac\xe5\x53\xc1\xf1\x8a\xe1\x98\x94\xff\x1e\x07\x2d\x61\x8f\xa2\x04\x08\x9c\x9c\xb7\x72\x6d\xc6\x75\x19\x22\x88\xad\x1e\x5a\xc6\x16\xba\x60\x70\x5e\x9b\x2a\xc5\x97\x75\x9a\x45\x8a\x02\x78\xec\xbb\x32\xe7\x4c\x19\x35\x84\x44\xe1\xfc\x21\xb6\x86\x61\x97\x74\x6f\xad\xf2\x91\x60\x35\x54\xe0\x17\xe7\x4c\xd5\x30\xfe\x80\x8c\x5f\x6b\x05\x99\x53\x2f\xfd\x1d\x91\x63\xb1\x45\xe8\xa3\x50\xc6\x06\xf8\x16\x30\xdd\xa6\x53\xc7\x0f\xef\x21\xf8\x80\x50\xac\xfb\x47\xb6\xf6\xe5\xac\x86\x92\x0a\x41\x5c\x50\x0a\x12\xd0\x5d\xd5\xfd\xf2\x89\x44\x91\x0b\xc8\x6c\xf5\x4c\xf6\x58\xa8\x22\x23\xcf\x2c\x3a\x5d\x40\xb9\x14\x34\x0a\xe2\x72\x26\x43\xd1\x6a\x3e\x82\x73\x8d\x91\xa2\x54\xd4\xbb\x2a\xd7\x39\xa9\xc9\x86\x8e\x2c\xfc\xb0\xe4\xfe\x9e\xb5\xbd\xe5\xe4\x01\xd1\xe1\xda\x7d\x49\x49\x0e\x03\xb1\x2d\x03\x81\x76\xeb\x6a\xd8\x0c\xa8\x27\xb1\x64\x45\x58\x04\x61\x4a\x0d\xc6\xd3\x8a\xa7\xa5\x18\x0f\x74\x70\xe7\xdf\x07\xa5\x18\x53\x76\x95\xcc\x4c\x8e\x6b\x45\x08\x95\x16\x91\xf5\x7d\xe3\x04\xc0\xd8\xab\x45\xb0\xca\x4a\x2b\xd5\xcc\x52\xad\x2c\x88\xb0\x61\x1c\x46\x00\xb3\xaf\xcb\xd0\x89\x25\x57\xcc\xcd\x14\x12\x61\x2d\x37\x43\x48\xf0\x4a\x02\x7f\xf0\x4a\xe1\x2b\xaf\x37\x00\x67\x3e\x94\xf5\x76\x17\x8a\x5b\x7f\xe6\xf3\x37\x6b\x08\x44\xca\xd1\x2f\x0d\xf6\xe8\x33\x1e\x65\xc6\x03\xea\x6d\x2b\xc1\x48\x68\x48\x10\x2d\x12\xe1\xff\x11\xec\x62\xa0\x85\xed\x3d\xd1\x3c\x68\xa8\xa2\x5a\x59\x08\xd4\x27\x24\xa7\x33\xce\x39\xe0\x6e\xa9\xb6\x2a\xc2\x4b\xee\x3b\x51\xec\xeb\x58\x43\xd3\x31\xd3\x94\x26\x9a\x24\x90\x49\xc4\x45\x08\x87\xa3\x79\xa2\xc3\xca\xf4\xa1\xc2\x0a\x88\x68\xb0\x59\x92\xcd\xb6\xdd\x02\x6a\x09\x9f\xfa\xc6\x2b\xb6\xe5\x16\x98\x28\x55\xd6\xab\x2a\xdf\x18\xcf\x25\xe2\x40\x6c\x9c\x17\x6d\x32\x88\x6f\xc9\x55\x45\x87\x36\x5b\xdd\x52\x38\x40\xa5\x4d\x90\x68\xba\x90\xd8\x3b\x22\xcd\x2d\x68\x12\x69\xbb\x0f\x11\xd1\xe5\x37\xe6\xde\x1d\xa0\x8d\x29\x4e\xd0\x1c\xb4\x37\x1e\xa5\xb6\x79\xe3\xa9\xc3\x6e\x55\x45\x19\x1e\x7f\x4b\xac\x71\x4d\xd1\xb8\x4f\x40\x88\x37\x93\x86\x60\x94\x83\x43\x56\x56\x9c\xf7\x49\xf6\x5f\x9b\xbe\xb5\xd9\xe5\x61\x6a\x8c\xd4\x43\xbe\x2c\x7c\x95\xb0\xa0\xd3\x88\xc7\x8c\x9d\xbe\x96\x92\xbb\x4a\x75\x17\xad\xc1\x49\x0a\xcf\xc2\xa0\xe0\x3f\xd9\xed\x1a\x2a\x1b\xdd\xda\x10\x24\x83\xa1\xcb\x6a\x17\xad\xe6\x0e\xc2\x49\xa8\x61\x25\x08\x1d\xa2\x04\x39\x08\x9a\xd0\xfc\x29\x61\x7c\xca\xd1\x9b\xb5\x66\x40\xf7\x79\xd7\x89\x07\x66\x8b\x49\x17\x4b\xc2\x3e\xe6\x98\x33\x71\x80\x65\xce\x76\x97\x5b\x4d\xa6\xd1\xcf\x7a\xc2\x34\x01\x84\x9b\x87\x02\x74\xde\xa1\x76\x10\x1e\x52\xcb\x45\xbd\xd1\x85\xe4\xf4\xdf\xe9\x70\x26\xa0\xf9\x9c\x02\x88\x5e\x65\xf3\xca\x7f\xce\x88\x46\xb5\x22\xc9\x28\xef\x60\xa6\x42\x2c\x78\x59\xdb\xcc\xae\xac\x8a\xc1\x4e\x41\x89\xcd\xa1\x85\xdc\x69\x7c\xa3\x78\xc0\x68\x27\x81\x7b\x08\x33\x51\x66\xa4\x4d\xa0\x07\x2f\x61\x36\x98\xb0\x94\x44\xd2\xeb\x69\xe8\x4a\x6a\xfe\x9b\xc2\xf4\x3b\x00\x6c\x4b\x6c\x6c\x3c\xfd\x21\x84\xf7\x5d\xce\xeb\x00\xae\xbe\x14\x12\x05\x28\xfc\x02\x2d\xd2\xc5\x5b\xb2\x6c\x7f\x9c\xa2\xb3\x62\xd8\x7d\x44\x5d\xff\xd6\x61\x55\x73\x86\xb3\xc7\x70\xe8\xfc\x45\xc4\x64\xfc\x26\xfe\x02\x47\x16\xc7\xed\x80\xea\xea\xf9\x72\xe5\x4d\x01\x0d\x76\x09\xaa\x84\x91\x16\x4e\x56\xcf\x51\x19\x5f\xb8\xf5\x02\xfe\x0d\xa5\x0d\xb6\x73\x90\x33\xf1\xfe\x59\x37\x49\x1a\xa3\x79\x9d\xba\x39\x49\x07\xb6\x7a\x90\xae\xbb\x10\x50\xc2\x37\x51\x98\xf2\x88\x91\xfe\x41\x15\x18\xef\x1c\x3c\x53\x72\xc9\xad\x39\xca\xdd\x5b\xae\x0e\x44\x3c\x1c\x77\xb9\x70\x86\xc4\x2a\xd0\x8b\x45\x90\xec\x99\x40\x10\x47\x8f\xbf\xb3\x8b\x04\x29\x15\xb2\x5a\xb2\x9d\x29\x6f\x8c\x59\xd8\xc7\xce\x4a\x0a\xed\x23\x62\x75\xc4\x45\xc7\x0f\x98\x87\x78\x23\xa0\xc4\xec\x5d\x8e\x42\x3a\xd2\xcf\x92\xbc\x26\x82\xbe\x8a\xc2\x8d\xd5\x3d\x65\xd0\x6e\x0b\xd4\x89\x3d\x54\xad\xa4\x60\xce\x18\x4d\x91\xf6\x5c\xc4\xf2\xce\x14\xba\xd2\xeb\xc4\xa6\x85\xdf\x43\x47\xc9\xb0\x0f\x8b\xa2\x1f\xc7\xe1\x12\x01\x0b\x80\x30\xb0\x38\xff\xf5\x59\x64\xbf\x51\xd4\x37\x96\xd0\xb6\x2d\x6c\x82\x82\xb0\x57\x74\xf4\x61\xa6\x96\x1d\x37\xd5\x6d\x56\x16\x53\x2c\xed\xfe\xf9\x0c\xbd\x78\xfd\xa2\x76\x62\xa7\x25\x26\x8c\x40\x79\xd0\x03\x8c\xdb\x4a\xb8\x9f\x33\xf6\x5f\x48\x7d\xa0\xa4\x86\xba\x8a\xc4\xc5\x35\x95\xde\xa8\x48\x30\xc7\x3c\x92\xf0\xd8\xb4\x97\x93\x94\x1a\x8b\x7c\xc6\x8c\x7e\xb7\x43\xfe\xdc\xf6\x41\xf7\xb5\xa1\x39\x89\xb6\x21\xa7\x84\x38\x06\x33\x9f\xe0\x01\x5d\x62\x1c\x09\x17\x86\xeb\xe0\x2d\x15\xfa\x73\x13\xe1\x01\x99\xd9\x7a\x7d\xde\xd9\x09\x78\xba\xeb\xe4\xe4\x19\x65\x4a\xe9\xea\xfc\xc5\x28\x3e\x0e\xd4\xb1\xe0\x40\xb3\x7a\xdc\xc7\x33\x68\x37\xa7\x8c\x76\x90\x20\xc1\xef\x1e\x59\x27\x87\xc8\xeb\x3e\x63\x0f\xa2\xc5\xbd\x64\x4a\x8d\xc2\xbf\x16\x28\x9d\xd5\xfc\x1c\xc1\x18\x34\xe4\x13\x1e\x92\xd6\x87\x41\x73\xeb\x0d\xd2\xea\xbb\xd1\xc0\x61\x99\xd8\x6b\x21\x46\x9b\x4d\xb8\xe3\xe3\x51\xb9\xcc\xe2\x03\x22\x72\x4e\xd5\xe4\x05\x11\xd2\x7c\x30\xc6\x9d\x48\xfe\xca\xe8\x74\x06\x5b\x81\xb5\xfa\xfb\xb2\xa3\xe2\x6f\xfc\xe2\x6a\x73\xf9\xfe\xde\x4f\x34\x34\xf8\xe8\x22\x63\x73\x4b\x81\x4c\xcb\x60\xad\xe0\x73\x19\x24\x8f\x86\xab\xbd\x97\x58\xc4\xbc\xd1\x78\x09\x10\xb7\x28\xdd\x1d\x7b\xa3\xe4\xb7\x21\xe8\x5d\x0d\xc5\x83\x5b\xed\xa4\x70\xec\x57\xc3\xc1\x71\xa4\xfd\x7f\x52\x48\x17\xd9\xee\x76\xd1\xa8\xd7\x01\xf4\x3a\x86\x0e\xe6\xc8\xf0\xed\x99\x46\x9d\x03\x04\x90\xc3\x4a\x1c\xe3\xf2\x3a\xfc\x09\x0d\xf1\x88\x13\x16\x28\xc8\xa3\x4e\x8d\xac\xe0\x10\x62\xa2\x83\x1b\x48\x71\xda\x24\x85\xfb\x0d\x00\xf3\xc1\x90\x20\x53\x81\xa5\xc2\xad\xdf\xd1\xb2\xde\x34\xf1\x85\xb4\x7f\x00\xf5\xe9\x3c\xb3\xa3\xde\xb1\x97\x5f\xa5\x3c\xc0\x2b\xc1\x12\x57\x51\x66\x38\x67\x8a\xb1\x50\x1b\x24\x64\xe1\x77\x0c\xcf\x61\x98\x1e\xff\xf9\x08\xdd\xfb\xbf\x60\x78\x4a\x26\x2a\x86\x88\xf5\xe0\xe4\xe1\x62\xa0\x56\x93\xf6\x1b\x93\x52\xa0\x31\xa2\xa4\x33\x4b\xb5\x4b\xc8\xac\x3a\xd3\x89\xf3\x8a\xfa\x8a\x1d\xc8\x70\x4b\x08\x31\x8b\xe0\xa8\xe0\x95\x53\x76\xf4\x35\x29\xdf\x7b\xdc\x30\xfb\xf9\x8c\x19\xfa\xb6\xcc\x73\x70\x61\xdd\x0e\x83\xc2\x3a\x71\xe7\x5b\x85\xa6\xb6\xa2\x35\x82\xac\xbf\x12\xdf\x4b\x53\x12\xe3\x6e\xcf\x96\xa6\x04\xe5\x51\xe8\x5d\x68\xc3\x5c\xe0\xad\x41\xeb\x34\xc6\x1b\x2f\xe3\x79\x4f\xd8\x7c\x0b\xdd\xeb\x3f\x89\x2b\x51\x06\xb1\xa8\x6b\xfc\xcf\x6e\xf6\x91\x2b\x54\xc2\x35\x2e\x15\xc8\xe6\xc9\xb6\xc9\x18\x25\x3d\xec\x40\x4b\xcf\xad\xe7\xcf\x62\x32\x85\xce\xd4\x9d\x97\xf4\xd0\x33\xea\x22\xd3\x6f\x23\x83\x8b\x29\xa0\xfa\xdd\x70\xf4\xdf\x49\x5c\xb2\xc9\x14\x92\x89\xa4\x4f\x8d\xad\xf1\x40\x82\xda\x85\xa5\x3d\xd6\xf5\xc1\x99\xda\x89\x99\xb3\x21\xf5\xcb\x51\x06\xa4\xcb\xa1\x35\xee\xea\x34\x29\x0c\x5b\x0d\x77\xfa\x60\xb5\xfc\x4b\x2f\xfd\xcd\x93\x44\x20\xa9\x1a\x83\x1e\x2f\x0a\x93\x77\x7c\x31\x51\xf3\x0d\x04\x07\x9c\x4a\x57\xe1\xdd\xc7\x17\xf7\x95\xc9\x16\x3f\xee\x20\x7a\xd2\xcc\x48\xe9\x79\x6b\x08\x4e\x23\x75\x14\xdb\xc7\x74\x7a\x15\x02\xfe\xe2\xf3\x5d\x03\x06\xc3\x85\xe3\x3e\x2b\x5d\x95\x8f\x65\x3f\xb1\xff\xda\x3b\xde\x99\x7c\xc4\x8a\x84\x5a\x29\x8b\x32\x2b\x24\x88\x9e\xfc\xcb\xf0\xe1\xf0\xac\x61\xe3\x6f\x0e\x2a\xf4\xf6\x73\x52\xbf\x97\xfe\x8d\x4b\xcc\x80\x33\x7a\x53\x3c\xd5\x55\x0a\xe2\x53\x02\x11\x80\xb8\x14\x6a\x21\x2e\x3d\xcb\x84\x9b\xbb\xdc\x0c\x1f\xe8\xd8\x1f\xa1\xbb\xf3\x4b\xd9\xcd\x38\x63\x09\xae\x59\x29\x81\x62\x24\xd7\x21\x82\x0c\x24\xfb\x97\x11\xf4\x15\x05\x22\x2d\x68\x6c\x7c\x8b\x46\x88\x96\x68\xcc\x26\x02\xfc\xd5\xd3\x5e\x15\xb4\x60\x2a\xcb\x4e\x2c\x51\x06\xe6\xf7\x39\xc8\xc0\x12\x29\x62\xf1\x27\xff\xd5\x47\x49\xec\x04\x3d\xa2\x45\x5c\xda\xcb\x3f\x8b\x7c\x1d\x55\x04\xbd\x79\x3c\x2e\x89\x3d\x7b\xfd\xc0\x6a\xd0\x9e\x0c\x99\x07\x28\xa2\x70\xd6\xdc\x72\xf9\x64\xcb\x11\x79\xa6\x22\xc9\x09\x45\x2c\x15\x69\x8e\x45\xed\xfc\x68\x40\x1e\x48\xf5\xae\x5e\x9a\x0e\x2d\x58\x3a\xc4\x2f\x95\x98\x9c\xb8\xac\x54\x33\xe4\x72\xaa\xcc\x50\x6d\x85\x47\x37\x4e\xb1\xae\x70\x48\xb2\x91\xa6\xe4\x21\x91\x6a\x08\x0e\x1a\xca\x0d\xc7\x4d\x82\x46\x74\xa5\xbd\xb2\xe2\xf8\x3a\xeb\xb9\x79\x02\x14\x48\x6c\xd1\x18\x4d\xb8\x75\x35\xc7\xc6\x13\x87\x61\x39\x54\x80\xad\x03\xcc\x61\x4a\x3c\xf5\xee\x71\x28\x67\xf7\x08\x9c\xf6\xa4\x63\x0b\xf1\xb1\x26\x75\x21\xc4\x99\x5d\xfa\x62\x07\xa8\x4b\xfc\x22\x89\xfa\x86\x4c\x3d\xc6\x13\x15\x6c\x56\xcb\x8a\xc0\xa3\x62\xd5\xe9\xef\xab\x05\x62\x70\x2c\xdb\x08\x73\x54\x34\xae\x59\xcf\x72\x87\x68\x9b\x84\x56\xed\xf5\xea\xb5\x0d\x9f\x5f\x53\x08\xf0\xae\x7d\xdd\xa7\x63\x88\x8b\x30\x66\x06\xaf\x7c\x6f\xab\x80\xe9\x64\xec\xcc\xf8\x59\x0c\x22\xc2\x31\x0d\xa9\xa5\x22\x35\x65\x22\x28\xd6\x0a\xb0\xbc\x7e\x59\xc6\x87\x40\x14\x20\x6c\x27\xa9\x95\x87\x21\x80\x08\x47\x68\xe1\x99\xea\x9d\x42\x22\x6f\x34\x7a\x5e\x11\xaa\x28\xb7\xb9\x4e\xa4\x3a\x9f\xef\x52\xd3\x61\x1d\xae\xd8\x93\xec\x71\x74\x5a\x66\xdd\x5d\x6c\xa7\xe0\x8f\x0b\x29\xeb\x0f\xd1\xcf\x1e\x69\x1c\x00\x0c\x07\xa4\x63\x85\xb9\x27\x84\x0f\x16\xd7\x2a\x7c\xf9\x74\x10\xd8\xcb\x51\x02\xdc\x8b\xbd\xa2\x36\x62\xf4\xe3\x7f\xd1\x8e\x3d\x85\x5a\x9f\x00\x30\xf0\xc0\xbb\x16\xce\xab\x61\x46\x20\x70\x5d\x90\x7e\x98\x3d\x4e\x6d\x5c\x39\x46\xc3\xfd\x19\xa8\x15\xce\x0a\x04\x86\xf8\x48\x70\xc6\x2e\xe6\x8e\xe8\x06\x3e\x42\xec\xb5\xd4\xc0\x54\x17\xfa\x91\x3e\x07\x41\x2f\x5c\x34\x8e\xaf\xb7\xf3\x2e\x45\x64\x71\xa4\x14\xa5\xd6\x84\x2a\x40\xaf\x6b\x55\x27\x34\x41\xae\xd5\x79\xde\x22\xed\xd8\xba\x06\xc1\x7f\xa2\x9a\xc3\xdf\x29\x81\x30\x0b\x13\x82\x96\x2a\xd9\x8c\x25\xd8\xa9\xd9\xd2\x2d\x6c\x4a\x28\x39\x0b\x01\xf0\x83\xad\x03\x1d\x3a\xe8\xbc\x03\x29\x17\x17\x49\x05\x43\xc3\xb8\x08\x1c\xa0\x0f\x99\xd1\x3b\x30\xde\x03\xb9\x3a\x58\x83\x4b\x4f\xe7\x63\x0c\x66\xaf\xdb\x09\x88\xff\xb4\xf9\x33\x70\x63\xe4\x31\x9b\x90\x37\x3a\x96\x5d\xb1\x4b\xa1\x9f\xc6\x43\x1d\xc3\x7e\xe9\xf6\xb8\x2d\xd7\x5f\xf0\x2b\x00\x99\x27\xc2\x59\xc3\x73\x86\xfb\x8d\x77\x93\x6e\x3f\x5d\xce\xb9\x20\x9b\xbb\xfb\xc8\x66\x34\x89\x6f\x28\x35\xa8\x61\xbd\xe9\x5c\x82\x2a\x85\xea\x85\x85\x15\x0c\xbd\x44\x74\x9d\xdc\x37\x48\x15\xe5\x9f\x81\x46\x7e\xb4\xb3\xb5\xec\x0d\x7b\xf0\x7e\x06\x70\x81\xe0\xfa\x76\xa4\x5a\x5d\xc0\x72\xa4\x68\xc8\xca\x56\xcb\x1d\xae\xbd\xbd\x84\xe6\x66\x14\x51\x33\x41\x2f\x70\xe7\xd4\xee\x20\x9d\xc4\x38\xc9\x88\x10\x82\x28\x57\x8e\xba\x15\xb2\xb2\x56\x29\x0b\x34\x98\x8a\x83\x46\x6a\x13\xd6\xca\xcd\x59\x7f\x1d\xc9\xd0\x34\xe2\x32\x4e\x6c\x3b\x43\x25\x8a\x87\x7d\x93\x83\x9f\xba\xd5\xba\x0a\xba\xb9\x78\xe7\x24\x02\x81\x59\x5b\x74\x4b\x8b\x90\x6b\xf9\xf5\x72\xc8\x35\x59\x4e\xe8\x6b\x85\xe1\x55\xd6\x2b\x82\x96\x16\x02\xc4\x18\xca\xff\x0e\x92\xbe\x1b\xe8\x5d\xe6\x3b\xb2\xd3\xb2\x5e\xf5\xa3\x38\x4a\xc1\x62\xc5\x60\xd0\xdb\x4e\x97\xb4\x9e\x58\x47\x02\xf7\xc2\x88\xe6\xf9\x9a\x12\x02\x42\x8f\x57\x0b\xff\xe8\x54\x08\x3c\xa5\xea\x05\xa4\xfa\xc8\xaf\xe5\xda\x37\x37\x36\x74\x01\x07\xad\x6d\x4f\xd8\x30\x4b\x5b\xec\xaa\x60\xf6\xbf\x0e\x05\xad\x25\x3f\xeb\xeb\xf1\xe2\x5d\x93\xfc\x4f\x91\x12\x64\x3a\xd5\xca\x7d\x48\x86\xb6\x0a\x08\x61\x4f\xe6\x59\xab\x9f\x14\xca\xdb\xf5\x99\x45\xd8\x7f\x01\xfd\x63\xc7\xeb\x27\xb0\xcc\x49\x73\x07\x8d\x77\x49\x20\x26\xcf\xe1\x22\x29\x43\xa4\x90\xe9\xbd\xfd\xfa\xbf\xf1\x8b\xcb\x08\xc0\xf5\x40\x71\x17\xdb\x96\x35\x68\x53\x2b\x0c\xcb\xc2\xc3\x03\x07\xa5\xc8\x09\x4b\xa0\xc7\xbf\x2e\x92\xc8\x5e\x38\x66\x52\x8d\x1f\x1f\xed\xab\xcb\x37\xe8\xb0\x35\x3b\xd6\xfd\x04\x17\x8b\xa6\x9b\x8e\xc4\xcf\xc4\xff\x57\x92\x6c\xde\x70\x01\x47\xa6\x02\x95\x02\xa1\x97\x12\xfc\x24\xfa\x01\xb2\xa9\x1c\x79\x59\x2e\x60\xe8\xdc\x48\x33\xc1\x3b\x47\x61\xc6\xc2\xa7\x80\x0e\x70\xa9\x5e\x71\xf8\x23\x1f\x6b\x94\xb9\xda\xc4\x2f\xc3\xd6\x01\x78\xbb\xa7\x58\x12\x63\x66\x4b\xc4\x12\x40\x24\xc0\x91\xd4\x36\x76\x6f\xad\xe7\x8a\x20\x97\x5f\x79\x10\x94\xac\xc1\xc8\x02\x13\x0f\x98\x55\x5b\xc8\x68\x4e\x49\xfa\x1c\x86\xf1\x06\xae\x09\xff\x5c\x0e\xb0\xe3\x61\x65\x26\x65\xe5\x9c\xc8\x00\x4c\x0f\xc5\x05\x62\x5d\x3b\xae\x17\x0b\x9f\x75\xc6\x1d\x84\xb3\xac\x1b\x80\x55\x18\x44\x06\x24\xc2\xc1\xdd\xd7\xb5\x15\xf9\xd1\x73\xa4\xeb\x87\xc1\x18\x66\xb3\x0b\xbc\x8c\x90\xa3\x70\xf1\x11\xad\x2b\xb5\x05\x21\x17\xa3\xb1\x7b\x40\xd6\xc8\xd0\x6b\x2d\x9d\xca\xab\x18\x6f\x10\x73\x2e\x35\x1c\x1d\x0d\x6a\x16\x58\xf1\x8f\x4e\xda\x81\x00\xaf\x64\xf9\x5a\xf9\x15\xed\xa3\x0f\x43\x31\xfb\x18\x0b\xcc\xbf\xb3\xc4\xa7\x78\x32\x76\x68\xc1\xc4\x02\x11\x28\xee\x19\xe5\x61\x57\xfd\x5d\x3e\xc5\x3c\x16\xf9\xbc\x4f\x8f\x60\xde\xc5\x9b\xcd\xe2\xf0\x1c\x0c\xbf\x06\x71\x78\x83\x2c\x7c\xa5\xe8\x2a\x89\xb0\xb9\x05\xc6\x9b\x07\x2b\xd5\xbb\x40\xba\xc8\x61\xbc\x7d\x79\x24\xe6\x96\x7f\xd3\xb4\x7e\x84\xe7\x17\x57\x33\xb7\x83\x90\x75\x44\x85\x07\xa2\x9c\xc7\xab\xc3\xe1\xd2\xf7\x23\xf0\x65\xb7\xf9\x77\x48\x14\x32\xd6\x95\xe1\x56\x7f\x91\xc8\x11\xcd\xfe\x0a\xec\xb8\x62\x76\xd9\x34\xa9\x59\xbf\x1f\x97\xc3\xdd\xa1\x86\xa2\xe0\x88\x00\x20\x45\x0c\x5b\x20\xc7\xa7\xe0\xcf\x39\xc9\xcc\xaa\x33\x85\x02\xd7\xff\x84\x34\x63\x5b\x3e\xa3\x2d\xe2\x26\x2d\x08\x33\x6e\x3a\xeb\x1f\xa7\x3e\xd3\x36\x60\x80\x47\x76\x0e\x80\x8e\xb6\x17\x4c\x1d\xec\xf0\xb7\xbd\x85\x81\x0d\x5c\xbe\x8f\x1c\x2c\xae\x90\x79\xaa\xcd\x06\xb3\x50\x4b\xf2\x82\xbc\x51\xc5\xd7\xf7\x81\xf5\xa3\x36\xc0\x4a\x61\xa1\xad\x55\xb1\xdb\x97\x3b\xa4\x1d\x5e\x24\x76\x6f\x21\x57\xed\x1a\x6d\xcc\x85\x92\xda\x76\x59\x6d\x3c\x49\x65\xc9\xfe\x4f\x8c\x64\xd7\x9c\x8f\x2c\xd4\x65\x36\x70\xb7\x0d\x83\x06\xe0\x71\xe4\x02\x4b\x2a\xe1\x5d\x79\x63\xe6\x51\x75\x04\x9b\x08\xe7\x8b\x4c\x2a\xad\x0a\xb4\x6a\x05\x25\xbb\xc5\x35\xd4\x14\x09\x25\xce\x5e\x7e\x02\x88\x06\x4a\xbb\x08\x7a\xab\xc3\x94\xa9\x94\xc5\xaa\x3d\x9d\xff\xf6\x49\x94\x16\x9c\x48\x78\xf3\xa9\xcf\x64\x6c\xef\xea\xf4\xf5\xfd\x92\x41\x0b\x9f\xf7\x4e\x87\xe9\xdb\x34\x8d\x45\xd9\x5f\x7f\x51\x01\x9a\xe7\x7f\xf7\x31\x4f\x0f\x80\x39\x40\x43\xdf\x36\x5e\x69\x7f\x74\x28\x2e\x5b\x18\x4c\x6d\x6c\xcc\x42\x00\xfe\x3b\x46\xda\x72\xc3\x60\x2f\x5c\xdd\x21\x51\x15\x9c\x21\xca\x28\x7f\x6f\x85\xed\x7b\x06\x1f\xc9\x79\x69\x5a\x29\x71\x83\x12\xb4\x1f\xf2\xbf\x1f\x7f\x2f\xc0\xfa\xfd\x94\x62\xc3\xbf\xf4\x0a\x88\x15\x0d\xc0\x50\xd5\x92\x01\x00\x60\x32\xff\x31\x1e\xba\xdb\xd8\x03\x00\x1a\x21\x57\x9a\xb2\xb1\x1b\x3c\x70\x5b\x0f\x68\xbc\x81\x32\x4a\xc5\x2e\x78\x94\xad\xae\x10\x9d\x97\x77\x1f\x12\xe4\x07\x11\x49\xae\xa5\x0f\x3e\x55\x03\x4c\xf9\x9c\xcc\x6f\xaf\xaf\xb4\x60\x05\x79\x3b\x7e\xb3\x1c\xde\x22\xa5\x42\x8e\x29\xd7\x4d\x98\x59\x32\x97\x86\x4b\x29\x51\x0d\xaa\x4e\x56\xad\x63\xe3\xdd\x4f\x13\x47\xf3\x19\x0d\x3b\x32\x46\x65\x9d\x2a\xba\x25\xa7\xe7\xa2\x10\x8d\x4d\x6c\xf3\x6b\x98\x61\x86\xe8\xfa\xce\x55\x96\xaf\x90\xf9\xc0\xe4\xf4\x92\x89\x61\xd1\x5a\xbe\x89\xfd\x26\xc1\xb9\x65\x46\x53\xd4\xb4\x57\x1b\xf5\x0d\x9d\x7d\xec\x10\x88\x7a\xc5\x8b\x7e\xe1\xd4\xbb\x5d\x59\x9e\x8c\x4b\xc6\xd5\xae\xe0\xd0\x1e\x0b\xfb\x8a\xa6\xda\x16\x24\xa8\x8e\xeb\x0e\xf2\x14\x0e\xea\xee\x40\x79\x64\x54\x8d\xa1\xca\xd5\x2c\x91\x86\x85\x7a\xf6\x1e\xc3\xb4\x3b\xab\x40\xe3\x91\x71\x8d\x28\xce\x3b\x1c\xed\x46\xa0\xf7\x3f\x2d\xa1\x2f\xfb\xbb\x29\x5e\x48\xd8\xa4\x92\xc6\xca\xf5\x7e\xb6\x52\x6f\xac\xeb\xa9\xda\x76\x00\xb4\x5b\xe7\x49\x18\x47\x18\x9a\xdd\xd8\xea\x82\x6f\x14\x2d\x17\x38\xea\x70\x27\x02\xd8\x9f\xc3\xfd\x51\x79\x91\x41\xb3\x76\x08\xd8\xbb\xcf\x04\x08\x2a\x22\x55\x09\x70\xed\x09\x09\x36\x11\x20\xca\xcb\x09\x3b\x32\xe8\x32\x7e\x00\xbc\x1a\x69\xfd\x8d\x13\x32\x88\xef\x71\xd6\x70\x2b\xaf\x68\x3b\xe6\xc0\x15\x8c\xba\xe9\xed\xd8\xae\x49\xfb\x07\x32\x0a\xcd\x3d\x09\x00\x02\x6d\xb5\x76\x8f\x07\xfe\x46\x18\xc9\x2a\xc4\x30\x66\x31\xac\xaa\x9e\xdb\xbf\xcc\x6c\xb7\x5f\x55\x3d\xf9\x08\x3e\xf7\x5b\xe6\x95\x4d\x1f\x50\x2b\x6e\x0f\xda\xd9\x6e\xe9\x82\xeb\xf0\xce\x89\x53\xd7\x4f\xbd\x3e\x9c\x00\x0d\x7c\x1a\xcf\xc8\xca\xcd\x2d\x0a\x03\xf5\x6d\xc7\xf6\xe0\xe1\x54\x9f\x35\xc0\x7f\xbb\x98\x4d\x27\x15\xcb\x9b\x1c\x8f\x19\x11\xf4\x39\x11\xe9\xaa\x03\x29\x52\xa4\x46\x2f\x16\x4e\xb6\x02\xb8\x74\xf1\xb9\xac\xb0\xe8\x10\x8b\xda\x19\xc7\x15\xb0\x39\x4f\x19\x30\x8f\xb3\x84\xf8\x1a\x15\x57\xc6\xde\x30\x32\x71\xc7\x15\x8e\x3a\x90\x76\x2c\x57\x9a\x83\xcd\x4c\x37\x35\xdc\x43\xfc\x56\x76\x35\xf0\xf0\xff\x3b\x05\x85\x1b\x8f\xca\x6b\xe4\x07\x9f\x89\xf2\x1b\xa6\x31\xc3\xe3\x83\xfb\xd7\x96\x43\x13\x34\x46\xd3\xe5\x23\xdb\x2e\x73\x16\xdd\x35\x93\xab\x22\x1e\x06\xf5\x08\x70\x5b\xd3\xd8\x84\x61\xbd\x91\x68\x61\x86\xae\x0a\xd2\x5b\xc8\x03\x48\x7c\x44\x67\xf6\x48\x52\xcd\xb6\x7a\x9a\xcc\x62\x85\xd2\xef\x0f\xdb\x43\xe1\xd4\x98\x71\x37\x88\x44\x5a\x4e\x4a\x1c\x78\x9e\x1a\x4a\x37\xcc\x10\x75\x43\x39\x91\xe0\x7b\xd7\xa6\xe7\xcd\x2a\x53\x5f\xed\x67\xf4\xe6\x16\x7a\x16\x72\x14\xae\x84\x3b\x82\xbc\xd9\xf3\x9d\x31\x96\x03\xfc\x52\xa7\xe0\x20\x09\x89\x1d\x22\x40\xc7\xb3\x41\x5e\xeb\x42\x1f\xa6\x0a\xb5\xa0\xea\x8e\xb7\x33\x08\xb0\x9c\xdc\x8a\x6d\x6e\xc3\xb2\x5c\x53\x25\x0e\x7d\x50\xc3\x8f\x6d\x49\x9f\x54\x54\xd4\xd9\x39\x7e\xfd\x60\xdc\x8d\x8e\x61\x4a\x46\x9d\xdf\xd8\x6b\x17\xa4\x7e\x7d\x6c\x87\x22\x85\xf7\x7e\x1e\x6b\x11\x51\xdf\x46\x0c\x60\x94\x7f\xae\x45\x3f\x88\xe7\x0b\x37\x6b\xf0\x29\x5d\x3e\xf5\x03\x3a\xe3\x94\x1a\xac\xe1\x17\x54\x05\x1d\xae\x68\xa5\x6d\xff\x46\x31\xc2\xa8\xa9\xf2\xcf\x19\x30\x98\xa2\x8f\x5a\x7f\x73\x6f\x3e\x92\x1e\x84\x2a\xba\x7a\xb6\xfc\xf7\xa5\xca\x7d\x8f\x15\x8e\xc1\x55\xea\x30\xb3\x5f\xfb\xfa\xde\xe7\x9b\x5c\xf8\xb1\x31\x14\xeb\xe7\x5d\x12\x49\x49\xa8\xff\x88\x5d\x80\x7a\x0a\xce\x1d\x20\x97\xff\x1f\x8e\x6f\xd1\xfc\x34\x3f\xa0\xea\xc6\xb0\x3e\xff\xf5\x41\x2f\xde\x3b\x44\xd6\xb5\xbe\xa9\x13\x7b\x96\x33\x00\x00\xf4\x90\x14\x11\x54\x31\xdf\xbe\xca\x90\x54\xc0\x5d\xc3\xf6\x2f\x73\x1c\xa5\x4e\x29\xb6\x74\x1c\xa3\xe6\x22\xeb\x28\x4e\xfc\x87\x47\x91\x52\xa9\xd2\x61\x5d\xc6\x63\x92\x76\x80\x8d\x18\xc1\xe2\x3e\xcc\x78\x60\x03\x0a\x39\x69\xc8\xf8\xcf\x10\x23\x42\x76\x94\x28\x1e\x29\x0b\x1c\x88\x08\x0a\x6e\x3a\x32\x9a\xbe\xe4\x1f\x33\x49\x13\xa4\x01\xcf\x8c\x42\x14\x0f\xbe\x9e\xa6\xc6\xc0\x60\xd9\xd7\x4e\xae\xa5\x34\x51\x90\x28\xb5\xff\x43\xf2\x75\xf3\xf7\xc7\xe7\x8b\xc1\x4e\xf1\x02\xec\xd6\x2e\x1e\x71\x5b\xf8\xbf\xd6\x77\x15\xd1\xed\x3c\x89\xd0\x76\xd0\x5f\x5a\x63\x37\x53\xe7\xb1\xbd\xf9\x79\x0c\x8c\xbb\x47\x77\xde\xb9\x34\x17\x1e\x7a\xcb\x7a\x24\x2d\x38\x87\xbf\x85\x1f\xad\x46\x21\xd6\xa9\x36\xa4\x5d\xb2\x53\x14\x55\x8a\x65\x5b\xeb\x6b\x4b\xe7\xd1\xf3\xf1\x2d\xdf\xee\x53\x0d\x42\x78\x4f\x28\x50\xa6\x16\xe3\xd4\x93\xc5\x66\x26\xa5\xe4\x65\x2c\x5c\x67\x7e\x27\xc5\xfa\x59\xa5\x28\xbe\xe3\x48\xf5\x3a\xcf\x5a\x9d\xa5\x33\xb0\x16\xd9\x1e\xbc\x0c\x1e\xec\x5d\xcd\xdc\x2c\x1a\x49\xf6\x4f\x62\x57\xe3\xbf\xc5\xa2\xd1\x0c\x27\xa4\x70\x27\x73\x15\x52\x7a\x20\x01\x5c\xee\xde\x93\x3d\x4b\x63\x74\x34\x3a\x25\x87\xf0\xa0\x05\x3d\xc4\x9d\xf5\x0b\x5f\x8d\x97\x15\x74\x45\xd2\xb4\x90\x59\xe2\x99\x43\x9e\x79\xc1\x43\xb1\xcb\x30\x73\xbf\x2e\xc1\x0a\x19\xa6\x91\x0e\xaf\xa7\xe5\x91\x97\x01\x96\x4c\x7d\xaf\x0b\x9a\xbf\xb9\x94\xb2\xd4\xed\xac\x6d\x34\x5d\x8a\x0e\xbe\x9a\x0d\x16\x69\x8d\x7f\xff\x5d\xdd\x55\x58\xeb\xef\x17\x0a\x62\x4b\xdc\xbe\x2e\xe0\xa2\x0b\xdc\xa7\xd9\xc4\x47\xcb\x60\xc8\x0e\xe5\x65\x72\x7d\x95\x71\x30\x7d\xfc\xdb\x63\x26\xf9\xb0\x4d\x94\x2a\xc4\x4e\x2c\x13\x5e\x7d\x28\x26\xb0\x9b\xa6\xef\x93\xd2\x56\xdf\xcb\x93\x70\x8d\x4a\x0c\xea\x9a\x16\x94\x5d\x60\x3a\xe7\x75\xcd\x87\x45\x03\x77\x8e\x86\xbe\x0e\x44\x9a\x1c\xf2\x82\xb3\xa0\x6a\x43\xc1\x22\xed\x2f\x00\x1d\x6b\x34\xbc\x82\xe0\xaf\x16\xf1\x12\x88\xd0\x49\x4e\x43\xc0\x1a\x0b\x6c\x55\x13\x20\x94\xa4\x6f\x02\x1b\xe1\x3c\x40\xbe\x2a\x11\x07\xd2\xc8\x6e\x90\x95\x07\x99\xa1\xe8\x71\x29\xa0\x35\x72\x36\x4f\x81\x54\x7f\xc9\x02\x2a\x50\x71\x53\xed\x74\x32\x78\x03\xfb\x28\xb0\x26\x7d\xf5\xd6\x11\xbe\x7d\x82\xf5\x34\x63\x7a\x42\xda\x61\x54\x78\x04\x66\xd9\x19\x74\xf4\x47\x50\x1b\xfe\xd6\x1b\xb2\x3a\xeb\x12\xac\xca\x0f\x29\xed\x75\x46\xbc\x19\xa4\x64\x91\x57\x56\x76\x2f\xf0\xd0\x75\xf0\xb6\xa7\xfd\x7e\xf8\xe2\x7e\x0f\xa5\x0c\xcf\x6e\x15\x9e\xad\x9c\x96\xb8\x13\xb7\xfb\xb8\x38\x93\x8f\x92\x2d\xad\x04\x37\xeb\xcb\xa7\x8c\xfe\x8d\x69\xc9\xfa\xb5\x72\xac\x7b\x89\x67\xb5\x04\x2d\x99\xd8\x70\x82\x88\x7a\xf8\xfc\x77\xd6\x39\x05\xe3\xd9\x33\x90\xe7\x18\xa5\xd5\x79\x62\xca\x72\x53\xc0\xe1\xf7\x27\x26\xff\xb7\x3b\x8c\x2e\xf0\x5a\xec\xf2\x73\x23\xe6\x6f\x4a\x9a\xb6\xde\xb2\x30\x38\xed\x85\x31\xf2\x43\x7f\x3c\x8e\xed\xd9\x84\xf5\xbc\xb0\x57\xfc\x54\xb6\x21\xbb\x0c\x82\xc3\x61\x0b\x52\xf3\x52\x3e\xfa\xa7\xd1\xa7\xdd\x58\xef\x67\xbf\x9d\x3e\xe8\x06\x7e\xa7\xeb\xe9\x39\x2c\xdc\x64\xf6\x78\xfc\x32\x81\xd0\xe5\xbd\x9a\xab\xe0\x47\xea\x5a\x30\x8e\x65\xde\x8e\x8f\x69\xe3\xd5\x11\xa8\x69\xf1\xe6\x16\x79\x98\xb9\x72\x1a\x70\x7d\xdf\x85\xfc\xbb\xe1\x3b\x2d\xef\xeb\x56\xfc\x25\x1b\x7d\x2c\xf6\xac\xdb\x87\xbe\x24\xc7\xed\x79\x25\x02\xf1\x3c\x87\xc0\xa8\x8b\x97\x5c\x68\xfb\xcb\xf8\x3b\x1a\xbb\x3f\xbb\xb0\x9a\x50\x2a\x9d\x6d\x2d\xfe\xaf\x96\xa5\x44\x4c\x7c\x82\x30\x86\xf9\xa0\x8d\x11\xb5\xb3\x15\x61\x2a\xe1\xbf\xa1\xde\x3b\x12\xe1\x57\xfc\x37\x64\xc5\xee\xfe\x46\x7f\x49\x40\x34\xfc\xde\xe3\x67\x9c\x9c\x0b\x35\x62\xb6\x52\x0c\xef\xf9\x27\x89\x36\x67\x7d\x7d\x2c\x43\x82\xa4\xa7\x1a\x03\x57\x71\x90\x28\x8f\x09\x7a\x1a\x62\x63\xd4\x95\x6a\xf4\xd1\xf2\x18\xcd\xef\xc8\x74\x1b\x46\x6e\x17\x65\x78\x38\xd1\x0e\x36\x96\x7f\xfa\x3c\x1e\xca\x15\x12\x3b\x87\xe9\x8c\x92\x2a\xc8\x2e\x67\xef\xf9\x4d\xb2\x04\xfa\x8b\x62\x30\x82\xd2\x56\xe1\x95\xe4\xad\x51\xe5\x08\x3f\xd7\x03\x34\x72\xe0\xf5\x5f\x7b\xf8\x53\x5a\x7c\xe2\x6b\xf5\x43\x13\xa1\xdb\xd9\x0f\x6a\x09\xde\xc2\x0a\x56\x8c\x69\xbc\x93\x46\x9c\x1c\xe4\x36\x30\x45\xe0\xe4\x41\xab\x4a\x43\xf4\x9e\xf2\xa0\xc4\x9e\x12\x97\x19\xf1\xea\xaf\xd1\xeb\x27\x4f\x20\x74\xf0\xcd\xfe\x73\xea\x35\x9d\xbf\xe6\xf6\xb4\xb6\xdf\xf4\x8a\xe8\x2b\x58\xad\x59\x56\xc8\x25\xa7\x95\x55\xe4\x67\x24\x9a\x93\x04\x59\xf7\xc0\xe8\x3b\xa9\x73\xac\xb8\x55\x28\x66\x60\x95\x68\x5d\xd8\x97\x04\x26\x23\x9c\x06\x62\xbc\x3e\x83\x63\xd4\x95\x5c\xdc\x47\x88\x45\xe8\x36\xd7\x7e\xfd\x62\xa9\x84\x5d\x44\x75\x90\xda\x64\x8f\x38\x26\x0a\x2a\x27\xc9\xed\x38\x36\xa7\xe7\x34\xba\xe8\x3b\x29\xa9\xe2\x12\x23\xbf\xb0\xfd\xcc\x58\x08\xa4\xe9\xd1\xcc\x1a\x37\xde\x98\x12\xf3\xe8\x4b\x6a\x93\xa7\xd2\xd1\x37\x53\xc5\x7e\xf4\x90\x3e\x32\xa0\x39\x11\x8a\x88\x65\x18\x1e\x25\x3a\x37\x6f\x8b\x42\x6c\x8c\x47\xd5\xa6\x5e\xc0\x62\x8b\x62\x33\x78\x59\x02\xb0\xf0\x94\x7d\x1d\x22\xca\xb7\x53\x63\xe8\x22\x14\x21\x04\xec\xa5\x48\xc7\x21\x8c\xe0\x88\xc0\x88\xe2\x4d\x98\x9f\x3c\x80\x75\x98\x9e\x08\x97\x98\xa6\x25\x1c\x0f\x0a\x5c\x80\xda\x82\x3e\x91\x8b\x0d\x52\x8a\x6e\x46\xa2\xfe\x86\xcf\x81\x15\xf8\xdc\x93\x97\xff\x26\x95\x83\x77\x48\x7a\x65\xdc\x57\x10\x85\xde\xc9\x9e\xe9\x5b\x6f\xef\x44\x4e\x6b\xe8\xc9\xf6\xbb\x3f\x6e\x6e\x12\x23\x1f\x73\x20\x99\x3f\x77\xac\xc7\xed\x47\x6e\xb0\xa3\x8f\x93\x3c\x86\x83\xca\xbc\xdf\x9f\xb1\x9b\x46\x1b\xeb\x5f\xf8\x1d\x61\xea\x3f\x00\xba\xbe\xd7\xff\x07\x73\x42\x6f\xa1\x57\x12\x4e\x16\x59\xe2\x4a\x0e\x73\xdd\x4e\xdf\x53\x06\x06\x04\x3e\x84\xc0\xb6\xc8\x0e\x8c\x2a\xd6\x9a\x03\x63\xa2\xfe\x28\x9b\xd6\xb1\x8c\xfb\xfd\xea\x3d\x8b\xc0\xda\xa0\x87\xff\x87\x44\xd7\x74\xc8\x3e\x6e\xaf\x30\xdd\xdf\xc2\x51\x42\x3f\x74\x0d\x0a\xbc\xcf\xb3\xe9\xaf\x0c\x05\xe2\xfb\xc2\x9b\xab\x3f\xc9\xc6\x4b\x26\x42\x9c\x61\x54\x6d\x2e\xea\xaa\x8f\x3d\x04\xe3\xfc\xbf\xa0\xd6\x31\xe2\x7f\x1d\x6a\xfa\x48\x65\xab\x9c\x4b\xc8\xbd\x71\x28\x0e\x97\xcb\x06\x47\xd6\x32\x6f\xdd\xcb\x7f\xd6\xe4\xef\x88\x4e\xac\xa6\xfb\xd7\x6a\x62\x79\xc5\x83\x46\x89\x6d\x7e\xe9\xef\x0d\x0c\x21\x65\x10\xf8\x26\xae\xc8\x2f\x14\x6d\x65\xbf\x8d\x28\xee\xe8\x48\x4e\xd3\x9c\xe9\x30\x72\x2a\x98\x98\x5c\xae\x54\x79\x3d\x81\x78\xf2\xbd\x39\x11\x12\x85\x01\x3d\xd0\xb0\x04\xf2\x98\x34\x9d\xf9\x18\xf7\x9b\x44\xfd\x9d\x19\xa1\x32\x17\xaa\x8e\x8f\xf3\x54\x8c\x99\x35\x06\x2a\xc4\xb6\x4f\x5c\x80\x08\x6b\xf0\xbf\x9f\xae\xe8\xb9\xc0\x8d\x5a\x94\x03\x8d\xe1\x9d\xf8\xdf\x58\x6f\x45\x72\x0c\x65\x09\x32\xb7\xca\xfc\x73\x9a\xb8\x9d\x23\x91\x9f\xe9\xc4\x6c\xed\x67\xb3\xc1\x18\x16\x66\xa6\xd9\x0d\x0e\x91\xa4\x53\x93\x61\x44\x50\xf1\x16\x18\x3f\x0d\x6b\x69\x5e\x7f\x8b\x3d\xbb\x05\x83\x01\x33\xe9\xda\xdd\xe9\x38\x4d\x97\x11\xd8\x20\xf5\xa2\xbd\xbb\xae\x3a\x84\x5a\xe7\x55\x55\x88\xee\x5f\x45\x81\x0e\x44\xf1\x52\x46\xec\x38\xba\x89\xe6\xe7\xaf\xf7\x8e\xe6\x32\x7c\x2f\xf7\x67\x4d\x07\x7d\x60\xc6\xb1\xb2\x25\xed\x7d\x58\x0b\x1e\x34\x05\x87\x3e\xd2\x19\x20\xbd\x6f\x4b\x84\xfc\x7c\xc4\xcf\xee\xb2\x36\x35\x62\x20\x10\x15\x47\x9e\xd8\xa1\xbf\xa2\xb0\x1e\xd7\x21\xf6\x79\xff\x41\x80\x36\x75\x56\x30\x32\x1e\x0b\xd3\xfc\xe3\x11\x68\xa6\xa6\x55\xb1\x26\x3a\xc6\xc3\xce\x84\x19\xe4\x32\xe7\xde\x38\x29\x0d\x9c\x70\xdd\x87\x4c\x31\x20\xe0\x31\x58\x51\x68\xc4\x9e\x68\x51\xc0\x11\x87\x69\x28\x8b\xed\x72\x15\xa2\xf0\x3f\x8b\x3d\xd6\x58\x3d\x24\xfc\x07\xd1\x28\xba\xe3\x70\x95\x8d\x05\x7a\x4b\x75\x68\x6b\x38\x96\x09\xf9\x0d\x8d\xcc\xfc\xcf\xf5\x0b\x24\x24\xfc\xd8\xbf\xe0\x65\x34\xb6\x03\xa5\xee\x0b\x93\xc8\xc7\xb6\x17\xd2\xd9\xc4\x93\xd1\xb3\xb9\xdc\x6b\xad\xfe\xe4\x4d\xc2\x2a\x1a\x58\x81\xd6\xf1\x6c\xb3\x4c\x92\x05\x61\x75\x8d\xf1\x3c\x2e\xca\x8f\xef\x10\x90\xdc\xdc\xf9\x6c\x01\x4e\x5a\x51\x23\x5e\x48\x4e\xeb\x02\xe0\x0f\x01\x56\x5c\x3b\xbb\x60\xda\x48\x5e\x71\x25\xba\x56\xbb\x06\x2b\x85\xf3\x08\xae\x5f\x6c\x72\xe3\x63\x5c\x8e\xf2\x68\x1f\x89\xe9\x3b\x79\x06\xe5\xdb\xe8\xc0\x40\xd6\x18\xc2\x04\xf1\x08\x2a\x40\x53\x72\x8b\x9c\x38\x7d\xd2\x6c\x9c\x78\x21\x32\xc4\x97\x4d\xae\xe5\x44\x9b\xea\xff\x8b\x39\x32\xfa\x66\x41\x20\x0e\xb7\x5f\xbe\xa5\x61\x32\x1f\xf9\x30\x6f\xaf\x80\x78\xe6\x2a\xc1\x94\x1f\xa6\x91\xd6\x38\x62\x6a\xd1\x43\xa4\x3b\x44\x9e\x99\xa9\x99\x83\x0f\x5c\x93\x71\xbd\x65\xf6\x9c\xdd\xf8\x5b\x95\xf6\x57\xd6\x40\x8a\x21\xde\x67\x7e\xd4\xa5\x61\xa4\x52\x5c\xdc\x3d\xb9\x16\xbb\x87\x08\x80\x1f\x8b\xb7\x3e\xd2\x51\xef\x00\x7f\x41\xbd\x5a\xcc\x5d\xe5\x06\x04\x4c\x64\x90\x16\x77\xb4\xb8\x0e\x8d\x6f\x66\x52\xf5\xbe\xf2\xbc\xf7\x58\xd1\x67\xf1\x16\xbb\xb5\xa9\x37\xd8\x1d\xb5\x74\xff\x63\x7e\x04\x07\x29\xad\xad\xd2\xdc\x91\xaf\xbe\x20\x76\xaf\x03\x2a\x0c\x36\x6e\x8e\x4b\xc1\x29\x6a\x10\xf6\xf0\xf1\x01\x15\x96\x27\xbc\x6e\x42\x8d\x9b\xb8\x20\x54\x5b\x3e\x61\x5c\x55\x15\x45\x7b\x21\x51\xc2\xab\x1f\xf0\x47\x80\x9a\xa8\x42\x7f\x6e\x03\xc4\x1a\x68\xb2\x12\x63\x25\xd3\x92\x7f\xbf\x6e\xd7\x32\xc8\xca\xa2\x76\x1f\x5c\x98\x31\x87\x87\x6c\x81\xe4\x72\x74\xb5\x69\x91\xeb\xde\x68\xf4\x1f\xde\x84\xa4\x44\x1c\x75\x1c\x7c\x4f\x1f\x32\x80\x53\xe1\xbc\x13\xf1\x53\xde\x13\x3a\x4e\xee\x23\x1e\xba\xff\xb8\xbc\xca\x7b\xde\xfb\x21\xf4\x7f\x9b\x11\xb5\x7f\xa2\x36\x25\xc5\xce\x7e\xc9\x7b\x80\x00\x00\x00\x72\x67\x51\x0d\x67\x63\x03\x67\x13\x6e\x23\x47\x13\x03\x67\x13\x00\x33\x23\x13\x3b\x1d\x23\x17\x1d\x33\x9b\x0a\x13\x0b\x37\x33\x2b\x37\x2b\x23\x1d\x23\x07\x37\x23\xe3\x30\x55\x32\xc1\xff\x26\xd8\xd8\x19\x5b\x98\x7a\xfc\x9f\x02\x13\xd7\x7f\x0b\x96\xf0\x66\xe8\x00\x00\x00\xfd\xbf\x04\x13\x77\x0b\x53\x6e\x61\x3b\x6b\x3b\x47\x65\x7b\x03\x23\x13\x00\x3b\x1b\x1b\x0b\x1b\x8b\x17\xc0\x16\x00\x00\x10\xff\x0f\x20\x62\xe0\x6c\xa2\x62\x61\xf3\xbf\x3e\xc1\xcc\xcd\xc8\xc2\xcd\xc8\x46\xc4\xc4\xc1\xcd\xc6\xce\xcd\xc6\x6e\x28\x30\xd8\x01\x00\x00\xb0\xff\x07\x16\x75\xb7\x30\x95\xb4\x31\x30\x33\x91\x31\xb1\x35\x73\x36\x07\x30\x31\x72\xb1\x91\xd8\x4e\x41\x00\x00\x00\xac\xff\x13\x52\xb7\x30\xfe\x2f\x86\x85\x99\xbd\xe2\x0e\x7d\x17\x00\x00\xa0\xfe\x6f\x8c\xbc\xa9\xa9\x93\x89\x33\x80\x89\x9d\x73\x68\xca\x8c\x1f\x00\x00\xd0\xfd\xcf\xb3\xb2\x9d\xa9\xb3\x9b\x81\xa3\x09\x40\xd0\xd8\xce\xd0\x84\x48\xc1\xdc\xce\xd9\xce\xc9\xdc\xce\x9e\x48\x58\x99\x8d\x9e\x89\x48\xd6\xc0\xc8\xc2\xf6\xbf\x6e\x7e\xb3\xbb\x3c\x01\x00\x00\xbc\xff\x11\x9d\xcd\x5d\x6c\x0c\x6d\x0d\x2c\xac\xb9\x85\xed\x6c\xec\x1d\x4d\x9c\x9c\x2c\xec\x6c\x01\xec\x6f\x26\xf6\xea\x00\x00\x80\xea\x3f\x70\x52\x0a\xa2\xe2\x92\xb6\xce\x26\x8e\x46\xe6\x06\xb6\x66\x26\x62\x76\x8e\x36\x06\xce\x00\x16\x46\x76\x21\xc9\x5a\x11\x00\x00\xc0\xf0\xff\xd7\xf9\xbf\x47\xc2\xca\xc6\xc9\xd6\xdf\x0f\x3d\x04\x00\x00\x08\xff\x83\xaa\x64\xe2\x64\x67\xed\xe2\x6c\x61\x67\xab\x6a\x6b\xe1\x0c\x60\x26\x17\xd0\x5b\x00\x00\x00\x44\xff\x01\xd5\xf8\x7f\x58\x00\x3b\x23\x23\x03\xd3\x51\x34\xdf\xd1\xff\x07\xab\xf9\xff\x66\x0d\x69\x9e\x4a\x01\x00\x00\x40\x52\x54\x4e\xa4\x56\xe8\x4f\xe0\xff\x15\x00\x00\xff\xff\x5a\xc3\xba\x2a\x98\x3d\x00\x00") 72 | 73 | func gopherPngBytes() ([]byte, error) { 74 | return bindataRead( 75 | _gopherPng, 76 | "gopher.png", 77 | ) 78 | } 79 | 80 | func gopherPng() (*asset, error) { 81 | bytes, err := gopherPngBytes() 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | info := bindataFileInfo{name: "gopher.png", size: 15768, mode: os.FileMode(420), modTime: time.Unix(1474835119, 0)} 87 | a := &asset{bytes: bytes, info: info} 88 | return a, nil 89 | } 90 | 91 | // Asset loads and returns the asset for the given name. 92 | // It returns an error if the asset could not be found or 93 | // could not be loaded. 94 | func Asset(name string) ([]byte, error) { 95 | cannonicalName := strings.Replace(name, "\\", "/", -1) 96 | if f, ok := _bindata[cannonicalName]; ok { 97 | a, err := f() 98 | if err != nil { 99 | return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) 100 | } 101 | return a.bytes, nil 102 | } 103 | return nil, fmt.Errorf("Asset %s not found", name) 104 | } 105 | 106 | // MustAsset is like Asset but panics when Asset would return an error. 107 | // It simplifies safe initialization of global variables. 108 | func MustAsset(name string) []byte { 109 | a, err := Asset(name) 110 | if err != nil { 111 | panic("asset: Asset(" + name + "): " + err.Error()) 112 | } 113 | 114 | return a 115 | } 116 | 117 | // AssetInfo loads and returns the asset info for the given name. 118 | // It returns an error if the asset could not be found or 119 | // could not be loaded. 120 | func AssetInfo(name string) (os.FileInfo, error) { 121 | cannonicalName := strings.Replace(name, "\\", "/", -1) 122 | if f, ok := _bindata[cannonicalName]; ok { 123 | a, err := f() 124 | if err != nil { 125 | return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) 126 | } 127 | return a.info, nil 128 | } 129 | return nil, fmt.Errorf("AssetInfo %s not found", name) 130 | } 131 | 132 | // AssetNames returns the names of the assets. 133 | func AssetNames() []string { 134 | names := make([]string, 0, len(_bindata)) 135 | for name := range _bindata { 136 | names = append(names, name) 137 | } 138 | return names 139 | } 140 | 141 | // _bindata is a table, holding each asset generator, mapped to its name. 142 | var _bindata = map[string]func() (*asset, error){ 143 | "gopher.png": gopherPng, 144 | } 145 | 146 | // AssetDir returns the file names below a certain 147 | // directory embedded in the file by go-bindata. 148 | // For example if you run go-bindata on data/... and data contains the 149 | // following hierarchy: 150 | // data/ 151 | // foo.txt 152 | // img/ 153 | // a.png 154 | // b.png 155 | // then AssetDir("data") would return []string{"foo.txt", "img"} 156 | // AssetDir("data/img") would return []string{"a.png", "b.png"} 157 | // AssetDir("foo.txt") and AssetDir("notexist") would return an error 158 | // AssetDir("") will return []string{"data"}. 159 | func AssetDir(name string) ([]string, error) { 160 | node := _bintree 161 | if len(name) != 0 { 162 | cannonicalName := strings.Replace(name, "\\", "/", -1) 163 | pathList := strings.Split(cannonicalName, "/") 164 | for _, p := range pathList { 165 | node = node.Children[p] 166 | if node == nil { 167 | return nil, fmt.Errorf("Asset %s not found", name) 168 | } 169 | } 170 | } 171 | if node.Func != nil { 172 | return nil, fmt.Errorf("Asset %s not found", name) 173 | } 174 | rv := make([]string, 0, len(node.Children)) 175 | for childName := range node.Children { 176 | rv = append(rv, childName) 177 | } 178 | return rv, nil 179 | } 180 | 181 | type bintree struct { 182 | Func func() (*asset, error) 183 | Children map[string]*bintree 184 | } 185 | var _bintree = &bintree{nil, map[string]*bintree{ 186 | "gopher.png": &bintree{gopherPng, map[string]*bintree{}}, 187 | }} 188 | 189 | // RestoreAsset restores an asset under the given directory 190 | func RestoreAsset(dir, name string) error { 191 | data, err := Asset(name) 192 | if err != nil { 193 | return err 194 | } 195 | info, err := AssetInfo(name) 196 | if err != nil { 197 | return err 198 | } 199 | err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) 200 | if err != nil { 201 | return err 202 | } 203 | err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) 204 | if err != nil { 205 | return err 206 | } 207 | err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) 208 | if err != nil { 209 | return err 210 | } 211 | return nil 212 | } 213 | 214 | // RestoreAssets restores an asset under the given directory recursively 215 | func RestoreAssets(dir, name string) error { 216 | children, err := AssetDir(name) 217 | // File 218 | if err != nil { 219 | return RestoreAsset(dir, name) 220 | } 221 | // Dir 222 | for _, child := range children { 223 | err = RestoreAssets(dir, filepath.Join(name, child)) 224 | if err != nil { 225 | return err 226 | } 227 | } 228 | return nil 229 | } 230 | 231 | func _filePath(dir, name string) string { 232 | cannonicalName := strings.Replace(name, "\\", "/", -1) 233 | return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) 234 | } 235 | 236 | -------------------------------------------------------------------------------- /examples/desktop-notification/gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexflint/gallium/bfa1ecd9241eb3b9f203869fb317c55c3c0c360f/examples/desktop-notification/gopher.png -------------------------------------------------------------------------------- /examples/desktop-notification/notification.go: -------------------------------------------------------------------------------- 1 | //go:generate go-bindata -o bindata.go gopher.png 2 | 3 | package main 4 | 5 | import ( 6 | "log" 7 | "os" 8 | "runtime" 9 | 10 | "github.com/alexflint/gallium" 11 | ) 12 | 13 | func main() { 14 | runtime.LockOSThread() 15 | gallium.RedirectStdoutStderr(os.ExpandEnv("$HOME/Library/Logs/Gallium.log")) 16 | gallium.Loop(os.Args, onReady) 17 | } 18 | 19 | func onReady(app *gallium.App) { 20 | img, err := gallium.ImageFromPNG(MustAsset("gopher.png")) 21 | if err != nil { 22 | log.Println(err) 23 | os.Exit(1) 24 | } 25 | 26 | app.Post(gallium.Notification{ 27 | Title: "Wow this is a notification", 28 | Subtitle: "The subtitle", 29 | Image: img, 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /examples/frameless/frameless.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "runtime" 8 | 9 | "github.com/alexflint/gallium" 10 | ) 11 | 12 | var index = ` 13 | 14 | 15 | ` 16 | 17 | func main() { 18 | runtime.LockOSThread() 19 | gallium.RedirectStdoutStderr(os.ExpandEnv("$HOME/Library/Logs/Gallium.log")) 20 | gallium.Loop(os.Args, onReady) 21 | } 22 | 23 | func handleIndex(w http.ResponseWriter, r *http.Request) { 24 | fmt.Fprint(w, index) 25 | } 26 | 27 | func onReady(app *gallium.App) { 28 | http.HandleFunc("/", handleIndex) 29 | go http.ListenAndServe(":8967", nil) 30 | 31 | opt := gallium.FramelessWindow 32 | opt.Title = "Frameless Window" 33 | _, err := app.OpenWindow("http://127.0.0.1:8967/", opt) 34 | if err != nil { 35 | fmt.Println(err) 36 | os.Exit(1) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/keys/keys.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "runtime" 7 | 8 | "github.com/alexflint/gallium" 9 | ) 10 | 11 | func handleMenuFirst() { 12 | log.Println("menu shortcut: first") 13 | } 14 | 15 | func handleMenuSecond() { 16 | log.Println("menu shortcut: second") 17 | } 18 | 19 | func handleMenuQuit() { 20 | os.Exit(0) 21 | } 22 | 23 | func onReady(app *gallium.App) { 24 | gallium.AddGlobalShortcut(gallium.MustParseKeys("shift cmd u"), func() { 25 | log.Println("global shortcut: U") 26 | }) 27 | gallium.AddGlobalShortcut(gallium.MustParseKeys("shift cmd o"), func() { 28 | log.Println("global shortcut: O") 29 | }) 30 | app.SetMenu([]gallium.Menu{ 31 | { 32 | Title: "Shortcut", 33 | Entries: []gallium.MenuEntry{ 34 | gallium.MenuItem{ 35 | Title: "First", 36 | Shortcut: gallium.MustParseKeys("cmd 1"), 37 | OnClick: handleMenuFirst, 38 | }, 39 | gallium.MenuItem{ 40 | Title: "Second", 41 | Shortcut: gallium.MustParseKeys("cmd 2"), 42 | OnClick: handleMenuSecond, 43 | }, 44 | gallium.MenuItem{ 45 | Title: "Quit", 46 | Shortcut: gallium.MustParseKeys("cmd q"), 47 | OnClick: handleMenuQuit, 48 | }, 49 | }, 50 | }, 51 | }) 52 | } 53 | 54 | func main() { 55 | runtime.LockOSThread() 56 | gallium.RedirectStdoutStderr(os.ExpandEnv("$HOME/Library/Logs/Gallium.log")) 57 | gallium.Loop(os.Args, onReady) 58 | } 59 | -------------------------------------------------------------------------------- /examples/menu/menu.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "runtime" 7 | 8 | "github.com/alexflint/gallium" 9 | ) 10 | 11 | func main() { 12 | runtime.LockOSThread() 13 | gallium.Loop(os.Args, onReady) 14 | } 15 | 16 | func onReady(app *gallium.App) { 17 | app.OpenWindow("http://example.com/", gallium.FramedWindow) 18 | app.SetMenu([]gallium.Menu{ 19 | gallium.Menu{ 20 | Title: "demo", 21 | Entries: []gallium.MenuEntry{ 22 | gallium.MenuItem{ 23 | Title: "About", 24 | OnClick: handleMenuAbout, 25 | }, 26 | gallium.Separator, 27 | gallium.MenuItem{ 28 | Title: "Quit", 29 | Shortcut: gallium.MustParseKeys("cmd q"), 30 | OnClick: handleMenuQuit, 31 | }, 32 | }, 33 | }, 34 | }) 35 | } 36 | 37 | func handleMenuAbout() { 38 | log.Println("about clicked") 39 | os.Exit(0) 40 | } 41 | 42 | func handleMenuQuit() { 43 | log.Println("quit clicked") 44 | os.Exit(0) 45 | } 46 | -------------------------------------------------------------------------------- /examples/native/native.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "runtime" 7 | 8 | "github.com/alexflint/gallium" 9 | ) 10 | 11 | /* 12 | #cgo CFLAGS: -x objective-c 13 | #cgo CFLAGS: -framework Cocoa 14 | #cgo LDFLAGS: -framework Cocoa 15 | 16 | #include 17 | #include 18 | 19 | void SetAlpha(void* window, float alpha) { 20 | // Cocoa requires that all UI operations happen on the main thread. Since 21 | // gallium.Loop will have initiated the Cocoa event loop, we can can use 22 | // dispatch_async to run code on the main thread. 23 | dispatch_async(dispatch_get_main_queue(), ^{ 24 | NSWindow* w = (NSWindow*)window; 25 | [w setAlphaValue:alpha]; 26 | }); 27 | } 28 | */ 29 | import "C" 30 | 31 | func onReady(ui *gallium.App) { 32 | window, err := ui.OpenWindow("http://example.com/", gallium.FramedWindow) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | C.SetAlpha(window.NativeWindow(), 0.5) 37 | } 38 | 39 | func main() { 40 | runtime.LockOSThread() 41 | gallium.RedirectStdoutStderr(os.ExpandEnv("$HOME/Library/Logs/Gallium.log")) 42 | gallium.Loop(os.Args, onReady) 43 | } 44 | -------------------------------------------------------------------------------- /examples/screens/screens.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | 7 | "github.com/alexflint/gallium" 8 | "github.com/kr/pretty" 9 | ) 10 | 11 | func onReady(app *gallium.App) { 12 | pretty.Println(gallium.FocusedScreen()) 13 | pretty.Println(gallium.Screens()) 14 | } 15 | 16 | func main() { 17 | runtime.LockOSThread() 18 | gallium.RedirectStdoutStderr(os.ExpandEnv("$HOME/Library/Logs/Gallium.log")) 19 | gallium.Loop(os.Args, onReady) 20 | } 21 | -------------------------------------------------------------------------------- /examples/simple/gopher.iconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexflint/gallium/bfa1ecd9241eb3b9f203869fb317c55c3c0c360f/examples/simple/gopher.iconset/icon_128x128.png -------------------------------------------------------------------------------- /examples/simple/gopher.iconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexflint/gallium/bfa1ecd9241eb3b9f203869fb317c55c3c0c360f/examples/simple/gopher.iconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /examples/simple/gopher.iconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexflint/gallium/bfa1ecd9241eb3b9f203869fb317c55c3c0c360f/examples/simple/gopher.iconset/icon_16x16.png -------------------------------------------------------------------------------- /examples/simple/gopher.iconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexflint/gallium/bfa1ecd9241eb3b9f203869fb317c55c3c0c360f/examples/simple/gopher.iconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /examples/simple/gopher.iconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexflint/gallium/bfa1ecd9241eb3b9f203869fb317c55c3c0c360f/examples/simple/gopher.iconset/icon_256x256.png -------------------------------------------------------------------------------- /examples/simple/gopher.iconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexflint/gallium/bfa1ecd9241eb3b9f203869fb317c55c3c0c360f/examples/simple/gopher.iconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /examples/simple/gopher.iconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexflint/gallium/bfa1ecd9241eb3b9f203869fb317c55c3c0c360f/examples/simple/gopher.iconset/icon_32x32.png -------------------------------------------------------------------------------- /examples/simple/gopher.iconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexflint/gallium/bfa1ecd9241eb3b9f203869fb317c55c3c0c360f/examples/simple/gopher.iconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /examples/simple/gopher.iconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexflint/gallium/bfa1ecd9241eb3b9f203869fb317c55c3c0c360f/examples/simple/gopher.iconset/icon_512x512.png -------------------------------------------------------------------------------- /examples/simple/simple.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "runtime" 7 | 8 | "github.com/alexflint/gallium" 9 | ) 10 | 11 | func main() { 12 | runtime.LockOSThread() 13 | gallium.RedirectStdoutStderr(os.ExpandEnv("$HOME/Library/Logs/Gallium.log")) 14 | gallium.Loop(os.Args, onReady) 15 | } 16 | 17 | func handleMenuQuit() { 18 | log.Println("quit clicked") 19 | os.Exit(0) 20 | } 21 | 22 | func handleDoSomething() { 23 | log.Println("do something") 24 | } 25 | 26 | func handleDoSomethingElse() { 27 | log.Println("do something else") 28 | } 29 | 30 | func onReady(app *gallium.App) { 31 | app.OpenWindow("http://example.com/", gallium.FramedWindow) 32 | app.SetMenu([]gallium.Menu{ 33 | { 34 | Title: "demo", 35 | Entries: []gallium.MenuEntry{ 36 | gallium.MenuItem{ 37 | Title: "Quit", 38 | Shortcut: gallium.MustParseKeys("cmd q"), 39 | OnClick: handleMenuQuit, 40 | }, 41 | }, 42 | }, 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /examples/statusbar/bindata.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-bindata. 2 | // sources: 3 | // icon.png 4 | // DO NOT EDIT! 5 | 6 | package main 7 | 8 | import ( 9 | "bytes" 10 | "compress/gzip" 11 | "fmt" 12 | "io" 13 | "io/ioutil" 14 | "os" 15 | "path/filepath" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | func bindataRead(data []byte, name string) ([]byte, error) { 21 | gz, err := gzip.NewReader(bytes.NewBuffer(data)) 22 | if err != nil { 23 | return nil, fmt.Errorf("Read %q: %v", name, err) 24 | } 25 | 26 | var buf bytes.Buffer 27 | _, err = io.Copy(&buf, gz) 28 | clErr := gz.Close() 29 | 30 | if err != nil { 31 | return nil, fmt.Errorf("Read %q: %v", name, err) 32 | } 33 | if clErr != nil { 34 | return nil, err 35 | } 36 | 37 | return buf.Bytes(), nil 38 | } 39 | 40 | type asset struct { 41 | bytes []byte 42 | info os.FileInfo 43 | } 44 | 45 | type bindataFileInfo struct { 46 | name string 47 | size int64 48 | mode os.FileMode 49 | modTime time.Time 50 | } 51 | 52 | func (fi bindataFileInfo) Name() string { 53 | return fi.name 54 | } 55 | func (fi bindataFileInfo) Size() int64 { 56 | return fi.size 57 | } 58 | func (fi bindataFileInfo) Mode() os.FileMode { 59 | return fi.mode 60 | } 61 | func (fi bindataFileInfo) ModTime() time.Time { 62 | return fi.modTime 63 | } 64 | func (fi bindataFileInfo) IsDir() bool { 65 | return false 66 | } 67 | func (fi bindataFileInfo) Sys() interface{} { 68 | return nil 69 | } 70 | 71 | var _iconPng = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x00\x4e\x04\xb1\xfb\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\x00\x00\x1d\x00\x00\x00\x16\x08\x04\x00\x00\x00\x96\x5d\x5f\x47\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xe0\x0a\x03\x09\x1b\x21\x68\x1b\xe6\x26\x00\x00\x03\x82\x49\x44\x41\x54\x38\xcb\x9d\x94\xcd\x6f\x1b\x55\x14\xc5\xcf\x9b\x4f\x7b\x66\x6c\x8f\x63\x3b\x6e\x1c\x27\x4d\xf3\xe9\xb4\x4d\x14\xa9\x6a\x25\xda\x00\x69\xa4\x46\x08\x44\x16\x50\xb5\x12\x74\x8b\xba\x83\x15\x42\xfd\x03\xd8\xc1\x82\x15\x2b\xd8\xb4\xaa\x10\xac\x2a\x0a\x8b\x22\x01\x69\x48\x9b\x28\x44\x85\x24\x28\xa9\x13\xc7\xce\x87\x93\xd8\x8e\x63\x7b\x9c\xf1\x8c\xc7\x33\x7e\x2c\x02\xc8\x49\x0a\x0b\xde\xee\x2c\x7e\xba\xe7\xbe\xab\x73\x80\xff\xfd\x48\xa3\xb8\x08\x46\xe6\x83\xe2\xae\xab\x56\xf2\x56\x79\x17\x91\x1c\xce\x64\x0c\xab\x3e\x4d\x08\x05\x8a\xff\x85\x8e\xc1\x19\xf2\xb5\x70\x9c\xc9\x56\x4a\xfc\x9e\x87\xa1\x12\x91\x44\x42\x2d\xe9\x54\x66\xd6\x49\x3e\x3c\x86\x72\x8d\x42\x80\x95\x8a\xf4\xa9\x7a\xe2\xd9\x83\xf5\x0f\xf0\x11\x3e\x46\x27\x93\x93\xf6\x06\xd9\x3e\x79\x99\x39\x61\x98\x6d\x14\xdd\x80\x88\x76\x76\xb6\xb4\x65\xe2\x6b\xdc\x27\x69\xf7\x32\xb3\x60\xb4\x6d\x92\x0d\xd2\x5d\xcd\xf3\x66\x0c\xa9\x17\xa3\xb7\xb1\x84\xd0\x79\xf7\x82\x95\xb9\x8f\x32\x44\x55\xb9\xac\xc6\x82\x3d\x11\x39\x9d\xd9\x28\x34\x45\xaa\xd1\x30\x53\x29\x74\x34\xc0\x6c\xe3\xcc\x40\x88\x06\x47\xe3\x04\x02\x1c\x4e\xb9\xac\xac\x06\xe2\xc4\x12\xfd\x94\x0d\x14\x60\x9b\xb6\x57\x51\x18\x49\x5b\x3c\xbe\xeb\xab\x90\xb0\xc3\x46\xfa\xa5\xc4\x24\xee\x62\x1c\x82\xdf\x67\xa7\xd6\xf5\x3b\xcc\x9b\xcc\x2d\xc7\x15\xf2\x78\x82\x94\xaf\x8c\x38\x8b\x74\xeb\xd8\x37\x8d\x80\x61\xb8\x2e\xb9\x0d\xc1\xe1\x5f\x34\x00\x04\x4e\x45\x77\xc7\x5c\xab\x0b\xae\x72\x90\x68\xa2\x34\x44\x78\xde\xd8\x7e\xe2\xc4\x9d\xa3\xbb\x5e\xc3\x38\x8c\x2b\x4d\x51\x6b\xcb\x95\x5d\xc9\x7e\x06\x40\x47\x73\x2d\xcf\x18\x97\xfc\xd9\xda\x8e\xa5\x76\x09\x51\x58\x82\xae\x66\xe6\x57\x72\x32\x39\x8b\xe7\x7f\xa1\x0c\x50\xc5\xb7\xaa\xda\x3b\xfa\x13\x5b\xe7\x74\x09\x00\xb0\x85\x35\x4c\xc4\xad\xc7\x85\x82\xb6\xbd\x32\xdd\xbc\x5c\xea\xd0\x06\xa4\xa9\x87\x7b\x37\x87\xaf\xdd\xcc\xbb\xc7\xc8\x6b\x7f\x4f\x8d\x81\x0b\x0b\xe4\x6c\x66\x52\x77\xb5\xfa\xb2\xfd\xf4\x77\x00\x39\x00\x89\xea\xea\x7e\xb2\xb4\x5b\xef\xa8\x1c\xec\xda\x9e\xac\xda\xd9\x55\xf5\x95\xeb\x55\xb1\x2e\x5b\xe5\x33\xf5\x14\x18\xc0\x81\x51\xd0\x05\xb3\xe7\xb6\x96\x2b\x6f\x5f\xa1\x03\xaf\x7b\xde\x38\x72\xfa\x2a\xaa\x54\x63\x59\x23\x94\xf7\x79\x58\xed\xbb\x39\x41\xf7\x76\xba\x0e\x0d\xb3\x18\xd0\xf4\xfc\x3c\xdf\xc2\x8c\xc6\xdd\x6e\x7a\xc1\x18\xe6\xbb\x6f\xf1\x0a\xbe\x01\x00\xbc\x8f\x90\x60\xb4\xd6\xa6\xb8\xa7\xf4\x40\x69\x57\x94\x2f\xa5\xa1\x41\xb7\x18\x38\x34\x9c\x40\x04\x4c\xa9\x7c\xf5\x59\x36\x5d\xe2\xcc\x72\x56\x5f\xb7\xc3\x46\x5f\xac\x39\xc9\x9e\x43\xbb\xc8\xf8\x9d\xd3\xb5\x2d\x21\x9b\xe2\x10\x71\x3c\x84\xc9\x0f\x1d\x54\x72\x33\xf5\xfa\xd2\xe1\x71\x38\xb8\xe5\xa0\xba\xde\x65\xd2\xf7\xbc\x5e\x5d\xc8\x25\x77\x3e\x17\xd1\x62\xf9\x2d\xd9\xe4\x59\x9b\xa4\x7c\xda\x5d\xbc\xd5\x6b\x17\xb5\x7b\x07\x72\xa9\x36\x53\x51\x69\xf2\x30\x39\xc3\x70\x02\x83\xef\x76\x9b\x1f\x7e\xf1\x4a\x4f\x8b\xff\xaa\x14\x35\x2b\xf3\xd3\xd6\x60\x6d\xbb\x7e\x07\x04\x8b\x38\x8f\xc7\xdc\xec\xc0\x52\x74\xe6\xfb\x4e\xe7\xc1\xd1\xd0\x8d\x00\xdd\xb1\x1b\xa7\xec\xde\x4f\xdf\x71\x5e\x52\xa5\x96\x36\x85\x2d\x71\x56\xaf\x2d\x13\x43\x68\xab\x13\x52\x50\x32\xe1\x34\x9b\x7b\x2a\x96\xee\x1d\xcf\xeb\xcb\x98\xc4\x27\xae\x3f\x2e\xed\x17\x2f\x2e\x24\xe8\x6f\x08\xbb\x45\x3f\x95\x29\x27\x82\x32\xaa\xc7\xee\xa7\x99\xe2\x4e\xfe\xb9\x54\xf9\xe1\x64\xe8\x36\x00\xec\xda\x4b\x9b\x5e\x7f\xae\x9d\x58\x86\xf1\xa3\x7d\xa1\xac\x16\x6a\x1a\xb5\x2c\xa5\xd0\x5e\x5c\x6b\x9a\xfa\x2a\x7d\xba\xf6\xf3\xbf\x17\xcc\x18\x1e\x61\xfc\x1c\x6d\x35\x17\xcb\x1d\xae\x32\xeb\x88\x8a\xb7\x4f\x0e\x65\x26\xce\xcc\x3d\xc1\xdc\x0b\xbb\xe9\x9f\x96\x78\x84\xeb\x40\xc5\xdd\xcc\x0f\xef\x4f\x38\x1a\x75\x04\xa7\xb6\x69\xbf\xcd\xee\xaf\xe1\xd7\x93\x4d\x04\x00\xf8\x13\x01\xc9\x7f\x2a\x56\x02\x5e\xab\x00\x00\x00\x25\x74\x45\x58\x74\x64\x61\x74\x65\x3a\x63\x72\x65\x61\x74\x65\x00\x32\x30\x31\x36\x2d\x31\x30\x2d\x30\x33\x54\x30\x39\x3a\x32\x37\x3a\x33\x33\x2d\x30\x37\x3a\x30\x30\x8d\xeb\xe4\x71\x00\x00\x00\x25\x74\x45\x58\x74\x64\x61\x74\x65\x3a\x6d\x6f\x64\x69\x66\x79\x00\x32\x30\x31\x36\x2d\x31\x30\x2d\x30\x33\x54\x30\x39\x3a\x32\x37\x3a\x33\x33\x2d\x30\x37\x3a\x30\x30\xfc\xb6\x5c\xcd\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\x01\x00\x00\xff\xff\xce\x59\xde\xaa\x4e\x04\x00\x00") 72 | 73 | func iconPngBytes() ([]byte, error) { 74 | return bindataRead( 75 | _iconPng, 76 | "icon.png", 77 | ) 78 | } 79 | 80 | func iconPng() (*asset, error) { 81 | bytes, err := iconPngBytes() 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | info := bindataFileInfo{name: "icon.png", size: 1102, mode: os.FileMode(420), modTime: time.Unix(1475512090, 0)} 87 | a := &asset{bytes: bytes, info: info} 88 | return a, nil 89 | } 90 | 91 | // Asset loads and returns the asset for the given name. 92 | // It returns an error if the asset could not be found or 93 | // could not be loaded. 94 | func Asset(name string) ([]byte, error) { 95 | cannonicalName := strings.Replace(name, "\\", "/", -1) 96 | if f, ok := _bindata[cannonicalName]; ok { 97 | a, err := f() 98 | if err != nil { 99 | return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) 100 | } 101 | return a.bytes, nil 102 | } 103 | return nil, fmt.Errorf("Asset %s not found", name) 104 | } 105 | 106 | // MustAsset is like Asset but panics when Asset would return an error. 107 | // It simplifies safe initialization of global variables. 108 | func MustAsset(name string) []byte { 109 | a, err := Asset(name) 110 | if err != nil { 111 | panic("asset: Asset(" + name + "): " + err.Error()) 112 | } 113 | 114 | return a 115 | } 116 | 117 | // AssetInfo loads and returns the asset info for the given name. 118 | // It returns an error if the asset could not be found or 119 | // could not be loaded. 120 | func AssetInfo(name string) (os.FileInfo, error) { 121 | cannonicalName := strings.Replace(name, "\\", "/", -1) 122 | if f, ok := _bindata[cannonicalName]; ok { 123 | a, err := f() 124 | if err != nil { 125 | return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) 126 | } 127 | return a.info, nil 128 | } 129 | return nil, fmt.Errorf("AssetInfo %s not found", name) 130 | } 131 | 132 | // AssetNames returns the names of the assets. 133 | func AssetNames() []string { 134 | names := make([]string, 0, len(_bindata)) 135 | for name := range _bindata { 136 | names = append(names, name) 137 | } 138 | return names 139 | } 140 | 141 | // _bindata is a table, holding each asset generator, mapped to its name. 142 | var _bindata = map[string]func() (*asset, error){ 143 | "icon.png": iconPng, 144 | } 145 | 146 | // AssetDir returns the file names below a certain 147 | // directory embedded in the file by go-bindata. 148 | // For example if you run go-bindata on data/... and data contains the 149 | // following hierarchy: 150 | // data/ 151 | // foo.txt 152 | // img/ 153 | // a.png 154 | // b.png 155 | // then AssetDir("data") would return []string{"foo.txt", "img"} 156 | // AssetDir("data/img") would return []string{"a.png", "b.png"} 157 | // AssetDir("foo.txt") and AssetDir("notexist") would return an error 158 | // AssetDir("") will return []string{"data"}. 159 | func AssetDir(name string) ([]string, error) { 160 | node := _bintree 161 | if len(name) != 0 { 162 | cannonicalName := strings.Replace(name, "\\", "/", -1) 163 | pathList := strings.Split(cannonicalName, "/") 164 | for _, p := range pathList { 165 | node = node.Children[p] 166 | if node == nil { 167 | return nil, fmt.Errorf("Asset %s not found", name) 168 | } 169 | } 170 | } 171 | if node.Func != nil { 172 | return nil, fmt.Errorf("Asset %s not found", name) 173 | } 174 | rv := make([]string, 0, len(node.Children)) 175 | for childName := range node.Children { 176 | rv = append(rv, childName) 177 | } 178 | return rv, nil 179 | } 180 | 181 | type bintree struct { 182 | Func func() (*asset, error) 183 | Children map[string]*bintree 184 | } 185 | var _bintree = &bintree{nil, map[string]*bintree{ 186 | "icon.png": &bintree{iconPng, map[string]*bintree{}}, 187 | }} 188 | 189 | // RestoreAsset restores an asset under the given directory 190 | func RestoreAsset(dir, name string) error { 191 | data, err := Asset(name) 192 | if err != nil { 193 | return err 194 | } 195 | info, err := AssetInfo(name) 196 | if err != nil { 197 | return err 198 | } 199 | err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) 200 | if err != nil { 201 | return err 202 | } 203 | err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) 204 | if err != nil { 205 | return err 206 | } 207 | err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) 208 | if err != nil { 209 | return err 210 | } 211 | return nil 212 | } 213 | 214 | // RestoreAssets restores an asset under the given directory recursively 215 | func RestoreAssets(dir, name string) error { 216 | children, err := AssetDir(name) 217 | // File 218 | if err != nil { 219 | return RestoreAsset(dir, name) 220 | } 221 | // Dir 222 | for _, child := range children { 223 | err = RestoreAssets(dir, filepath.Join(name, child)) 224 | if err != nil { 225 | return err 226 | } 227 | } 228 | return nil 229 | } 230 | 231 | func _filePath(dir, name string) string { 232 | cannonicalName := strings.Replace(name, "\\", "/", -1) 233 | return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) 234 | } 235 | 236 | -------------------------------------------------------------------------------- /examples/statusbar/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexflint/gallium/bfa1ecd9241eb3b9f203869fb317c55c3c0c360f/examples/statusbar/icon.png -------------------------------------------------------------------------------- /examples/statusbar/statusbar.go: -------------------------------------------------------------------------------- 1 | //go:generate go-bindata -o bindata.go icon.png 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "os" 9 | "runtime" 10 | 11 | "github.com/alexflint/gallium" 12 | ) 13 | 14 | func main() { 15 | runtime.LockOSThread() 16 | gallium.RedirectStdoutStderr(os.ExpandEnv("$HOME/Library/Logs/Gallium.log")) 17 | gallium.Loop(os.Args, onReady) 18 | } 19 | 20 | func handleMenuQuit() { 21 | log.Println("quit clicked") 22 | os.Exit(0) 23 | } 24 | 25 | func handleDoSomething() { 26 | log.Println("do something") 27 | } 28 | 29 | func handleDoSomethingElse() { 30 | log.Println("do something else") 31 | } 32 | 33 | func onReady(app *gallium.App) { 34 | img, err := gallium.ImageFromPNG(MustAsset("icon.png")) 35 | if err != nil { 36 | fmt.Println("unable to decode icon-gray.png:", err) 37 | os.Exit(1) 38 | } 39 | 40 | app.AddStatusItem(gallium.StatusItemOptions{ 41 | Image: img, 42 | Width: 27.5, 43 | Highlight: true, 44 | Menu: []gallium.MenuEntry{ 45 | gallium.MenuItem{Title: "Do something", OnClick: handleDoSomething}, 46 | gallium.MenuItem{Title: "Do something else", OnClick: handleDoSomethingElse}, 47 | }, 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /examples/window/window.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | 8 | "github.com/alexflint/gallium" 9 | ) 10 | 11 | func init() { 12 | gallium.RedirectStdoutStderr(os.ExpandEnv("$HOME/Library/Logs/Gallium.log")) 13 | } 14 | 15 | func main() { 16 | runtime.LockOSThread() 17 | gallium.Loop(os.Args, onReady) 18 | } 19 | 20 | func onReady(app *gallium.App) { 21 | opt := gallium.FramedWindow 22 | opt.Title = "Framed Window" 23 | _, err := app.OpenWindow("http://example.com/", opt) 24 | if err != nil { 25 | fmt.Println(err) 26 | os.Exit(1) 27 | } 28 | 29 | opt = gallium.FramelessWindow 30 | opt.Title = "Frameless Window" 31 | _, err = app.OpenWindow("http://example.com/", opt) 32 | if err != nil { 33 | fmt.Println(err) 34 | os.Exit(1) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /globalshortcut.go: -------------------------------------------------------------------------------- 1 | package gallium 2 | 3 | /* 4 | #cgo CFLAGS: -mmacosx-version-min=10.8 5 | #cgo CFLAGS: -DGALLIUM_DIR=${SRCDIR} 6 | #cgo CFLAGS: -Idist/include 7 | 8 | #include 9 | #include "gallium/globalshortcut.h" 10 | 11 | extern void cgo_onGlobalShortcut(int64_t); 12 | 13 | // This is a wrapper around NSMenu_AddMenuItem that adds the function pointer 14 | // argument, since this does not seem to be possible from Go directly. 15 | static inline void helper_AddGlobalShortcut( 16 | int ID, 17 | const char* shortcutKey, 18 | gallium_modifier_t shortcutModifier) { 19 | 20 | GalliumAddGlobalShortcut( 21 | ID, 22 | shortcutKey, 23 | shortcutModifier, 24 | &cgo_onGlobalShortcut); 25 | } 26 | */ 27 | import "C" 28 | import ( 29 | "errors" 30 | "fmt" 31 | "log" 32 | "strings" 33 | ) 34 | 35 | var ( 36 | nextID int 37 | handlers = make(map[int]func()) 38 | ) 39 | 40 | //export cgo_onGlobalShortcut 41 | func cgo_onGlobalShortcut(id int) { 42 | fmt.Println("cgo_onGlobalShortcut received ID", id) 43 | if handler, found := handlers[id]; found { 44 | handler() 45 | } else { 46 | log.Println("no handler for global shortcut ID", id) 47 | } 48 | } 49 | 50 | // Modifier represents zero or more modifier keys (control, shift, option, etc) 51 | type Modifier int 52 | 53 | // these must be kept in sync with the gallium_modifier_t enum in cocoa.h 54 | const ( 55 | ModifierCmd Modifier = 1 << iota 56 | ModifierCtrl 57 | ModifierCmdOrCtrl 58 | ModifierAltOrOption 59 | ModifierFn 60 | ModifierShift 61 | ) 62 | 63 | // KeyCombination represents a key together with zero or more modifiers. It 64 | // is used to set up keyboard shortcuts. 65 | type KeyCombination struct { 66 | Key string 67 | Modifiers Modifier 68 | } 69 | 70 | var ( 71 | errEmptyKey = errors.New("empty key") 72 | errEmptyShortcut = errors.New("empty shortcut") 73 | ) 74 | 75 | // ParseKeys parses a key combination specified as a string like "cmd shift a". 76 | func ParseKeys(s string) (KeyCombination, error) { 77 | // for backwards compatibility split on both "+" and " " 78 | parts := strings.Split(s, " ") 79 | if strings.Contains(s, "+") { 80 | parts = strings.Split(s, "+") 81 | } 82 | if len(parts) == 0 { 83 | return KeyCombination{}, errEmptyShortcut 84 | } 85 | var keys KeyCombination 86 | keys.Key = parts[len(parts)-1] 87 | if len(keys.Key) == 0 { 88 | return KeyCombination{}, errEmptyKey 89 | } 90 | for _, part := range parts[:len(parts)-1] { 91 | switch strings.ToLower(part) { 92 | case "cmd": 93 | keys.Modifiers |= ModifierCmd 94 | case "ctrl": 95 | keys.Modifiers |= ModifierCtrl 96 | case "cmdctrl": 97 | keys.Modifiers |= ModifierCmdOrCtrl 98 | case "alt": 99 | keys.Modifiers |= ModifierAltOrOption 100 | case "option": 101 | keys.Modifiers |= ModifierAltOrOption 102 | case "fn": 103 | keys.Modifiers |= ModifierFn 104 | case "shift": 105 | keys.Modifiers |= ModifierShift 106 | default: 107 | return KeyCombination{}, fmt.Errorf("unknown modifier: %s", part) 108 | } 109 | } 110 | return keys, nil 111 | } 112 | 113 | // MustParseKeys is like ParseKeys but panics on error 114 | func MustParseKeys(s string) KeyCombination { 115 | keys, err := ParseKeys(s) 116 | if err != nil { 117 | panic(err) 118 | } 119 | return keys 120 | } 121 | 122 | // AddGlobalShortcut calls the handler whenever the key combination is pressed 123 | // in any application. 124 | func AddGlobalShortcut(keys KeyCombination, handler func()) { 125 | id := nextID 126 | nextID++ 127 | handlers[id] = handler 128 | 129 | C.helper_AddGlobalShortcut( 130 | C.int(id), 131 | C.CString(keys.Key), 132 | C.gallium_modifier_t(keys.Modifiers)) 133 | } 134 | -------------------------------------------------------------------------------- /linkflags.go: -------------------------------------------------------------------------------- 1 | package gallium 2 | 3 | /* 4 | #cgo LDFLAGS: -F${SRCDIR}/dist 5 | #cgo LDFLAGS: -framework Gallium 6 | #cgo LDFLAGS: -Wl,-rpath,@executable_path/../Frameworks 7 | #cgo LDFLAGS: -Wl,-rpath,@loader_path/../Frameworks 8 | #cgo LDFLAGS: -Wl,-rpath,${SRCDIR}/dist 9 | #cgo LDFLAGS: -mmacosx-version-min=10.8 10 | */ 11 | import "C" 12 | -------------------------------------------------------------------------------- /rect.go: -------------------------------------------------------------------------------- 1 | package gallium 2 | 3 | /* 4 | #cgo CFLAGS: -mmacosx-version-min=10.8 5 | #cgo CFLAGS: -DGALLIUM_DIR=${SRCDIR} 6 | #cgo CFLAGS: -Idist/include 7 | 8 | #include 9 | #include "gallium/core.h" 10 | */ 11 | import "C" 12 | 13 | // Rect represents a rectangular region on the screen 14 | type Rect struct { 15 | Width int // Width in pixels 16 | Height int // Height in pixels 17 | Left int // Left is offset from left in pixel 18 | Bottom int // Left is offset from top in pixels 19 | } 20 | 21 | func rectFromC(c C.gallium_rect_t) Rect { 22 | return Rect{ 23 | Width: int(c.width), 24 | Height: int(c.height), 25 | Left: int(c.left), 26 | Bottom: int(c.bottom), 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /redirect.go: -------------------------------------------------------------------------------- 1 | package gallium 2 | 3 | import ( 4 | "os" 5 | "syscall" 6 | ) 7 | 8 | // RedirectStdoutStderr overwrites the stdout and stderr streams with a file 9 | // descriptor that writes to the given path. This is done with os.Dup2, 10 | // meaning that even C functions that write to stdout or stderr will be 11 | // redirected to the file. 12 | func RedirectStdoutStderr(path string) (*os.File, error) { 13 | return redirect(path, os.Stdout.Fd(), os.Stderr.Fd()) 14 | } 15 | 16 | // RedirectStdout overwrites the stdout streams with a file 17 | // descriptor that writes to the given path. This is done with os.Dup2, 18 | // meaning that even C functions that write to stdout will be 19 | // redirected to the file. 20 | func RedirectStdout(path string) (*os.File, error) { 21 | return redirect(path, os.Stdout.Fd()) 22 | } 23 | 24 | // RedirectStderr overwrites the stderr streams with a file 25 | // descriptor that writes to the given path. This is done with os.Dup2, 26 | // meaning that even C functions that write to stderr will be 27 | // redirected to the file. 28 | func RedirectStderr(path string) (*os.File, error) { 29 | return redirect(path, os.Stderr.Fd()) 30 | } 31 | 32 | func redirect(path string, fds ...uintptr) (*os.File, error) { 33 | f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0777) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | for _, fd := range fds { 39 | err = syscall.Dup2(int(f.Fd()), int(fd)) 40 | if err != nil { 41 | return nil, err 42 | } 43 | } 44 | 45 | return f, nil 46 | } 47 | -------------------------------------------------------------------------------- /screen.go: -------------------------------------------------------------------------------- 1 | package gallium 2 | 3 | /* 4 | #cgo CFLAGS: -mmacosx-version-min=10.8 5 | #cgo CFLAGS: -DGALLIUM_DIR=${SRCDIR} 6 | #cgo CFLAGS: -Idist/include 7 | 8 | #include 9 | #include "gallium/screen.h" 10 | */ 11 | import "C" 12 | import "fmt" 13 | 14 | // A screen represents a rectangular display, normally corresponding to a 15 | // physical display. "Device coordinates" means a position on a screen 16 | // measured from (0, 0) at the bottom left of the device. "Global coordinates" 17 | // means the coordinate system in which each of the screens are positioned 18 | // relative to each other. Global and device coordinates almost always have 19 | // the same scale factor. It is possible for screens to overlap in global 20 | // coordinates (such as when mirroring a display.) 21 | type Screen struct { 22 | Shape Rect // the size and position of this screen in global coords 23 | Usable Rect // excludes the menubar and dock 24 | BitsPerPixel int // color depth of this screen (total of all color components) 25 | ID int // unique identifier for this screen 26 | } 27 | 28 | func screenFromC(c *C.gallium_screen_t) Screen { 29 | return Screen{ 30 | Shape: rectFromC(C.GalliumScreenShape(c)), 31 | Usable: rectFromC(C.GalliumScreenUsable(c)), 32 | BitsPerPixel: int(C.GalliumScreenBitsPerPixel(c)), 33 | ID: int(C.GalliumScreenID(c)), 34 | } 35 | } 36 | 37 | // Screens gets a list of available screens 38 | func Screens() []Screen { 39 | var screens []Screen 40 | n := int(C.GalliumScreenCount()) 41 | for i := 0; i < n; i++ { 42 | c := C.GalliumScreen(C.int(i)) 43 | if c == nil { 44 | panic(fmt.Sprintf("GalliumScreen returned nil for index %d", i)) 45 | } 46 | screens = append(screens, screenFromC(c)) 47 | } 48 | return screens 49 | } 50 | 51 | // FocusedScreen gets the screen containing the currently focused window 52 | func FocusedScreen() Screen { 53 | c := C.GalliumFocusedScreen() 54 | if c == nil { 55 | panic("GalliumFocusedScreen returned nil") 56 | } 57 | return screenFromC(c) 58 | } 59 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-arg/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-arg/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - tip 4 | before_install: 5 | - go get github.com/axw/gocov/gocov 6 | - go get github.com/mattn/goveralls 7 | - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi 8 | script: 9 | - $HOME/gopath/bin/goveralls -service=travis-ci 10 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-arg/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Alex Flint 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-arg/README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/alexflint/go-arg?status.svg)](https://godoc.org/github.com/alexflint/go-arg) 2 | [![Build Status](https://travis-ci.org/alexflint/go-arg.svg?branch=master)](https://travis-ci.org/alexflint/go-arg) 3 | [![Coverage Status](https://coveralls.io/repos/alexflint/go-arg/badge.svg?branch=master&service=github)](https://coveralls.io/github/alexflint/go-arg?branch=master) 4 | [![Report Card](https://goreportcard.com/badge/github.com/alexflint/go-arg)](https://goreportcard.com/badge/github.com/alexflint/go-arg) 5 | 6 | ## Structured argument parsing for Go 7 | 8 | ```shell 9 | go get github.com/alexflint/go-arg 10 | ``` 11 | 12 | Declare the command line arguments your program accepts by defining a struct. 13 | 14 | ```go 15 | var args struct { 16 | Foo string 17 | Bar bool 18 | } 19 | arg.MustParse(&args) 20 | fmt.Println(args.Foo, args.Bar) 21 | ``` 22 | 23 | ```shell 24 | $ ./example --foo=hello --bar 25 | hello true 26 | ``` 27 | 28 | ### Required arguments 29 | 30 | ```go 31 | var args struct { 32 | ID int `arg:"required"` 33 | Timeout time.Duration 34 | } 35 | arg.MustParse(&args) 36 | ``` 37 | 38 | ```shell 39 | $ ./example 40 | usage: example --id ID [--timeout TIMEOUT] 41 | error: --id is required 42 | ``` 43 | 44 | ### Positional arguments 45 | 46 | ```go 47 | var args struct { 48 | Input string `arg:"positional"` 49 | Output []string `arg:"positional"` 50 | } 51 | arg.MustParse(&args) 52 | fmt.Println("Input:", args.Input) 53 | fmt.Println("Output:", args.Output) 54 | ``` 55 | 56 | ``` 57 | $ ./example src.txt x.out y.out z.out 58 | Input: src.txt 59 | Output: [x.out y.out z.out] 60 | ``` 61 | 62 | ### Environment variables 63 | 64 | ```go 65 | var args struct { 66 | Workers int `arg:"env"` 67 | } 68 | arg.MustParse(&args) 69 | fmt.Println("Workers:", args.Workers) 70 | ``` 71 | 72 | ``` 73 | $ WORKERS=4 ./example 74 | Workers: 4 75 | ``` 76 | 77 | ``` 78 | $ WORKERS=4 ./example --workers=6 79 | Workers: 6 80 | ``` 81 | 82 | You can also override the name of the environment variable: 83 | 84 | ```go 85 | var args struct { 86 | Workers int `arg:"env:NUM_WORKERS"` 87 | } 88 | arg.MustParse(&args) 89 | fmt.Println("Workers:", args.Workers) 90 | ``` 91 | 92 | ``` 93 | $ NUM_WORKERS=4 ./example 94 | Workers: 4 95 | ``` 96 | 97 | ### Usage strings 98 | ```go 99 | var args struct { 100 | Input string `arg:"positional"` 101 | Output []string `arg:"positional"` 102 | Verbose bool `arg:"-v,help:verbosity level"` 103 | Dataset string `arg:"help:dataset to use"` 104 | Optimize int `arg:"-O,help:optimization level"` 105 | } 106 | arg.MustParse(&args) 107 | ``` 108 | 109 | ```shell 110 | $ ./example -h 111 | usage: [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] [--help] INPUT [OUTPUT [OUTPUT ...]] 112 | 113 | positional arguments: 114 | input 115 | output 116 | 117 | options: 118 | --verbose, -v verbosity level 119 | --dataset DATASET dataset to use 120 | --optimize OPTIMIZE, -O OPTIMIZE 121 | optimization level 122 | --help, -h print this help message 123 | ``` 124 | 125 | ### Default values 126 | 127 | ```go 128 | var args struct { 129 | Foo string 130 | Bar bool 131 | } 132 | args.Foo = "default value" 133 | arg.MustParse(&args) 134 | ``` 135 | 136 | ### Arguments with multiple values 137 | ```go 138 | var args struct { 139 | Database string 140 | IDs []int64 141 | } 142 | arg.MustParse(&args) 143 | fmt.Printf("Fetching the following IDs from %s: %q", args.Database, args.IDs) 144 | ``` 145 | 146 | ```shell 147 | ./example -database foo -ids 1 2 3 148 | Fetching the following IDs from foo: [1 2 3] 149 | ``` 150 | 151 | ### Custom validation 152 | ```go 153 | var args struct { 154 | Foo string 155 | Bar string 156 | } 157 | p := arg.MustParse(&args) 158 | if args.Foo == "" && args.Bar == "" { 159 | p.Fail("you must provide one of --foo and --bar") 160 | } 161 | ``` 162 | 163 | ```shell 164 | ./example 165 | usage: samples [--foo FOO] [--bar BAR] 166 | error: you must provide one of --foo and --bar 167 | ``` 168 | 169 | ### Version strings 170 | 171 | ```go 172 | type args struct { 173 | ... 174 | } 175 | 176 | func (args) Version() string { 177 | return "someprogram 4.3.0" 178 | } 179 | 180 | func main() { 181 | var args args 182 | arg.MustParse(&args) 183 | } 184 | ``` 185 | 186 | ```shell 187 | $ ./example --version 188 | someprogram 4.3.0 189 | ``` 190 | 191 | ### Custom parsing 192 | 193 | You can implement your own argument parser by implementing `encoding.TextUnmarshaler`: 194 | 195 | ```go 196 | package main 197 | 198 | import ( 199 | "fmt" 200 | "strings" 201 | 202 | "github.com/alexflint/go-arg" 203 | ) 204 | 205 | // Accepts command line arguments of the form "head.tail" 206 | type NameDotName struct { 207 | Head, Tail string 208 | } 209 | 210 | func (n *NameDotName) UnmarshalText(b []byte) error { 211 | s := string(b) 212 | pos := strings.Index(s, ".") 213 | if pos == -1 { 214 | return fmt.Errorf("missing period in %s", s) 215 | } 216 | n.Head = s[:pos] 217 | n.Tail = s[pos+1:] 218 | return nil 219 | } 220 | 221 | func main() { 222 | var args struct { 223 | Name *NameDotName 224 | } 225 | arg.MustParse(&args) 226 | fmt.Printf("%#v\n", args.Name) 227 | } 228 | ``` 229 | ```shell 230 | $ ./example --name=foo.bar 231 | &main.NameDotName{Head:"foo", Tail:"bar"} 232 | 233 | $ ./example --name=oops 234 | usage: example [--name NAME] 235 | error: error processing --name: missing period in "oops" 236 | ``` 237 | 238 | ### Documentation 239 | 240 | https://godoc.org/github.com/alexflint/go-arg 241 | 242 | ### Rationale 243 | 244 | There are many command line argument parsing libraries for Go, including one in the standard library, so why build another? 245 | 246 | The shortcomings of the `flag` library that ships in the standard library are well known. Positional arguments must preceed options, so `./prog x --foo=1` does what you expect but `./prog --foo=1 x` does not. Arguments cannot have both long (`--foo`) and short (`-f`) forms. 247 | 248 | Many third-party argument parsing libraries are geared for writing sophisticated command line interfaces. The excellent `codegangsta/cli` is perfect for working with multiple sub-commands and nested flags, but is probably overkill for a simple script with a handful of flags. 249 | 250 | The main idea behind `go-arg` is that Go already has an excellent way to describe data structures using Go structs, so there is no need to develop more levels of abstraction on top of this. Instead of one API to specify which arguments your program accepts, and then another API to get the values of those arguments, why not replace both with a single struct? 251 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-arg/doc.go: -------------------------------------------------------------------------------- 1 | // Package arg parses command line arguments using the fields from a struct. 2 | // 3 | // For example, 4 | // 5 | // var args struct { 6 | // Iter int 7 | // Debug bool 8 | // } 9 | // arg.MustParse(&args) 10 | // 11 | // defines two command line arguments, which can be set using any of 12 | // 13 | // ./example --iter=1 --debug // debug is a boolean flag so its value is set to true 14 | // ./example -iter 1 // debug defaults to its zero value (false) 15 | // ./example --debug=true // iter defaults to its zero value (zero) 16 | // 17 | // The fastest way to see how to use go-arg is to read the examples below. 18 | // 19 | // Fields can be bool, string, any float type, or any signed or unsigned integer type. 20 | // They can also be slices of any of the above, or slices of pointers to any of the above. 21 | // 22 | // Tags can be specified using the `arg` package name: 23 | // 24 | // var args struct { 25 | // Input string `arg:"positional"` 26 | // Log string `arg:"positional,required"` 27 | // Debug bool `arg:"-d,help:turn on debug mode"` 28 | // RealMode bool `arg:"--real" 29 | // Wr io.Writer `arg:"-"` 30 | // } 31 | // 32 | // The valid tag strings are `positional`, `required`, and `help`. Further, any tag string 33 | // that starts with a single hyphen is the short form for an argument (e.g. `./example -d`), 34 | // and any tag string that starts with two hyphens is the long form for the argument 35 | // (instead of the field name). Fields can be excluded from processing with `arg:"-"`. 36 | package arg 37 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-arg/parse.go: -------------------------------------------------------------------------------- 1 | package arg 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "reflect" 9 | "strings" 10 | ) 11 | 12 | // spec represents a command line option 13 | type spec struct { 14 | dest reflect.Value 15 | long string 16 | short string 17 | multiple bool 18 | required bool 19 | positional bool 20 | help string 21 | env string 22 | wasPresent bool 23 | boolean bool 24 | fieldName string // for generating helpful errors 25 | } 26 | 27 | // ErrHelp indicates that -h or --help were provided 28 | var ErrHelp = errors.New("help requested by user") 29 | 30 | // ErrVersion indicates that --version was provided 31 | var ErrVersion = errors.New("version requested by user") 32 | 33 | // MustParse processes command line arguments and exits upon failure 34 | func MustParse(dest ...interface{}) *Parser { 35 | p, err := NewParser(Config{}, dest...) 36 | if err != nil { 37 | fmt.Println(err) 38 | os.Exit(-1) 39 | } 40 | err = p.Parse(os.Args[1:]) 41 | if err == ErrHelp { 42 | p.WriteHelp(os.Stdout) 43 | os.Exit(0) 44 | } 45 | if err == ErrVersion { 46 | fmt.Println(p.version) 47 | os.Exit(0) 48 | } 49 | if err != nil { 50 | p.Fail(err.Error()) 51 | } 52 | return p 53 | } 54 | 55 | // Parse processes command line arguments and stores them in dest 56 | func Parse(dest ...interface{}) error { 57 | p, err := NewParser(Config{}, dest...) 58 | if err != nil { 59 | return err 60 | } 61 | return p.Parse(os.Args[1:]) 62 | } 63 | 64 | // Config represents configuration options for an argument parser 65 | type Config struct { 66 | Program string // Program is the name of the program used in the help text 67 | } 68 | 69 | // Parser represents a set of command line options with destination values 70 | type Parser struct { 71 | spec []*spec 72 | config Config 73 | version string 74 | } 75 | 76 | // Versioned is the interface that the destination struct should implement to 77 | // make a version string appear at the top of the help message. 78 | type Versioned interface { 79 | // Version returns the version string that will be printed on a line by itself 80 | // at the top of the help message. 81 | Version() string 82 | } 83 | 84 | // NewParser constructs a parser from a list of destination structs 85 | func NewParser(config Config, dests ...interface{}) (*Parser, error) { 86 | p := Parser{ 87 | config: config, 88 | } 89 | for _, dest := range dests { 90 | if dest, ok := dest.(Versioned); ok { 91 | p.version = dest.Version() 92 | } 93 | v := reflect.ValueOf(dest) 94 | if v.Kind() != reflect.Ptr { 95 | panic(fmt.Sprintf("%s is not a pointer (did you forget an ampersand?)", v.Type())) 96 | } 97 | v = v.Elem() 98 | if v.Kind() != reflect.Struct { 99 | panic(fmt.Sprintf("%T is not a struct pointer", dest)) 100 | } 101 | 102 | t := v.Type() 103 | for i := 0; i < t.NumField(); i++ { 104 | // Check for the ignore switch in the tag 105 | field := t.Field(i) 106 | tag := field.Tag.Get("arg") 107 | if tag == "-" { 108 | continue 109 | } 110 | 111 | spec := spec{ 112 | long: strings.ToLower(field.Name), 113 | dest: v.Field(i), 114 | fieldName: t.Name() + "." + field.Name, 115 | } 116 | 117 | // Check whether this field is supported. It's good to do this here rather than 118 | // wait until setScalar because it means that a program with invalid argument 119 | // fields will always fail regardless of whether the arguments it received 120 | // exercised those fields. 121 | var parseable bool 122 | parseable, spec.boolean, spec.multiple = canParse(field.Type) 123 | if !parseable { 124 | return nil, fmt.Errorf("%s.%s: %s fields are not supported", t.Name(), field.Name, field.Type.String()) 125 | } 126 | 127 | // Look at the tag 128 | if tag != "" { 129 | for _, key := range strings.Split(tag, ",") { 130 | var value string 131 | if pos := strings.Index(key, ":"); pos != -1 { 132 | value = key[pos+1:] 133 | key = key[:pos] 134 | } 135 | 136 | switch { 137 | case strings.HasPrefix(key, "--"): 138 | spec.long = key[2:] 139 | case strings.HasPrefix(key, "-"): 140 | if len(key) != 2 { 141 | return nil, fmt.Errorf("%s.%s: short arguments must be one character only", t.Name(), field.Name) 142 | } 143 | spec.short = key[1:] 144 | case key == "required": 145 | spec.required = true 146 | case key == "positional": 147 | spec.positional = true 148 | case key == "help": 149 | spec.help = value 150 | case key == "env": 151 | // Use override name if provided 152 | if value != "" { 153 | spec.env = value 154 | } else { 155 | spec.env = strings.ToUpper(field.Name) 156 | } 157 | default: 158 | return nil, fmt.Errorf("unrecognized tag '%s' on field %s", key, tag) 159 | } 160 | } 161 | } 162 | p.spec = append(p.spec, &spec) 163 | } 164 | } 165 | if p.config.Program == "" { 166 | p.config.Program = "program" 167 | if len(os.Args) > 0 { 168 | p.config.Program = filepath.Base(os.Args[0]) 169 | } 170 | } 171 | return &p, nil 172 | } 173 | 174 | // Parse processes the given command line option, storing the results in the field 175 | // of the structs from which NewParser was constructed 176 | func (p *Parser) Parse(args []string) error { 177 | // If -h or --help were specified then print usage 178 | for _, arg := range args { 179 | if arg == "-h" || arg == "--help" { 180 | return ErrHelp 181 | } 182 | if arg == "--version" { 183 | return ErrVersion 184 | } 185 | if arg == "--" { 186 | break 187 | } 188 | } 189 | 190 | // Process all command line arguments 191 | err := process(p.spec, args) 192 | if err != nil { 193 | return err 194 | } 195 | 196 | // Validate 197 | return validate(p.spec) 198 | } 199 | 200 | // process goes through arguments one-by-one, parses them, and assigns the result to 201 | // the underlying struct field 202 | func process(specs []*spec, args []string) error { 203 | // construct a map from --option to spec 204 | optionMap := make(map[string]*spec) 205 | for _, spec := range specs { 206 | if spec.positional { 207 | continue 208 | } 209 | if spec.long != "" { 210 | optionMap[spec.long] = spec 211 | } 212 | if spec.short != "" { 213 | optionMap[spec.short] = spec 214 | } 215 | if spec.env != "" { 216 | if value, found := os.LookupEnv(spec.env); found { 217 | err := setScalar(spec.dest, value) 218 | if err != nil { 219 | return fmt.Errorf("error processing environment variable %s: %v", spec.env, err) 220 | } 221 | spec.wasPresent = true 222 | } 223 | } 224 | } 225 | 226 | // process each string from the command line 227 | var allpositional bool 228 | var positionals []string 229 | 230 | // must use explicit for loop, not range, because we manipulate i inside the loop 231 | for i := 0; i < len(args); i++ { 232 | arg := args[i] 233 | if arg == "--" { 234 | allpositional = true 235 | continue 236 | } 237 | 238 | if !strings.HasPrefix(arg, "-") || allpositional { 239 | positionals = append(positionals, arg) 240 | continue 241 | } 242 | 243 | // check for an equals sign, as in "--foo=bar" 244 | var value string 245 | opt := strings.TrimLeft(arg, "-") 246 | if pos := strings.Index(opt, "="); pos != -1 { 247 | value = opt[pos+1:] 248 | opt = opt[:pos] 249 | } 250 | 251 | // lookup the spec for this option 252 | spec, ok := optionMap[opt] 253 | if !ok { 254 | return fmt.Errorf("unknown argument %s", arg) 255 | } 256 | spec.wasPresent = true 257 | 258 | // deal with the case of multiple values 259 | if spec.multiple { 260 | var values []string 261 | if value == "" { 262 | for i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") { 263 | values = append(values, args[i+1]) 264 | i++ 265 | } 266 | } else { 267 | values = append(values, value) 268 | } 269 | err := setSlice(spec.dest, values) 270 | if err != nil { 271 | return fmt.Errorf("error processing %s: %v", arg, err) 272 | } 273 | continue 274 | } 275 | 276 | // if it's a flag and it has no value then set the value to true 277 | // use boolean because this takes account of TextUnmarshaler 278 | if spec.boolean && value == "" { 279 | value = "true" 280 | } 281 | 282 | // if we have something like "--foo" then the value is the next argument 283 | if value == "" { 284 | if i+1 == len(args) || strings.HasPrefix(args[i+1], "-") { 285 | return fmt.Errorf("missing value for %s", arg) 286 | } 287 | value = args[i+1] 288 | i++ 289 | } 290 | 291 | err := setScalar(spec.dest, value) 292 | if err != nil { 293 | return fmt.Errorf("error processing %s: %v", arg, err) 294 | } 295 | } 296 | 297 | // process positionals 298 | for _, spec := range specs { 299 | if spec.positional { 300 | if spec.multiple { 301 | err := setSlice(spec.dest, positionals) 302 | if err != nil { 303 | return fmt.Errorf("error processing %s: %v", spec.long, err) 304 | } 305 | positionals = nil 306 | } else if len(positionals) > 0 { 307 | err := setScalar(spec.dest, positionals[0]) 308 | if err != nil { 309 | return fmt.Errorf("error processing %s: %v", spec.long, err) 310 | } 311 | positionals = positionals[1:] 312 | } else if spec.required { 313 | return fmt.Errorf("%s is required", spec.long) 314 | } 315 | } 316 | } 317 | if len(positionals) > 0 { 318 | return fmt.Errorf("too many positional arguments at '%s'", positionals[0]) 319 | } 320 | return nil 321 | } 322 | 323 | // validate an argument spec after arguments have been parse 324 | func validate(spec []*spec) error { 325 | for _, arg := range spec { 326 | if !arg.positional && arg.required && !arg.wasPresent { 327 | return fmt.Errorf("--%s is required", arg.long) 328 | } 329 | } 330 | return nil 331 | } 332 | 333 | // parse a value as the appropriate type and store it in the struct 334 | func setSlice(dest reflect.Value, values []string) error { 335 | if !dest.CanSet() { 336 | return fmt.Errorf("field is not writable") 337 | } 338 | 339 | var ptr bool 340 | elem := dest.Type().Elem() 341 | if elem.Kind() == reflect.Ptr { 342 | ptr = true 343 | elem = elem.Elem() 344 | } 345 | 346 | // Truncate the dest slice in case default values exist 347 | if !dest.IsNil() { 348 | dest.SetLen(0) 349 | } 350 | 351 | for _, s := range values { 352 | v := reflect.New(elem) 353 | if err := setScalar(v.Elem(), s); err != nil { 354 | return err 355 | } 356 | if !ptr { 357 | v = v.Elem() 358 | } 359 | dest.Set(reflect.Append(dest, v)) 360 | } 361 | return nil 362 | } 363 | 364 | // canParse returns true if the type can be parsed from a string 365 | func canParse(t reflect.Type) (parseable, boolean, multiple bool) { 366 | parseable, boolean = isScalar(t) 367 | if parseable { 368 | return 369 | } 370 | 371 | // Look inside pointer types 372 | if t.Kind() == reflect.Ptr { 373 | t = t.Elem() 374 | } 375 | // Look inside slice types 376 | if t.Kind() == reflect.Slice { 377 | multiple = true 378 | t = t.Elem() 379 | } 380 | 381 | parseable, boolean = isScalar(t) 382 | if parseable { 383 | return 384 | } 385 | 386 | // Look inside pointer types (again, in case of []*Type) 387 | if t.Kind() == reflect.Ptr { 388 | t = t.Elem() 389 | } 390 | 391 | parseable, boolean = isScalar(t) 392 | if parseable { 393 | return 394 | } 395 | 396 | return false, false, false 397 | } 398 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-arg/scalar.go: -------------------------------------------------------------------------------- 1 | package arg 2 | 3 | import ( 4 | "encoding" 5 | "fmt" 6 | "net" 7 | "net/mail" 8 | "reflect" 9 | "strconv" 10 | "time" 11 | ) 12 | 13 | // The reflected form of some special types 14 | var ( 15 | textUnmarshalerType = reflect.TypeOf([]encoding.TextUnmarshaler{}).Elem() 16 | durationType = reflect.TypeOf(time.Duration(0)) 17 | mailAddressType = reflect.TypeOf(mail.Address{}) 18 | ipType = reflect.TypeOf(net.IP{}) 19 | macType = reflect.TypeOf(net.HardwareAddr{}) 20 | ) 21 | 22 | // isScalar returns true if the type can be parsed from a single string 23 | func isScalar(t reflect.Type) (scalar, boolean bool) { 24 | // If it implements encoding.TextUnmarshaler then use that 25 | if t.Implements(textUnmarshalerType) { 26 | // scalar=YES, boolean=NO 27 | return true, false 28 | } 29 | 30 | // If we have a pointer then dereference it 31 | if t.Kind() == reflect.Ptr { 32 | t = t.Elem() 33 | } 34 | 35 | // Check for other special types 36 | switch t { 37 | case durationType, mailAddressType, ipType, macType: 38 | // scalar=YES, boolean=NO 39 | return true, false 40 | } 41 | 42 | // Fall back to checking the kind 43 | switch t.Kind() { 44 | case reflect.Bool: 45 | // scalar=YES, boolean=YES 46 | return true, true 47 | case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 48 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, 49 | reflect.Float32, reflect.Float64: 50 | // scalar=YES, boolean=NO 51 | return true, false 52 | } 53 | // scalar=NO, boolean=NO 54 | return false, false 55 | } 56 | 57 | // set a value from a string 58 | func setScalar(v reflect.Value, s string) error { 59 | if !v.CanSet() { 60 | return fmt.Errorf("field is not exported") 61 | } 62 | 63 | // If we have a nil pointer then allocate a new object 64 | if v.Kind() == reflect.Ptr && v.IsNil() { 65 | v.Set(reflect.New(v.Type().Elem())) 66 | } 67 | 68 | // Get the object as an interface 69 | scalar := v.Interface() 70 | 71 | // If it implements encoding.TextUnmarshaler then use that 72 | if scalar, ok := scalar.(encoding.TextUnmarshaler); ok { 73 | return scalar.UnmarshalText([]byte(s)) 74 | } 75 | 76 | // If we have a pointer then dereference it 77 | if v.Kind() == reflect.Ptr { 78 | v = v.Elem() 79 | } 80 | 81 | // Switch on concrete type 82 | switch scalar.(type) { 83 | case time.Duration: 84 | duration, err := time.ParseDuration(s) 85 | if err != nil { 86 | return err 87 | } 88 | v.Set(reflect.ValueOf(duration)) 89 | return nil 90 | case mail.Address: 91 | addr, err := mail.ParseAddress(s) 92 | if err != nil { 93 | return err 94 | } 95 | v.Set(reflect.ValueOf(*addr)) 96 | return nil 97 | case net.IP: 98 | ip := net.ParseIP(s) 99 | if ip == nil { 100 | return fmt.Errorf(`invalid IP address: "%s"`, s) 101 | } 102 | v.Set(reflect.ValueOf(ip)) 103 | return nil 104 | case net.HardwareAddr: 105 | ip, err := net.ParseMAC(s) 106 | if err != nil { 107 | return err 108 | } 109 | v.Set(reflect.ValueOf(ip)) 110 | return nil 111 | } 112 | 113 | // Switch on kind so that we can handle derived types 114 | switch v.Kind() { 115 | case reflect.String: 116 | v.SetString(s) 117 | case reflect.Bool: 118 | x, err := strconv.ParseBool(s) 119 | if err != nil { 120 | return err 121 | } 122 | v.SetBool(x) 123 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 124 | x, err := strconv.ParseInt(s, 10, v.Type().Bits()) 125 | if err != nil { 126 | return err 127 | } 128 | v.SetInt(x) 129 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 130 | x, err := strconv.ParseUint(s, 10, v.Type().Bits()) 131 | if err != nil { 132 | return err 133 | } 134 | v.SetUint(x) 135 | case reflect.Float32, reflect.Float64: 136 | x, err := strconv.ParseFloat(s, v.Type().Bits()) 137 | if err != nil { 138 | return err 139 | } 140 | v.SetFloat(x) 141 | default: 142 | return fmt.Errorf("cannot parse argument into %s", v.Type().String()) 143 | } 144 | return nil 145 | } 146 | -------------------------------------------------------------------------------- /vendor/github.com/alexflint/go-arg/usage.go: -------------------------------------------------------------------------------- 1 | package arg 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "reflect" 8 | "strings" 9 | ) 10 | 11 | // the width of the left column 12 | const colWidth = 25 13 | 14 | // Fail prints usage information to stderr and exits with non-zero status 15 | func (p *Parser) Fail(msg string) { 16 | p.WriteUsage(os.Stderr) 17 | fmt.Fprintln(os.Stderr, "error:", msg) 18 | os.Exit(-1) 19 | } 20 | 21 | // WriteUsage writes usage information to the given writer 22 | func (p *Parser) WriteUsage(w io.Writer) { 23 | var positionals, options []*spec 24 | for _, spec := range p.spec { 25 | if spec.positional { 26 | positionals = append(positionals, spec) 27 | } else { 28 | options = append(options, spec) 29 | } 30 | } 31 | 32 | if p.version != "" { 33 | fmt.Fprintln(w, p.version) 34 | } 35 | 36 | fmt.Fprintf(w, "usage: %s", p.config.Program) 37 | 38 | // write the option component of the usage message 39 | for _, spec := range options { 40 | // prefix with a space 41 | fmt.Fprint(w, " ") 42 | if !spec.required { 43 | fmt.Fprint(w, "[") 44 | } 45 | fmt.Fprint(w, synopsis(spec, "--"+spec.long)) 46 | if !spec.required { 47 | fmt.Fprint(w, "]") 48 | } 49 | } 50 | 51 | // write the positional component of the usage message 52 | for _, spec := range positionals { 53 | // prefix with a space 54 | fmt.Fprint(w, " ") 55 | up := strings.ToUpper(spec.long) 56 | if spec.multiple { 57 | fmt.Fprintf(w, "[%s [%s ...]]", up, up) 58 | } else { 59 | fmt.Fprint(w, up) 60 | } 61 | } 62 | fmt.Fprint(w, "\n") 63 | } 64 | 65 | // WriteHelp writes the usage string followed by the full help string for each option 66 | func (p *Parser) WriteHelp(w io.Writer) { 67 | var positionals, options []*spec 68 | for _, spec := range p.spec { 69 | if spec.positional { 70 | positionals = append(positionals, spec) 71 | } else { 72 | options = append(options, spec) 73 | } 74 | } 75 | 76 | p.WriteUsage(w) 77 | 78 | // write the list of positionals 79 | if len(positionals) > 0 { 80 | fmt.Fprint(w, "\npositional arguments:\n") 81 | for _, spec := range positionals { 82 | left := " " + spec.long 83 | fmt.Fprint(w, left) 84 | if spec.help != "" { 85 | if len(left)+2 < colWidth { 86 | fmt.Fprint(w, strings.Repeat(" ", colWidth-len(left))) 87 | } else { 88 | fmt.Fprint(w, "\n"+strings.Repeat(" ", colWidth)) 89 | } 90 | fmt.Fprint(w, spec.help) 91 | } 92 | fmt.Fprint(w, "\n") 93 | } 94 | } 95 | 96 | // write the list of options 97 | fmt.Fprint(w, "\noptions:\n") 98 | for _, spec := range options { 99 | printOption(w, spec) 100 | } 101 | 102 | // write the list of built in options 103 | printOption(w, &spec{boolean: true, long: "help", short: "h", help: "display this help and exit"}) 104 | if p.version != "" { 105 | printOption(w, &spec{boolean: true, long: "version", help: "display version and exit"}) 106 | } 107 | } 108 | 109 | func printOption(w io.Writer, spec *spec) { 110 | left := " " + synopsis(spec, "--"+spec.long) 111 | if spec.short != "" { 112 | left += ", " + synopsis(spec, "-"+spec.short) 113 | } 114 | fmt.Fprint(w, left) 115 | if spec.help != "" { 116 | if len(left)+2 < colWidth { 117 | fmt.Fprint(w, strings.Repeat(" ", colWidth-len(left))) 118 | } else { 119 | fmt.Fprint(w, "\n"+strings.Repeat(" ", colWidth)) 120 | } 121 | fmt.Fprint(w, spec.help) 122 | } 123 | // If spec.dest is not the zero value then a default value has been added. 124 | v := spec.dest 125 | if v.IsValid() { 126 | z := reflect.Zero(v.Type()) 127 | if (v.Type().Comparable() && z.Type().Comparable() && v.Interface() != z.Interface()) || v.Kind() == reflect.Slice && !v.IsNil() { 128 | fmt.Fprintf(w, " [default: %v]", v) 129 | } 130 | } 131 | fmt.Fprint(w, "\n") 132 | } 133 | 134 | func synopsis(spec *spec, form string) string { 135 | if spec.boolean { 136 | return form 137 | } 138 | return form + " " + strings.ToUpper(spec.long) 139 | } 140 | --------------------------------------------------------------------------------