load_windows.go (3981B)
1 // Copyright 2018 The Ebiten Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package glfw 16 17 import ( 18 "bytes" 19 "compress/gzip" 20 "fmt" 21 "io" 22 "os" 23 "path/filepath" 24 "unsafe" 25 26 "golang.org/x/sys/windows" 27 ) 28 29 type dll struct { 30 d *windows.LazyDLL 31 procs map[string]*windows.LazyProc 32 } 33 34 func (d *dll) call(name string, args ...uintptr) uintptr { 35 if d.procs == nil { 36 d.procs = map[string]*windows.LazyProc{} 37 } 38 if _, ok := d.procs[name]; !ok { 39 d.procs[name] = d.d.NewProc(name) 40 } 41 // It looks like there is no way to handle Windows errors correctly. 42 r, _, _ := d.procs[name].Call(args...) 43 return r 44 } 45 46 func writeDLLFile(name string) error { 47 f, err := gzip.NewReader(bytes.NewReader(glfwDLLCompressed)) 48 if err != nil { 49 return err 50 } 51 defer f.Close() 52 53 out, err := os.Create(name) 54 if err != nil { 55 return err 56 } 57 defer out.Close() 58 59 if _, err := io.Copy(out, f); err != nil { 60 return err 61 } 62 return nil 63 } 64 65 func loadDLL() (*dll, error) { 66 cachedir, err := os.UserCacheDir() 67 if err != nil { 68 return nil, err 69 } 70 71 dir := filepath.Join(cachedir, "ebiten") 72 if err := os.MkdirAll(dir, 0755); err != nil { 73 return nil, err 74 } 75 76 fn := filepath.Join(dir, glfwDLLHash+".dll") 77 if _, err := os.Stat(fn); err != nil { 78 if !os.IsNotExist(err) { 79 return nil, err 80 } 81 82 // Create a DLL as a temporary file and then rename it later. 83 // Without the temporary file, writing a DLL might fail in the process of writing and Ebiten cannot 84 // notice that the DLL file is incomplete. 85 if err := writeDLLFile(fn + ".tmp"); err != nil { 86 return nil, err 87 } 88 89 if err := os.Rename(fn+".tmp", fn); err != nil { 90 return nil, err 91 } 92 } 93 94 return &dll{ 95 d: windows.NewLazyDLL(fn), 96 }, nil 97 } 98 99 func (d *dll) unload() error { 100 if err := windows.FreeLibrary(windows.Handle(d.d.Handle())); err != nil { 101 return err 102 } 103 return nil 104 } 105 106 func bytePtrToString(ptr *byte) string { 107 var bs []byte 108 for i := uintptr(0); ; i++ { 109 b := *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + i)) 110 if b == 0 { 111 break 112 } 113 bs = append(bs, b) 114 } 115 return string(bs) 116 } 117 118 type glfwError struct { 119 code ErrorCode 120 desc string 121 } 122 123 func (e *glfwError) Error() string { 124 return fmt.Sprintf("glfw: %s: %s", e.code.String(), e.desc) 125 } 126 127 var lastErr = make(chan *glfwError, 1) 128 129 func fetchError() *glfwError { 130 select { 131 case err := <-lastErr: 132 return err 133 default: 134 return nil 135 } 136 } 137 138 func panicError() { 139 if err := acceptError(); err != nil { 140 panic(err) 141 } 142 } 143 144 func flushErrors() { 145 if err := fetchError(); err != nil { 146 panic(fmt.Sprintf("glfw: uncaught error: %s", err.Error())) 147 } 148 } 149 150 func acceptError(codes ...ErrorCode) error { 151 err := fetchError() 152 if err == nil { 153 return nil 154 } 155 for _, c := range codes { 156 if err.code == c { 157 return err 158 } 159 } 160 switch err.code { 161 case PlatformError: 162 // TODO: Should we log this? 163 return nil 164 case NotInitialized, NoCurrentContext, InvalidEnum, InvalidValue, OutOfMemory: 165 panic(err) 166 default: 167 panic(fmt.Sprintf("glfw: uncaught error: %s", err.Error())) 168 } 169 return err 170 } 171 172 func goGLFWErrorCallback(code uintptr, desc *byte) uintptr { 173 flushErrors() 174 err := &glfwError{ 175 code: ErrorCode(code), 176 desc: bytePtrToString(desc), 177 } 178 select { 179 case lastErr <- err: 180 default: 181 panic(fmt.Sprintf("glfw: uncaught error: %s", err.Error())) 182 } 183 return 0 184 } 185 186 var glfwDLL *dll 187 188 func init() { 189 dll, err := loadDLL() 190 if err != nil { 191 panic(err) 192 } 193 glfwDLL = dll 194 195 glfwDLL.call("glfwSetErrorCallback", windows.NewCallbackCDecl(goGLFWErrorCallback)) 196 }