Initial release: RadiantWave v1.0
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Eric Woroshow
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
//go:build required
|
||||
// +build required
|
||||
|
||||
// Package dummy prevents go tooling from stripping the c dependencies.
|
||||
package dummy
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
#ifndef __khrplatform_h_
|
||||
#define __khrplatform_h_
|
||||
|
||||
/*
|
||||
** Copyright (c) 2008-2018 The Khronos Group Inc.
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining a
|
||||
** copy of this software and/or associated documentation files (the
|
||||
** "Materials"), to deal in the Materials without restriction, including
|
||||
** without limitation the rights to use, copy, modify, merge, publish,
|
||||
** distribute, sublicense, and/or sell copies of the Materials, and to
|
||||
** permit persons to whom the Materials are furnished to do so, subject to
|
||||
** the following conditions:
|
||||
**
|
||||
** The above copyright notice and this permission notice shall be included
|
||||
** in all copies or substantial portions of the Materials.
|
||||
**
|
||||
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
|
||||
*/
|
||||
|
||||
/* Khronos platform-specific types and definitions.
|
||||
*
|
||||
* The master copy of khrplatform.h is maintained in the Khronos EGL
|
||||
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
|
||||
* The last semantic modification to khrplatform.h was at commit ID:
|
||||
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
|
||||
*
|
||||
* Adopters may modify this file to suit their platform. Adopters are
|
||||
* encouraged to submit platform specific modifications to the Khronos
|
||||
* group so that they can be included in future versions of this file.
|
||||
* Please submit changes by filing pull requests or issues on
|
||||
* the EGL Registry repository linked above.
|
||||
*
|
||||
*
|
||||
* See the Implementer's Guidelines for information about where this file
|
||||
* should be located on your system and for more details of its use:
|
||||
* http://www.khronos.org/registry/implementers_guide.pdf
|
||||
*
|
||||
* This file should be included as
|
||||
* #include <KHR/khrplatform.h>
|
||||
* by Khronos client API header files that use its types and defines.
|
||||
*
|
||||
* The types in khrplatform.h should only be used to define API-specific types.
|
||||
*
|
||||
* Types defined in khrplatform.h:
|
||||
* khronos_int8_t signed 8 bit
|
||||
* khronos_uint8_t unsigned 8 bit
|
||||
* khronos_int16_t signed 16 bit
|
||||
* khronos_uint16_t unsigned 16 bit
|
||||
* khronos_int32_t signed 32 bit
|
||||
* khronos_uint32_t unsigned 32 bit
|
||||
* khronos_int64_t signed 64 bit
|
||||
* khronos_uint64_t unsigned 64 bit
|
||||
* khronos_intptr_t signed same number of bits as a pointer
|
||||
* khronos_uintptr_t unsigned same number of bits as a pointer
|
||||
* khronos_ssize_t signed size
|
||||
* khronos_usize_t unsigned size
|
||||
* khronos_float_t signed 32 bit floating point
|
||||
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
|
||||
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
|
||||
* nanoseconds
|
||||
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
|
||||
* khronos_boolean_enum_t enumerated boolean type. This should
|
||||
* only be used as a base type when a client API's boolean type is
|
||||
* an enum. Client APIs which use an integer or other type for
|
||||
* booleans cannot use this as the base type for their boolean.
|
||||
*
|
||||
* Tokens defined in khrplatform.h:
|
||||
*
|
||||
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
|
||||
*
|
||||
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
|
||||
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
|
||||
*
|
||||
* Calling convention macros defined in this file:
|
||||
* KHRONOS_APICALL
|
||||
* KHRONOS_APIENTRY
|
||||
* KHRONOS_APIATTRIBUTES
|
||||
*
|
||||
* These may be used in function prototypes as:
|
||||
*
|
||||
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
|
||||
* int arg1,
|
||||
* int arg2) KHRONOS_APIATTRIBUTES;
|
||||
*/
|
||||
|
||||
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
|
||||
# define KHRONOS_STATIC 1
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APICALL
|
||||
*-------------------------------------------------------------------------
|
||||
* This precedes the return type of the function in the function prototype.
|
||||
*/
|
||||
#if defined(KHRONOS_STATIC)
|
||||
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
|
||||
* header compatible with static linking. */
|
||||
# define KHRONOS_APICALL
|
||||
#elif defined(_WIN32)
|
||||
# define KHRONOS_APICALL __declspec(dllimport)
|
||||
#elif defined (__SYMBIAN32__)
|
||||
# define KHRONOS_APICALL IMPORT_C
|
||||
#elif defined(__ANDROID__)
|
||||
# define KHRONOS_APICALL __attribute__((visibility("default")))
|
||||
#else
|
||||
# define KHRONOS_APICALL
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APIENTRY
|
||||
*-------------------------------------------------------------------------
|
||||
* This follows the return type of the function and precedes the function
|
||||
* name in the function prototype.
|
||||
*/
|
||||
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
|
||||
/* Win32 but not WinCE */
|
||||
# define KHRONOS_APIENTRY __stdcall
|
||||
#else
|
||||
# define KHRONOS_APIENTRY
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APIATTRIBUTES
|
||||
*-------------------------------------------------------------------------
|
||||
* This follows the closing parenthesis of the function prototype arguments.
|
||||
*/
|
||||
#if defined (__ARMCC_2__)
|
||||
#define KHRONOS_APIATTRIBUTES __softfp
|
||||
#else
|
||||
#define KHRONOS_APIATTRIBUTES
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* basic type definitions
|
||||
*-----------------------------------------------------------------------*/
|
||||
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
|
||||
|
||||
|
||||
/*
|
||||
* Using <stdint.h>
|
||||
*/
|
||||
#include <stdint.h>
|
||||
typedef int32_t khronos_int32_t;
|
||||
typedef uint32_t khronos_uint32_t;
|
||||
typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif defined(__VMS ) || defined(__sgi)
|
||||
|
||||
/*
|
||||
* Using <inttypes.h>
|
||||
*/
|
||||
#include <inttypes.h>
|
||||
typedef int32_t khronos_int32_t;
|
||||
typedef uint32_t khronos_uint32_t;
|
||||
typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
|
||||
|
||||
/*
|
||||
* Win32
|
||||
*/
|
||||
typedef __int32 khronos_int32_t;
|
||||
typedef unsigned __int32 khronos_uint32_t;
|
||||
typedef __int64 khronos_int64_t;
|
||||
typedef unsigned __int64 khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif defined(__sun__) || defined(__digital__)
|
||||
|
||||
/*
|
||||
* Sun or Digital
|
||||
*/
|
||||
typedef int khronos_int32_t;
|
||||
typedef unsigned int khronos_uint32_t;
|
||||
#if defined(__arch64__) || defined(_LP64)
|
||||
typedef long int khronos_int64_t;
|
||||
typedef unsigned long int khronos_uint64_t;
|
||||
#else
|
||||
typedef long long int khronos_int64_t;
|
||||
typedef unsigned long long int khronos_uint64_t;
|
||||
#endif /* __arch64__ */
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#elif 0
|
||||
|
||||
/*
|
||||
* Hypothetical platform with no float or int64 support
|
||||
*/
|
||||
typedef int khronos_int32_t;
|
||||
typedef unsigned int khronos_uint32_t;
|
||||
#define KHRONOS_SUPPORT_INT64 0
|
||||
#define KHRONOS_SUPPORT_FLOAT 0
|
||||
|
||||
#else
|
||||
|
||||
/*
|
||||
* Generic fallback
|
||||
*/
|
||||
#include <stdint.h>
|
||||
typedef int32_t khronos_int32_t;
|
||||
typedef uint32_t khronos_uint32_t;
|
||||
typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Types that are (so far) the same on all platforms
|
||||
*/
|
||||
typedef signed char khronos_int8_t;
|
||||
typedef unsigned char khronos_uint8_t;
|
||||
typedef signed short int khronos_int16_t;
|
||||
typedef unsigned short int khronos_uint16_t;
|
||||
|
||||
/*
|
||||
* Types that differ between LLP64 and LP64 architectures - in LLP64,
|
||||
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
|
||||
* to be the only LLP64 architecture in current use.
|
||||
*/
|
||||
#ifdef _WIN64
|
||||
typedef signed long long int khronos_intptr_t;
|
||||
typedef unsigned long long int khronos_uintptr_t;
|
||||
typedef signed long long int khronos_ssize_t;
|
||||
typedef unsigned long long int khronos_usize_t;
|
||||
#else
|
||||
typedef signed long int khronos_intptr_t;
|
||||
typedef unsigned long int khronos_uintptr_t;
|
||||
typedef signed long int khronos_ssize_t;
|
||||
typedef unsigned long int khronos_usize_t;
|
||||
#endif
|
||||
|
||||
#if KHRONOS_SUPPORT_FLOAT
|
||||
/*
|
||||
* Float type
|
||||
*/
|
||||
typedef float khronos_float_t;
|
||||
#endif
|
||||
|
||||
#if KHRONOS_SUPPORT_INT64
|
||||
/* Time types
|
||||
*
|
||||
* These types can be used to represent a time interval in nanoseconds or
|
||||
* an absolute Unadjusted System Time. Unadjusted System Time is the number
|
||||
* of nanoseconds since some arbitrary system event (e.g. since the last
|
||||
* time the system booted). The Unadjusted System Time is an unsigned
|
||||
* 64 bit value that wraps back to 0 every 584 years. Time intervals
|
||||
* may be either signed or unsigned.
|
||||
*/
|
||||
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
|
||||
typedef khronos_int64_t khronos_stime_nanoseconds_t;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Dummy value used to pad enum types to 32 bits.
|
||||
*/
|
||||
#ifndef KHRONOS_MAX_ENUM
|
||||
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Enumerated boolean type
|
||||
*
|
||||
* Values other than zero should be considered to be true. Therefore
|
||||
* comparisons should not be made against KHRONOS_TRUE.
|
||||
*/
|
||||
typedef enum {
|
||||
KHRONOS_FALSE = 0,
|
||||
KHRONOS_TRUE = 1,
|
||||
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
|
||||
} khronos_boolean_enum_t;
|
||||
|
||||
#endif /* __khrplatform_h_ */
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
//go:build required
|
||||
// +build required
|
||||
|
||||
package gl
|
||||
|
||||
// This file exists purely to prevent the golang toolchain from stripping
|
||||
// away the c source directories and files when `go mod vendor` is used
|
||||
// to populate a `vendor/` directory of a project depending on `go-gl/go`.
|
||||
//
|
||||
// How it works:
|
||||
// - every directory which only includes c source files receives a dummy.go file.
|
||||
// - every directory we want to preserve is included here as a _ import.
|
||||
// - this file is given a build tag to exclude it from the regular build.
|
||||
import (
|
||||
// Prevent go tooling from stripping out the c source files.
|
||||
_ "github.com/go-gl/gl/v3.3-core/gl/KHR"
|
||||
)
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
// Code generated by glow (https://github.com/go-gl/glow). DO NOT EDIT.
|
||||
|
||||
package gl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include <stdlib.h>
|
||||
import "C"
|
||||
|
||||
// Ptr takes a slice or pointer (to a singular scalar value or the first
|
||||
// element of an array or slice) and returns its GL-compatible address.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// var data []uint8
|
||||
// ...
|
||||
// gl.TexImage2D(gl.TEXTURE_2D, ..., gl.UNSIGNED_BYTE, gl.Ptr(&data[0]))
|
||||
func Ptr(data interface{}) unsafe.Pointer {
|
||||
if data == nil {
|
||||
return unsafe.Pointer(nil)
|
||||
}
|
||||
var addr unsafe.Pointer
|
||||
v := reflect.ValueOf(data)
|
||||
switch v.Type().Kind() {
|
||||
case reflect.Ptr:
|
||||
e := v.Elem()
|
||||
switch e.Kind() {
|
||||
case
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64:
|
||||
addr = unsafe.Pointer(e.UnsafeAddr())
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported pointer to type %s; must be a slice or pointer to a singular scalar value or the first element of an array or slice", e.Kind()))
|
||||
}
|
||||
case reflect.Uintptr:
|
||||
addr = unsafe.Pointer(data.(uintptr))
|
||||
case reflect.Slice:
|
||||
addr = unsafe.Pointer(v.Index(0).UnsafeAddr())
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported type %s; must be a slice or pointer to a singular scalar value or the first element of an array or slice", v.Type()))
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// PtrOffset takes a pointer offset and returns a GL-compatible pointer.
|
||||
// Originally intended for functions such as glVertexAttribPointer that take pointer
|
||||
// parameters also for offsets, since Go 1.14 this is no longer recommended.
|
||||
//
|
||||
// Use a corresponding offset-compatible variant of the function instead.
|
||||
// For example, for gl.VertexAttribPointer() there is gl.VertexAttribPointerWithOffset().
|
||||
//
|
||||
// See https://github.com/go-gl/gl#go-114-and-checkptr for more details on the checkptr detector.
|
||||
// See https://github.com/go-gl/glow#overloads, about adding new overloads.
|
||||
//
|
||||
// Deprecated: Use more appropriate overload function instead
|
||||
func PtrOffset(offset int) unsafe.Pointer {
|
||||
return unsafe.Pointer(uintptr(offset))
|
||||
}
|
||||
|
||||
// Str takes a null-terminated Go string and returns its GL-compatible address.
|
||||
// This function reaches into Go string storage in an unsafe way so the caller
|
||||
// must ensure the string is not garbage collected.
|
||||
func Str(str string) *uint8 {
|
||||
if !strings.HasSuffix(str, "\x00") {
|
||||
panic("str argument missing null terminator: " + str)
|
||||
}
|
||||
header := (*reflect.StringHeader)(unsafe.Pointer(&str))
|
||||
return (*uint8)(unsafe.Pointer(header.Data))
|
||||
}
|
||||
|
||||
// GoStr takes a null-terminated string returned by OpenGL and constructs a
|
||||
// corresponding Go string.
|
||||
func GoStr(cstr *uint8) string {
|
||||
return C.GoString((*C.char)(unsafe.Pointer(cstr)))
|
||||
}
|
||||
|
||||
// Strs takes a list of Go strings (with or without null-termination) and
|
||||
// returns their C counterpart.
|
||||
//
|
||||
// The returned free function must be called once you are done using the strings
|
||||
// in order to free the memory.
|
||||
//
|
||||
// If no strings are provided as a parameter this function will panic.
|
||||
func Strs(strs ...string) (cstrs **uint8, free func()) {
|
||||
if len(strs) == 0 {
|
||||
panic("Strs: expected at least 1 string")
|
||||
}
|
||||
|
||||
// Allocate a contiguous array large enough to hold all the strings' contents.
|
||||
n := 0
|
||||
for i := range strs {
|
||||
n += len(strs[i])
|
||||
}
|
||||
if n == 0 {
|
||||
n = 1 // avoid allocating zero bytes in case all strings are empty.
|
||||
}
|
||||
data := C.malloc(C.size_t(n))
|
||||
|
||||
// Copy all the strings into data.
|
||||
dataSlice := (*[1 << 30]byte)(data)[:n]
|
||||
css := make([]*uint8, len(strs)) // Populated with pointers to each string.
|
||||
offset := 0
|
||||
for i := range strs {
|
||||
copy(dataSlice[offset:offset+len(strs[i])], strs[i][:]) // Copy strs[i] into proper data location.
|
||||
css[i] = (*uint8)(unsafe.Pointer(&dataSlice[offset])) // Set a pointer to it.
|
||||
offset += len(strs[i])
|
||||
}
|
||||
|
||||
return (**uint8)(&css[0]), func() { C.free(data) }
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// Code generated by glow (https://github.com/go-gl/glow). DO NOT EDIT.
|
||||
|
||||
package gl
|
||||
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
type DebugProc func(
|
||||
source uint32,
|
||||
gltype uint32,
|
||||
id uint32,
|
||||
severity uint32,
|
||||
length int32,
|
||||
message string,
|
||||
userParam unsafe.Pointer)
|
||||
|
||||
var userDebugCallback DebugProc
|
||||
|
||||
//export glowDebugCallback_glcore33
|
||||
func glowDebugCallback_glcore33(
|
||||
source uint32,
|
||||
gltype uint32,
|
||||
id uint32,
|
||||
severity uint32,
|
||||
length int32,
|
||||
message *uint8,
|
||||
userParam unsafe.Pointer) {
|
||||
if userDebugCallback != nil {
|
||||
userDebugCallback(source, gltype, id, severity, length, GoStr(message), userParam)
|
||||
}
|
||||
}
|
||||
+15867
File diff suppressed because it is too large
Load Diff
+75
@@ -0,0 +1,75 @@
|
||||
// Code generated by glow (https://github.com/go-gl/glow). DO NOT EDIT.
|
||||
|
||||
// This file implements GlowGetProcAddress for every supported platform. The
|
||||
// correct version is chosen automatically based on build tags:
|
||||
//
|
||||
// windows: WGL
|
||||
// darwin: CGL
|
||||
// linux freebsd netbsd openbsd: GLX
|
||||
//
|
||||
// Use of EGL instead of the platform's default (listed above) is made possible
|
||||
// via the "egl" build tag.
|
||||
//
|
||||
// It is also possible to install your own function outside this package for
|
||||
// retrieving OpenGL function pointers, to do this see InitWithProcAddrFunc.
|
||||
|
||||
package gl
|
||||
|
||||
/*
|
||||
#cgo windows CFLAGS: -DTAG_WINDOWS
|
||||
#cgo !gles2,windows LDFLAGS: -lopengl32
|
||||
#cgo gles2,windows LDFLAGS: -lGLESv2
|
||||
#cgo darwin CFLAGS: -DTAG_DARWIN
|
||||
#cgo !gles2,darwin LDFLAGS: -framework OpenGL
|
||||
#cgo gles2,darwin LDFLAGS: -framework OpenGLES
|
||||
#cgo linux freebsd netbsd openbsd CFLAGS: -DTAG_POSIX
|
||||
#cgo !egl,linux !egl,freebsd !egl,netbsd !egl,openbsd pkg-config: gl
|
||||
#cgo egl,linux egl,freebsd egl,netbsd egl,openbsd egl,windows CFLAGS: -DTAG_EGL
|
||||
#cgo egl,linux egl,freebsd egl,netbsd egl,openbsd pkg-config: egl
|
||||
#cgo egl,windows LDFLAGS: -lEGL
|
||||
#cgo egl,darwin LDFLAGS: -lEGL
|
||||
// Check the EGL tag first as it takes priority over the platform's default
|
||||
// configuration of WGL/GLX/CGL.
|
||||
#if defined(TAG_EGL)
|
||||
#include <stdlib.h>
|
||||
#include <EGL/egl.h>
|
||||
static void* GlowGetProcAddress(const char* name) {
|
||||
return eglGetProcAddress(name);
|
||||
}
|
||||
#elif defined(TAG_WINDOWS)
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
#include <windows.h>
|
||||
#include <stdlib.h>
|
||||
static HMODULE ogl32dll = NULL;
|
||||
static void* GlowGetProcAddress(const char* name) {
|
||||
void* pf = wglGetProcAddress((LPCSTR) name);
|
||||
if (pf) {
|
||||
return pf;
|
||||
}
|
||||
if (ogl32dll == NULL) {
|
||||
ogl32dll = LoadLibraryA("opengl32.dll");
|
||||
}
|
||||
return GetProcAddress(ogl32dll, (LPCSTR) name);
|
||||
}
|
||||
#elif defined(TAG_DARWIN)
|
||||
#include <stdlib.h>
|
||||
#include <dlfcn.h>
|
||||
static void* GlowGetProcAddress(const char* name) {
|
||||
return dlsym(RTLD_DEFAULT, name);
|
||||
}
|
||||
#elif defined(TAG_POSIX)
|
||||
#include <stdlib.h>
|
||||
#include <GL/glx.h>
|
||||
static void* GlowGetProcAddress(const char* name) {
|
||||
return glXGetProcAddress((const GLubyte *) name);
|
||||
}
|
||||
#endif
|
||||
*/
|
||||
import "C"
|
||||
import "unsafe"
|
||||
|
||||
func getProcAddress(namea string) unsafe.Pointer {
|
||||
cname := C.CString(namea)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
return C.GlowGetProcAddress(cname)
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
# This is the official list of go-gl/mathgl authors for copyright purposes.
|
||||
# This file is distinct from the CONTRIBUTORS files.
|
||||
# See the latter for an explanation.
|
||||
|
||||
# Names should be added to this file as
|
||||
# Name or Organization <email address>
|
||||
# The email address is not required for organizations.
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Alex Vasi <eee@someuser.com>
|
||||
Dmitri Shuralyov <shurcooL@gmail.com>
|
||||
GlenKelley <???>
|
||||
Zoe Juozapaitis <jragonmiris@gmail.com>
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# This is the official list of people who can contribute
|
||||
# (and typically have contributed) code to the mathgl
|
||||
# repository.
|
||||
#
|
||||
# The AUTHORS file lists the copyright holders; this file
|
||||
# lists people. For example, Google employees would be listed here
|
||||
# but not in AUTHORS, because Google would hold the copyright.
|
||||
#
|
||||
# When adding J Random Contributor's name to this file,
|
||||
# either J's name or J's organization's name should be
|
||||
# added to the AUTHORS file.
|
||||
#
|
||||
# Names should be added to this file like so:
|
||||
# Name <email address>
|
||||
#
|
||||
# Please keep the list sorted.
|
||||
|
||||
Alex Vasi <eee@someuser.com>
|
||||
Dmitri Shuralyov <shurcooL@gmail.com>
|
||||
GlenKelley <???>
|
||||
Zoe Juozapaitis <jragonmiris@gmail.com>
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
Copyright ©2013 The go-gl Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the gonum project nor the names of its authors and
|
||||
contributors may be used to endorse or promote products derived from this
|
||||
software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
// Copyright 2014 The go-gl Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mgl32
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// CartesianToSpherical converts 3-dimensional cartesian coordinates (x,y,z) to spherical
|
||||
// coordinates with radius r, inclination theta, and azimuth phi.
|
||||
//
|
||||
// All angles are in radians.
|
||||
func CartesianToSpherical(coord Vec3) (r, theta, phi float32) {
|
||||
r = coord.Len()
|
||||
theta = float32(math.Acos(float64(coord[2] / r)))
|
||||
phi = float32(math.Atan2(float64(coord[1]), float64(coord[0])))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CartesianToCylindical converts 3-dimensional cartesian coordinates (x,y,z) to
|
||||
// cylindrical coordinates with radial distance r, azimuth phi, and height z.
|
||||
//
|
||||
// All angles are in radians.
|
||||
func CartesianToCylindical(coord Vec3) (rho, phi, z float32) {
|
||||
rho = float32(math.Hypot(float64(coord[0]), float64(coord[1])))
|
||||
|
||||
phi = float32(math.Atan2(float64(coord[1]), float64(coord[0])))
|
||||
|
||||
z = coord[2]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SphericalToCartesian converts spherical coordinates with radius r, inclination theta,
|
||||
// and azimuth phi to cartesian coordinates (x,y,z).
|
||||
//
|
||||
// Angles are in radians.
|
||||
func SphericalToCartesian(r, theta, phi float32) Vec3 {
|
||||
st, ct := math.Sincos(float64(theta))
|
||||
sp, cp := math.Sincos(float64(phi))
|
||||
|
||||
return Vec3{r * float32(st*cp), r * float32(st*sp), r * float32(ct)}
|
||||
}
|
||||
|
||||
// SphericalToCylindrical converts spherical coordinates with radius r,
|
||||
// inclination theta, and azimuth phi to cylindrical coordinates with radial
|
||||
// distance r, azimuth phi, and height z.
|
||||
//
|
||||
// Angles are in radians
|
||||
func SphericalToCylindrical(r, theta, phi float32) (rho, phi2, z float32) {
|
||||
s, c := math.Sincos(float64(theta))
|
||||
|
||||
rho = r * float32(s)
|
||||
z = r * float32(c)
|
||||
phi2 = phi
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CylindircalToSpherical converts cylindrical coordinates with radial distance
|
||||
// r, azimuth phi, and height z to spherical coordinates with radius r,
|
||||
// inclination theta, and azimuth phi.
|
||||
//
|
||||
// Angles are in radians
|
||||
func CylindircalToSpherical(rho, phi, z float32) (r, theta, phi2 float32) {
|
||||
r = float32(math.Hypot(float64(rho), float64(z)))
|
||||
phi2 = phi
|
||||
theta = float32(math.Atan2(float64(rho), float64(z)))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CylindricalToCartesian converts cylindrical coordinates with radial distance
|
||||
// r, azimuth phi, and height z to cartesian coordinates (x,y,z)
|
||||
//
|
||||
// Angles are in radians.
|
||||
func CylindricalToCartesian(rho, phi, z float32) Vec3 {
|
||||
s, c := math.Sincos(float64(phi))
|
||||
|
||||
return Vec3{rho * float32(c), rho * float32(s), z}
|
||||
}
|
||||
|
||||
// DegToRad converts degrees to radians
|
||||
func DegToRad(angle float32) float32 {
|
||||
return angle * float32(math.Pi) / 180
|
||||
}
|
||||
|
||||
// RadToDeg converts radians to degrees
|
||||
func RadToDeg(angle float32) float32 {
|
||||
return angle * 180 / float32(math.Pi)
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// Copyright 2014 The go-gl Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package mgl[32|64] (an abbreviation of mathgl since the packages were split between 32 and 64-bit versions)
|
||||
is a pure Go math package specialized for 3D math, with inspiration from GLM. It provides statically-sized vectors and matrices with
|
||||
compile-time generated calculations for most basic math operations. It also provides several basic graphics utilities such as bezier curves and surfaces,
|
||||
generation of basic primitives like circles, easy creation of common matrices such as perspective or rotation, and common operations like converting
|
||||
to/from screen/OpenGL coordinates or Projecting/Unprojecting from an MVP matrix. Quaternions are also supported.
|
||||
|
||||
The basic vectors and matrices are written with code generation, so looking directly at the source will probably be a bit confusing. I recommend looking at the Godoc
|
||||
instead, as all basic functions are documented.
|
||||
|
||||
This package is written in Column Major Order to make it easier with OpenGL. This means for uniform blocks you can use the default ordering, and when you call
|
||||
pass-in functions you can leave the "transpose" argument as false.
|
||||
|
||||
The package now contains variable sized vectors and matrices. Using these is discouraged. They exist for corner cases where you need "small" matrices that are still
|
||||
bigger than 4x4. An example may be a Jacobean used for inverse kinematics. Things like computer vision or general linear algebra are best left to packages
|
||||
more directly suited for that task -- OpenCV, BLAS, LAPACK, numpy, gonum (if you want to stay in Go), and so on.
|
||||
*/
|
||||
package mgl32
|
||||
+511
@@ -0,0 +1,511 @@
|
||||
// Copyright 2014 The go-gl Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mgl32
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// MatMxN is an arbitrary mxn matrix backed by a slice of floats.
|
||||
//
|
||||
// This is emphatically not recommended for hardcore n-dimensional
|
||||
// linear algebra. For that purpose I recommend github.com/gonum/matrix or
|
||||
// well-tested C libraries such as BLAS or LAPACK.
|
||||
//
|
||||
// This is meant to complement future algorithms that may require matrices larger than
|
||||
// 4x4, but still relatively small (e.g. Jacobeans for inverse kinematics).
|
||||
//
|
||||
// It makes use of the same memory sync.Pool set that VecN does, with the same sizing rules.
|
||||
//
|
||||
// MatMN will always check if the receiver is nil on any method. Meaning MathMN(nil).Add(dst,m2)
|
||||
// should always work. Except for the Reshape function, the semantics of this is to "propogate" nils
|
||||
// forward, so if an invalid operation occurs in a long chain of matrix operations, the overall result will be nil.
|
||||
type MatMxN struct {
|
||||
m, n int
|
||||
dat []float32
|
||||
}
|
||||
|
||||
// NewMatrix creates a matrix backed by a new slice of size m*n
|
||||
func NewMatrix(m, n int) (mat *MatMxN) {
|
||||
if shouldPool {
|
||||
return &MatMxN{m: m, n: n, dat: grabFromPool(m * n)}
|
||||
} else {
|
||||
return &MatMxN{m: m, n: n, dat: make([]float32, m*n)}
|
||||
}
|
||||
}
|
||||
|
||||
// NewMatrixFromData returns a matrix with data specified by the data in src
|
||||
//
|
||||
// For instance, to create a 3x3 MatMN from a Mat3
|
||||
//
|
||||
// m1 := mgl32.Rotate3DX(3.14159)
|
||||
// mat := mgl32.NewBackedMatrix(m1[:],3,3)
|
||||
//
|
||||
// will create an MN matrix matching the data in the original rotation matrix.
|
||||
// This matrix is NOT backed by the initial slice; it's a copy of the data
|
||||
//
|
||||
// If m*n > cap(src), this function will panic.
|
||||
func NewMatrixFromData(src []float32, m, n int) *MatMxN {
|
||||
var internal []float32
|
||||
if shouldPool {
|
||||
internal = grabFromPool(m * n)
|
||||
} else {
|
||||
internal = make([]float32, m*n)
|
||||
}
|
||||
copy(internal, src[:m*n])
|
||||
|
||||
return &MatMxN{m: m, n: n, dat: internal}
|
||||
}
|
||||
|
||||
// CopyMatMN copies src into dst. This Reshapes dst
|
||||
// to the same size as src.
|
||||
//
|
||||
// If dst or src is nil, this is a no-op
|
||||
func CopyMatMN(dst, src *MatMxN) {
|
||||
if dst == nil || src == nil {
|
||||
return
|
||||
}
|
||||
dst.Reshape(src.m, src.n)
|
||||
copy(dst.dat, src.dat)
|
||||
}
|
||||
|
||||
// IdentN stores the NxN identity matrix in dst, reallocating as necessary.
|
||||
func IdentN(dst *MatMxN, n int) *MatMxN {
|
||||
dst = dst.Reshape(n, n)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
for j := 0; j < n; j++ {
|
||||
if i == j {
|
||||
dst.Set(i, j, 1)
|
||||
} else {
|
||||
dst.Set(i, j, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// DiagN creates an NxN diagonal matrix seeded by the diagonal vector diag.
|
||||
// Meaning: for all entries, where i==j, dst.At(i,j) = diag[i]. Otherwise
|
||||
// dst.At(i,j) = 0
|
||||
//
|
||||
// This reshapes dst to the correct size, returning/grabbing from the memory
|
||||
// pool as necessary.
|
||||
func DiagN(dst *MatMxN, diag *VecN) *MatMxN {
|
||||
dst = dst.Reshape(len(diag.vec), len(diag.vec))
|
||||
n := len(diag.vec)
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
for j := 0; j < n; j++ {
|
||||
if i == j {
|
||||
dst.Set(i, j, diag.vec[i])
|
||||
} else {
|
||||
dst.Set(i, j, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// Zero reshapes the matrix to m by n and zeroes out all
|
||||
// elements.
|
||||
func (mat *MatMxN) Zero(m, n int) {
|
||||
if mat == nil {
|
||||
return
|
||||
}
|
||||
|
||||
mat.Reshape(m, n)
|
||||
for i := range mat.dat {
|
||||
mat.dat[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// destroy returns the underlying matrix slice to the memory pool
|
||||
func (mat *MatMxN) destroy() {
|
||||
if mat == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if shouldPool && mat.dat != nil {
|
||||
returnToPool(mat.dat)
|
||||
}
|
||||
mat.m, mat.n = 0, 0
|
||||
mat.dat = nil
|
||||
}
|
||||
|
||||
// Reshape reshapes the matrix to the desired dimensions.
|
||||
// If the overall size of the new matrix (m*n) is bigger
|
||||
// than the current size, the underlying slice will
|
||||
// be grown, sending the current slice to the memory pool
|
||||
// and grabbing a bigger one if necessary
|
||||
//
|
||||
// If the caller is a nil pointer, the return value will be a new
|
||||
// matrix, as if NewMatrix(m,n) had been called. Otherwise it's
|
||||
// simply the caller.
|
||||
func (mat *MatMxN) Reshape(m, n int) *MatMxN {
|
||||
if mat == nil {
|
||||
return NewMatrix(m, n)
|
||||
}
|
||||
|
||||
if m*n <= cap(mat.dat) {
|
||||
if mat.dat != nil {
|
||||
mat.dat = mat.dat[:m*n]
|
||||
} else {
|
||||
mat.dat = []float32{}
|
||||
}
|
||||
mat.m, mat.n = m, n
|
||||
return mat
|
||||
}
|
||||
|
||||
if shouldPool && mat.dat != nil {
|
||||
returnToPool(mat.dat)
|
||||
}
|
||||
(*mat) = (*NewMatrix(m, n))
|
||||
|
||||
return mat
|
||||
}
|
||||
|
||||
// InferMatrix infers an MxN matrix from a constant matrix from this package.
|
||||
// For instance, a Mat2x3 inferred with this function will work just like
|
||||
// NewMatrixFromData(m[:],2,3) where m is the Mat2x3. This uses a type switch.
|
||||
//
|
||||
// I personally recommend using NewMatrixFromData, because it avoids a
|
||||
// potentially costly type switch. However, this is also more robust and less
|
||||
// error prone if you change the size of your matrix somewhere.
|
||||
//
|
||||
// If the value passed in is not recognized, it returns an InferMatrixError.
|
||||
func (mat *MatMxN) InferMatrix(m interface{}) (*MatMxN, error) {
|
||||
switch raw := m.(type) {
|
||||
case Mat2:
|
||||
return NewMatrixFromData(raw[:], 2, 2), nil
|
||||
case Mat2x3:
|
||||
return NewMatrixFromData(raw[:], 2, 3), nil
|
||||
case Mat2x4:
|
||||
return NewMatrixFromData(raw[:], 2, 4), nil
|
||||
case Mat3:
|
||||
return NewMatrixFromData(raw[:], 3, 3), nil
|
||||
case Mat3x2:
|
||||
return NewMatrixFromData(raw[:], 3, 2), nil
|
||||
case Mat3x4:
|
||||
return NewMatrixFromData(raw[:], 3, 4), nil
|
||||
case Mat4:
|
||||
return NewMatrixFromData(raw[:], 4, 4), nil
|
||||
case Mat4x2:
|
||||
return NewMatrixFromData(raw[:], 4, 2), nil
|
||||
case Mat4x3:
|
||||
return NewMatrixFromData(raw[:], 4, 3), nil
|
||||
default:
|
||||
return nil, InferMatrixError{}
|
||||
}
|
||||
}
|
||||
|
||||
// Trace returns the trace of a square matrix (sum of all diagonal elements). If
|
||||
// the matrix is nil, or not square, the result will be NaN.
|
||||
func (mat *MatMxN) Trace() float32 {
|
||||
if mat == nil || mat.m != mat.n {
|
||||
return float32(math.NaN())
|
||||
}
|
||||
|
||||
var out float32
|
||||
for i := 0; i < mat.m; i++ {
|
||||
out += mat.At(i, i)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// Transpose takes the transpose of mat and puts it in dst.
|
||||
//
|
||||
// If dst is not of the correct dimensions, it will be Reshaped,
|
||||
// if dst and mat are the same, a temporary matrix of the correct size will
|
||||
// be allocated; these resources will be released via the memory pool.
|
||||
//
|
||||
// This should be improved in the future.
|
||||
func (mat *MatMxN) Transpose(dst *MatMxN) (t *MatMxN) {
|
||||
if mat == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dst == mat {
|
||||
dst = NewMatrix(mat.n, mat.m)
|
||||
|
||||
// Copy data to correct matrix,
|
||||
// delete temporary buffer,
|
||||
// and set the return value to the
|
||||
// correct one
|
||||
defer func() {
|
||||
copy(mat.dat, dst.dat)
|
||||
|
||||
mat.m, mat.n = mat.n, mat.m
|
||||
|
||||
dst.destroy()
|
||||
t = mat
|
||||
}()
|
||||
|
||||
return mat
|
||||
} else {
|
||||
dst = dst.Reshape(mat.n, mat.m)
|
||||
}
|
||||
|
||||
for r := 0; r < mat.m; r++ {
|
||||
for c := 0; c < mat.n; c++ {
|
||||
dst.dat[r*dst.m+c] = mat.dat[c*mat.m+r]
|
||||
}
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// Raw returns the raw slice backing this matrix
|
||||
func (mat *MatMxN) Raw() []float32 {
|
||||
if mat == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return mat.dat
|
||||
}
|
||||
|
||||
// NumRows returns the number of rows in this matrix
|
||||
func (mat *MatMxN) NumRows() int {
|
||||
return mat.m
|
||||
}
|
||||
|
||||
// NumCols returns the number of columns in this matrix
|
||||
func (mat *MatMxN) NumCols() int {
|
||||
return mat.n
|
||||
}
|
||||
|
||||
// NumRowCols returns the number of rows and columns in this matrix
|
||||
// as a single operation
|
||||
func (mat *MatMxN) NumRowCols() (rows, cols int) {
|
||||
return mat.m, mat.n
|
||||
}
|
||||
|
||||
// At returns the element at the given row and column.
|
||||
// This is garbage in/garbage out and does no bounds
|
||||
// checking. If the computation happens to lead to an invalid
|
||||
// element, it will be returned; or it may panic.
|
||||
func (mat *MatMxN) At(row, col int) float32 {
|
||||
return mat.dat[col*mat.m+row]
|
||||
}
|
||||
|
||||
// Set sets the element at the given row and column.
|
||||
// This is garbage in/garbage out and does no bounds
|
||||
// checking. If the computation happens to lead to an invalid
|
||||
// element, it will be set; or it may panic.
|
||||
func (mat *MatMxN) Set(row, col int, val float32) {
|
||||
mat.dat[col*mat.m+row] = val
|
||||
}
|
||||
|
||||
// Add is the arithemtic + operator defined on a MatMxN.
|
||||
func (mat *MatMxN) Add(dst *MatMxN, addend *MatMxN) *MatMxN {
|
||||
if mat == nil || addend == nil || mat.m != addend.m || mat.n != addend.n {
|
||||
return nil
|
||||
}
|
||||
|
||||
dst = dst.Reshape(mat.m, mat.n)
|
||||
|
||||
// No need to care about rows and columns
|
||||
// since it's element-wise anyway
|
||||
for i, el := range mat.dat {
|
||||
dst.dat[i] = el + addend.dat[i]
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// Sub is the arithemtic - operator defined on a MatMxN.
|
||||
func (mat *MatMxN) Sub(dst *MatMxN, subtrahend *MatMxN) *MatMxN {
|
||||
if mat == nil || subtrahend == nil || mat.m != subtrahend.m || mat.n != subtrahend.n {
|
||||
return nil
|
||||
}
|
||||
|
||||
dst = dst.Reshape(mat.m, mat.n)
|
||||
|
||||
// No need to care about rows and columns
|
||||
// since it's element-wise anyway
|
||||
for i, el := range mat.dat {
|
||||
dst.dat[i] = el - subtrahend.dat[i]
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// MulMxN performs matrix multiplication on MxN matrix mat and NxO matrix mul,
|
||||
// storing the result in dst. This returns dst, or nil if the operation is not
|
||||
// able to be performed.
|
||||
//
|
||||
// If mat == dst, or mul == dst a temporary matrix will be used.
|
||||
//
|
||||
// This uses the naive algorithm (though on smaller matrices, this can actually
|
||||
// be faster; about len(mat)+len(mul) < ~100)
|
||||
func (mat *MatMxN) MulMxN(dst *MatMxN, mul *MatMxN) *MatMxN {
|
||||
if mat == nil || mul == nil || mat.n != mul.m {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dst == mul {
|
||||
mul = NewMatrix(mul.m, mul.n)
|
||||
copy(mul.dat, dst.dat)
|
||||
|
||||
// If mat==dst==mul, we need to change
|
||||
// mat too or we have a bug
|
||||
if mat == dst {
|
||||
mat = mul
|
||||
}
|
||||
|
||||
defer mul.destroy()
|
||||
} else if dst == mat {
|
||||
mat = NewMatrix(mat.m, mat.n)
|
||||
copy(mat.dat, dst.dat)
|
||||
|
||||
defer mat.destroy()
|
||||
}
|
||||
|
||||
dst = dst.Reshape(mat.m, mul.n)
|
||||
for r1 := 0; r1 < mat.m; r1++ {
|
||||
for c2 := 0; c2 < mul.n; c2++ {
|
||||
|
||||
dst.dat[c2*mat.m+r1] = 0
|
||||
for i := 0; i < mat.n; i++ {
|
||||
dst.dat[c2*mat.m+r1] += mat.dat[i*mat.m+r1] * mul.dat[c2*mul.m+i]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// Mul performs a scalar multiplication between mat and some constant c,
|
||||
// storing the result in dst. Mat and dst can be equal. If dst is not the
|
||||
// correct size, a Reshape will occur.
|
||||
func (mat *MatMxN) Mul(dst *MatMxN, c float32) *MatMxN {
|
||||
if mat == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
dst = dst.Reshape(mat.m, mat.n)
|
||||
|
||||
for i, el := range mat.dat {
|
||||
dst.dat[i] = el * c
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// MulNx1 multiplies the matrix by a vector of size n. If mat or v is nil, this
|
||||
// returns nil. If the number of columns in mat does not match the Size of v,
|
||||
// this also returns nil.
|
||||
//
|
||||
// Dst will be resized if it's not big enough. If dst == v; a temporary vector
|
||||
// will be allocated and returned via the realloc callback when complete.
|
||||
func (mat *MatMxN) MulNx1(dst, v *VecN) *VecN {
|
||||
if mat == nil || v == nil || mat.n != len(v.vec) {
|
||||
return nil
|
||||
}
|
||||
if dst == v {
|
||||
v = NewVecN(len(v.vec))
|
||||
copy(v.vec, dst.vec)
|
||||
|
||||
defer v.destroy()
|
||||
}
|
||||
|
||||
dst = dst.Resize(mat.m)
|
||||
|
||||
for r := 0; r < mat.m; r++ {
|
||||
dst.vec[r] = 0
|
||||
|
||||
for c := 0; c < mat.n; c++ {
|
||||
dst.vec[r] += mat.At(r, c) * v.vec[c]
|
||||
}
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// ApproxEqual returns whether the two vectors are approximately equal (See
|
||||
// FloatEqual).
|
||||
func (mat *MatMxN) ApproxEqual(m2 *MatMxN) bool {
|
||||
if mat == m2 {
|
||||
return true
|
||||
}
|
||||
if mat.m != m2.m || mat.n != m2.n {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, el := range mat.dat {
|
||||
if !FloatEqual(el, m2.dat[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ApproxEqualThreshold returns whether the two vectors are approximately equal
|
||||
// to within the given threshold given by "epsilon" (See ApproxEqualThreshold).
|
||||
func (mat *MatMxN) ApproxEqualThreshold(m2 *MatMxN, epsilon float32) bool {
|
||||
if mat == m2 {
|
||||
return true
|
||||
}
|
||||
if mat.m != m2.m || mat.n != m2.n {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, el := range mat.dat {
|
||||
if !FloatEqualThreshold(el, m2.dat[i], epsilon) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ApproxEqualFunc returns whether the two vectors are approximately equal,
|
||||
// given a function which compares two scalar elements.
|
||||
func (mat *MatMxN) ApproxEqualFunc(m2 *MatMxN, comp func(float32, float32) bool) bool {
|
||||
if mat == m2 {
|
||||
return true
|
||||
}
|
||||
if mat.m != m2.m || mat.n != m2.n {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, el := range mat.dat {
|
||||
if !comp(el, m2.dat[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// InferMatrixError may be returned by InferMatrix.
|
||||
//
|
||||
// Make sure you're using a constant matrix such as Mat3 from within the same
|
||||
// package (meaning: mgl32.MatMxN can't handle a mgl64.Mat2x3).
|
||||
type InferMatrixError struct{}
|
||||
|
||||
func (me InferMatrixError) Error() string {
|
||||
return "could not infer matrix. Make sure you're using a constant matrix such as Mat3 from within the same package (meaning: mgl32.MatMxN can't handle a mgl64.Mat2x3)."
|
||||
}
|
||||
|
||||
// RectangularMatrixError is returned when a rectangular matrix was expected but
|
||||
// not given.
|
||||
type RectangularMatrixError struct{}
|
||||
|
||||
func (mse RectangularMatrixError) Error() string {
|
||||
return "the matrix was the wrong shape, needed a square matrix."
|
||||
}
|
||||
|
||||
// NilMatrixError is returned when an operand to a function was unexpectedly 'nil'.
|
||||
type NilMatrixError struct{}
|
||||
|
||||
func (me NilMatrixError) Error() string {
|
||||
return "the matrix is nil"
|
||||
}
|
||||
+2430
File diff suppressed because it is too large
Load Diff
+379
@@ -0,0 +1,379 @@
|
||||
// Copyright 2014 The go-gl/mathgl Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// <<.Comment>>
|
||||
// Edit <<.TemplateName>> and run "go generate" to make changes.
|
||||
|
||||
package mgl32
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
type Mat2 [2*2]float32
|
||||
type Mat2x3 [2*3]float32
|
||||
type Mat2x4 [2*4]float32
|
||||
type Mat3x2 [3*2]float32
|
||||
type Mat3 [3*3]float32
|
||||
type Mat3x4 [3*4]float32
|
||||
type Mat4x2 [4*2]float32
|
||||
type Mat4x3 [4*3]float32
|
||||
type Mat4 [4*4]float32
|
||||
|
||||
func (m Mat2) Mat3() Mat3 {
|
||||
col0, col1 := m.Cols()
|
||||
return Mat3FromCols(
|
||||
col0.Vec3(0),
|
||||
col1.Vec3(0),
|
||||
Vec3{0, 0, 1},
|
||||
)
|
||||
}
|
||||
|
||||
func (m Mat2) Mat4() Mat4 {
|
||||
col0, col1 := m.Cols()
|
||||
return Mat4FromCols(
|
||||
col0.Vec4(0, 0),
|
||||
col1.Vec4(0, 0),
|
||||
Vec4{0, 0, 1, 0},
|
||||
Vec4{0, 0, 0, 1},
|
||||
)
|
||||
}
|
||||
|
||||
func (m Mat3) Mat2() Mat2 {
|
||||
col0, col1, _ := m.Cols()
|
||||
return Mat2FromCols(
|
||||
col0.Vec2(),
|
||||
col1.Vec2(),
|
||||
)
|
||||
}
|
||||
|
||||
func (m Mat3) Mat4() Mat4 {
|
||||
col0, col1, col2 := m.Cols()
|
||||
return Mat4FromCols(
|
||||
col0.Vec4(0),
|
||||
col1.Vec4(0),
|
||||
col2.Vec4(0),
|
||||
Vec4{0, 0, 0, 1},
|
||||
)
|
||||
}
|
||||
|
||||
func (m Mat4) Mat2() Mat2 {
|
||||
col0, col1, _, _ := m.Cols()
|
||||
return Mat2FromCols(
|
||||
col0.Vec2(),
|
||||
col1.Vec2(),
|
||||
)
|
||||
}
|
||||
|
||||
func (m Mat4) Mat3() Mat3 {
|
||||
col0, col1, col2, _ := m.Cols()
|
||||
return Mat3FromCols(
|
||||
col0.Vec3(),
|
||||
col1.Vec3(),
|
||||
col2.Vec3(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
<</* Common functions for all matrices */>>
|
||||
<<range $m := enum 2 3 4>><<range $n := enum 2 3 4>>
|
||||
<<$type := typename $m $n>>
|
||||
|
||||
// SetCol sets a Column within the Matrix, so it mutates the calling matrix.
|
||||
func (m *<<$type>>) SetCol(col int, v <<typename $m 1>>) {
|
||||
<<range $i := iter 0 $m>><<sep "," $i>>m[col*<<$m>>+<<$i>>]<<end>> = <<repeat $m "v[%d]" ",">>
|
||||
}
|
||||
|
||||
// SetRow sets a Row within the Matrix, so it mutates the calling matrix.
|
||||
func (m *<<$type>>) SetRow(row int, v <<typename $n 1>>) {
|
||||
<<range $i := iter 0 $n>><<sep "," $i>>m[row+<<mul $m $i>>]<<end>> = <<repeat $n "v[%d]" ",">>
|
||||
}
|
||||
|
||||
<<if eq $m $n>>
|
||||
// Diag is a basic operation on a square matrix that simply
|
||||
// returns main diagonal (meaning all elements such that row==col).
|
||||
func (m <<$type>>) Diag() <<typename $m 1>> {
|
||||
return <<typename $m 1>>{<<range $i := iter 0 $m>>m[<<mul $i $m | add $i>>],<<end>>}
|
||||
}
|
||||
<<end>>
|
||||
|
||||
<<if eq $m $n>>
|
||||
// Ident<<$m>> returns the <<$m>>x<<$n>> identity matrix.
|
||||
// The identity matrix is a square matrix with the value 1 on its
|
||||
// diagonals. The characteristic property of the identity matrix is that
|
||||
// any matrix multiplied by it is itself. (MI = M; IN = N)
|
||||
func Ident<<$m>>() <<$type>> {
|
||||
return <<$type>>{<<range $i := matiter $m $n>><<if eq $i.M $i.N>>1<<else>>0<<end>>,<<end>>}
|
||||
}
|
||||
<<end>>
|
||||
|
||||
<<if eq $m $n>>
|
||||
// Diag<<$m>> creates a diagonal matrix from the entries of the input vector.
|
||||
// That is, for each pointer for row==col, vector[row] is the entry. Otherwise it's 0.
|
||||
//
|
||||
// Another way to think about it is that the identity is this function where the every vector element is 1.
|
||||
func Diag<<$m>>(v <<typename $m 1>>) <<$type>> {
|
||||
var m <<$type>>
|
||||
<<range $i := iter 0 $m>><<sep "," $i>>m[<<mul $i $m | add $i>>]<<end>> = <<repeat $m "v[%d]" ",">>
|
||||
return m
|
||||
}
|
||||
<<end>>
|
||||
|
||||
// <<$type>>FromRows builds a new matrix from row vectors.
|
||||
// The resulting matrix will still be in column major order, but this can be
|
||||
// good for hand-building matrices.
|
||||
|
||||
func <<$type>>FromRows(<<range $i := iter 0 $m>><<sep "," $i>>row<<$i>><<end>> <<typename $n 1>>) <<$type>> {
|
||||
return <<$type>>{<<range $i := matiter $m $n>>row<<$i.M>>[<<$i.N>>],<<end>>}
|
||||
}
|
||||
|
||||
// <<$type>>FromCols builds a new matrix from column vectors.
|
||||
func <<$type>>FromCols(<<repeat $n "col%d" ",">> <<typename $m 1>>) <<$type>> {
|
||||
return <<$type>>{<<range $i := matiter $m $n>>col<<$i.N>>[<<$i.M>>], <<end>>}
|
||||
}
|
||||
|
||||
// Add performs an element-wise addition of two matrices, this is
|
||||
// equivalent to iterating over every element of m1 and adding the corresponding value of m2.
|
||||
func (m1 <<$type>>) Add(m2 <<$type>>) <<$type>> {
|
||||
return <<$type>>{<< range $i := matiter $m $n>>m1[<<$i>>] + m2[<<$i>>], <<end>>}
|
||||
}
|
||||
|
||||
// Sub performs an element-wise subtraction of two matrices, this is
|
||||
// equivalent to iterating over every element of m1 and subtracting the corresponding value of m2.
|
||||
func (m1 <<$type>>) Sub(m2 <<$type>>) <<$type>> {
|
||||
return <<$type>>{<< range $i := matiter $m $n>>m1[<<$i>>] - m2[<<$i>>], <<end>>}
|
||||
}
|
||||
|
||||
// Mul performs a scalar multiplcation of the matrix. This is equivalent to iterating
|
||||
// over every element of the matrix and multiply it by c.
|
||||
func (m1 <<$type>>) Mul(c float32) <<$type>> {
|
||||
return <<$type>>{<< range $i := matiter $m $n>>m1[<<$i>>] * c, <<end>>}
|
||||
}
|
||||
|
||||
<<range $o := enum 1 2 3 4>>
|
||||
// Mul<<$n>><<if ne $n $o>>x<<$o>><<end>> performs a "matrix product" between this matrix
|
||||
// and another of the given dimension. For any two matrices of dimensionality
|
||||
// MxN and NxO, the result will be MxO. For instance, Mat4 multiplied using
|
||||
// Mul4x2 will result in a Mat4x2.
|
||||
func (m1 <<$type>>) Mul<<$n>><<if ne $n $o>>x<<$o>><<end>>(m2 <<typename $n $o>>) <<typename $m $o>> {
|
||||
return <<typename $m $o>>{<<range $i := matiter $m $o>>
|
||||
<<range $k := iter 0 $n>><<sep "+" $k>>m1[<<mul $k $m | add $i.M>>]*m2[<<mul $i.N $n| add $k>>]<<end>>,<<end>>
|
||||
}
|
||||
}
|
||||
<<end>>
|
||||
|
||||
// Transpose produces the transpose of this matrix. For any MxN matrix
|
||||
// the transpose is an NxM matrix with the rows swapped with the columns. For instance
|
||||
// the transpose of the Mat3x2 is a Mat2x3 like so:
|
||||
//
|
||||
// [[a b]] [[a c e]]
|
||||
// [[c d]] = [[b d f]]
|
||||
// [[e f]]
|
||||
func (m1 <<$type>>) Transpose() <<typename $n $m>> {
|
||||
return <<typename $n $m>>{<<range $i := matiter $n $m>>m1[<<mul $m $i.M | add $i.N>>], <<end>>}
|
||||
}
|
||||
|
||||
<<if eq $m $n>>
|
||||
// Det returns the determinant of a matrix. It is a measure of a square matrix's
|
||||
// singularity and invertability, among other things. In this library, the
|
||||
// determinant is hard coded based on pre-computed cofactor expansion, and uses
|
||||
// no loops. Of course, the addition and multiplication must still be done.
|
||||
func (m <<$type>>) Det() float32 {
|
||||
<<if eq $m 2 ->>
|
||||
return m[0]*m[3] - m[1]*m[2]
|
||||
<<else if eq $m 3 ->>
|
||||
return m[0]*m[4]*m[8] + m[3]*m[7]*m[2] + m[6]*m[1]*m[5] - m[6]*m[4]*m[2] - m[3]*m[1]*m[8] - m[0]*m[7]*m[5]
|
||||
<<else if eq $m 4 ->>
|
||||
return m[0]*m[5]*m[10]*m[15] - m[0]*m[5]*m[11]*m[14] - m[0]*m[6]*m[9]*m[15] + m[0]*m[6]*m[11]*m[13] + m[0]*m[7]*m[9]*m[14] - m[0]*m[7]*m[10]*m[13] - m[1]*m[4]*m[10]*m[15] + m[1]*m[4]*m[11]*m[14] + m[1]*m[6]*m[8]*m[15] - m[1]*m[6]*m[11]*m[12] - m[1]*m[7]*m[8]*m[14] + m[1]*m[7]*m[10]*m[12] + m[2]*m[4]*m[9]*m[15] - m[2]*m[4]*m[11]*m[13] - m[2]*m[5]*m[8]*m[15] + m[2]*m[5]*m[11]*m[12] + m[2]*m[7]*m[8]*m[13] - m[2]*m[7]*m[9]*m[12] - m[3]*m[4]*m[9]*m[14] + m[3]*m[4]*m[10]*m[13] + m[3]*m[5]*m[8]*m[14] - m[3]*m[5]*m[10]*m[12] - m[3]*m[6]*m[8]*m[13] + m[3]*m[6]*m[9]*m[12]
|
||||
<<end ->>
|
||||
}
|
||||
<<end>>
|
||||
|
||||
<<if eq $m $n>>
|
||||
// Inv computes the inverse of a square matrix. An inverse is a square matrix such that when multiplied by the
|
||||
// original, yields the identity.
|
||||
//
|
||||
// M_inv * M = M * M_inv = I
|
||||
//
|
||||
// In this library, the math is precomputed, and uses no loops, though the multiplications, additions, determinant calculation, and scaling
|
||||
// are still done. This can still be (relatively) expensive for a 4x4.
|
||||
//
|
||||
// This function checks the determinant to see if the matrix is invertible.
|
||||
// If the determinant is 0.0, this function returns the zero matrix. However, due to floating point errors, it is
|
||||
// entirely plausible to get a false positive or negative.
|
||||
// In the future, an alternate function may be written which takes in a pre-computed determinant.
|
||||
func (m <<$type>>) Inv() <<$type>> {
|
||||
det := m.Det()
|
||||
if FloatEqual(det, float32(0.0)) {
|
||||
return <<$type>>{}
|
||||
}
|
||||
<<if eq $m 2>>
|
||||
retMat := Mat2{m[3], -m[1], -m[2], m[0]}
|
||||
<<else if eq $m 3>>
|
||||
retMat := Mat3{
|
||||
m[4]*m[8] - m[5]*m[7],
|
||||
m[2]*m[7] - m[1]*m[8],
|
||||
m[1]*m[5] - m[2]*m[4],
|
||||
m[5]*m[6] - m[3]*m[8],
|
||||
m[0]*m[8] - m[2]*m[6],
|
||||
m[2]*m[3] - m[0]*m[5],
|
||||
m[3]*m[7] - m[4]*m[6],
|
||||
m[1]*m[6] - m[0]*m[7],
|
||||
m[0]*m[4] - m[1]*m[3],
|
||||
}
|
||||
<<else if eq $m 4>>
|
||||
retMat := Mat4{
|
||||
-m[7]*m[10]*m[13] + m[6]*m[11]*m[13] + m[7]*m[9]*m[14] - m[5]*m[11]*m[14] - m[6]*m[9]*m[15] + m[5]*m[10]*m[15],
|
||||
m[3]*m[10]*m[13] - m[2]*m[11]*m[13] - m[3]*m[9]*m[14] + m[1]*m[11]*m[14] + m[2]*m[9]*m[15] - m[1]*m[10]*m[15],
|
||||
-m[3]*m[6]*m[13] + m[2]*m[7]*m[13] + m[3]*m[5]*m[14] - m[1]*m[7]*m[14] - m[2]*m[5]*m[15] + m[1]*m[6]*m[15],
|
||||
m[3]*m[6]*m[9] - m[2]*m[7]*m[9] - m[3]*m[5]*m[10] + m[1]*m[7]*m[10] + m[2]*m[5]*m[11] - m[1]*m[6]*m[11],
|
||||
m[7]*m[10]*m[12] - m[6]*m[11]*m[12] - m[7]*m[8]*m[14] + m[4]*m[11]*m[14] + m[6]*m[8]*m[15] - m[4]*m[10]*m[15],
|
||||
-m[3]*m[10]*m[12] + m[2]*m[11]*m[12] + m[3]*m[8]*m[14] - m[0]*m[11]*m[14] - m[2]*m[8]*m[15] + m[0]*m[10]*m[15],
|
||||
m[3]*m[6]*m[12] - m[2]*m[7]*m[12] - m[3]*m[4]*m[14] + m[0]*m[7]*m[14] + m[2]*m[4]*m[15] - m[0]*m[6]*m[15],
|
||||
-m[3]*m[6]*m[8] + m[2]*m[7]*m[8] + m[3]*m[4]*m[10] - m[0]*m[7]*m[10] - m[2]*m[4]*m[11] + m[0]*m[6]*m[11],
|
||||
-m[7]*m[9]*m[12] + m[5]*m[11]*m[12] + m[7]*m[8]*m[13] - m[4]*m[11]*m[13] - m[5]*m[8]*m[15] + m[4]*m[9]*m[15],
|
||||
m[3]*m[9]*m[12] - m[1]*m[11]*m[12] - m[3]*m[8]*m[13] + m[0]*m[11]*m[13] + m[1]*m[8]*m[15] - m[0]*m[9]*m[15],
|
||||
-m[3]*m[5]*m[12] + m[1]*m[7]*m[12] + m[3]*m[4]*m[13] - m[0]*m[7]*m[13] - m[1]*m[4]*m[15] + m[0]*m[5]*m[15],
|
||||
m[3]*m[5]*m[8] - m[1]*m[7]*m[8] - m[3]*m[4]*m[9] + m[0]*m[7]*m[9] + m[1]*m[4]*m[11] - m[0]*m[5]*m[11],
|
||||
m[6]*m[9]*m[12] - m[5]*m[10]*m[12] - m[6]*m[8]*m[13] + m[4]*m[10]*m[13] + m[5]*m[8]*m[14] - m[4]*m[9]*m[14],
|
||||
-m[2]*m[9]*m[12] + m[1]*m[10]*m[12] + m[2]*m[8]*m[13] - m[0]*m[10]*m[13] - m[1]*m[8]*m[14] + m[0]*m[9]*m[14],
|
||||
m[2]*m[5]*m[12] - m[1]*m[6]*m[12] - m[2]*m[4]*m[13] + m[0]*m[6]*m[13] + m[1]*m[4]*m[14] - m[0]*m[5]*m[14],
|
||||
-m[2]*m[5]*m[8] + m[1]*m[6]*m[8] + m[2]*m[4]*m[9] - m[0]*m[6]*m[9] - m[1]*m[4]*m[10] + m[0]*m[5]*m[10],
|
||||
}
|
||||
<<end>>
|
||||
return retMat.Mul(1 / det)
|
||||
}
|
||||
<<end>>
|
||||
|
||||
// ApproxEqual performs an element-wise approximate equality test between two matrices,
|
||||
// as if FloatEqual had been used.
|
||||
func (m1 <<$type>>) ApproxEqual(m2 <<$type>>) bool {
|
||||
for i := range m1 {
|
||||
if !FloatEqual(m1[i], m2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ApproxEqualThreshold performs an element-wise approximate equality test between two matrices
|
||||
// with a given epsilon threshold, as if FloatEqualThreshold had been used.
|
||||
func (m1 <<$type>>) ApproxEqualThreshold(m2 <<$type>>, threshold float32) bool {
|
||||
for i := range m1 {
|
||||
if !FloatEqualThreshold(m1[i], m2[i], threshold) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ApproxFuncEqual performs an element-wise approximate equality test between two matrices
|
||||
// with a given equality functions, intended to be used with FloatEqualFunc; although and comparison
|
||||
// function may be used in practice.
|
||||
func (m1 <<$type>>) ApproxFuncEqual(m2 <<$type>>, eq func(float32, float32) bool) bool {
|
||||
for i := range m1 {
|
||||
if !eq(m1[i], m2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// NumRows returns the number of rows in this matrix
|
||||
func (m <<$type>>) NumRows() int {
|
||||
return <<$m>>
|
||||
}
|
||||
|
||||
// NumCols returns the number of columns in this matrix
|
||||
func (m <<$type>>) NumCols() int {
|
||||
return <<$n>>
|
||||
}
|
||||
|
||||
// At returns the matrix element at the given row and column.
|
||||
// This is equivalent to mat[col * numRow + row] where numRow is constant
|
||||
// (E.G. for a Mat3x2 it's equal to 3)
|
||||
//
|
||||
// This method is garbage-in garbage-out. For instance, on a Mat4 asking for
|
||||
// At(5,0) will work just like At(1,1). Or it may panic if it's out of bounds.
|
||||
func (m <<$type>>) At(row, col int) float32 {
|
||||
return m[col*<<$m>>+row]
|
||||
}
|
||||
|
||||
// Set sets the corresponding matrix element at the given row and column.
|
||||
// This has a pointer receiver because it mutates the matrix.
|
||||
//
|
||||
// This method is garbage-in garbage-out. For instance, on a Mat4 asking for
|
||||
// Set(5,0,val) will work just like Set(1,1,val). Or it may panic if it's out of bounds.
|
||||
func (m *<<$type>>) Set(row, col int, value float32) {
|
||||
m[col*<<$m>>+row] = value
|
||||
}
|
||||
|
||||
// Index returns the index of the given row and column, to be used with direct
|
||||
// access. E.G. Index(0,0) = 0.
|
||||
//
|
||||
// This is a garbage-in garbage-out method. For instance, on a Mat4 asking for the index of
|
||||
// (5,0) will work the same as asking for (1,1). Or it may give you a value that will cause
|
||||
// a panic if you try to access the array with it if it's truly out of bounds.
|
||||
func (m <<$type>>) Index(row, col int) int {
|
||||
return col*<<$m>> + row
|
||||
}
|
||||
|
||||
// Row returns a vector representing the corresponding row (starting at row 0).
|
||||
// This package makes no distinction between row and column vectors, so it
|
||||
// will be a normal VecM for a MxN matrix.
|
||||
func (m <<$type>>) Row(row int) <<typename $n 1>> {
|
||||
return <<typename $n 1>>{<<range $i := iter 0 $n>>m[row+<<mul $m $i>>],<<end>>}
|
||||
}
|
||||
|
||||
// Rows decomposes a matrix into its corresponding row vectors.
|
||||
// This is equivalent to calling mat.Row for each row.
|
||||
func (m <<$type>>) Rows() (<<repeat $m "row%d" ",">> <<typename $n 1>>) {
|
||||
return <<repeat $m "m.Row(%d)" ",">>
|
||||
}
|
||||
|
||||
// Col returns a vector representing the corresponding column (starting at col 0).
|
||||
// This package makes no distinction between row and column vectors, so it
|
||||
// will be a normal VecN for a MxN matrix.
|
||||
func (m <<$type>>) Col(col int) <<typename $m 1>> {
|
||||
return <<typename $m 1>>{<<range $i := iter 0 $m>>m[col*<<$m>>+<<$i>>],<<end>>}
|
||||
}
|
||||
|
||||
// Cols decomposes a matrix into its corresponding column vectors.
|
||||
// This is equivalent to calling mat.Col for each column.
|
||||
func (m <<$type>>) Cols() (<<repeat $n "col%d" ",">> <<typename $m 1>>) {
|
||||
return <<repeat $n "m.Col(%d)" ",">>
|
||||
}
|
||||
|
||||
<<if eq $m $n>>
|
||||
// Trace is a basic operation on a square matrix that simply
|
||||
// sums up all elements on the main diagonal (meaning all elements such that row==col).
|
||||
func (m <<$type>>) Trace() float32 {
|
||||
return <<range $i := iter 0 $m>><<sep "+" $i>> m[<<mul $i $m | add $i>>]<<end>>
|
||||
}
|
||||
<<end>>
|
||||
|
||||
// Abs returns the element-wise absolute value of this matrix
|
||||
func (m <<$type>>) Abs() <<$type>> {
|
||||
return <<$type>>{<<repeat (mul $m $n) "Abs(m[%d])" ",">>}
|
||||
}
|
||||
|
||||
// Pretty prints the matrix
|
||||
func (m <<$type>>) String() string {
|
||||
buf := new(bytes.Buffer)
|
||||
w := tabwriter.NewWriter(buf, 4, 4, 1, ' ', tabwriter.AlignRight)
|
||||
for i := 0; i < <<$m>>; i++ {
|
||||
for _, col := range m.Row(i) {
|
||||
fmt.Fprintf(w, "%f\t", col)
|
||||
}
|
||||
|
||||
fmt.Fprintln(w, "")
|
||||
}
|
||||
w.Flush()
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
<<end>><<end>> <</* range $m range $n */>>
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
// Copyright 2014 The go-gl Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mgl32
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
slicePools []*sync.Pool
|
||||
listLock sync.RWMutex
|
||||
)
|
||||
|
||||
var shouldPool = true
|
||||
|
||||
func DisableMemoryPooling() {
|
||||
shouldPool = false
|
||||
}
|
||||
|
||||
// Returns the given memory pool. If the pool doesn't exist, it will
|
||||
// create all pools up to element i. The number "i" corresponds to "p"
|
||||
// in most other comments. That is, it's Ceil(log_2(whatever)). So i=0
|
||||
// means you'll get the pool for slices of size 1, i=1 for size 2, i=2 for size 4,
|
||||
// and so on.
|
||||
//
|
||||
// This is concurrency safe and uses an RWMutex to protect the list expansion.
|
||||
func getPool(i int) *sync.Pool {
|
||||
listLock.RLock()
|
||||
if i >= len(slicePools) {
|
||||
|
||||
// Promote to a write lock because we now
|
||||
// need to mutate the pool
|
||||
listLock.RUnlock()
|
||||
listLock.Lock()
|
||||
defer listLock.Unlock()
|
||||
|
||||
for n := i - len(slicePools); n >= 0; n-- {
|
||||
newFunc := genPoolNew(1 << uint(len(slicePools)))
|
||||
slicePools = append(slicePools, &sync.Pool{New: newFunc})
|
||||
}
|
||||
} else {
|
||||
defer listLock.RUnlock()
|
||||
}
|
||||
|
||||
return slicePools[i]
|
||||
}
|
||||
|
||||
func genPoolNew(i int) func() interface{} {
|
||||
return func() interface{} {
|
||||
return make([]float32, 0, i)
|
||||
}
|
||||
}
|
||||
|
||||
// Grabs a slice from the memory pool, such that its cap
|
||||
// is 2^p where p is Ceil(log_2(size)). It will be downsliced
|
||||
// such that the len is size.
|
||||
func grabFromPool(size int) []float32 {
|
||||
pool, exact := binLog(size)
|
||||
|
||||
// Tried to grab something of size
|
||||
// zero or less
|
||||
if pool == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the log is not exact, we
|
||||
// need to "overallocate" so we have
|
||||
// log+1
|
||||
if !exact {
|
||||
pool++
|
||||
}
|
||||
|
||||
slice := getPool(pool).Get().([]float32)
|
||||
slice = slice[:size]
|
||||
return slice
|
||||
}
|
||||
|
||||
// Returns a slice to the appropriate pool. If the slice does not have a cap that's precisely
|
||||
// a power of 2, this will panic.
|
||||
func returnToPool(slice []float32) {
|
||||
if cap(slice) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
pool, exact := binLog(cap(slice))
|
||||
|
||||
if !exact {
|
||||
panic("attempt to pool slice with non-exact cap. If you're a user, please file an issue with github.com/go-gl/mathgl about this bug. This should never happen.")
|
||||
}
|
||||
|
||||
getPool(pool).Put(slice)
|
||||
}
|
||||
|
||||
// This returns the integer base 2 log of the value
|
||||
// and whether the log is exact or rounded down.
|
||||
//
|
||||
// This is only for positive integers.
|
||||
//
|
||||
// There are faster ways to do this, I'm open to suggestions. Most rely on knowing system endianness
|
||||
// which Go makes hard to do. I'm hesistant to use float conversions and the math package because of off-by-one errors.
|
||||
func binLog(val int) (int, bool) {
|
||||
if val <= 0 {
|
||||
return -1, false
|
||||
}
|
||||
|
||||
exact := true
|
||||
l := 0
|
||||
for ; val > 1; val = val >> 1 {
|
||||
// If the current lsb is 1 and the number
|
||||
// is not equal to 1, this is not an exact
|
||||
// log, but rather a rounding of it
|
||||
if val&1 != 0 {
|
||||
exact = false
|
||||
}
|
||||
l++
|
||||
}
|
||||
|
||||
return l, exact
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
// Copyright 2014 The go-gl Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mgl32
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Ortho generates an Ortho Matrix.
|
||||
func Ortho(left, right, bottom, top, near, far float32) Mat4 {
|
||||
rml, tmb, fmn := (right - left), (top - bottom), (far - near)
|
||||
|
||||
return Mat4{float32(2. / rml), 0, 0, 0, 0, float32(2. / tmb), 0, 0, 0, 0, float32(-2. / fmn), 0, float32(-(right + left) / rml), float32(-(top + bottom) / tmb), float32(-(far + near) / fmn), 1}
|
||||
}
|
||||
|
||||
// Ortho2D is equivalent to Ortho with the near and far planes being -1 and 1,
|
||||
// respectively.
|
||||
func Ortho2D(left, right, bottom, top float32) Mat4 {
|
||||
return Ortho(left, right, bottom, top, -1, 1)
|
||||
}
|
||||
|
||||
// Perspective generates a Perspective Matrix.
|
||||
func Perspective(fovy, aspect, near, far float32) Mat4 {
|
||||
// fovy = (fovy * math.Pi) / 180.0 // convert from degrees to radians
|
||||
nmf, f := near-far, float32(1./math.Tan(float64(fovy)/2.0))
|
||||
|
||||
return Mat4{float32(f / aspect), 0, 0, 0, 0, float32(f), 0, 0, 0, 0, float32((near + far) / nmf), -1, 0, 0, float32((2. * far * near) / nmf), 0}
|
||||
}
|
||||
|
||||
// Frustum generates a Frustum Matrix.
|
||||
func Frustum(left, right, bottom, top, near, far float32) Mat4 {
|
||||
rml, tmb, fmn := (right - left), (top - bottom), (far - near)
|
||||
A, B, C, D := (right+left)/rml, (top+bottom)/tmb, -(far+near)/fmn, -(2*far*near)/fmn
|
||||
|
||||
return Mat4{float32((2. * near) / rml), 0, 0, 0, 0, float32((2. * near) / tmb), 0, 0, float32(A), float32(B), float32(C), -1, 0, 0, float32(D), 0}
|
||||
}
|
||||
|
||||
// LookAt generates a transform matrix from world space to the given eye space.
|
||||
func LookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ float32) Mat4 {
|
||||
return LookAtV(Vec3{eyeX, eyeY, eyeZ}, Vec3{centerX, centerY, centerZ}, Vec3{upX, upY, upZ})
|
||||
}
|
||||
|
||||
// LookAtV generates a transform matrix from world space into the specific eye
|
||||
// space.
|
||||
func LookAtV(eye, center, up Vec3) Mat4 {
|
||||
f := center.Sub(eye).Normalize()
|
||||
s := f.Cross(up).Normalize()
|
||||
u := s.Cross(f)
|
||||
|
||||
M := Mat4{
|
||||
s[0], u[0], -f[0], 0,
|
||||
s[1], u[1], -f[1], 0,
|
||||
s[2], u[2], -f[2], 0,
|
||||
0, 0, 0, 1,
|
||||
}
|
||||
|
||||
return M.Mul4(Translate3D(float32(-eye[0]), float32(-eye[1]), float32(-eye[2])))
|
||||
}
|
||||
|
||||
// Project transforms a set of coordinates from object space (in obj) to window
|
||||
// coordinates (with depth).
|
||||
//
|
||||
// Window coordinates are continuous, not discrete (well, as continuous as an
|
||||
// IEEE Floating Point can be), so you won't get exact pixel locations without
|
||||
// rounding or similar
|
||||
func Project(obj Vec3, modelview, projection Mat4, initialX, initialY, width, height int) (win Vec3) {
|
||||
obj4 := obj.Vec4(1)
|
||||
|
||||
vpp := projection.Mul4(modelview).Mul4x1(obj4)
|
||||
vpp = vpp.Mul(1 / vpp.W())
|
||||
win[0] = float32(initialX) + (float32(width)*(vpp[0]+1))/2
|
||||
win[1] = float32(initialY) + (float32(height)*(vpp[1]+1))/2
|
||||
win[2] = (vpp[2] + 1) / 2
|
||||
|
||||
return win
|
||||
}
|
||||
|
||||
// UnProject transforms a set of window coordinates to object space. If your MVP
|
||||
// (projection.Mul(modelview) matrix is not invertible, this will return an
|
||||
// error.
|
||||
//
|
||||
// Note that the projection may not be perfect if you use strict pixel locations
|
||||
// rather than the exact values given by Projectf. (It's still unlikely to be
|
||||
// perfect due to precision errors, but it will be closer)
|
||||
func UnProject(win Vec3, modelview, projection Mat4, initialX, initialY, width, height int) (obj Vec3, err error) {
|
||||
inv := projection.Mul4(modelview).Inv()
|
||||
var blank Mat4
|
||||
if inv == blank {
|
||||
return Vec3{}, errors.New("Could not find matrix inverse (projection times modelview is probably non-singular)")
|
||||
}
|
||||
|
||||
obj4 := inv.Mul4x1(Vec4{
|
||||
(2 * (win[0] - float32(initialX)) / float32(width)) - 1,
|
||||
(2 * (win[1] - float32(initialY)) / float32(height)) - 1,
|
||||
2*win[2] - 1,
|
||||
1.0,
|
||||
})
|
||||
obj = obj4.Vec3()
|
||||
|
||||
//if obj4[3] > MinValue {}
|
||||
obj[0] /= obj4[3]
|
||||
obj[1] /= obj4[3]
|
||||
obj[2] /= obj4[3]
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
+469
@@ -0,0 +1,469 @@
|
||||
// Copyright 2014 The go-gl Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mgl32
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// RotationOrder is the order in which rotations will be transformed for the
|
||||
// purposes of AnglesToQuat.
|
||||
type RotationOrder int
|
||||
|
||||
// The RotationOrder constants represent a series of rotations along the given
|
||||
// axes for the use of AnglesToQuat.
|
||||
const (
|
||||
XYX RotationOrder = iota
|
||||
XYZ
|
||||
XZX
|
||||
XZY
|
||||
YXY
|
||||
YXZ
|
||||
YZY
|
||||
YZX
|
||||
ZYZ
|
||||
ZYX
|
||||
ZXZ
|
||||
ZXY
|
||||
)
|
||||
|
||||
// Quat represents a Quaternion, which is an extension of the imaginary numbers;
|
||||
// there's all sorts of interesting theory behind it. In 3D graphics we mostly
|
||||
// use it as a cheap way of representing rotation since quaternions are cheaper
|
||||
// to multiply by, and easier to interpolate than matrices.
|
||||
//
|
||||
// A Quaternion has two parts: W, the so-called scalar component, and "V", the
|
||||
// vector component. The vector component is considered to be the part in 3D
|
||||
// space, while W (loosely interpreted) is its 4D coordinate.
|
||||
type Quat struct {
|
||||
W float32
|
||||
V Vec3
|
||||
}
|
||||
|
||||
// QuatIdent returns the quaternion identity: W=1; V=(0,0,0).
|
||||
//
|
||||
// As with all identities, multiplying any quaternion by this will yield the same
|
||||
// quaternion you started with.
|
||||
func QuatIdent() Quat {
|
||||
return Quat{1., Vec3{0, 0, 0}}
|
||||
}
|
||||
|
||||
// QuatRotate creates an angle from an axis and an angle relative to that axis.
|
||||
//
|
||||
// This is cheaper than HomogRotate3D.
|
||||
func QuatRotate(angle float32, axis Vec3) Quat {
|
||||
// angle = (float32(math.Pi) * angle) / 180.0
|
||||
|
||||
sn, cs := math.Sincos(float64(angle / 2))
|
||||
s, c := float32(sn), float32(cs)
|
||||
|
||||
return Quat{c, axis.Mul(s)}
|
||||
}
|
||||
|
||||
// X is a convenient alias for q.V[0]
|
||||
func (q Quat) X() float32 {
|
||||
return q.V[0]
|
||||
}
|
||||
|
||||
// Y is a convenient alias for q.V[1]
|
||||
func (q Quat) Y() float32 {
|
||||
return q.V[1]
|
||||
}
|
||||
|
||||
// Z is a convenient alias for q.V[2]
|
||||
func (q Quat) Z() float32 {
|
||||
return q.V[2]
|
||||
}
|
||||
|
||||
// Add adds two quaternions. It's no more complicated than
|
||||
// adding their W and V components.
|
||||
func (q1 Quat) Add(q2 Quat) Quat {
|
||||
return Quat{q1.W + q2.W, q1.V.Add(q2.V)}
|
||||
}
|
||||
|
||||
// Sub subtracts two quaternions. It's no more complicated than
|
||||
// subtracting their W and V components.
|
||||
func (q1 Quat) Sub(q2 Quat) Quat {
|
||||
return Quat{q1.W - q2.W, q1.V.Sub(q2.V)}
|
||||
}
|
||||
|
||||
// Mul multiplies two quaternions. This can be seen as a rotation. Note that
|
||||
// Multiplication is NOT commutative, meaning q1.Mul(q2) does not necessarily
|
||||
// equal q2.Mul(q1).
|
||||
func (q1 Quat) Mul(q2 Quat) Quat {
|
||||
return Quat{q1.W*q2.W - q1.V.Dot(q2.V), q1.V.Cross(q2.V).Add(q2.V.Mul(q1.W)).Add(q1.V.Mul(q2.W))}
|
||||
}
|
||||
|
||||
// Scale every element of the quaternion by some constant factor.
|
||||
func (q1 Quat) Scale(c float32) Quat {
|
||||
return Quat{q1.W * c, Vec3{q1.V[0] * c, q1.V[1] * c, q1.V[2] * c}}
|
||||
}
|
||||
|
||||
// Conjugate returns the conjugate of a quaternion. Equivalent to
|
||||
// Quat{q1.W, q1.V.Mul(-1)}.
|
||||
func (q1 Quat) Conjugate() Quat {
|
||||
return Quat{q1.W, q1.V.Mul(-1)}
|
||||
}
|
||||
|
||||
// Len gives the Length of the quaternion, also known as its Norm. This is the
|
||||
// same thing as the Len of a Vec4.
|
||||
func (q1 Quat) Len() float32 {
|
||||
return float32(math.Sqrt(float64(q1.W*q1.W + q1.V[0]*q1.V[0] + q1.V[1]*q1.V[1] + q1.V[2]*q1.V[2])))
|
||||
}
|
||||
|
||||
// Norm is an alias for Len() since both are very common terms.
|
||||
func (q1 Quat) Norm() float32 {
|
||||
return q1.Len()
|
||||
}
|
||||
|
||||
// Normalize the quaternion, returning its versor (unit quaternion).
|
||||
//
|
||||
// This is the same as normalizing it as a Vec4.
|
||||
func (q1 Quat) Normalize() Quat {
|
||||
length := q1.Len()
|
||||
|
||||
if FloatEqual(1, length) {
|
||||
return q1
|
||||
}
|
||||
if length == 0 {
|
||||
return QuatIdent()
|
||||
}
|
||||
if length == InfPos {
|
||||
length = MaxValue
|
||||
}
|
||||
|
||||
return Quat{q1.W * 1 / length, q1.V.Mul(1 / length)}
|
||||
}
|
||||
|
||||
// Inverse of a quaternion. The inverse is equivalent
|
||||
// to the conjugate divided by the square of the length.
|
||||
//
|
||||
// This method computes the square norm by directly adding the sum
|
||||
// of the squares of all terms instead of actually squaring q1.Len(),
|
||||
// both for performance and precision.
|
||||
func (q1 Quat) Inverse() Quat {
|
||||
return q1.Conjugate().Scale(1 / q1.Dot(q1))
|
||||
}
|
||||
|
||||
// Rotate a vector by the rotation this quaternion represents.
|
||||
// This will result in a 3D vector. Strictly speaking, this is
|
||||
// equivalent to q1.v.q* where the "."" is quaternion multiplication and v is interpreted
|
||||
// as a quaternion with W 0 and V v. In code:
|
||||
// q1.Mul(Quat{0,v}).Mul(q1.Conjugate()), and
|
||||
// then retrieving the imaginary (vector) part.
|
||||
//
|
||||
// In practice, we hand-compute this in the general case and simplify
|
||||
// to save a few operations.
|
||||
func (q1 Quat) Rotate(v Vec3) Vec3 {
|
||||
cross := q1.V.Cross(v)
|
||||
// v + 2q_w * (q_v x v) + 2q_v x (q_v x v)
|
||||
return v.Add(cross.Mul(2 * q1.W)).Add(q1.V.Mul(2).Cross(cross))
|
||||
}
|
||||
|
||||
// Mat4 returns the homogeneous 3D rotation matrix corresponding to the
|
||||
// quaternion.
|
||||
func (q1 Quat) Mat4() Mat4 {
|
||||
w, x, y, z := q1.W, q1.V[0], q1.V[1], q1.V[2]
|
||||
return Mat4{
|
||||
1 - 2*y*y - 2*z*z, 2*x*y + 2*w*z, 2*x*z - 2*w*y, 0,
|
||||
2*x*y - 2*w*z, 1 - 2*x*x - 2*z*z, 2*y*z + 2*w*x, 0,
|
||||
2*x*z + 2*w*y, 2*y*z - 2*w*x, 1 - 2*x*x - 2*y*y, 0,
|
||||
0, 0, 0, 1,
|
||||
}
|
||||
}
|
||||
|
||||
// Dot product between two quaternions, equivalent to if this was a Vec4.
|
||||
func (q1 Quat) Dot(q2 Quat) float32 {
|
||||
return q1.W*q2.W + q1.V[0]*q2.V[0] + q1.V[1]*q2.V[1] + q1.V[2]*q2.V[2]
|
||||
}
|
||||
|
||||
// ApproxEqual returns whether the quaternions are approximately equal, as if
|
||||
// FloatEqual was called on each matching element
|
||||
func (q1 Quat) ApproxEqual(q2 Quat) bool {
|
||||
return FloatEqual(q1.W, q2.W) && q1.V.ApproxEqual(q2.V)
|
||||
}
|
||||
|
||||
// ApproxEqualThreshold returns whether the quaternions are approximately equal with a given tolerence, as if
|
||||
// FloatEqualThreshold was called on each matching element with the given epsilon
|
||||
func (q1 Quat) ApproxEqualThreshold(q2 Quat, epsilon float32) bool {
|
||||
return FloatEqualThreshold(q1.W, q2.W, epsilon) && q1.V.ApproxEqualThreshold(q2.V, epsilon)
|
||||
}
|
||||
|
||||
// ApproxEqualFunc returns whether the quaternions are approximately equal using the given comparison function, as if
|
||||
// the function had been called on each individual element
|
||||
func (q1 Quat) ApproxEqualFunc(q2 Quat, f func(float32, float32) bool) bool {
|
||||
return f(q1.W, q2.W) && q1.V.ApproxFuncEqual(q2.V, f)
|
||||
}
|
||||
|
||||
// OrientationEqual returns whether the quaternions represents the same orientation
|
||||
//
|
||||
// Different values can represent the same orientation (q == -q) because quaternions avoid singularities
|
||||
// and discontinuities involved with rotation in 3 dimensions by adding extra dimensions
|
||||
func (q1 Quat) OrientationEqual(q2 Quat) bool {
|
||||
return q1.OrientationEqualThreshold(q2, Epsilon)
|
||||
}
|
||||
|
||||
// OrientationEqualThreshold returns whether the quaternions represents the same orientation with a given tolerence
|
||||
func (q1 Quat) OrientationEqualThreshold(q2 Quat, epsilon float32) bool {
|
||||
return Abs(q1.Normalize().Dot(q2.Normalize())) > 1-epsilon
|
||||
}
|
||||
|
||||
// QuatSlerp is *S*pherical *L*inear Int*erp*olation, a method of interpolating
|
||||
// between two quaternions. This always takes the straightest and shortest path on the sphere between
|
||||
// the two quaternions, and maintains constant velocity.
|
||||
//
|
||||
// However, it's expensive and QuatSlerp(q1,q2) is not the same as QuatSlerp(q2,q1)
|
||||
func QuatSlerp(q1, q2 Quat, amount float32) Quat {
|
||||
q1, q2 = q1.Normalize(), q2.Normalize()
|
||||
dot := q1.Dot(q2)
|
||||
|
||||
// Make sure we take the shortest path in case dot product is negative.
|
||||
if dot < 0.0 {
|
||||
q2 = q2.Scale(-1)
|
||||
dot = -dot
|
||||
}
|
||||
|
||||
// If the inputs are too close for comfort, linearly interpolate and normalize the result.
|
||||
if dot > 0.9995 {
|
||||
return QuatNlerp(q1, q2, amount)
|
||||
}
|
||||
|
||||
// This is here for precision errors, I'm perfectly aware that *technically* the dot is bound [-1,1], but since Acos will freak out if it's not (even if it's just a liiiiitle bit over due to normal error) we need to clamp it
|
||||
dot = Clamp(dot, -1, 1)
|
||||
|
||||
theta := float32(math.Acos(float64(dot))) * amount
|
||||
sn, cs := math.Sincos(float64(theta))
|
||||
s, c := float32(sn), float32(cs)
|
||||
rel := q2.Sub(q1.Scale(dot)).Normalize()
|
||||
|
||||
return q1.Scale(c).Add(rel.Scale(s))
|
||||
}
|
||||
|
||||
// QuatLerp is a *L*inear Int*erp*olation between two Quaternions, cheap and simple.
|
||||
//
|
||||
// Not excessively useful, but uses can be found.
|
||||
func QuatLerp(q1, q2 Quat, amount float32) Quat {
|
||||
return q1.Add(q2.Sub(q1).Scale(amount))
|
||||
}
|
||||
|
||||
// QuatNlerp is a *Normalized* *L*inear Int*erp*olation between two Quaternions. Cheaper than Slerp
|
||||
// and usually just as good. This is literally Lerp with Normalize() called on it.
|
||||
//
|
||||
// Unlike Slerp, constant velocity isn't maintained, but it's much faster and
|
||||
// Nlerp(q1,q2) and Nlerp(q2,q1) return the same path. You should probably
|
||||
// use this more often unless you're suffering from choppiness due to the
|
||||
// non-constant velocity problem.
|
||||
func QuatNlerp(q1, q2 Quat, amount float32) Quat {
|
||||
return QuatLerp(q1, q2, amount).Normalize()
|
||||
}
|
||||
|
||||
// AnglesToQuat performs a rotation in the specified order. If the order is not
|
||||
// a valid RotationOrder, this function will panic
|
||||
//
|
||||
// The rotation "order" is more of an axis descriptor. For instance XZX would
|
||||
// tell the function to interpret angle1 as a rotation about the X axis, angle2 about
|
||||
// the Z axis, and angle3 about the X axis again.
|
||||
//
|
||||
// Based off the code for the Matlab function "angle2quat", though this implementation
|
||||
// only supports 3 single angles as opposed to multiple angles.
|
||||
func AnglesToQuat(angle1, angle2, angle3 float32, order RotationOrder) Quat {
|
||||
var s [3]float64
|
||||
var c [3]float64
|
||||
|
||||
s[0], c[0] = math.Sincos(float64(angle1 / 2))
|
||||
s[1], c[1] = math.Sincos(float64(angle2 / 2))
|
||||
s[2], c[2] = math.Sincos(float64(angle3 / 2))
|
||||
|
||||
ret := Quat{}
|
||||
switch order {
|
||||
case ZYX:
|
||||
ret.W = float32(c[0]*c[1]*c[2] + s[0]*s[1]*s[2])
|
||||
ret.V = Vec3{float32(c[0]*c[1]*s[2] - s[0]*s[1]*c[2]),
|
||||
float32(c[0]*s[1]*c[2] + s[0]*c[1]*s[2]),
|
||||
float32(s[0]*c[1]*c[2] - c[0]*s[1]*s[2]),
|
||||
}
|
||||
case ZYZ:
|
||||
ret.W = float32(c[0]*c[1]*c[2] - s[0]*c[1]*s[2])
|
||||
ret.V = Vec3{float32(c[0]*s[1]*s[2] - s[0]*s[1]*c[2]),
|
||||
float32(c[0]*s[1]*c[2] + s[0]*s[1]*s[2]),
|
||||
float32(s[0]*c[1]*c[2] + c[0]*c[1]*s[2]),
|
||||
}
|
||||
case ZXY:
|
||||
ret.W = float32(c[0]*c[1]*c[2] - s[0]*s[1]*s[2])
|
||||
ret.V = Vec3{float32(c[0]*s[1]*c[2] - s[0]*c[1]*s[2]),
|
||||
float32(c[0]*c[1]*s[2] + s[0]*s[1]*c[2]),
|
||||
float32(c[0]*s[1]*s[2] + s[0]*c[1]*c[2]),
|
||||
}
|
||||
case ZXZ:
|
||||
ret.W = float32(c[0]*c[1]*c[2] - s[0]*c[1]*s[2])
|
||||
ret.V = Vec3{float32(c[0]*s[1]*c[2] + s[0]*s[1]*s[2]),
|
||||
float32(s[0]*s[1]*c[2] - c[0]*s[1]*s[2]),
|
||||
float32(c[0]*c[1]*s[2] + s[0]*c[1]*c[2]),
|
||||
}
|
||||
case YXZ:
|
||||
ret.W = float32(c[0]*c[1]*c[2] + s[0]*s[1]*s[2])
|
||||
ret.V = Vec3{float32(c[0]*s[1]*c[2] + s[0]*c[1]*s[2]),
|
||||
float32(s[0]*c[1]*c[2] - c[0]*s[1]*s[2]),
|
||||
float32(c[0]*c[1]*s[2] - s[0]*s[1]*c[2]),
|
||||
}
|
||||
case YXY:
|
||||
ret.W = float32(c[0]*c[1]*c[2] - s[0]*c[1]*s[2])
|
||||
ret.V = Vec3{float32(c[0]*s[1]*c[2] + s[0]*s[1]*s[2]),
|
||||
float32(s[0]*c[1]*c[2] + c[0]*c[1]*s[2]),
|
||||
float32(c[0]*s[1]*s[2] - s[0]*s[1]*c[2]),
|
||||
}
|
||||
case YZX:
|
||||
ret.W = float32(c[0]*c[1]*c[2] - s[0]*s[1]*s[2])
|
||||
ret.V = Vec3{float32(c[0]*c[1]*s[2] + s[0]*s[1]*c[2]),
|
||||
float32(c[0]*s[1]*s[2] + s[0]*c[1]*c[2]),
|
||||
float32(c[0]*s[1]*c[2] - s[0]*c[1]*s[2]),
|
||||
}
|
||||
case YZY:
|
||||
ret.W = float32(c[0]*c[1]*c[2] - s[0]*c[1]*s[2])
|
||||
ret.V = Vec3{float32(s[0]*s[1]*c[2] - c[0]*s[1]*s[2]),
|
||||
float32(c[0]*c[1]*s[2] + s[0]*c[1]*c[2]),
|
||||
float32(c[0]*s[1]*c[2] + s[0]*s[1]*s[2]),
|
||||
}
|
||||
case XYZ:
|
||||
ret.W = float32(c[0]*c[1]*c[2] - s[0]*s[1]*s[2])
|
||||
ret.V = Vec3{float32(c[0]*s[1]*s[2] + s[0]*c[1]*c[2]),
|
||||
float32(c[0]*s[1]*c[2] - s[0]*c[1]*s[2]),
|
||||
float32(c[0]*c[1]*s[2] + s[0]*s[1]*c[2]),
|
||||
}
|
||||
case XYX:
|
||||
ret.W = float32(c[0]*c[1]*c[2] - s[0]*c[1]*s[2])
|
||||
ret.V = Vec3{float32(c[0]*c[1]*s[2] + s[0]*c[1]*c[2]),
|
||||
float32(c[0]*s[1]*c[2] + s[0]*s[1]*s[2]),
|
||||
float32(s[0]*s[1]*c[2] - c[0]*s[1]*s[2]),
|
||||
}
|
||||
case XZY:
|
||||
ret.W = float32(c[0]*c[1]*c[2] + s[0]*s[1]*s[2])
|
||||
ret.V = Vec3{float32(s[0]*c[1]*c[2] - c[0]*s[1]*s[2]),
|
||||
float32(c[0]*c[1]*s[2] - s[0]*s[1]*c[2]),
|
||||
float32(c[0]*s[1]*c[2] + s[0]*c[1]*s[2]),
|
||||
}
|
||||
case XZX:
|
||||
ret.W = float32(c[0]*c[1]*c[2] - s[0]*c[1]*s[2])
|
||||
ret.V = Vec3{float32(c[0]*c[1]*s[2] + s[0]*c[1]*c[2]),
|
||||
float32(c[0]*s[1]*s[2] - s[0]*s[1]*c[2]),
|
||||
float32(c[0]*s[1]*c[2] + s[0]*s[1]*s[2]),
|
||||
}
|
||||
default:
|
||||
panic("Unsupported rotation order")
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Mat4ToQuat converts a pure rotation matrix into a quaternion
|
||||
func Mat4ToQuat(m Mat4) Quat {
|
||||
// http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
|
||||
|
||||
if tr := m[0] + m[5] + m[10]; tr > 0 {
|
||||
s := float32(0.5 / math.Sqrt(float64(tr+1.0)))
|
||||
return Quat{
|
||||
0.25 / s,
|
||||
Vec3{
|
||||
(m[6] - m[9]) * s,
|
||||
(m[8] - m[2]) * s,
|
||||
(m[1] - m[4]) * s,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (m[0] > m[5]) && (m[0] > m[10]) {
|
||||
s := float32(2.0 * math.Sqrt(float64(1.0+m[0]-m[5]-m[10])))
|
||||
return Quat{
|
||||
(m[6] - m[9]) / s,
|
||||
Vec3{
|
||||
0.25 * s,
|
||||
(m[4] + m[1]) / s,
|
||||
(m[8] + m[2]) / s,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if m[5] > m[10] {
|
||||
s := float32(2.0 * math.Sqrt(float64(1.0+m[5]-m[0]-m[10])))
|
||||
return Quat{
|
||||
(m[8] - m[2]) / s,
|
||||
Vec3{
|
||||
(m[4] + m[1]) / s,
|
||||
0.25 * s,
|
||||
(m[9] + m[6]) / s,
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
s := float32(2.0 * math.Sqrt(float64(1.0+m[10]-m[0]-m[5])))
|
||||
return Quat{
|
||||
(m[1] - m[4]) / s,
|
||||
Vec3{
|
||||
(m[8] + m[2]) / s,
|
||||
(m[9] + m[6]) / s,
|
||||
0.25 * s,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// QuatLookAtV creates a rotation from an eye vector to a center vector
|
||||
//
|
||||
// It assumes the front of the rotated object at Z- and up at Y+
|
||||
func QuatLookAtV(eye, center, up Vec3) Quat {
|
||||
// http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-17-quaternions/#I_need_an_equivalent_of_gluLookAt__How_do_I_orient_an_object_towards_a_point__
|
||||
// https://bitbucket.org/sinbad/ogre/src/d2ef494c4a2f5d6e2f0f17d3bfb9fd936d5423bb/OgreMain/src/OgreCamera.cpp?at=default#cl-161
|
||||
|
||||
direction := center.Sub(eye).Normalize()
|
||||
|
||||
// Find the rotation between the front of the object (that we assume towards Z-,
|
||||
// but this depends on your model) and the desired direction
|
||||
rotDir := QuatBetweenVectors(Vec3{0, 0, -1}, direction)
|
||||
|
||||
// Recompute up so that it's perpendicular to the direction
|
||||
// You can skip that part if you really want to force up
|
||||
//right := direction.Cross(up)
|
||||
//up = right.Cross(direction)
|
||||
|
||||
// Because of the 1rst rotation, the up is probably completely screwed up.
|
||||
// Find the rotation between the "up" of the rotated object, and the desired up
|
||||
upCur := rotDir.Rotate(Vec3{0, 1, 0})
|
||||
rotUp := QuatBetweenVectors(upCur, up)
|
||||
|
||||
rotTarget := rotUp.Mul(rotDir) // remember, in reverse order.
|
||||
return rotTarget.Inverse() // camera rotation should be inversed!
|
||||
}
|
||||
|
||||
// QuatBetweenVectors calculates the rotation between two vectors
|
||||
func QuatBetweenVectors(start, dest Vec3) Quat {
|
||||
// http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-17-quaternions/#I_need_an_equivalent_of_gluLookAt__How_do_I_orient_an_object_towards_a_point__
|
||||
// https://github.com/g-truc/glm/blob/0.9.5/glm/gtx/quaternion.inl#L225
|
||||
// https://bitbucket.org/sinbad/ogre/src/d2ef494c4a2f5d6e2f0f17d3bfb9fd936d5423bb/OgreMain/include/OgreVector3.h?at=default#cl-654
|
||||
|
||||
start = start.Normalize()
|
||||
dest = dest.Normalize()
|
||||
epsilon := float32(0.001)
|
||||
|
||||
cosTheta := start.Dot(dest)
|
||||
if cosTheta < -1.0+epsilon {
|
||||
// special case when vectors in opposite directions:
|
||||
// there is no "ideal" rotation axis
|
||||
// So guess one; any will do as long as it's perpendicular to start
|
||||
axis := Vec3{1, 0, 0}.Cross(start)
|
||||
if axis.Dot(axis) < epsilon {
|
||||
// bad luck, they were parallel, try again!
|
||||
axis = Vec3{0, 1, 0}.Cross(start)
|
||||
}
|
||||
|
||||
return QuatRotate(math.Pi, axis.Normalize())
|
||||
}
|
||||
|
||||
axis := start.Cross(dest)
|
||||
s := float32(math.Sqrt(float64(1.0+cosTheta) * 2.0))
|
||||
|
||||
return Quat{
|
||||
s * 0.5,
|
||||
axis.Mul(1.0 / s),
|
||||
}
|
||||
}
|
||||
+321
@@ -0,0 +1,321 @@
|
||||
// Copyright 2014 The go-gl Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mgl32
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Circle generates a circle centered at (0,0) with a given radius.
|
||||
// The radii are assumed to be in GL's coordinate sizing.
|
||||
//
|
||||
// Technically this draws an ellipse with two axes that match with the X and Y axes, the reason it has a radiusX and radiusY is because GL's coordinate system
|
||||
// is proportional to screen width and screen height. So if you have a non-square viewport, a single radius will appear
|
||||
// to "squash" the circle in one direction (usually the Y direction), so the X and Y radius allow for a circle to be made.
|
||||
// A good way to get the correct radii is with mathgl.ScreenToGLCoords(radius, radius, screenWidth, screenHeight) which will get you the correct
|
||||
// proportional GL coords.
|
||||
//
|
||||
// The numSlices argument specifies how many triangles you want your circle divided into, setting this
|
||||
// number to too low a value may cause problem (and too high will cause it to take a lot of memory and time to compute
|
||||
// without much gain in resolution).
|
||||
//
|
||||
// This uses discrete triangles, not a triangle fan
|
||||
func Circle(radiusX, radiusY float32, numSlices int) []Vec2 {
|
||||
twoPi := float32(2.0 * math.Pi)
|
||||
|
||||
circlePoints := make([]Vec2, 0, numSlices*3)
|
||||
center := Vec2{0.0, 0.0}
|
||||
previous := Vec2{radiusX, 0.0}
|
||||
|
||||
for theta := twoPi / float32(numSlices); !FloatEqual(theta, twoPi); theta = Clamp(theta+twoPi/float32(numSlices), 0.0, twoPi) {
|
||||
sin, cos := math.Sincos(float64(theta))
|
||||
curr := Vec2{float32(cos) * radiusX, float32(sin) * radiusY}
|
||||
|
||||
circlePoints = append(circlePoints, center, previous, curr)
|
||||
previous = curr
|
||||
}
|
||||
|
||||
// Now add the final point at theta=2pi
|
||||
circlePoints = append(circlePoints, center, previous, Vec2{radiusX, 0.0})
|
||||
return circlePoints
|
||||
}
|
||||
|
||||
// Rect generates a 2-triangle rectangle for use with GL_TRIANGLES. The width and height should use GL's proportions (that is, where a width of 1.0
|
||||
// is equivalent to half of the width of the render target); however, the y-coordinates grow downwards, not upwards. That is, it
|
||||
// assumes you want the origin of the rectangle with the top-left corner at (0.0,0.0).
|
||||
//
|
||||
// Keep in mind that GL's coordinate system is proportional, so width=height will not result in a square unless your viewport is square.
|
||||
// If you want to maintain proportionality regardless of screen size, use the results of w,h := ScreenToGLCoordsf(absoluteWidth, absoluteHeight, screenWidth, screenHeight);
|
||||
// w,h=w+1,h-1 in the call to this function. (The w+1,h-1 step maps the coordinates to start at 0.0 rather than -1.0)
|
||||
func Rect(width, height float32) []Vec2 {
|
||||
return []Vec2{
|
||||
{0.0, 0.0},
|
||||
{0.0, -height},
|
||||
{width, -height},
|
||||
|
||||
{0.0, 0.0},
|
||||
{width, -height},
|
||||
{width, 0.0},
|
||||
}
|
||||
}
|
||||
|
||||
// QuadraticBezierCurve2D interpolates the point t on the given bezier curve.
|
||||
func QuadraticBezierCurve2D(t float32, cPoint1, cPoint2, cPoint3 Vec2) Vec2 {
|
||||
if t < 0.0 || t > 1.0 {
|
||||
panic("Can't interpolate on bezier curve with t out of range [0.0,1.0]")
|
||||
}
|
||||
|
||||
return cPoint1.Mul((1.0 - t) * (1.0 - t)).Add(cPoint2.Mul(2 * (1 - t) * t)).Add(cPoint3.Mul(t * t))
|
||||
}
|
||||
|
||||
// QuadraticBezierCurve3D interpolates the point t on the given bezier curve.
|
||||
func QuadraticBezierCurve3D(t float32, cPoint1, cPoint2, cPoint3 Vec3) Vec3 {
|
||||
if t < 0.0 || t > 1.0 {
|
||||
panic("Can't interpolate on bezier curve with t out of range [0.0,1.0]")
|
||||
}
|
||||
|
||||
return cPoint1.Mul((1.0 - t) * (1.0 - t)).Add(cPoint2.Mul(2 * (1 - t) * t)).Add(cPoint3.Mul(t * t))
|
||||
}
|
||||
|
||||
// CubicBezierCurve2D interpolates the point t on the given bezier curve.
|
||||
func CubicBezierCurve2D(t float32, cPoint1, cPoint2, cPoint3, cPoint4 Vec2) Vec2 {
|
||||
if t < 0.0 || t > 1.0 {
|
||||
panic("Can't interpolate on bezier curve with t out of range [0.0,1.0]")
|
||||
}
|
||||
|
||||
sum14 := cPoint1.Mul((1 - t) * (1 - t) * (1 - t)).Add(cPoint4.Mul(t * t * t))
|
||||
sum23 := cPoint2.Mul(3 * (1 - t) * (1 - t) * t).Add(cPoint3.Mul(3 * (1 - t) * t * t))
|
||||
return sum14.Add(sum23)
|
||||
}
|
||||
|
||||
// CubicBezierCurve3D interpolates the point t on the given bezier curve.
|
||||
func CubicBezierCurve3D(t float32, cPoint1, cPoint2, cPoint3, cPoint4 Vec3) Vec3 {
|
||||
if t < 0.0 || t > 1.0 {
|
||||
panic("Can't interpolate on bezier curve with t out of range [0.0,1.0]")
|
||||
}
|
||||
|
||||
sum14 := cPoint1.Mul((1 - t) * (1 - t) * (1 - t)).Add(cPoint4.Mul(t * t * t))
|
||||
sum23 := cPoint2.Mul(3 * (1 - t) * (1 - t) * t).Add(cPoint3.Mul(3 * (1 - t) * t * t))
|
||||
return sum14.Add(sum23)
|
||||
}
|
||||
|
||||
// BezierCurve2D returns the point at point t along an n-control point Bezier curve
|
||||
//
|
||||
// t must be in the range 0.0 and 1.0 or this function will panic. Consider [0.0,1.0] to be similar to a percentage,
|
||||
// 0.0 is first control point, and the point at 1.0 is the last control point. Any point in between is how far along the path you are between 0 and 1.
|
||||
//
|
||||
// This function is not sensitive to the coordinate system of the control points. It will correctly interpolate regardless of whether they're in screen coords,
|
||||
// gl coords, or something else entirely
|
||||
func BezierCurve2D(t float32, cPoints []Vec2) Vec2 {
|
||||
if t < 0.0 || t > 1.0 {
|
||||
panic("Input to bezier has t not in range [0,1]. If you think this is a precision error, use mathgl.Clamp[f|d] before calling this function")
|
||||
}
|
||||
|
||||
n := len(cPoints) - 1
|
||||
point := cPoints[0].Mul(float32(math.Pow(float64(1.0-t), float64(n))))
|
||||
|
||||
for i := 1; i <= n; i++ {
|
||||
point = point.Add(cPoints[i].Mul(float32(float64(choose(n, i)) * math.Pow(float64(1-t), float64(n-i)) * math.Pow(float64(t), float64(i))))) // P += P_i * nCi * (1-t)^(n-i) * t^i
|
||||
}
|
||||
|
||||
return point
|
||||
}
|
||||
|
||||
// BezierCurve3D same as the 2D version, except the line is in 3D space
|
||||
func BezierCurve3D(t float32, cPoints []Vec3) Vec3 {
|
||||
if t < 0.0 || t > 1.0 {
|
||||
panic("Input to bezier has t not in range [0,1]. If you think this is a precision error, use mathgl.Clamp[f|d] before calling this function")
|
||||
}
|
||||
|
||||
n := len(cPoints) - 1
|
||||
point := cPoints[0].Mul(float32(math.Pow(float64(1.0-t), float64(n))))
|
||||
|
||||
for i := 1; i <= n; i++ {
|
||||
point = point.Add(cPoints[i].Mul(float32(float64(choose(n, i)) * math.Pow(float64(1-t), float64(n-i)) * math.Pow(float64(t), float64(i))))) // P += P_i * nCi * (1-t)^(n-i) * t^i
|
||||
}
|
||||
|
||||
return point
|
||||
}
|
||||
|
||||
// MakeBezierCurve2D generates a bezier curve with controlPoints cPoints. The
|
||||
// numPoints argument determines how many "samples" it makes along the line. For
|
||||
// instance, a call to this with numPoints 2 will have exactly two points: the
|
||||
// start and end points For any points above 2 it will divide it into
|
||||
// numPoints-1 chunks (which means it will generate numPoints-2 vertices other
|
||||
// than the beginning and end). So for 3 points it will divide it in half, 4
|
||||
// points into thirds, and so on.
|
||||
//
|
||||
// This is likely to get rather expensive for anything over perhaps a cubic
|
||||
// curve.
|
||||
func MakeBezierCurve2D(numPoints int, cPoints []Vec2) (line []Vec2) {
|
||||
line = make([]Vec2, numPoints)
|
||||
if numPoints == 0 {
|
||||
return
|
||||
} else if numPoints == 1 {
|
||||
line[0] = cPoints[0]
|
||||
return
|
||||
} else if numPoints == 2 {
|
||||
line[0] = cPoints[0]
|
||||
line[1] = cPoints[len(cPoints)-1]
|
||||
return
|
||||
}
|
||||
|
||||
line[0] = cPoints[0]
|
||||
for i := 1; i < numPoints-1; i++ {
|
||||
line[i] = BezierCurve2D(Clamp(float32(i)/float32(numPoints-1), 0.0, 1.0), cPoints)
|
||||
}
|
||||
line[numPoints-1] = cPoints[len(cPoints)-1]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MakeBezierCurve3D same as the 2D version, except with the line in 3D space.
|
||||
func MakeBezierCurve3D(numPoints int, cPoints []Vec3) (line []Vec3) {
|
||||
line = make([]Vec3, numPoints)
|
||||
if numPoints == 0 {
|
||||
return
|
||||
} else if numPoints == 1 {
|
||||
line[0] = cPoints[0]
|
||||
return
|
||||
} else if numPoints == 2 {
|
||||
line[0] = cPoints[0]
|
||||
line[1] = cPoints[len(cPoints)-1]
|
||||
return
|
||||
}
|
||||
|
||||
line[0] = cPoints[0]
|
||||
for i := 1; i < numPoints-1; i++ {
|
||||
line[i] = BezierCurve3D(Clamp(float32(i)/float32(numPoints-1), 0.0, 1.0), cPoints)
|
||||
}
|
||||
line[numPoints-1] = cPoints[len(cPoints)-1]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// BezierSurface creates a 2-dimensional Bezier surface of arbitrary degree in
|
||||
// 3D Space Like the curve functions, if u or v are not in the range [0.0,1.0]
|
||||
// the function will panic, use Clamp[f|d] to ensure it is correct.
|
||||
//
|
||||
// The control point matrix must not be jagged, or this will end up panicking
|
||||
// from an index out of bounds exception
|
||||
func BezierSurface(u, v float32, cPoints [][]Vec3) Vec3 {
|
||||
if u < 0.0 || u > 1.0 || v < 1.0 || v > 1.0 {
|
||||
panic("u or v not in range [0.0,1.0] in BezierSurface")
|
||||
}
|
||||
|
||||
n := len(cPoints) - 1
|
||||
m := len(cPoints[0]) - 1
|
||||
|
||||
point := cPoints[0][0].Mul(float32(math.Pow(float64(1.0-u), float64(n)) * math.Pow(float64(1.0-v), float64(m))))
|
||||
|
||||
for i := 0; i <= n; i++ {
|
||||
for j := 0; j <= m; j++ {
|
||||
if i == 0 && j == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
point = point.Add(cPoints[i][j].Mul(float32(float64(choose(n, i)) * math.Pow(float64(u), float64(i)) * math.Pow(float64(1.0-u), float64(n-i)) * float64(choose(m, j)) * math.Pow(float64(v), float64(j)) * math.Pow(float64(1.0-v), float64(m-j)))))
|
||||
}
|
||||
}
|
||||
|
||||
return point
|
||||
}
|
||||
|
||||
// BezierSplineInterpolate2D does interpolation over a spline of several bezier
|
||||
// curves. Each bezier curve must have a finite range, though the spline may be
|
||||
// disjoint. The bezier curves are not required to be in any particular order.
|
||||
//
|
||||
// If t is out of the range of all given curves, this function will panic
|
||||
func BezierSplineInterpolate2D(t float32, ranges [][2]float32, cPoints [][]Vec2) Vec2 {
|
||||
if len(ranges) != len(cPoints) {
|
||||
panic("Each bezier curve needs a range")
|
||||
}
|
||||
|
||||
for i, curveRange := range ranges {
|
||||
if t >= curveRange[0] && t <= curveRange[1] {
|
||||
return BezierCurve2D((t-curveRange[0])/(curveRange[1]-curveRange[0]), cPoints[i])
|
||||
}
|
||||
}
|
||||
|
||||
panic("t is out of the range of all bezier curves in this spline")
|
||||
}
|
||||
|
||||
// BezierSplineInterpolate3D does interpolation over a spline of several bezier
|
||||
// curves. Each bezier curve must have a finite range, though the spline may be
|
||||
// disjoint. The bezier curves are not required to be in any particular order.
|
||||
//
|
||||
// If t is out of the range of all given curves, this function will panic
|
||||
func BezierSplineInterpolate3D(t float32, ranges [][2]float32, cPoints [][]Vec3) Vec3 {
|
||||
if len(ranges) != len(cPoints) {
|
||||
panic("Each bezier curve needs a range")
|
||||
}
|
||||
|
||||
for i, curveRange := range ranges {
|
||||
if t >= curveRange[0] && t <= curveRange[1] {
|
||||
return BezierCurve3D((t-curveRange[0])/(curveRange[1]-curveRange[0]), cPoints[i])
|
||||
}
|
||||
}
|
||||
|
||||
panic("t is out of the range of all bezier curves in this spline")
|
||||
}
|
||||
|
||||
// ReticulateSplines reticulates ALL the Splines.
|
||||
//
|
||||
// For the overly serious: the function is just for fun. It does nothing except prints a Maxis reference. Technically you could "reticulate splines"
|
||||
// by joining a bunch of splines together, but that ruins the joke.
|
||||
func ReticulateSplines(ranges [][][2]float32, cPoints [][][]Vec2, withLlamas bool) {
|
||||
if !withLlamas {
|
||||
fmt.Println("You can't reticulate splines without llamas, silly.")
|
||||
} else {
|
||||
fmt.Println("Actually, you can't even reticulate splines WITH llamas")
|
||||
}
|
||||
}
|
||||
|
||||
// ScreenToGLCoords transforms from pixel coordinates to GL coordinates.
|
||||
//
|
||||
// This assumes that your pixel coordinate system considers its origin to be in the top left corner (GL's is in the bottom left).
|
||||
// The coordinates x and y may be out of the range [0,screenWidth-1] and [0,screeneHeight-1].
|
||||
//
|
||||
// GL's coordinate system maps [screenWidth-1,0] to [1.0,1.0] and [0,screenHeight-1] to [-1.0,-1.0]. If x and y are out of the range, they'll still
|
||||
// be mapped correctly, just off the screen. (e.g. if y = 2*(screenHeight-1) you'll get -3.0 for yOut)
|
||||
//
|
||||
// This is similar to Unproject, except for 2D cases and much simpler (especially since an inverse may always be found)
|
||||
func ScreenToGLCoords(x, y int, screenWidth, screenHeight int) (xOut, yOut float32) {
|
||||
xOut = 2.0*float32(x)/float32(screenWidth-1) - 1.0
|
||||
yOut = -2.0*float32(y)/float32(screenHeight-1) + 1.0
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GLToScreenCoords transforms from GL's proportional system to pixel coordinates.
|
||||
//
|
||||
// Assumes the pixel coordinate system has its origin in the top left corner. (GL's is in the bottom left)
|
||||
//
|
||||
// GL's coordinate system maps [screenWidth-1,0] to [1.0,1.0] and [0,screenHeight-1] to [-1.0,-1.0]. If x and y are out of the range, they'll still
|
||||
// be mapped correctly, just off the screen. (e.g. if y=-3.0, you'll get 2*(screenHeight-1) for yOut)
|
||||
//
|
||||
// This is similar to Project, except for 2D cases and much simpler
|
||||
func GLToScreenCoords(x, y float32, screenWidth, screenHeight int) (xOut, yOut int) {
|
||||
xOut = int((x + 1.0) * float32(screenWidth-1) / 2.0)
|
||||
yOut = int((1.0 - y) * float32(screenHeight-1) / 2.0)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// choose calculates the binomial coefficient C(n,k) aka nCk
|
||||
func choose(n, k int) int {
|
||||
if k == 0 {
|
||||
return 1
|
||||
} else if n == 0 {
|
||||
return 0
|
||||
}
|
||||
result := float32(n - (k - 1))
|
||||
for i := 2; i <= k; i++ {
|
||||
result *= float32(n-(k-i)) / float32(i)
|
||||
}
|
||||
|
||||
return int(result)
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
// Copyright 2014 The go-gl Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mgl32
|
||||
|
||||
import "math"
|
||||
|
||||
// Rotate2D returns a rotation Matrix about a angle in 2-D space. Specifically about the origin.
|
||||
// It is a 2x2 matrix, if you need a 3x3 for Homogeneous math (e.g. composition with a Translation matrix)
|
||||
// see HomogRotate2D
|
||||
func Rotate2D(angle float32) Mat2 {
|
||||
//angle = (angle * math.Pi) / 180.0
|
||||
sn, cs := math.Sincos(float64(angle))
|
||||
sin, cos := float32(sn), float32(cs)
|
||||
return Mat2{cos, sin, -sin, cos}
|
||||
}
|
||||
|
||||
// Rotate3DX returns a 3x3 (non-homogeneous) Matrix that rotates by angle about the X-axis
|
||||
//
|
||||
// Where c is cos(angle) and s is sin(angle)
|
||||
//
|
||||
// [1 0 0]
|
||||
// [0 c -s]
|
||||
// [0 s c ]
|
||||
func Rotate3DX(angle float32) Mat3 {
|
||||
//angle = (angle * math.Pi) / 180.0
|
||||
sn, cs := math.Sincos(float64(angle))
|
||||
sin, cos := float32(sn), float32(cs)
|
||||
return Mat3{1, 0, 0, 0, cos, sin, 0, -sin, cos}
|
||||
}
|
||||
|
||||
// Rotate3DY returns a 3x3 (non-homogeneous) Matrix that rotates by angle about the Y-axis
|
||||
//
|
||||
// Where c is cos(angle) and s is sin(angle)
|
||||
//
|
||||
// [c 0 s]
|
||||
// [0 1 0]
|
||||
// [s 0 c ]
|
||||
func Rotate3DY(angle float32) Mat3 {
|
||||
//angle = (angle * math.Pi) / 180.0
|
||||
sn, cs := math.Sincos(float64(angle))
|
||||
sin, cos := float32(sn), float32(cs)
|
||||
return Mat3{cos, 0, -sin, 0, 1, 0, sin, 0, cos}
|
||||
}
|
||||
|
||||
// Rotate3DZ returns a 3x3 (non-homogeneous) Matrix that rotates by angle about the Z-axis
|
||||
//
|
||||
// Where c is cos(angle) and s is sin(angle)
|
||||
//
|
||||
// [c -s 0]
|
||||
// [s c 0]
|
||||
// [0 0 1 ]
|
||||
func Rotate3DZ(angle float32) Mat3 {
|
||||
//angle = (angle * math.Pi) / 180.0
|
||||
sn, cs := math.Sincos(float64(angle))
|
||||
sin, cos := float32(sn), float32(cs)
|
||||
return Mat3{cos, sin, 0, -sin, cos, 0, 0, 0, 1}
|
||||
}
|
||||
|
||||
// Translate2D returns a homogeneous (3x3 for 2D-space) Translation matrix that moves a point by Tx units in the x-direction and Ty units in the y-direction
|
||||
//
|
||||
// [[1, 0, Tx]]
|
||||
// [[0, 1, Ty]]
|
||||
// [[0, 0, 1 ]]
|
||||
func Translate2D(Tx, Ty float32) Mat3 {
|
||||
return Mat3{1, 0, 0, 0, 1, 0, float32(Tx), float32(Ty), 1}
|
||||
}
|
||||
|
||||
// Translate3D returns a homogeneous (4x4 for 3D-space) Translation matrix that moves a point by Tx units in the x-direction, Ty units in the y-direction,
|
||||
// and Tz units in the z-direction
|
||||
//
|
||||
// [[1, 0, 0, Tx]]
|
||||
// [[0, 1, 0, Ty]]
|
||||
// [[0, 0, 1, Tz]]
|
||||
// [[0, 0, 0, 1 ]]
|
||||
func Translate3D(Tx, Ty, Tz float32) Mat4 {
|
||||
return Mat4{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, float32(Tx), float32(Ty), float32(Tz), 1}
|
||||
}
|
||||
|
||||
// HomogRotate2D is the same as Rotate2D, except homogeneous (3x3 with the extra row/col being all zeroes with a one in the bottom right)
|
||||
func HomogRotate2D(angle float32) Mat3 {
|
||||
//angle = (angle * math.Pi) / 180.0
|
||||
sn, cs := math.Sincos(float64(angle))
|
||||
sin, cos := float32(sn), float32(cs)
|
||||
return Mat3{cos, sin, 0, -sin, cos, 0, 0, 0, 1}
|
||||
}
|
||||
|
||||
// HomogRotate3DX is the same as Rotate3DX, except homogeneous (4x4 with the extra row/col being all zeroes with a one in the bottom right)
|
||||
func HomogRotate3DX(angle float32) Mat4 {
|
||||
//angle = (angle * math.Pi) / 180.0
|
||||
sn, cs := math.Sincos(float64(angle))
|
||||
sin, cos := float32(sn), float32(cs)
|
||||
|
||||
return Mat4{1, 0, 0, 0, 0, cos, sin, 0, 0, -sin, cos, 0, 0, 0, 0, 1}
|
||||
}
|
||||
|
||||
// HomogRotate3DY is the same as Rotate3DY, except homogeneous (4x4 with the extra row/col being all zeroes with a one in the bottom right)
|
||||
func HomogRotate3DY(angle float32) Mat4 {
|
||||
//angle = (angle * math.Pi) / 180.0
|
||||
sn, cs := math.Sincos(float64(angle))
|
||||
sin, cos := float32(sn), float32(cs)
|
||||
return Mat4{cos, 0, -sin, 0, 0, 1, 0, 0, sin, 0, cos, 0, 0, 0, 0, 1}
|
||||
}
|
||||
|
||||
// HomogRotate3DZ is the same as Rotate3DZ, except homogeneous (4x4 with the extra row/col being all zeroes with a one in the bottom right)
|
||||
func HomogRotate3DZ(angle float32) Mat4 {
|
||||
//angle = (angle * math.Pi) / 180.0
|
||||
sn, cs := math.Sincos(float64(angle))
|
||||
sin, cos := float32(sn), float32(cs)
|
||||
return Mat4{cos, sin, 0, 0, -sin, cos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}
|
||||
}
|
||||
|
||||
// Scale3D creates a homogeneous 3D scaling matrix
|
||||
// [[ scaleX, 0 , 0 , 0 ]]
|
||||
// [[ 0 , scaleY, 0 , 0 ]]
|
||||
// [[ 0 , 0 , scaleZ, 0 ]]
|
||||
// [[ 0 , 0 , 0 , 1 ]]
|
||||
func Scale3D(scaleX, scaleY, scaleZ float32) Mat4 {
|
||||
|
||||
return Mat4{float32(scaleX), 0, 0, 0, 0, float32(scaleY), 0, 0, 0, 0, float32(scaleZ), 0, 0, 0, 0, 1}
|
||||
}
|
||||
|
||||
// Scale2D creates a homogeneous 2D scaling matrix
|
||||
// [[ scaleX, 0 , 0 ]]
|
||||
// [[ 0 , scaleY, 0 ]]
|
||||
// [[ 0 , 0 , 1 ]]
|
||||
func Scale2D(scaleX, scaleY float32) Mat3 {
|
||||
return Mat3{float32(scaleX), 0, 0, 0, float32(scaleY), 0, 0, 0, 1}
|
||||
}
|
||||
|
||||
// ShearX2D creates a homogeneous 2D shear matrix along the X-axis
|
||||
func ShearX2D(shear float32) Mat3 {
|
||||
return Mat3{1, 0, 0, float32(shear), 1, 0, 0, 0, 1}
|
||||
}
|
||||
|
||||
// ShearY2D creates a homogeneous 2D shear matrix along the Y-axis
|
||||
func ShearY2D(shear float32) Mat3 {
|
||||
return Mat3{1, float32(shear), 0, 0, 1, 0, 0, 0, 1}
|
||||
}
|
||||
|
||||
// ShearX3D creates a homogeneous 3D shear matrix along the X-axis
|
||||
func ShearX3D(shearY, shearZ float32) Mat4 {
|
||||
|
||||
return Mat4{1, float32(shearY), float32(shearZ), 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}
|
||||
}
|
||||
|
||||
// ShearY3D creates a homogeneous 3D shear matrix along the Y-axis
|
||||
func ShearY3D(shearX, shearZ float32) Mat4 {
|
||||
return Mat4{1, 0, 0, 0, float32(shearX), 1, float32(shearZ), 0, 0, 0, 1, 0, 0, 0, 0, 1}
|
||||
}
|
||||
|
||||
// ShearZ3D creates a homogeneous 3D shear matrix along the Z-axis
|
||||
func ShearZ3D(shearX, shearY float32) Mat4 {
|
||||
return Mat4{1, 0, 0, 0, 0, 1, 0, 0, float32(shearX), float32(shearY), 1, 0, 0, 0, 0, 1}
|
||||
}
|
||||
|
||||
// HomogRotate3D creates a 3D rotation Matrix that rotates by (radian) angle about some arbitrary axis given by a normalized Vector.
|
||||
// It produces a homogeneous matrix (4x4)
|
||||
//
|
||||
// Where c is cos(angle) and s is sin(angle), and x, y, and z are the first, second, and third elements of the axis vector (respectively):
|
||||
//
|
||||
// [[ x^2(1-c)+c, xy(1-c)-zs, xz(1-c)+ys, 0 ]]
|
||||
// [[ xy(1-c)+zs, y^2(1-c)+c, yz(1-c)-xs, 0 ]]
|
||||
// [[ xz(1-c)-ys, yz(1-c)+xs, z^2(1-c)+c, 0 ]]
|
||||
// [[ 0 , 0 , 0 , 1 ]]
|
||||
func HomogRotate3D(angle float32, axis Vec3) Mat4 {
|
||||
x, y, z := axis[0], axis[1], axis[2]
|
||||
sn, cs := math.Sincos(float64(angle))
|
||||
s, c := float32(sn), float32(cs)
|
||||
k := 1 - c
|
||||
|
||||
return Mat4{x*x*k + c, x*y*k + z*s, x*z*k - y*s, 0, x*y*k - z*s, y*y*k + c, y*z*k + x*s, 0, x*z*k + y*s, y*z*k - x*s, z*z*k + c, 0, 0, 0, 0, 1}
|
||||
}
|
||||
|
||||
// Extract3DScale extracts the 3d scaling from a homogeneous matrix
|
||||
func Extract3DScale(m Mat4) (x, y, z float32) {
|
||||
return float32(math.Sqrt(float64(m[0]*m[0] + m[1]*m[1] + m[2]*m[2]))),
|
||||
float32(math.Sqrt(float64(m[4]*m[4] + m[5]*m[5] + m[6]*m[6]))),
|
||||
float32(math.Sqrt(float64(m[8]*m[8] + m[9]*m[9] + m[10]*m[10])))
|
||||
}
|
||||
|
||||
// ExtractMaxScale extracts the maximum scaling from a homogeneous matrix
|
||||
func ExtractMaxScale(m Mat4) float32 {
|
||||
scaleX := float64(m[0]*m[0] + m[1]*m[1] + m[2]*m[2])
|
||||
scaleY := float64(m[4]*m[4] + m[5]*m[5] + m[6]*m[6])
|
||||
scaleZ := float64(m[8]*m[8] + m[9]*m[9] + m[10]*m[10])
|
||||
|
||||
return float32(math.Sqrt(math.Max(scaleX, math.Max(scaleY, scaleZ))))
|
||||
}
|
||||
|
||||
// Mat4Normal calculates the Normal of the Matrix (aka the inverse transpose)
|
||||
func Mat4Normal(m Mat4) Mat3 {
|
||||
n := m.Inv().Transpose()
|
||||
return Mat3{n[0], n[1], n[2], n[4], n[5], n[6], n[8], n[9], n[10]}
|
||||
}
|
||||
|
||||
// TransformCoordinate multiplies a 3D vector by a transformation given by
|
||||
// the homogeneous 4D matrix m, applying any translation.
|
||||
// If this transformation is non-affine, it will project this
|
||||
// vector onto the plane w=1 before returning the result.
|
||||
//
|
||||
// This is similar to saying you're transforming and projecting a point.
|
||||
//
|
||||
// This is effectively equivalent to the GLSL code
|
||||
//
|
||||
// vec4 r = (m * vec4(v,1.));
|
||||
// r = r/r.w;
|
||||
// vec3 newV = r.xyz;
|
||||
func TransformCoordinate(v Vec3, m Mat4) Vec3 {
|
||||
t := v.Vec4(1)
|
||||
t = m.Mul4x1(t)
|
||||
t = t.Mul(1 / t[3])
|
||||
|
||||
return t.Vec3()
|
||||
}
|
||||
|
||||
// TransformNormal multiplies a 3D vector by a transformation given by
|
||||
// the homogeneous 4D matrix m, NOT applying any translations.
|
||||
//
|
||||
// This is similar to saying you're applying a transformation
|
||||
// to a direction or normal. Rotation still applies (as does scaling),
|
||||
// but translating a direction or normal is meaningless.
|
||||
//
|
||||
// This is effectively equivalent to the GLSL code
|
||||
//
|
||||
// vec4 r = (m * vec4(v,0.));
|
||||
// vec3 newV = r.xyz
|
||||
func TransformNormal(v Vec3, m Mat4) Vec3 {
|
||||
t := v.Vec4(0)
|
||||
t = m.Mul4x1(t)
|
||||
|
||||
return t.Vec3()
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
// Copyright 2014 The go-gl Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:generate go run codegen.go -template vector.tmpl -output vector.go
|
||||
//go:generate go run codegen.go -template matrix.tmpl -output matrix.go
|
||||
//go:generate go run codegen.go -mgl64
|
||||
|
||||
package mgl32
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// Epsilon is some tiny value that determines how precisely equal we want our floats to be
|
||||
// This is exported and left as a variable in case you want to change the default threshold for the
|
||||
// purposes of certain methods (e.g. Unproject uses the default epsilon when determining
|
||||
// if the determinant is "close enough" to zero to mean there's no inverse).
|
||||
//
|
||||
// This is, obviously, not mutex protected so be **absolutely sure** that no functions using Epsilon
|
||||
// are being executed when you change this.
|
||||
var Epsilon float32 = 1e-10
|
||||
|
||||
// Abs is a direct copy of the math package's Abs. This is here for the mgl32
|
||||
// package, to prevent rampant type conversions during equality tests.
|
||||
func Abs(a float32) float32 {
|
||||
if a < 0 {
|
||||
return -a
|
||||
} else if a == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// FloatEqual is a safe utility function to compare floats.
|
||||
// It's Taken from http://floating-point-gui.de/errors/comparison/
|
||||
//
|
||||
// It is slightly altered to not call Abs when not needed.
|
||||
func FloatEqual(a, b float32) bool {
|
||||
return FloatEqualThreshold(a, b, Epsilon)
|
||||
}
|
||||
|
||||
// FloatEqualFunc is a utility closure that will generate a function that
|
||||
// always approximately compares floats like FloatEqualThreshold with a different
|
||||
// threshold.
|
||||
func FloatEqualFunc(epsilon float32) func(float32, float32) bool {
|
||||
return func(a, b float32) bool {
|
||||
return FloatEqualThreshold(a, b, epsilon)
|
||||
}
|
||||
}
|
||||
|
||||
// Various useful constants.
|
||||
var (
|
||||
MinNormal = float32(1.1754943508222875e-38) // 1 / 2**(127 - 1)
|
||||
MinValue = float32(math.SmallestNonzeroFloat32)
|
||||
MaxValue = float32(math.MaxFloat32)
|
||||
|
||||
InfPos = float32(math.Inf(1))
|
||||
InfNeg = float32(math.Inf(-1))
|
||||
NaN = float32(math.NaN())
|
||||
)
|
||||
|
||||
// FloatEqualThreshold is a utility function to compare floats.
|
||||
// It's Taken from http://floating-point-gui.de/errors/comparison/
|
||||
//
|
||||
// It is slightly altered to not call Abs when not needed.
|
||||
//
|
||||
// This differs from FloatEqual in that it lets you pass in your comparison threshold, so that you can adjust the comparison value to your specific needs
|
||||
func FloatEqualThreshold(a, b, epsilon float32) bool {
|
||||
if a == b { // Handles the case of inf or shortcuts the loop when no significant error has accumulated
|
||||
return true
|
||||
}
|
||||
|
||||
diff := Abs(a - b)
|
||||
if a*b == 0 || diff < MinNormal { // If a or b are 0 or both are extremely close to it
|
||||
return diff < epsilon*epsilon
|
||||
}
|
||||
|
||||
// Else compare difference
|
||||
return diff/(Abs(a)+Abs(b)) < epsilon
|
||||
}
|
||||
|
||||
// Clamp takes in a value and two thresholds. If the value is smaller than the low
|
||||
// threshold, it returns the low threshold. If it's bigger than the high threshold
|
||||
// it returns the high threshold. Otherwise it returns the value.
|
||||
//
|
||||
// Useful to prevent some functions from freaking out because a value was
|
||||
// teeeeechnically out of range.
|
||||
func Clamp(a, low, high float32) float32 {
|
||||
if a < low {
|
||||
return low
|
||||
} else if a > high {
|
||||
return high
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// ClampFunc generates a closure that returns its parameter
|
||||
// clamped to the range [low,high].
|
||||
func ClampFunc(low, high float32) func(float32) float32 {
|
||||
return func(a float32) float32 {
|
||||
return Clamp(a, low, high)
|
||||
}
|
||||
}
|
||||
|
||||
// IsClamped checks if a is clamped between low and high as if
|
||||
// Clamp(a, low, high) had been called.
|
||||
//
|
||||
// In most cases it's probably better to just call Clamp
|
||||
// without checking this since it's relatively cheap.
|
||||
//
|
||||
// The IsClamped functions use strict equality (meaning: not the FloatEqual
|
||||
// function) there shouldn't be any major issues with this since clamp is often
|
||||
// used to fix minor errors
|
||||
func IsClamped(a, low, high float32) bool {
|
||||
return a >= low && a <= high
|
||||
}
|
||||
|
||||
// SetMin sets a to b if a > b.
|
||||
func SetMin(a, b *float32) {
|
||||
if *b < *a {
|
||||
*a = *b
|
||||
}
|
||||
}
|
||||
|
||||
// SetMax sets a to b if a < b.
|
||||
func SetMax(a, b *float32) {
|
||||
if *a < *b {
|
||||
*a = *b
|
||||
}
|
||||
}
|
||||
|
||||
// Round shortens a float32 value to a specified precision (number of digits after the decimal point)
|
||||
// with "round half up" tie-braking rule. Half-way values (23.5) are always rounded up (24).
|
||||
func Round(v float32, precision int) float32 {
|
||||
p := float64(precision)
|
||||
t := float64(v) * math.Pow(10, p)
|
||||
if t >= 0 {
|
||||
return float32(math.Floor(t+0.5) / math.Pow(10, p))
|
||||
}
|
||||
return float32(math.Ceil(t-0.5) / math.Pow(10, p))
|
||||
}
|
||||
+371
@@ -0,0 +1,371 @@
|
||||
// Copyright 2014 The go-gl Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package mgl32
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// VecN represents a vector of N elements backed by a slice.
|
||||
//
|
||||
// As with MatMxN, this is not for hardcore linear algebra with large dimensions. Use github.com/gonum/matrix
|
||||
// or something like BLAS/LAPACK for that. This is for corner cases in 3D math where you require
|
||||
// something a little bigger that 4D, but still relatively small.
|
||||
//
|
||||
// This VecN uses several sync.Pool objects as a memory pool. The rule is that for any sized vector, the backing slice
|
||||
// has CAPACITY (not length) of 2^p where p is Ceil(log_2(N)) -- or in other words, rounding up the base-2
|
||||
// log of the size of the vector. E.G. a VecN of size 17 will have a backing slice of Cap 32.
|
||||
type VecN struct {
|
||||
vec []float32
|
||||
}
|
||||
|
||||
// NewVecNFromData creates a new vector with a backing slice filled with the contents
|
||||
// of initial. It is NOT backed by initial, but rather a slice with cap
|
||||
// 2^p where p is Ceil(log_2(len(initial))), with the data from initial copied into
|
||||
// it.
|
||||
func NewVecNFromData(initial []float32) *VecN {
|
||||
if initial == nil {
|
||||
return &VecN{}
|
||||
}
|
||||
var internal []float32
|
||||
if shouldPool {
|
||||
internal = grabFromPool(len(initial))
|
||||
} else {
|
||||
internal = make([]float32, len(initial))
|
||||
}
|
||||
copy(internal, initial)
|
||||
return &VecN{vec: internal}
|
||||
}
|
||||
|
||||
// NewVecN creates a new vector with a backing slice of
|
||||
// 2^p where p = Ceil(log_2(n))
|
||||
func NewVecN(n int) *VecN {
|
||||
if shouldPool {
|
||||
return &VecN{vec: grabFromPool(n)}
|
||||
} else {
|
||||
return &VecN{vec: make([]float32, n)}
|
||||
}
|
||||
}
|
||||
|
||||
// Raw returns the raw slice backing the VecN
|
||||
//
|
||||
// This may be sent back to the memory pool at any time
|
||||
// and you aren't advised to rely on this value
|
||||
func (vn VecN) Raw() []float32 {
|
||||
return vn.vec
|
||||
}
|
||||
|
||||
// Get the element at index i from the vector. This does not bounds check, and
|
||||
// will panic if i is out of range.
|
||||
func (vn VecN) Get(i int) float32 {
|
||||
return vn.vec[i]
|
||||
}
|
||||
|
||||
// Set the element at index i to val.
|
||||
func (vn *VecN) Set(i int, val float32) {
|
||||
vn.vec[i] = val
|
||||
}
|
||||
|
||||
// Sends the allocated memory through the callback if it exists
|
||||
func (vn *VecN) destroy() {
|
||||
if vn == nil || vn.vec == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if shouldPool {
|
||||
returnToPool(vn.vec)
|
||||
}
|
||||
vn.vec = nil
|
||||
}
|
||||
|
||||
// Resize the underlying slice to the desired amount, reallocating or retrieving
|
||||
// from the pool if necessary. The values after a Resize cannot be expected to
|
||||
// be related to the values before a Resize.
|
||||
//
|
||||
// If the caller is a nil pointer, this returns a value as if NewVecN(n) had
|
||||
// been called, otherwise it simply returns the caller.
|
||||
func (vn *VecN) Resize(n int) *VecN {
|
||||
if vn == nil {
|
||||
return NewVecN(n)
|
||||
}
|
||||
|
||||
if n <= cap(vn.vec) {
|
||||
if vn.vec != nil {
|
||||
vn.vec = vn.vec[:n]
|
||||
} else {
|
||||
vn.vec = []float32{}
|
||||
}
|
||||
return vn
|
||||
}
|
||||
|
||||
if shouldPool && vn.vec != nil {
|
||||
returnToPool(vn.vec)
|
||||
}
|
||||
*vn = (*NewVecN(n))
|
||||
|
||||
return vn
|
||||
}
|
||||
|
||||
// SetBackingSlice sets the vector's backing slice to the given
|
||||
// new one.
|
||||
func (vn *VecN) SetBackingSlice(newSlice []float32) {
|
||||
vn.vec = newSlice
|
||||
}
|
||||
|
||||
// Size returns the len of the vector's underlying slice.
|
||||
// This is not titled Len because it conflicts the package's
|
||||
// convention of calling the Norm the Len.
|
||||
func (vn *VecN) Size() int {
|
||||
return len(vn.vec)
|
||||
}
|
||||
|
||||
// Cap Returns the cap of the vector's underlying slice.
|
||||
func (vn *VecN) Cap() int {
|
||||
return cap(vn.vec)
|
||||
}
|
||||
|
||||
// Zero sets the vector's size to n and zeroes out the vector.
|
||||
// If n is bigger than the vector's size, it will realloc.
|
||||
func (vn *VecN) Zero(n int) {
|
||||
vn.Resize(n)
|
||||
for i := range vn.vec {
|
||||
vn.vec[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds vn and addend, storing the result in dst.
|
||||
// If dst does not have sufficient size it will be resized
|
||||
// Dst may be one of the other arguments. If dst is nil, it will be allocated.
|
||||
// The value returned is dst, for easier method chaining
|
||||
//
|
||||
// If vn and addend are not the same size, this function will add min(vn.Size(), addend.Size())
|
||||
// elements.
|
||||
func (vn *VecN) Add(dst *VecN, subtrahend *VecN) *VecN {
|
||||
if vn == nil || subtrahend == nil {
|
||||
return nil
|
||||
}
|
||||
size := intMin(len(vn.vec), len(subtrahend.vec))
|
||||
dst = dst.Resize(size)
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
dst.vec[i] = vn.vec[i] + subtrahend.vec[i]
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// Sub subtracts addend from vn, storing the result in dst.
|
||||
// If dst does not have sufficient size it will be resized
|
||||
// Dst may be one of the other arguments. If dst is nil, it will be allocated.
|
||||
// The value returned is dst, for easier method chaining
|
||||
//
|
||||
// If vn and addend are not the same size, this function will add min(vn.Size(), addend.Size())
|
||||
// elements.
|
||||
func (vn *VecN) Sub(dst *VecN, addend *VecN) *VecN {
|
||||
if vn == nil || addend == nil {
|
||||
return nil
|
||||
}
|
||||
size := intMin(len(vn.vec), len(addend.vec))
|
||||
dst = dst.Resize(size)
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
dst.vec[i] = vn.vec[i] - addend.vec[i]
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// Cross takes the binary cross product of vn and other, and stores it in dst.
|
||||
// If either vn or other are not of size 3 this function will panic
|
||||
//
|
||||
// If dst is not of sufficient size, or is nil, a new slice is allocated.
|
||||
// Dst is permitted to be one of the other arguments
|
||||
func (vn *VecN) Cross(dst *VecN, other *VecN) *VecN {
|
||||
if vn == nil || other == nil {
|
||||
return nil
|
||||
}
|
||||
if len(vn.vec) != 3 || len(other.vec) != 3 {
|
||||
panic("Cannot take binary cross product of non-3D elements (7D cross product not implemented)")
|
||||
}
|
||||
|
||||
dst = dst.Resize(3)
|
||||
dst.vec[0], dst.vec[1], dst.vec[2] = vn.vec[1]*other.vec[2]-vn.vec[2]*other.vec[1], vn.vec[2]*other.vec[0]-vn.vec[0]*other.vec[2], vn.vec[0]*other.vec[1]-vn.vec[1]*other.vec[0]
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
func intMin(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// Dot computes the dot product of two VecNs, if
|
||||
// the two vectors are not of the same length -- this
|
||||
// will return NaN.
|
||||
func (vn *VecN) Dot(other *VecN) float32 {
|
||||
if vn == nil || other == nil || len(vn.vec) != len(other.vec) {
|
||||
return float32(math.NaN())
|
||||
}
|
||||
|
||||
var result float32 = 0.0
|
||||
for i, el := range vn.vec {
|
||||
result += el * other.vec[i]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Len computes the vector length (also called the Norm) of the
|
||||
// vector. Equivalent to math.Sqrt(vn.Dot(vn)) with the appropriate
|
||||
// type conversions.
|
||||
//
|
||||
// If vn is nil, this returns NaN
|
||||
func (vn *VecN) Len() float32 {
|
||||
if vn == nil {
|
||||
return float32(math.NaN())
|
||||
}
|
||||
if len(vn.vec) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return float32(math.Sqrt(float64(vn.Dot(vn))))
|
||||
}
|
||||
|
||||
// LenSqr returns the vector's square length. This is equivalent to the sum of the squares of all elements.
|
||||
func (vn *VecN) LenSqr() float32 {
|
||||
if vn == nil {
|
||||
return float32(math.NaN())
|
||||
}
|
||||
if len(vn.vec) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return vn.Dot(vn)
|
||||
}
|
||||
|
||||
// Normalize the vector and stores the result in dst, which
|
||||
// will be returned. Dst will be appropraitely resized to the
|
||||
// size of vn.
|
||||
//
|
||||
// The destination can be vn itself and nothing will go wrong.
|
||||
//
|
||||
// This is equivalent to vn.Mul(dst, 1/vn.Len())
|
||||
func (vn *VecN) Normalize(dst *VecN) *VecN {
|
||||
if vn == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return vn.Mul(dst, 1/vn.Len())
|
||||
}
|
||||
|
||||
// Mul multiplies the vector by some scalar value and stores the result in dst,
|
||||
// which will be returned. Dst will be appropriately resized to the size of vn.
|
||||
//
|
||||
// The destination can be vn itself and nothing will go wrong.
|
||||
func (vn *VecN) Mul(dst *VecN, c float32) *VecN {
|
||||
if vn == nil {
|
||||
return nil
|
||||
}
|
||||
dst = dst.Resize(len(vn.vec))
|
||||
|
||||
for i, el := range vn.vec {
|
||||
dst.vec[i] = el * c
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// OuterProd performs the vector outer product between vn and v2.
|
||||
// The outer product is like a "reverse" dot product. Where the dot product
|
||||
// aligns both vectors with the "sized" part facing "inward" (Vec3*Vec3=Mat1x3*Mat3x1=Mat1x1=Scalar).
|
||||
// The outer product multiplied them with it facing "outward"
|
||||
// (Vec3*Vec3=Mat3x1*Mat1x3=Mat3x3).
|
||||
//
|
||||
// The matrix dst will be Reshaped to the correct size, if vn or v2 are nil,
|
||||
// this returns nil.
|
||||
func (vn *VecN) OuterProd(dst *MatMxN, v2 *VecN) *MatMxN {
|
||||
if vn == nil || v2 == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
dst = dst.Reshape(len(vn.vec), len(v2.vec))
|
||||
|
||||
for c, el1 := range v2.vec {
|
||||
for r, el2 := range vn.vec {
|
||||
dst.Set(r, c, el1*el2)
|
||||
}
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// ApproxEqual returns whether the two vectors are approximately equal (See
|
||||
// FloatEqual).
|
||||
func (vn *VecN) ApproxEqual(vn2 *VecN) bool {
|
||||
if vn == nil || vn2 == nil || len(vn.vec) != len(vn2.vec) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, el := range vn.vec {
|
||||
if !FloatEqual(el, vn2.vec[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ApproxEqualThreshold returns whether the two vectors are approximately equal
|
||||
// to within the given threshold given by "epsilon" (See ApproxEqualThreshold).
|
||||
func (vn *VecN) ApproxEqualThreshold(vn2 *VecN, epsilon float32) bool {
|
||||
if vn == nil || vn2 == nil || len(vn.vec) != len(vn2.vec) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, el := range vn.vec {
|
||||
if !FloatEqualThreshold(el, vn2.vec[i], epsilon) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ApproxEqualFunc returns whether the two vectors are approximately equal,
|
||||
// given a function which compares two scalar elements.
|
||||
func (vn *VecN) ApproxEqualFunc(vn2 *VecN, comp func(float32, float32) bool) bool {
|
||||
if vn == nil || vn2 == nil || len(vn.vec) != len(vn2.vec) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, el := range vn.vec {
|
||||
if !comp(el, vn2.vec[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Vec2 constructs a 2-dimensional vector by discarding coordinates.
|
||||
func (vn *VecN) Vec2() Vec2 {
|
||||
raw := vn.Raw()
|
||||
return Vec2{raw[0], raw[1]}
|
||||
}
|
||||
|
||||
// Vec3 constructs a 3-dimensional vector by discarding coordinates.
|
||||
func (vn *VecN) Vec3() Vec3 {
|
||||
raw := vn.Raw()
|
||||
return Vec3{raw[0], raw[1], raw[2]}
|
||||
}
|
||||
|
||||
// Vec4 constructs a 4-dimensional vector by discarding coordinates.
|
||||
func (vn *VecN) Vec4() Vec4 {
|
||||
raw := vn.Raw()
|
||||
return Vec4{raw[0], raw[1], raw[2], raw[3]}
|
||||
}
|
||||
+591
@@ -0,0 +1,591 @@
|
||||
// Copyright 2014 The go-gl/mathgl Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file is generated by codegen.go; DO NOT EDIT
|
||||
// Edit vector.tmpl and run "go generate" to make changes.
|
||||
|
||||
package mgl32
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
type Vec2 [2]float32
|
||||
type Vec3 [3]float32
|
||||
type Vec4 [4]float32
|
||||
|
||||
// Vec3 constructs a 3-dimensional vector by appending the given coordinates.
|
||||
func (v Vec2) Vec3(z float32) Vec3 {
|
||||
return Vec3{v[0], v[1], z}
|
||||
}
|
||||
|
||||
// Vec4 constructs a 4-dimensional vector by appending the given coordinates.
|
||||
func (v Vec2) Vec4(z, w float32) Vec4 {
|
||||
return Vec4{v[0], v[1], z, w}
|
||||
}
|
||||
|
||||
// Vec4 constructs a 4-dimensional vector by appending the given coordinates.
|
||||
func (v Vec3) Vec4(w float32) Vec4 {
|
||||
return Vec4{v[0], v[1], v[2], w}
|
||||
}
|
||||
|
||||
// Vec2 constructs a 2-dimensional vector by discarding coordinates.
|
||||
func (v Vec3) Vec2() Vec2 {
|
||||
return Vec2{v[0], v[1]}
|
||||
}
|
||||
|
||||
// Vec2 constructs a 2-dimensional vector by discarding coordinates.
|
||||
func (v Vec4) Vec2() Vec2 {
|
||||
return Vec2{v[0], v[1]}
|
||||
}
|
||||
|
||||
// Vec3 constructs a 3-dimensional vector by discarding coordinates.
|
||||
func (v Vec4) Vec3() Vec3 {
|
||||
return Vec3{v[0], v[1], v[2]}
|
||||
}
|
||||
|
||||
// Elem extracts the elements of the vector for direct value assignment.
|
||||
func (v Vec2) Elem() (x, y float32) {
|
||||
return v[0], v[1]
|
||||
}
|
||||
|
||||
// Elem extracts the elements of the vector for direct value assignment.
|
||||
func (v Vec3) Elem() (x, y, z float32) {
|
||||
return v[0], v[1], v[2]
|
||||
}
|
||||
|
||||
// Elem extracts the elements of the vector for direct value assignment.
|
||||
func (v Vec4) Elem() (x, y, z, w float32) {
|
||||
return v[0], v[1], v[2], v[3]
|
||||
}
|
||||
|
||||
// Cross is the vector cross product. This operation is only defined on 3D
|
||||
// vectors. It is equivalent to Vec3{v1[1]*v2[2]-v1[2]*v2[1],
|
||||
// v1[2]*v2[0]-v1[0]*v2[2], v1[0]*v2[1] - v1[1]*v2[0]}. Another interpretation
|
||||
// is that it's the vector whose magnitude is |v1||v2|sin(theta) where theta is
|
||||
// the angle between v1 and v2.
|
||||
//
|
||||
// The cross product is most often used for finding surface normals. The cross
|
||||
// product of vectors will generate a vector that is perpendicular to the plane
|
||||
// they form.
|
||||
//
|
||||
// Technically, a generalized cross product exists as an "(N-1)ary" operation
|
||||
// (that is, the 4D cross product requires 3 4D vectors). But the binary 3D (and
|
||||
// 7D) cross product is the most important. It can be considered the area of a
|
||||
// parallelogram with sides v1 and v2.
|
||||
//
|
||||
// Like the dot product, the cross product is roughly a measure of
|
||||
// directionality. Two normalized perpendicular vectors will return a vector
|
||||
// with a magnitude of 1.0 or -1.0 and two parallel vectors will return a vector
|
||||
// with magnitude 0.0. The cross product is "anticommutative" meaning
|
||||
// v1.Cross(v2) = -v2.Cross(v1), this property can be useful to know when
|
||||
// finding normals, as taking the wrong cross product can lead to the opposite
|
||||
// normal of the one you want.
|
||||
func (v1 Vec3) Cross(v2 Vec3) Vec3 {
|
||||
return Vec3{v1[1]*v2[2] - v1[2]*v2[1], v1[2]*v2[0] - v1[0]*v2[2], v1[0]*v2[1] - v1[1]*v2[0]}
|
||||
}
|
||||
|
||||
// Quat reinterprets this vector as a quaternion, with the individual elements
|
||||
// staying the same.
|
||||
func (v Vec4) Quat() Quat {
|
||||
return Quat{v[3], Vec3{v[0], v[1], v[2]}}
|
||||
}
|
||||
|
||||
// Add performs element-wise addition between two vectors. It is equivalent to iterating
|
||||
// over every element of v1 and adding the corresponding element of v2 to it.
|
||||
func (v1 Vec2) Add(v2 Vec2) Vec2 {
|
||||
return Vec2{v1[0] + v2[0], v1[1] + v2[1]}
|
||||
}
|
||||
|
||||
// Sub performs element-wise subtraction between two vectors. It is equivalent to iterating
|
||||
// over every element of v1 and subtracting the corresponding element of v2 from it.
|
||||
func (v1 Vec2) Sub(v2 Vec2) Vec2 {
|
||||
return Vec2{v1[0] - v2[0], v1[1] - v2[1]}
|
||||
}
|
||||
|
||||
// Mul performs a scalar multiplication between the vector and some constant value
|
||||
// c. This is equivalent to iterating over every vector element and multiplying by c.
|
||||
func (v1 Vec2) Mul(c float32) Vec2 {
|
||||
return Vec2{v1[0] * c, v1[1] * c}
|
||||
}
|
||||
|
||||
// Dot returns the dot product of this vector with another. There are multiple ways
|
||||
// to describe this value. One is the multiplication of their lengths and cos(theta) where
|
||||
// theta is the angle between the vectors: v1.v2 = |v1||v2|cos(theta).
|
||||
//
|
||||
// The other (and what is actually done) is the sum of the element-wise multiplication of all
|
||||
// elements. So for instance, two Vec3s would yield v1.x * v2.x + v1.y * v2.y + v1.z * v2.z.
|
||||
//
|
||||
// This means that the dot product of a vector and itself is the square of its Len (within
|
||||
// the bounds of floating points error).
|
||||
//
|
||||
// The dot product is roughly a measure of how closely two vectors are to pointing in the same
|
||||
// direction. If both vectors are normalized, the value will be -1 for opposite pointing,
|
||||
// one for same pointing, and 0 for perpendicular vectors.
|
||||
func (v1 Vec2) Dot(v2 Vec2) float32 {
|
||||
return v1[0]*v2[0] + v1[1]*v2[1]
|
||||
}
|
||||
|
||||
// Len returns the vector's length. Note that this is NOT the dimension of
|
||||
// the vector (len(v)), but the mathematical length. This is equivalent to the square
|
||||
// root of the sum of the squares of all elements. E.G. for a Vec2 it's
|
||||
// math.Hypot(v[0], v[1]).
|
||||
func (v1 Vec2) Len() float32 {
|
||||
|
||||
return float32(math.Hypot(float64(v1[0]), float64(v1[1])))
|
||||
|
||||
}
|
||||
|
||||
// LenSqr returns the vector's square length. This is equivalent to the sum of the squares of all elements.
|
||||
func (v1 Vec2) LenSqr() float32 {
|
||||
return v1[0]*v1[0] + v1[1]*v1[1]
|
||||
}
|
||||
|
||||
// Normalize normalizes the vector. Normalization is (1/|v|)*v,
|
||||
// making this equivalent to v.Scale(1/v.Len()). If the len is 0.0,
|
||||
// this function will return an infinite value for all elements due
|
||||
// to how floating point division works in Go (n/0.0 = math.Inf(Sign(n))).
|
||||
//
|
||||
// Normalization makes a vector's Len become 1.0 (within the margin of floating point error),
|
||||
// while maintaining its directionality.
|
||||
//
|
||||
// (Can be seen here: http://play.golang.org/p/Aaj7SnbqIp )
|
||||
func (v1 Vec2) Normalize() Vec2 {
|
||||
l := 1.0 / v1.Len()
|
||||
return Vec2{v1[0] * l, v1[1] * l}
|
||||
}
|
||||
|
||||
// ApproxEqual takes in a vector and does an element-wise approximate float
|
||||
// comparison as if FloatEqual had been used
|
||||
func (v1 Vec2) ApproxEqual(v2 Vec2) bool {
|
||||
for i := range v1 {
|
||||
if !FloatEqual(v1[i], v2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ApproxEqualThreshold takes in a threshold for comparing two floats, and uses
|
||||
// it to do an element-wise comparison of the vector to another.
|
||||
func (v1 Vec2) ApproxEqualThreshold(v2 Vec2, threshold float32) bool {
|
||||
for i := range v1 {
|
||||
if !FloatEqualThreshold(v1[i], v2[i], threshold) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ApproxFuncEqual takes in a func that compares two floats, and uses it to do an element-wise
|
||||
// comparison of the vector to another. This is intended to be used with FloatEqualFunc
|
||||
func (v1 Vec2) ApproxFuncEqual(v2 Vec2, eq func(float32, float32) bool) bool {
|
||||
for i := range v1 {
|
||||
if !eq(v1[i], v2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// X is an element access func, it is equivalent to v[n] where
|
||||
// n is some valid index. The mappings are XYZW (X=0, Y=1 etc). Benchmarks
|
||||
// show that this is more or less as fast as direct acces, probably due to
|
||||
// inlining, so use v[0] or v.X() depending on personal preference.
|
||||
func (v Vec2) X() float32 {
|
||||
return v[0]
|
||||
}
|
||||
|
||||
// Y is an element access func, it is equivalent to v[n] where
|
||||
// n is some valid index. The mappings are XYZW (X=0, Y=1 etc). Benchmarks
|
||||
// show that this is more or less as fast as direct acces, probably due to
|
||||
// inlining, so use v[0] or v.X() depending on personal preference.
|
||||
func (v Vec2) Y() float32 {
|
||||
return v[1]
|
||||
}
|
||||
|
||||
// OuterProd2 does the vector outer product
|
||||
// of two vectors. The outer product produces an
|
||||
// 2x2 matrix. E.G. a Vec2 * Vec2 = Mat2.
|
||||
//
|
||||
// The outer product can be thought of as the "opposite"
|
||||
// of the Dot product. The Dot product treats both vectors like matrices
|
||||
// oriented such that the left one has N columns and the right has N rows.
|
||||
// So Vec3.Vec3 = Mat1x3*Mat3x1 = Mat1 = Scalar.
|
||||
//
|
||||
// The outer product orients it so they're facing "outward": Vec2*Vec3
|
||||
// = Mat2x1*Mat1x3 = Mat2x3.
|
||||
func (v1 Vec2) OuterProd2(v2 Vec2) Mat2 {
|
||||
return Mat2{v1[0] * v2[0], v1[1] * v2[0], v1[0] * v2[1], v1[1] * v2[1]}
|
||||
}
|
||||
|
||||
// OuterProd3 does the vector outer product
|
||||
// of two vectors. The outer product produces an
|
||||
// 2x3 matrix. E.G. a Vec2 * Vec3 = Mat2x3.
|
||||
//
|
||||
// The outer product can be thought of as the "opposite"
|
||||
// of the Dot product. The Dot product treats both vectors like matrices
|
||||
// oriented such that the left one has N columns and the right has N rows.
|
||||
// So Vec3.Vec3 = Mat1x3*Mat3x1 = Mat1 = Scalar.
|
||||
//
|
||||
// The outer product orients it so they're facing "outward": Vec2*Vec3
|
||||
// = Mat2x1*Mat1x3 = Mat2x3.
|
||||
func (v1 Vec2) OuterProd3(v2 Vec3) Mat2x3 {
|
||||
return Mat2x3{v1[0] * v2[0], v1[1] * v2[0], v1[0] * v2[1], v1[1] * v2[1], v1[0] * v2[2], v1[1] * v2[2]}
|
||||
}
|
||||
|
||||
// OuterProd4 does the vector outer product
|
||||
// of two vectors. The outer product produces an
|
||||
// 2x4 matrix. E.G. a Vec2 * Vec4 = Mat2x4.
|
||||
//
|
||||
// The outer product can be thought of as the "opposite"
|
||||
// of the Dot product. The Dot product treats both vectors like matrices
|
||||
// oriented such that the left one has N columns and the right has N rows.
|
||||
// So Vec3.Vec3 = Mat1x3*Mat3x1 = Mat1 = Scalar.
|
||||
//
|
||||
// The outer product orients it so they're facing "outward": Vec2*Vec3
|
||||
// = Mat2x1*Mat1x3 = Mat2x3.
|
||||
func (v1 Vec2) OuterProd4(v2 Vec4) Mat2x4 {
|
||||
return Mat2x4{v1[0] * v2[0], v1[1] * v2[0], v1[0] * v2[1], v1[1] * v2[1], v1[0] * v2[2], v1[1] * v2[2], v1[0] * v2[3], v1[1] * v2[3]}
|
||||
}
|
||||
|
||||
// Add performs element-wise addition between two vectors. It is equivalent to iterating
|
||||
// over every element of v1 and adding the corresponding element of v2 to it.
|
||||
func (v1 Vec3) Add(v2 Vec3) Vec3 {
|
||||
return Vec3{v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2]}
|
||||
}
|
||||
|
||||
// Sub performs element-wise subtraction between two vectors. It is equivalent to iterating
|
||||
// over every element of v1 and subtracting the corresponding element of v2 from it.
|
||||
func (v1 Vec3) Sub(v2 Vec3) Vec3 {
|
||||
return Vec3{v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]}
|
||||
}
|
||||
|
||||
// Mul performs a scalar multiplication between the vector and some constant value
|
||||
// c. This is equivalent to iterating over every vector element and multiplying by c.
|
||||
func (v1 Vec3) Mul(c float32) Vec3 {
|
||||
return Vec3{v1[0] * c, v1[1] * c, v1[2] * c}
|
||||
}
|
||||
|
||||
// Dot returns the dot product of this vector with another. There are multiple ways
|
||||
// to describe this value. One is the multiplication of their lengths and cos(theta) where
|
||||
// theta is the angle between the vectors: v1.v2 = |v1||v2|cos(theta).
|
||||
//
|
||||
// The other (and what is actually done) is the sum of the element-wise multiplication of all
|
||||
// elements. So for instance, two Vec3s would yield v1.x * v2.x + v1.y * v2.y + v1.z * v2.z.
|
||||
//
|
||||
// This means that the dot product of a vector and itself is the square of its Len (within
|
||||
// the bounds of floating points error).
|
||||
//
|
||||
// The dot product is roughly a measure of how closely two vectors are to pointing in the same
|
||||
// direction. If both vectors are normalized, the value will be -1 for opposite pointing,
|
||||
// one for same pointing, and 0 for perpendicular vectors.
|
||||
func (v1 Vec3) Dot(v2 Vec3) float32 {
|
||||
return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]
|
||||
}
|
||||
|
||||
// Len returns the vector's length. Note that this is NOT the dimension of
|
||||
// the vector (len(v)), but the mathematical length. This is equivalent to the square
|
||||
// root of the sum of the squares of all elements. E.G. for a Vec2 it's
|
||||
// math.Hypot(v[0], v[1]).
|
||||
func (v1 Vec3) Len() float32 {
|
||||
|
||||
return float32(math.Sqrt(float64(v1[0]*v1[0] + v1[1]*v1[1] + v1[2]*v1[2])))
|
||||
|
||||
}
|
||||
|
||||
// LenSqr returns the vector's square length. This is equivalent to the sum of the squares of all elements.
|
||||
func (v1 Vec3) LenSqr() float32 {
|
||||
return v1[0]*v1[0] + v1[1]*v1[1] + v1[2]*v1[2]
|
||||
}
|
||||
|
||||
// Normalize normalizes the vector. Normalization is (1/|v|)*v,
|
||||
// making this equivalent to v.Scale(1/v.Len()). If the len is 0.0,
|
||||
// this function will return an infinite value for all elements due
|
||||
// to how floating point division works in Go (n/0.0 = math.Inf(Sign(n))).
|
||||
//
|
||||
// Normalization makes a vector's Len become 1.0 (within the margin of floating point error),
|
||||
// while maintaining its directionality.
|
||||
//
|
||||
// (Can be seen here: http://play.golang.org/p/Aaj7SnbqIp )
|
||||
func (v1 Vec3) Normalize() Vec3 {
|
||||
l := 1.0 / v1.Len()
|
||||
return Vec3{v1[0] * l, v1[1] * l, v1[2] * l}
|
||||
}
|
||||
|
||||
// ApproxEqual takes in a vector and does an element-wise approximate float
|
||||
// comparison as if FloatEqual had been used
|
||||
func (v1 Vec3) ApproxEqual(v2 Vec3) bool {
|
||||
for i := range v1 {
|
||||
if !FloatEqual(v1[i], v2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ApproxEqualThreshold takes in a threshold for comparing two floats, and uses
|
||||
// it to do an element-wise comparison of the vector to another.
|
||||
func (v1 Vec3) ApproxEqualThreshold(v2 Vec3, threshold float32) bool {
|
||||
for i := range v1 {
|
||||
if !FloatEqualThreshold(v1[i], v2[i], threshold) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ApproxFuncEqual takes in a func that compares two floats, and uses it to do an element-wise
|
||||
// comparison of the vector to another. This is intended to be used with FloatEqualFunc
|
||||
func (v1 Vec3) ApproxFuncEqual(v2 Vec3, eq func(float32, float32) bool) bool {
|
||||
for i := range v1 {
|
||||
if !eq(v1[i], v2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// X is an element access func, it is equivalent to v[n] where
|
||||
// n is some valid index. The mappings are XYZW (X=0, Y=1 etc). Benchmarks
|
||||
// show that this is more or less as fast as direct acces, probably due to
|
||||
// inlining, so use v[0] or v.X() depending on personal preference.
|
||||
func (v Vec3) X() float32 {
|
||||
return v[0]
|
||||
}
|
||||
|
||||
// Y is an element access func, it is equivalent to v[n] where
|
||||
// n is some valid index. The mappings are XYZW (X=0, Y=1 etc). Benchmarks
|
||||
// show that this is more or less as fast as direct acces, probably due to
|
||||
// inlining, so use v[0] or v.X() depending on personal preference.
|
||||
func (v Vec3) Y() float32 {
|
||||
return v[1]
|
||||
}
|
||||
|
||||
// Z is an element access func, it is equivalent to v[n] where
|
||||
// n is some valid index. The mappings are XYZW (X=0, Y=1 etc). Benchmarks
|
||||
// show that this is more or less as fast as direct acces, probably due to
|
||||
// inlining, so use v[0] or v.X() depending on personal preference.
|
||||
func (v Vec3) Z() float32 {
|
||||
return v[2]
|
||||
}
|
||||
|
||||
// OuterProd2 does the vector outer product
|
||||
// of two vectors. The outer product produces an
|
||||
// 3x2 matrix. E.G. a Vec3 * Vec2 = Mat3x2.
|
||||
//
|
||||
// The outer product can be thought of as the "opposite"
|
||||
// of the Dot product. The Dot product treats both vectors like matrices
|
||||
// oriented such that the left one has N columns and the right has N rows.
|
||||
// So Vec3.Vec3 = Mat1x3*Mat3x1 = Mat1 = Scalar.
|
||||
//
|
||||
// The outer product orients it so they're facing "outward": Vec2*Vec3
|
||||
// = Mat2x1*Mat1x3 = Mat2x3.
|
||||
func (v1 Vec3) OuterProd2(v2 Vec2) Mat3x2 {
|
||||
return Mat3x2{v1[0] * v2[0], v1[1] * v2[0], v1[2] * v2[0], v1[0] * v2[1], v1[1] * v2[1], v1[2] * v2[1]}
|
||||
}
|
||||
|
||||
// OuterProd3 does the vector outer product
|
||||
// of two vectors. The outer product produces an
|
||||
// 3x3 matrix. E.G. a Vec3 * Vec3 = Mat3.
|
||||
//
|
||||
// The outer product can be thought of as the "opposite"
|
||||
// of the Dot product. The Dot product treats both vectors like matrices
|
||||
// oriented such that the left one has N columns and the right has N rows.
|
||||
// So Vec3.Vec3 = Mat1x3*Mat3x1 = Mat1 = Scalar.
|
||||
//
|
||||
// The outer product orients it so they're facing "outward": Vec2*Vec3
|
||||
// = Mat2x1*Mat1x3 = Mat2x3.
|
||||
func (v1 Vec3) OuterProd3(v2 Vec3) Mat3 {
|
||||
return Mat3{v1[0] * v2[0], v1[1] * v2[0], v1[2] * v2[0], v1[0] * v2[1], v1[1] * v2[1], v1[2] * v2[1], v1[0] * v2[2], v1[1] * v2[2], v1[2] * v2[2]}
|
||||
}
|
||||
|
||||
// OuterProd4 does the vector outer product
|
||||
// of two vectors. The outer product produces an
|
||||
// 3x4 matrix. E.G. a Vec3 * Vec4 = Mat3x4.
|
||||
//
|
||||
// The outer product can be thought of as the "opposite"
|
||||
// of the Dot product. The Dot product treats both vectors like matrices
|
||||
// oriented such that the left one has N columns and the right has N rows.
|
||||
// So Vec3.Vec3 = Mat1x3*Mat3x1 = Mat1 = Scalar.
|
||||
//
|
||||
// The outer product orients it so they're facing "outward": Vec2*Vec3
|
||||
// = Mat2x1*Mat1x3 = Mat2x3.
|
||||
func (v1 Vec3) OuterProd4(v2 Vec4) Mat3x4 {
|
||||
return Mat3x4{v1[0] * v2[0], v1[1] * v2[0], v1[2] * v2[0], v1[0] * v2[1], v1[1] * v2[1], v1[2] * v2[1], v1[0] * v2[2], v1[1] * v2[2], v1[2] * v2[2], v1[0] * v2[3], v1[1] * v2[3], v1[2] * v2[3]}
|
||||
}
|
||||
|
||||
// Add performs element-wise addition between two vectors. It is equivalent to iterating
|
||||
// over every element of v1 and adding the corresponding element of v2 to it.
|
||||
func (v1 Vec4) Add(v2 Vec4) Vec4 {
|
||||
return Vec4{v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2], v1[3] + v2[3]}
|
||||
}
|
||||
|
||||
// Sub performs element-wise subtraction between two vectors. It is equivalent to iterating
|
||||
// over every element of v1 and subtracting the corresponding element of v2 from it.
|
||||
func (v1 Vec4) Sub(v2 Vec4) Vec4 {
|
||||
return Vec4{v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2], v1[3] - v2[3]}
|
||||
}
|
||||
|
||||
// Mul performs a scalar multiplication between the vector and some constant value
|
||||
// c. This is equivalent to iterating over every vector element and multiplying by c.
|
||||
func (v1 Vec4) Mul(c float32) Vec4 {
|
||||
return Vec4{v1[0] * c, v1[1] * c, v1[2] * c, v1[3] * c}
|
||||
}
|
||||
|
||||
// Dot returns the dot product of this vector with another. There are multiple ways
|
||||
// to describe this value. One is the multiplication of their lengths and cos(theta) where
|
||||
// theta is the angle between the vectors: v1.v2 = |v1||v2|cos(theta).
|
||||
//
|
||||
// The other (and what is actually done) is the sum of the element-wise multiplication of all
|
||||
// elements. So for instance, two Vec3s would yield v1.x * v2.x + v1.y * v2.y + v1.z * v2.z.
|
||||
//
|
||||
// This means that the dot product of a vector and itself is the square of its Len (within
|
||||
// the bounds of floating points error).
|
||||
//
|
||||
// The dot product is roughly a measure of how closely two vectors are to pointing in the same
|
||||
// direction. If both vectors are normalized, the value will be -1 for opposite pointing,
|
||||
// one for same pointing, and 0 for perpendicular vectors.
|
||||
func (v1 Vec4) Dot(v2 Vec4) float32 {
|
||||
return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2] + v1[3]*v2[3]
|
||||
}
|
||||
|
||||
// Len returns the vector's length. Note that this is NOT the dimension of
|
||||
// the vector (len(v)), but the mathematical length. This is equivalent to the square
|
||||
// root of the sum of the squares of all elements. E.G. for a Vec2 it's
|
||||
// math.Hypot(v[0], v[1]).
|
||||
func (v1 Vec4) Len() float32 {
|
||||
|
||||
return float32(math.Sqrt(float64(v1[0]*v1[0] + v1[1]*v1[1] + v1[2]*v1[2] + v1[3]*v1[3])))
|
||||
|
||||
}
|
||||
|
||||
// LenSqr returns the vector's square length. This is equivalent to the sum of the squares of all elements.
|
||||
func (v1 Vec4) LenSqr() float32 {
|
||||
return v1[0]*v1[0] + v1[1]*v1[1] + v1[2]*v1[2] + v1[3]*v1[3]
|
||||
}
|
||||
|
||||
// Normalize normalizes the vector. Normalization is (1/|v|)*v,
|
||||
// making this equivalent to v.Scale(1/v.Len()). If the len is 0.0,
|
||||
// this function will return an infinite value for all elements due
|
||||
// to how floating point division works in Go (n/0.0 = math.Inf(Sign(n))).
|
||||
//
|
||||
// Normalization makes a vector's Len become 1.0 (within the margin of floating point error),
|
||||
// while maintaining its directionality.
|
||||
//
|
||||
// (Can be seen here: http://play.golang.org/p/Aaj7SnbqIp )
|
||||
func (v1 Vec4) Normalize() Vec4 {
|
||||
l := 1.0 / v1.Len()
|
||||
return Vec4{v1[0] * l, v1[1] * l, v1[2] * l, v1[3] * l}
|
||||
}
|
||||
|
||||
// ApproxEqual takes in a vector and does an element-wise approximate float
|
||||
// comparison as if FloatEqual had been used
|
||||
func (v1 Vec4) ApproxEqual(v2 Vec4) bool {
|
||||
for i := range v1 {
|
||||
if !FloatEqual(v1[i], v2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ApproxEqualThreshold takes in a threshold for comparing two floats, and uses
|
||||
// it to do an element-wise comparison of the vector to another.
|
||||
func (v1 Vec4) ApproxEqualThreshold(v2 Vec4, threshold float32) bool {
|
||||
for i := range v1 {
|
||||
if !FloatEqualThreshold(v1[i], v2[i], threshold) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ApproxFuncEqual takes in a func that compares two floats, and uses it to do an element-wise
|
||||
// comparison of the vector to another. This is intended to be used with FloatEqualFunc
|
||||
func (v1 Vec4) ApproxFuncEqual(v2 Vec4, eq func(float32, float32) bool) bool {
|
||||
for i := range v1 {
|
||||
if !eq(v1[i], v2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// X is an element access func, it is equivalent to v[n] where
|
||||
// n is some valid index. The mappings are XYZW (X=0, Y=1 etc). Benchmarks
|
||||
// show that this is more or less as fast as direct acces, probably due to
|
||||
// inlining, so use v[0] or v.X() depending on personal preference.
|
||||
func (v Vec4) X() float32 {
|
||||
return v[0]
|
||||
}
|
||||
|
||||
// Y is an element access func, it is equivalent to v[n] where
|
||||
// n is some valid index. The mappings are XYZW (X=0, Y=1 etc). Benchmarks
|
||||
// show that this is more or less as fast as direct acces, probably due to
|
||||
// inlining, so use v[0] or v.X() depending on personal preference.
|
||||
func (v Vec4) Y() float32 {
|
||||
return v[1]
|
||||
}
|
||||
|
||||
// Z is an element access func, it is equivalent to v[n] where
|
||||
// n is some valid index. The mappings are XYZW (X=0, Y=1 etc). Benchmarks
|
||||
// show that this is more or less as fast as direct acces, probably due to
|
||||
// inlining, so use v[0] or v.X() depending on personal preference.
|
||||
func (v Vec4) Z() float32 {
|
||||
return v[2]
|
||||
}
|
||||
|
||||
// W is an element access func, it is equivalent to v[n] where
|
||||
// n is some valid index. The mappings are XYZW (X=0, Y=1 etc). Benchmarks
|
||||
// show that this is more or less as fast as direct acces, probably due to
|
||||
// inlining, so use v[0] or v.X() depending on personal preference.
|
||||
func (v Vec4) W() float32 {
|
||||
return v[3]
|
||||
}
|
||||
|
||||
// OuterProd2 does the vector outer product
|
||||
// of two vectors. The outer product produces an
|
||||
// 4x2 matrix. E.G. a Vec4 * Vec2 = Mat4x2.
|
||||
//
|
||||
// The outer product can be thought of as the "opposite"
|
||||
// of the Dot product. The Dot product treats both vectors like matrices
|
||||
// oriented such that the left one has N columns and the right has N rows.
|
||||
// So Vec3.Vec3 = Mat1x3*Mat3x1 = Mat1 = Scalar.
|
||||
//
|
||||
// The outer product orients it so they're facing "outward": Vec2*Vec3
|
||||
// = Mat2x1*Mat1x3 = Mat2x3.
|
||||
func (v1 Vec4) OuterProd2(v2 Vec2) Mat4x2 {
|
||||
return Mat4x2{v1[0] * v2[0], v1[1] * v2[0], v1[2] * v2[0], v1[3] * v2[0], v1[0] * v2[1], v1[1] * v2[1], v1[2] * v2[1], v1[3] * v2[1]}
|
||||
}
|
||||
|
||||
// OuterProd3 does the vector outer product
|
||||
// of two vectors. The outer product produces an
|
||||
// 4x3 matrix. E.G. a Vec4 * Vec3 = Mat4x3.
|
||||
//
|
||||
// The outer product can be thought of as the "opposite"
|
||||
// of the Dot product. The Dot product treats both vectors like matrices
|
||||
// oriented such that the left one has N columns and the right has N rows.
|
||||
// So Vec3.Vec3 = Mat1x3*Mat3x1 = Mat1 = Scalar.
|
||||
//
|
||||
// The outer product orients it so they're facing "outward": Vec2*Vec3
|
||||
// = Mat2x1*Mat1x3 = Mat2x3.
|
||||
func (v1 Vec4) OuterProd3(v2 Vec3) Mat4x3 {
|
||||
return Mat4x3{v1[0] * v2[0], v1[1] * v2[0], v1[2] * v2[0], v1[3] * v2[0], v1[0] * v2[1], v1[1] * v2[1], v1[2] * v2[1], v1[3] * v2[1], v1[0] * v2[2], v1[1] * v2[2], v1[2] * v2[2], v1[3] * v2[2]}
|
||||
}
|
||||
|
||||
// OuterProd4 does the vector outer product
|
||||
// of two vectors. The outer product produces an
|
||||
// 4x4 matrix. E.G. a Vec4 * Vec4 = Mat4.
|
||||
//
|
||||
// The outer product can be thought of as the "opposite"
|
||||
// of the Dot product. The Dot product treats both vectors like matrices
|
||||
// oriented such that the left one has N columns and the right has N rows.
|
||||
// So Vec3.Vec3 = Mat1x3*Mat3x1 = Mat1 = Scalar.
|
||||
//
|
||||
// The outer product orients it so they're facing "outward": Vec2*Vec3
|
||||
// = Mat2x1*Mat1x3 = Mat2x3.
|
||||
func (v1 Vec4) OuterProd4(v2 Vec4) Mat4 {
|
||||
return Mat4{v1[0] * v2[0], v1[1] * v2[0], v1[2] * v2[0], v1[3] * v2[0], v1[0] * v2[1], v1[1] * v2[1], v1[2] * v2[1], v1[3] * v2[1], v1[0] * v2[2], v1[1] * v2[2], v1[2] * v2[2], v1[3] * v2[2], v1[0] * v2[3], v1[1] * v2[3], v1[2] * v2[3], v1[3] * v2[3]}
|
||||
}
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
// Copyright 2014 The go-gl/mathgl Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// <<.Comment>>
|
||||
// Edit <<.TemplateName>> and run "go generate" to make changes.
|
||||
|
||||
package mgl32
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
type Vec2 [2]float32
|
||||
type Vec3 [3]float32
|
||||
type Vec4 [4]float32
|
||||
|
||||
// Vec3 constructs a 3-dimensional vector by appending the given coordinates.
|
||||
func (v Vec2) Vec3(z float32) Vec3 {
|
||||
return Vec3{v[0], v[1], z}
|
||||
}
|
||||
|
||||
// Vec4 constructs a 4-dimensional vector by appending the given coordinates.
|
||||
func (v Vec2) Vec4(z, w float32) Vec4 {
|
||||
return Vec4{v[0], v[1], z, w}
|
||||
}
|
||||
|
||||
// Vec4 constructs a 4-dimensional vector by appending the given coordinates.
|
||||
func (v Vec3) Vec4(w float32) Vec4 {
|
||||
return Vec4{v[0], v[1], v[2], w}
|
||||
}
|
||||
|
||||
// Vec2 constructs a 2-dimensional vector by discarding coordinates.
|
||||
func (v Vec3) Vec2() Vec2 {
|
||||
return Vec2{v[0], v[1]}
|
||||
}
|
||||
|
||||
// Vec2 constructs a 2-dimensional vector by discarding coordinates.
|
||||
func (v Vec4) Vec2() Vec2 {
|
||||
return Vec2{v[0], v[1]}
|
||||
}
|
||||
|
||||
// Vec3 constructs a 3-dimensional vector by discarding coordinates.
|
||||
func (v Vec4) Vec3() Vec3 {
|
||||
return Vec3{v[0], v[1], v[2]}
|
||||
}
|
||||
|
||||
// Elem extracts the elements of the vector for direct value assignment.
|
||||
func (v Vec2) Elem() (x, y float32) {
|
||||
return v[0], v[1]
|
||||
}
|
||||
|
||||
// Elem extracts the elements of the vector for direct value assignment.
|
||||
func (v Vec3) Elem() (x, y, z float32) {
|
||||
return v[0], v[1], v[2]
|
||||
}
|
||||
|
||||
// Elem extracts the elements of the vector for direct value assignment.
|
||||
func (v Vec4) Elem() (x, y, z, w float32) {
|
||||
return v[0], v[1], v[2], v[3]
|
||||
}
|
||||
|
||||
// Cross is the vector cross product. This operation is only defined on 3D
|
||||
// vectors. It is equivalent to Vec3{v1[1]*v2[2]-v1[2]*v2[1],
|
||||
// v1[2]*v2[0]-v1[0]*v2[2], v1[0]*v2[1] - v1[1]*v2[0]}. Another interpretation
|
||||
// is that it's the vector whose magnitude is |v1||v2|sin(theta) where theta is
|
||||
// the angle between v1 and v2.
|
||||
//
|
||||
// The cross product is most often used for finding surface normals. The cross
|
||||
// product of vectors will generate a vector that is perpendicular to the plane
|
||||
// they form.
|
||||
//
|
||||
// Technically, a generalized cross product exists as an "(N-1)ary" operation
|
||||
// (that is, the 4D cross product requires 3 4D vectors). But the binary 3D (and
|
||||
// 7D) cross product is the most important. It can be considered the area of a
|
||||
// parallelogram with sides v1 and v2.
|
||||
//
|
||||
// Like the dot product, the cross product is roughly a measure of
|
||||
// directionality. Two normalized perpendicular vectors will return a vector
|
||||
// with a magnitude of 1.0 or -1.0 and two parallel vectors will return a vector
|
||||
// with magnitude 0.0. The cross product is "anticommutative" meaning
|
||||
// v1.Cross(v2) = -v2.Cross(v1), this property can be useful to know when
|
||||
// finding normals, as taking the wrong cross product can lead to the opposite
|
||||
// normal of the one you want.
|
||||
func (v1 Vec3) Cross(v2 Vec3) Vec3 {
|
||||
return Vec3{v1[1]*v2[2] - v1[2]*v2[1], v1[2]*v2[0] - v1[0]*v2[2], v1[0]*v2[1] - v1[1]*v2[0]}
|
||||
}
|
||||
|
||||
// Quat reinterprets this vector as a quaternion, with the individual elements
|
||||
// staying the same.
|
||||
func (v Vec4) Quat() Quat {
|
||||
return Quat{v[3], Vec3{v[0], v[1], v[2]}}
|
||||
}
|
||||
|
||||
|
||||
<</* Common functions for all vectors */>>
|
||||
<<range $m := enum 2 3 4>>
|
||||
<<$type := typename $m 1>>
|
||||
|
||||
// Add performs element-wise addition between two vectors. It is equivalent to iterating
|
||||
// over every element of v1 and adding the corresponding element of v2 to it.
|
||||
func (v1 <<$type>>) Add(v2 <<$type>>) <<$type>> {
|
||||
return <<$type>>{<<range $i := iter 0 $m>> v1[<<$i>>] + v2[<<$i>>], <<end>>}
|
||||
}
|
||||
|
||||
// Sub performs element-wise subtraction between two vectors. It is equivalent to iterating
|
||||
// over every element of v1 and subtracting the corresponding element of v2 from it.
|
||||
func (v1 <<$type>>) Sub(v2 <<$type>>) <<$type>> {
|
||||
return <<$type>>{<<range $i := iter 0 $m>> v1[<<$i>>] - v2[<<$i>>], <<end>>}
|
||||
}
|
||||
|
||||
// Mul performs a scalar multiplication between the vector and some constant value
|
||||
// c. This is equivalent to iterating over every vector element and multiplying by c.
|
||||
func (v1 <<$type>>) Mul(c float32) <<$type>> {
|
||||
return <<$type>>{<<range $i := iter 0 $m>> v1[<<$i>>] * c, <<end>>}
|
||||
}
|
||||
|
||||
// Dot returns the dot product of this vector with another. There are multiple ways
|
||||
// to describe this value. One is the multiplication of their lengths and cos(theta) where
|
||||
// theta is the angle between the vectors: v1.v2 = |v1||v2|cos(theta).
|
||||
//
|
||||
// The other (and what is actually done) is the sum of the element-wise multiplication of all
|
||||
// elements. So for instance, two Vec3s would yield v1.x * v2.x + v1.y * v2.y + v1.z * v2.z.
|
||||
//
|
||||
// This means that the dot product of a vector and itself is the square of its Len (within
|
||||
// the bounds of floating points error).
|
||||
//
|
||||
// The dot product is roughly a measure of how closely two vectors are to pointing in the same
|
||||
// direction. If both vectors are normalized, the value will be -1 for opposite pointing,
|
||||
// one for same pointing, and 0 for perpendicular vectors.
|
||||
func (v1 <<$type>>) Dot(v2 <<$type>>) float32 {
|
||||
return <<range $i := iter 0 $m>><<sep "+" $i>> v1[<<$i>>]*v2[<<$i>>] <<end>>
|
||||
}
|
||||
|
||||
// Len returns the vector's length. Note that this is NOT the dimension of
|
||||
// the vector (len(v)), but the mathematical length. This is equivalent to the square
|
||||
// root of the sum of the squares of all elements. E.G. for a Vec2 it's
|
||||
// math.Hypot(v[0], v[1]).
|
||||
func (v1 <<$type>>) Len() float32 {
|
||||
<<if eq $m 2 >>
|
||||
return float32(math.Hypot(float64(v1[0]), float64(v1[1])))
|
||||
<<else>>
|
||||
return float32(math.Sqrt(float64(<<repeat $m "v1[%d]*v1[%d]" "+">>)))
|
||||
<<end>>
|
||||
}
|
||||
|
||||
// LenSqr returns the vector's square length. This is equivalent to the sum of the squares of all elements.
|
||||
func (v1 <<$type>>) LenSqr() float32 {
|
||||
return <<repeat $m "v1[%d]*v1[%d]" "+">>
|
||||
}
|
||||
|
||||
// Normalize normalizes the vector. Normalization is (1/|v|)*v,
|
||||
// making this equivalent to v.Scale(1/v.Len()). If the len is 0.0,
|
||||
// this function will return an infinite value for all elements due
|
||||
// to how floating point division works in Go (n/0.0 = math.Inf(Sign(n))).
|
||||
//
|
||||
// Normalization makes a vector's Len become 1.0 (within the margin of floating point error),
|
||||
// while maintaining its directionality.
|
||||
//
|
||||
// (Can be seen here: http://play.golang.org/p/Aaj7SnbqIp )
|
||||
func (v1 <<$type>>) Normalize() <<$type>> {
|
||||
l := 1.0 / v1.Len()
|
||||
return <<$type>>{<<range $i := iter 0 $m>>v1[<<$i>>] * l,<<end>>}
|
||||
}
|
||||
|
||||
// ApproxEqual takes in a vector and does an element-wise approximate float
|
||||
// comparison as if FloatEqual had been used
|
||||
func (v1 <<$type>>) ApproxEqual(v2 <<$type>>) bool {
|
||||
for i := range v1 {
|
||||
if !FloatEqual(v1[i], v2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ApproxEqualThreshold takes in a threshold for comparing two floats, and uses
|
||||
// it to do an element-wise comparison of the vector to another.
|
||||
func (v1 <<$type>>) ApproxEqualThreshold(v2 <<$type>>, threshold float32) bool {
|
||||
for i := range v1 {
|
||||
if !FloatEqualThreshold(v1[i], v2[i], threshold) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ApproxFuncEqual takes in a func that compares two floats, and uses it to do an element-wise
|
||||
// comparison of the vector to another. This is intended to be used with FloatEqualFunc
|
||||
func (v1 <<$type>>) ApproxFuncEqual(v2 <<$type>>, eq func(float32, float32) bool) bool {
|
||||
for i := range v1 {
|
||||
if !eq(v1[i], v2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
<<range $i := iter 0 $m>>
|
||||
// <<elementname $i>> is an element access func, it is equivalent to v[n] where
|
||||
// n is some valid index. The mappings are XYZW (X=0, Y=1 etc). Benchmarks
|
||||
// show that this is more or less as fast as direct acces, probably due to
|
||||
// inlining, so use v[0] or v.X() depending on personal preference.
|
||||
func (v <<$type>>) <<elementname $i>>() float32 {
|
||||
return v[<<$i>>]
|
||||
}
|
||||
<<end>>
|
||||
|
||||
<<range $n := enum 2 3 4>>
|
||||
// OuterProd<<$n>> does the vector outer product
|
||||
// of two vectors. The outer product produces an
|
||||
// <<$m>>x<<$n>> matrix. E.G. a Vec<<$m>> * Vec<<$n>> = <<typename $m $n>>.
|
||||
//
|
||||
// The outer product can be thought of as the "opposite"
|
||||
// of the Dot product. The Dot product treats both vectors like matrices
|
||||
// oriented such that the left one has N columns and the right has N rows.
|
||||
// So Vec3.Vec3 = Mat1x3*Mat3x1 = Mat1 = Scalar.
|
||||
//
|
||||
// The outer product orients it so they're facing "outward": Vec2*Vec3
|
||||
// = Mat2x1*Mat1x3 = Mat2x3.
|
||||
func (v1 <<$type>>) OuterProd<<$n>>(v2 <<typename $n 1>>) <<typename $m $n>> {
|
||||
return <<typename $m $n>>{<<range $i := matiter $m $n>>v1[<<$i.M>>] * v2[<<$i.N>>], <<end>>}
|
||||
}
|
||||
<<end>>
|
||||
|
||||
<<end>> <</* range $m */>>
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
# How to Contribute
|
||||
|
||||
## Getting Started
|
||||
|
||||
- Fork the repository on GitHub
|
||||
- Read the [README](README.markdown) for build and test instructions
|
||||
- Play with the project, submit bugs, submit patches!
|
||||
|
||||
## Contribution Flow
|
||||
|
||||
This is a rough outline of what a contributor's workflow looks like:
|
||||
|
||||
- Create a topic branch from where you want to base your work (usually master).
|
||||
- Make commits of logical units.
|
||||
- Make sure your commit messages are in the proper format (see below).
|
||||
- Push your changes to a topic branch in your fork of the repository.
|
||||
- Make sure the tests pass, and add any new tests as appropriate.
|
||||
- Submit a pull request to the original repository.
|
||||
|
||||
Thanks for your contributions!
|
||||
|
||||
### Format of the Commit Message
|
||||
|
||||
We follow a rough convention for commit messages that is designed to answer two
|
||||
questions: what changed and why. The subject line should feature the what and
|
||||
the body of the commit should describe the why.
|
||||
|
||||
```
|
||||
scripts: add the test-cluster command
|
||||
|
||||
this uses tmux to setup a test cluster that you can easily kill and
|
||||
start for debugging.
|
||||
|
||||
Fixes #38
|
||||
```
|
||||
|
||||
The format can be described more formally as follows:
|
||||
|
||||
```
|
||||
<subsystem>: <what changed>
|
||||
<BLANK LINE>
|
||||
<why this change was made>
|
||||
<BLANK LINE>
|
||||
<footer>
|
||||
```
|
||||
|
||||
The first line is the subject and should be no longer than 70 characters, the
|
||||
second line is always blank, and other lines should be wrapped at 80 characters.
|
||||
This allows the message to be easier to read on GitHub as well as in various
|
||||
git tools.
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
Copyright (c) 2013, Georg Reinke (<guelfey at gmail dot com>), Google
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
Brandon Philips <brandon@ifup.org> (@philips)
|
||||
Brian Waldon <brian@waldon.cc> (@bcwaldon)
|
||||
John Southworth <jsouthwo@brocade.com> (@jsouthworth)
|
||||
+46
@@ -0,0 +1,46 @@
|
||||

|
||||
|
||||
dbus
|
||||
----
|
||||
|
||||
dbus is a simple library that implements native Go client bindings for the
|
||||
D-Bus message bus system.
|
||||
|
||||
### Features
|
||||
|
||||
* Complete native implementation of the D-Bus message protocol
|
||||
* Go-like API (channels for signals / asynchronous method calls, Goroutine-safe connections)
|
||||
* Subpackages that help with the introspection / property interfaces
|
||||
|
||||
### Installation
|
||||
|
||||
This packages requires Go 1.12 or later. It can be installed by running the command below:
|
||||
|
||||
```
|
||||
go get github.com/godbus/dbus/v5
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
The complete package documentation and some simple examples are available at
|
||||
[godoc.org](http://godoc.org/github.com/godbus/dbus). Also, the
|
||||
[_examples](https://github.com/godbus/dbus/tree/master/_examples) directory
|
||||
gives a short overview over the basic usage.
|
||||
|
||||
#### Projects using godbus
|
||||
- [fyne](https://github.com/fyne-io/fyne) a cross platform GUI in Go inspired by Material Design.
|
||||
- [fynedesk](https://github.com/fyne-io/fynedesk) a full desktop environment for Linux/Unix using Fyne.
|
||||
- [go-bluetooth](https://github.com/muka/go-bluetooth) provides a bluetooth client over bluez dbus API.
|
||||
- [iwd](https://github.com/shibumi/iwd) go bindings for the internet wireless daemon "iwd".
|
||||
- [notify](https://github.com/esiqveland/notify) provides desktop notifications over dbus into a library.
|
||||
- [playerbm](https://github.com/altdesktop/playerbm) a bookmark utility for media players.
|
||||
|
||||
Please note that the API is considered unstable for now and may change without
|
||||
further notice.
|
||||
|
||||
### License
|
||||
|
||||
go.dbus is available under the Simplified BSD License; see LICENSE for the full
|
||||
text.
|
||||
|
||||
Nearly all of the credit for this library goes to github.com/guelfey/go.dbus.
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// AuthStatus represents the Status of an authentication mechanism.
|
||||
type AuthStatus byte
|
||||
|
||||
const (
|
||||
// AuthOk signals that authentication is finished; the next command
|
||||
// from the server should be an OK.
|
||||
AuthOk AuthStatus = iota
|
||||
|
||||
// AuthContinue signals that additional data is needed; the next command
|
||||
// from the server should be a DATA.
|
||||
AuthContinue
|
||||
|
||||
// AuthError signals an error; the server sent invalid data or some
|
||||
// other unexpected thing happened and the current authentication
|
||||
// process should be aborted.
|
||||
AuthError
|
||||
)
|
||||
|
||||
type authState byte
|
||||
|
||||
const (
|
||||
waitingForData authState = iota
|
||||
waitingForOk
|
||||
waitingForReject
|
||||
)
|
||||
|
||||
// Auth defines the behaviour of an authentication mechanism.
|
||||
type Auth interface {
|
||||
// Return the name of the mechanism, the argument to the first AUTH command
|
||||
// and the next status.
|
||||
FirstData() (name, resp []byte, status AuthStatus)
|
||||
|
||||
// Process the given DATA command, and return the argument to the DATA
|
||||
// command and the next status. If len(resp) == 0, no DATA command is sent.
|
||||
HandleData(data []byte) (resp []byte, status AuthStatus)
|
||||
}
|
||||
|
||||
// Auth authenticates the connection, trying the given list of authentication
|
||||
// mechanisms (in that order). If nil is passed, the EXTERNAL and
|
||||
// DBUS_COOKIE_SHA1 mechanisms are tried for the current user. For private
|
||||
// connections, this method must be called before sending any messages to the
|
||||
// bus. Auth must not be called on shared connections.
|
||||
func (conn *Conn) Auth(methods []Auth) error {
|
||||
if methods == nil {
|
||||
uid := strconv.Itoa(os.Geteuid())
|
||||
methods = []Auth{AuthExternal(uid), AuthCookieSha1(uid, getHomeDir())}
|
||||
}
|
||||
in := bufio.NewReader(conn.transport)
|
||||
err := conn.transport.SendNullByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = authWriteLine(conn.transport, []byte("AUTH"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s, err := authReadLine(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(s) < 2 || !bytes.Equal(s[0], []byte("REJECTED")) {
|
||||
return errors.New("dbus: authentication protocol error")
|
||||
}
|
||||
s = s[1:]
|
||||
for _, v := range s {
|
||||
for _, m := range methods {
|
||||
if name, _, status := m.FirstData(); bytes.Equal(v, name) {
|
||||
var ok bool
|
||||
err = authWriteLine(conn.transport, []byte("AUTH"), v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch status {
|
||||
case AuthOk:
|
||||
err, ok = conn.tryAuth(m, waitingForOk, in)
|
||||
case AuthContinue:
|
||||
err, ok = conn.tryAuth(m, waitingForData, in)
|
||||
default:
|
||||
panic("dbus: invalid authentication status")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
if conn.transport.SupportsUnixFDs() {
|
||||
err = authWriteLine(conn, []byte("NEGOTIATE_UNIX_FD"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
line, err := authReadLine(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case bytes.Equal(line[0], []byte("AGREE_UNIX_FD")):
|
||||
conn.EnableUnixFDs()
|
||||
conn.unixFD = true
|
||||
case bytes.Equal(line[0], []byte("ERROR")):
|
||||
default:
|
||||
return errors.New("dbus: authentication protocol error")
|
||||
}
|
||||
}
|
||||
err = authWriteLine(conn.transport, []byte("BEGIN"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go conn.inWorker()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors.New("dbus: authentication failed")
|
||||
}
|
||||
|
||||
// tryAuth tries to authenticate with m as the mechanism, using state as the
|
||||
// initial authState and in for reading input. It returns (nil, true) on
|
||||
// success, (nil, false) on a REJECTED and (someErr, false) if some other
|
||||
// error occurred.
|
||||
func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (error, bool) {
|
||||
for {
|
||||
s, err := authReadLine(in)
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
switch {
|
||||
case state == waitingForData && string(s[0]) == "DATA":
|
||||
if len(s) != 2 {
|
||||
err = authWriteLine(conn.transport, []byte("ERROR"))
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
continue
|
||||
}
|
||||
data, status := m.HandleData(s[1])
|
||||
switch status {
|
||||
case AuthOk, AuthContinue:
|
||||
if len(data) != 0 {
|
||||
err = authWriteLine(conn.transport, []byte("DATA"), data)
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
}
|
||||
if status == AuthOk {
|
||||
state = waitingForOk
|
||||
}
|
||||
case AuthError:
|
||||
err = authWriteLine(conn.transport, []byte("ERROR"))
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
}
|
||||
case state == waitingForData && string(s[0]) == "REJECTED":
|
||||
return nil, false
|
||||
case state == waitingForData && string(s[0]) == "ERROR":
|
||||
err = authWriteLine(conn.transport, []byte("CANCEL"))
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
state = waitingForReject
|
||||
case state == waitingForData && string(s[0]) == "OK":
|
||||
if len(s) != 2 {
|
||||
err = authWriteLine(conn.transport, []byte("CANCEL"))
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
state = waitingForReject
|
||||
} else {
|
||||
conn.uuid = string(s[1])
|
||||
return nil, true
|
||||
}
|
||||
case state == waitingForData:
|
||||
err = authWriteLine(conn.transport, []byte("ERROR"))
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
case state == waitingForOk && string(s[0]) == "OK":
|
||||
if len(s) != 2 {
|
||||
err = authWriteLine(conn.transport, []byte("CANCEL"))
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
state = waitingForReject
|
||||
} else {
|
||||
conn.uuid = string(s[1])
|
||||
return nil, true
|
||||
}
|
||||
case state == waitingForOk && string(s[0]) == "DATA":
|
||||
err = authWriteLine(conn.transport, []byte("DATA"))
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
case state == waitingForOk && string(s[0]) == "REJECTED":
|
||||
return nil, false
|
||||
case state == waitingForOk && string(s[0]) == "ERROR":
|
||||
err = authWriteLine(conn.transport, []byte("CANCEL"))
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
state = waitingForReject
|
||||
case state == waitingForOk:
|
||||
err = authWriteLine(conn.transport, []byte("ERROR"))
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
case state == waitingForReject && string(s[0]) == "REJECTED":
|
||||
return nil, false
|
||||
case state == waitingForReject:
|
||||
return errors.New("dbus: authentication protocol error"), false
|
||||
default:
|
||||
panic("dbus: invalid auth state")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// authReadLine reads a line and separates it into its fields.
|
||||
func authReadLine(in *bufio.Reader) ([][]byte, error) {
|
||||
data, err := in.ReadBytes('\n')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = bytes.TrimSuffix(data, []byte("\r\n"))
|
||||
return bytes.Split(data, []byte{' '}), nil
|
||||
}
|
||||
|
||||
// authWriteLine writes the given line in the authentication protocol format
|
||||
// (elements of data separated by a " " and terminated by "\r\n").
|
||||
func authWriteLine(out io.Writer, data ...[]byte) error {
|
||||
buf := make([]byte, 0)
|
||||
for i, v := range data {
|
||||
buf = append(buf, v...)
|
||||
if i != len(data)-1 {
|
||||
buf = append(buf, ' ')
|
||||
}
|
||||
}
|
||||
buf = append(buf, '\r')
|
||||
buf = append(buf, '\n')
|
||||
n, err := out.Write(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != len(buf) {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package dbus
|
||||
|
||||
// AuthAnonymous returns an Auth that uses the ANONYMOUS mechanism.
|
||||
func AuthAnonymous() Auth {
|
||||
return &authAnonymous{}
|
||||
}
|
||||
|
||||
type authAnonymous struct{}
|
||||
|
||||
func (a *authAnonymous) FirstData() (name, resp []byte, status AuthStatus) {
|
||||
return []byte("ANONYMOUS"), nil, AuthOk
|
||||
}
|
||||
|
||||
func (a *authAnonymous) HandleData(data []byte) (resp []byte, status AuthStatus) {
|
||||
return nil, AuthError
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// AuthExternal returns an Auth that authenticates as the given user with the
|
||||
// EXTERNAL mechanism.
|
||||
func AuthExternal(user string) Auth {
|
||||
return authExternal{user}
|
||||
}
|
||||
|
||||
// AuthExternal implements the EXTERNAL authentication mechanism.
|
||||
type authExternal struct {
|
||||
user string
|
||||
}
|
||||
|
||||
func (a authExternal) FirstData() ([]byte, []byte, AuthStatus) {
|
||||
b := make([]byte, 2*len(a.user))
|
||||
hex.Encode(b, []byte(a.user))
|
||||
return []byte("EXTERNAL"), b, AuthOk
|
||||
}
|
||||
|
||||
func (a authExternal) HandleData(b []byte) ([]byte, AuthStatus) {
|
||||
return nil, AuthError
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
)
|
||||
|
||||
// AuthCookieSha1 returns an Auth that authenticates as the given user with the
|
||||
// DBUS_COOKIE_SHA1 mechanism. The home parameter should specify the home
|
||||
// directory of the user.
|
||||
func AuthCookieSha1(user, home string) Auth {
|
||||
return authCookieSha1{user, home}
|
||||
}
|
||||
|
||||
type authCookieSha1 struct {
|
||||
user, home string
|
||||
}
|
||||
|
||||
func (a authCookieSha1) FirstData() ([]byte, []byte, AuthStatus) {
|
||||
b := make([]byte, 2*len(a.user))
|
||||
hex.Encode(b, []byte(a.user))
|
||||
return []byte("DBUS_COOKIE_SHA1"), b, AuthContinue
|
||||
}
|
||||
|
||||
func (a authCookieSha1) HandleData(data []byte) ([]byte, AuthStatus) {
|
||||
challenge := make([]byte, len(data)/2)
|
||||
_, err := hex.Decode(challenge, data)
|
||||
if err != nil {
|
||||
return nil, AuthError
|
||||
}
|
||||
b := bytes.Split(challenge, []byte{' '})
|
||||
if len(b) != 3 {
|
||||
return nil, AuthError
|
||||
}
|
||||
context := b[0]
|
||||
id := b[1]
|
||||
svchallenge := b[2]
|
||||
cookie := a.getCookie(context, id)
|
||||
if cookie == nil {
|
||||
return nil, AuthError
|
||||
}
|
||||
clchallenge := a.generateChallenge()
|
||||
if clchallenge == nil {
|
||||
return nil, AuthError
|
||||
}
|
||||
hash := sha1.New()
|
||||
hash.Write(bytes.Join([][]byte{svchallenge, clchallenge, cookie}, []byte{':'}))
|
||||
hexhash := make([]byte, 2*hash.Size())
|
||||
hex.Encode(hexhash, hash.Sum(nil))
|
||||
data = append(clchallenge, ' ')
|
||||
data = append(data, hexhash...)
|
||||
resp := make([]byte, 2*len(data))
|
||||
hex.Encode(resp, data)
|
||||
return resp, AuthOk
|
||||
}
|
||||
|
||||
// getCookie searches for the cookie identified by id in context and returns
|
||||
// the cookie content or nil. (Since HandleData can't return a specific error,
|
||||
// but only whether an error occurred, this function also doesn't bother to
|
||||
// return an error.)
|
||||
func (a authCookieSha1) getCookie(context, id []byte) []byte {
|
||||
file, err := os.Open(a.home + "/.dbus-keyrings/" + string(context))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer file.Close()
|
||||
rd := bufio.NewReader(file)
|
||||
for {
|
||||
line, err := rd.ReadBytes('\n')
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
line = line[:len(line)-1]
|
||||
b := bytes.Split(line, []byte{' '})
|
||||
if len(b) != 3 {
|
||||
return nil
|
||||
}
|
||||
if bytes.Equal(b[0], id) {
|
||||
return b[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generateChallenge returns a random, hex-encoded challenge, or nil on error
|
||||
// (see above).
|
||||
func (a authCookieSha1) generateChallenge() []byte {
|
||||
b := make([]byte, 16)
|
||||
n, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if n != 16 {
|
||||
return nil
|
||||
}
|
||||
enc := make([]byte, 32)
|
||||
hex.Encode(enc, b)
|
||||
return enc
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var errSignature = errors.New("dbus: mismatched signature")
|
||||
|
||||
// Call represents a pending or completed method call.
|
||||
type Call struct {
|
||||
Destination string
|
||||
Path ObjectPath
|
||||
Method string
|
||||
Args []interface{}
|
||||
|
||||
// Strobes when the call is complete.
|
||||
Done chan *Call
|
||||
|
||||
// After completion, the error status. If this is non-nil, it may be an
|
||||
// error message from the peer (with Error as its type) or some other error.
|
||||
Err error
|
||||
|
||||
// Holds the response once the call is done.
|
||||
Body []interface{}
|
||||
|
||||
// ResponseSequence stores the sequence number of the DBus message containing
|
||||
// the call response (or error). This can be compared to the sequence number
|
||||
// of other call responses and signals on this connection to determine their
|
||||
// relative ordering on the underlying DBus connection.
|
||||
// For errors, ResponseSequence is populated only if the error came from a
|
||||
// DBusMessage that was received or if there was an error receiving. In case of
|
||||
// failure to make the call, ResponseSequence will be NoSequence.
|
||||
ResponseSequence Sequence
|
||||
|
||||
// tracks context and canceler
|
||||
ctx context.Context
|
||||
ctxCanceler context.CancelFunc
|
||||
}
|
||||
|
||||
func (c *Call) Context() context.Context {
|
||||
if c.ctx == nil {
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
return c.ctx
|
||||
}
|
||||
|
||||
func (c *Call) ContextCancel() {
|
||||
if c.ctxCanceler != nil {
|
||||
c.ctxCanceler()
|
||||
}
|
||||
}
|
||||
|
||||
// Store stores the body of the reply into the provided pointers. It returns
|
||||
// an error if the signatures of the body and retvalues don't match, or if
|
||||
// the error status is not nil.
|
||||
func (c *Call) Store(retvalues ...interface{}) error {
|
||||
if c.Err != nil {
|
||||
return c.Err
|
||||
}
|
||||
|
||||
return Store(c.Body, retvalues...)
|
||||
}
|
||||
|
||||
func (c *Call) done() {
|
||||
c.Done <- c
|
||||
c.ContextCancel()
|
||||
}
|
||||
+996
@@ -0,0 +1,996 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
systemBus *Conn
|
||||
systemBusLck sync.Mutex
|
||||
sessionBus *Conn
|
||||
sessionBusLck sync.Mutex
|
||||
)
|
||||
|
||||
// ErrClosed is the error returned by calls on a closed connection.
|
||||
var ErrClosed = errors.New("dbus: connection closed by user")
|
||||
|
||||
// Conn represents a connection to a message bus (usually, the system or
|
||||
// session bus).
|
||||
//
|
||||
// Connections are either shared or private. Shared connections
|
||||
// are shared between calls to the functions that return them. As a result,
|
||||
// the methods Close, Auth and Hello must not be called on them.
|
||||
//
|
||||
// Multiple goroutines may invoke methods on a connection simultaneously.
|
||||
type Conn struct {
|
||||
transport
|
||||
|
||||
ctx context.Context
|
||||
cancelCtx context.CancelFunc
|
||||
|
||||
closeOnce sync.Once
|
||||
closeErr error
|
||||
|
||||
busObj BusObject
|
||||
unixFD bool
|
||||
uuid string
|
||||
|
||||
handler Handler
|
||||
signalHandler SignalHandler
|
||||
serialGen SerialGenerator
|
||||
inInt Interceptor
|
||||
outInt Interceptor
|
||||
auth []Auth
|
||||
|
||||
names *nameTracker
|
||||
calls *callTracker
|
||||
outHandler *outputHandler
|
||||
|
||||
eavesdropped chan<- *Message
|
||||
eavesdroppedLck sync.Mutex
|
||||
}
|
||||
|
||||
// SessionBus returns a shared connection to the session bus, connecting to it
|
||||
// if not already done.
|
||||
func SessionBus() (conn *Conn, err error) {
|
||||
sessionBusLck.Lock()
|
||||
defer sessionBusLck.Unlock()
|
||||
if sessionBus != nil &&
|
||||
sessionBus.Connected() {
|
||||
return sessionBus, nil
|
||||
}
|
||||
defer func() {
|
||||
if conn != nil {
|
||||
sessionBus = conn
|
||||
}
|
||||
}()
|
||||
conn, err = ConnectSessionBus()
|
||||
return
|
||||
}
|
||||
|
||||
func getSessionBusAddress(autolaunch bool) (string, error) {
|
||||
if address := os.Getenv("DBUS_SESSION_BUS_ADDRESS"); address != "" && address != "autolaunch:" {
|
||||
return address, nil
|
||||
|
||||
} else if address := tryDiscoverDbusSessionBusAddress(); address != "" {
|
||||
os.Setenv("DBUS_SESSION_BUS_ADDRESS", address)
|
||||
return address, nil
|
||||
}
|
||||
if !autolaunch {
|
||||
return "", errors.New("dbus: couldn't determine address of session bus")
|
||||
}
|
||||
return getSessionBusPlatformAddress()
|
||||
}
|
||||
|
||||
// SessionBusPrivate returns a new private connection to the session bus.
|
||||
func SessionBusPrivate(opts ...ConnOption) (*Conn, error) {
|
||||
address, err := getSessionBusAddress(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Dial(address, opts...)
|
||||
}
|
||||
|
||||
// SessionBusPrivate returns a new private connection to the session bus. If
|
||||
// the session bus is not already open, do not attempt to launch it.
|
||||
func SessionBusPrivateNoAutoStartup(opts ...ConnOption) (*Conn, error) {
|
||||
address, err := getSessionBusAddress(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Dial(address, opts...)
|
||||
}
|
||||
|
||||
// SessionBusPrivate returns a new private connection to the session bus.
|
||||
//
|
||||
// Deprecated: use SessionBusPrivate with options instead.
|
||||
func SessionBusPrivateHandler(handler Handler, signalHandler SignalHandler) (*Conn, error) {
|
||||
return SessionBusPrivate(WithHandler(handler), WithSignalHandler(signalHandler))
|
||||
}
|
||||
|
||||
// SystemBus returns a shared connection to the system bus, connecting to it if
|
||||
// not already done.
|
||||
func SystemBus() (conn *Conn, err error) {
|
||||
systemBusLck.Lock()
|
||||
defer systemBusLck.Unlock()
|
||||
if systemBus != nil &&
|
||||
systemBus.Connected() {
|
||||
return systemBus, nil
|
||||
}
|
||||
defer func() {
|
||||
if conn != nil {
|
||||
systemBus = conn
|
||||
}
|
||||
}()
|
||||
conn, err = ConnectSystemBus()
|
||||
return
|
||||
}
|
||||
|
||||
// ConnectSessionBus connects to the session bus.
|
||||
func ConnectSessionBus(opts ...ConnOption) (*Conn, error) {
|
||||
address, err := getSessionBusAddress(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return Connect(address, opts...)
|
||||
}
|
||||
|
||||
// ConnectSystemBus connects to the system bus.
|
||||
func ConnectSystemBus(opts ...ConnOption) (*Conn, error) {
|
||||
return Connect(getSystemBusPlatformAddress(), opts...)
|
||||
}
|
||||
|
||||
// Connect connects to the given address.
|
||||
//
|
||||
// Returned connection is ready to use and doesn't require calling
|
||||
// Auth and Hello methods to make it usable.
|
||||
func Connect(address string, opts ...ConnOption) (*Conn, error) {
|
||||
conn, err := Dial(address, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = conn.Auth(conn.auth); err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
if err = conn.Hello(); err != nil {
|
||||
_ = conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// SystemBusPrivate returns a new private connection to the system bus.
|
||||
// Note: this connection is not ready to use. One must perform Auth and Hello
|
||||
// on the connection before it is usable.
|
||||
func SystemBusPrivate(opts ...ConnOption) (*Conn, error) {
|
||||
return Dial(getSystemBusPlatformAddress(), opts...)
|
||||
}
|
||||
|
||||
// SystemBusPrivateHandler returns a new private connection to the system bus, using the provided handlers.
|
||||
//
|
||||
// Deprecated: use SystemBusPrivate with options instead.
|
||||
func SystemBusPrivateHandler(handler Handler, signalHandler SignalHandler) (*Conn, error) {
|
||||
return SystemBusPrivate(WithHandler(handler), WithSignalHandler(signalHandler))
|
||||
}
|
||||
|
||||
// Dial establishes a new private connection to the message bus specified by address.
|
||||
func Dial(address string, opts ...ConnOption) (*Conn, error) {
|
||||
tr, err := getTransport(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newConn(tr, opts...)
|
||||
}
|
||||
|
||||
// DialHandler establishes a new private connection to the message bus specified by address, using the supplied handlers.
|
||||
//
|
||||
// Deprecated: use Dial with options instead.
|
||||
func DialHandler(address string, handler Handler, signalHandler SignalHandler) (*Conn, error) {
|
||||
return Dial(address, WithHandler(handler), WithSignalHandler(signalHandler))
|
||||
}
|
||||
|
||||
// ConnOption is a connection option.
|
||||
type ConnOption func(conn *Conn) error
|
||||
|
||||
// WithHandler overrides the default handler.
|
||||
func WithHandler(handler Handler) ConnOption {
|
||||
return func(conn *Conn) error {
|
||||
conn.handler = handler
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSignalHandler overrides the default signal handler.
|
||||
func WithSignalHandler(handler SignalHandler) ConnOption {
|
||||
return func(conn *Conn) error {
|
||||
conn.signalHandler = handler
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSerialGenerator overrides the default signals generator.
|
||||
func WithSerialGenerator(gen SerialGenerator) ConnOption {
|
||||
return func(conn *Conn) error {
|
||||
conn.serialGen = gen
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithAuth sets authentication methods for the auth conversation.
|
||||
func WithAuth(methods ...Auth) ConnOption {
|
||||
return func(conn *Conn) error {
|
||||
conn.auth = methods
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Interceptor intercepts incoming and outgoing messages.
|
||||
type Interceptor func(msg *Message)
|
||||
|
||||
// WithIncomingInterceptor sets the given interceptor for incoming messages.
|
||||
func WithIncomingInterceptor(interceptor Interceptor) ConnOption {
|
||||
return func(conn *Conn) error {
|
||||
conn.inInt = interceptor
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithOutgoingInterceptor sets the given interceptor for outgoing messages.
|
||||
func WithOutgoingInterceptor(interceptor Interceptor) ConnOption {
|
||||
return func(conn *Conn) error {
|
||||
conn.outInt = interceptor
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithContext overrides the default context for the connection.
|
||||
func WithContext(ctx context.Context) ConnOption {
|
||||
return func(conn *Conn) error {
|
||||
conn.ctx = ctx
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewConn creates a new private *Conn from an already established connection.
|
||||
func NewConn(conn io.ReadWriteCloser, opts ...ConnOption) (*Conn, error) {
|
||||
return newConn(genericTransport{conn}, opts...)
|
||||
}
|
||||
|
||||
// NewConnHandler creates a new private *Conn from an already established connection, using the supplied handlers.
|
||||
//
|
||||
// Deprecated: use NewConn with options instead.
|
||||
func NewConnHandler(conn io.ReadWriteCloser, handler Handler, signalHandler SignalHandler) (*Conn, error) {
|
||||
return NewConn(genericTransport{conn}, WithHandler(handler), WithSignalHandler(signalHandler))
|
||||
}
|
||||
|
||||
// newConn creates a new *Conn from a transport.
|
||||
func newConn(tr transport, opts ...ConnOption) (*Conn, error) {
|
||||
conn := new(Conn)
|
||||
conn.transport = tr
|
||||
for _, opt := range opts {
|
||||
if err := opt(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if conn.ctx == nil {
|
||||
conn.ctx = context.Background()
|
||||
}
|
||||
conn.ctx, conn.cancelCtx = context.WithCancel(conn.ctx)
|
||||
|
||||
conn.calls = newCallTracker()
|
||||
if conn.handler == nil {
|
||||
conn.handler = NewDefaultHandler()
|
||||
}
|
||||
if conn.signalHandler == nil {
|
||||
conn.signalHandler = NewDefaultSignalHandler()
|
||||
}
|
||||
if conn.serialGen == nil {
|
||||
conn.serialGen = newSerialGenerator()
|
||||
}
|
||||
conn.outHandler = &outputHandler{conn: conn}
|
||||
conn.names = newNameTracker()
|
||||
conn.busObj = conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")
|
||||
|
||||
go func() {
|
||||
<-conn.ctx.Done()
|
||||
conn.Close()
|
||||
}()
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// BusObject returns the object owned by the bus daemon which handles
|
||||
// administrative requests.
|
||||
func (conn *Conn) BusObject() BusObject {
|
||||
return conn.busObj
|
||||
}
|
||||
|
||||
// Close closes the connection. Any blocked operations will return with errors
|
||||
// and the channels passed to Eavesdrop and Signal are closed. This method must
|
||||
// not be called on shared connections.
|
||||
func (conn *Conn) Close() error {
|
||||
conn.closeOnce.Do(func() {
|
||||
conn.outHandler.close()
|
||||
if term, ok := conn.signalHandler.(Terminator); ok {
|
||||
term.Terminate()
|
||||
}
|
||||
|
||||
if term, ok := conn.handler.(Terminator); ok {
|
||||
term.Terminate()
|
||||
}
|
||||
|
||||
conn.eavesdroppedLck.Lock()
|
||||
if conn.eavesdropped != nil {
|
||||
close(conn.eavesdropped)
|
||||
}
|
||||
conn.eavesdroppedLck.Unlock()
|
||||
|
||||
conn.cancelCtx()
|
||||
|
||||
conn.closeErr = conn.transport.Close()
|
||||
})
|
||||
return conn.closeErr
|
||||
}
|
||||
|
||||
// Context returns the context associated with the connection. The
|
||||
// context will be cancelled when the connection is closed.
|
||||
func (conn *Conn) Context() context.Context {
|
||||
return conn.ctx
|
||||
}
|
||||
|
||||
// Connected returns whether conn is connected
|
||||
func (conn *Conn) Connected() bool {
|
||||
return conn.ctx.Err() == nil
|
||||
}
|
||||
|
||||
// Eavesdrop causes conn to send all incoming messages to the given channel
|
||||
// without further processing. Method replies, errors and signals will not be
|
||||
// sent to the appropriate channels and method calls will not be handled. If nil
|
||||
// is passed, the normal behaviour is restored.
|
||||
//
|
||||
// The caller has to make sure that ch is sufficiently buffered;
|
||||
// if a message arrives when a write to ch is not possible, the message is
|
||||
// discarded.
|
||||
func (conn *Conn) Eavesdrop(ch chan<- *Message) {
|
||||
conn.eavesdroppedLck.Lock()
|
||||
conn.eavesdropped = ch
|
||||
conn.eavesdroppedLck.Unlock()
|
||||
}
|
||||
|
||||
// getSerial returns an unused serial.
|
||||
func (conn *Conn) getSerial() uint32 {
|
||||
return conn.serialGen.GetSerial()
|
||||
}
|
||||
|
||||
// Hello sends the initial org.freedesktop.DBus.Hello call. This method must be
|
||||
// called after authentication, but before sending any other messages to the
|
||||
// bus. Hello must not be called for shared connections.
|
||||
func (conn *Conn) Hello() error {
|
||||
var s string
|
||||
err := conn.busObj.Call("org.freedesktop.DBus.Hello", 0).Store(&s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn.names.acquireUniqueConnectionName(s)
|
||||
return nil
|
||||
}
|
||||
|
||||
// inWorker runs in an own goroutine, reading incoming messages from the
|
||||
// transport and dispatching them appropriately.
|
||||
func (conn *Conn) inWorker() {
|
||||
sequenceGen := newSequenceGenerator()
|
||||
for {
|
||||
msg, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
if _, ok := err.(InvalidMessageError); !ok {
|
||||
// Some read error occurred (usually EOF); we can't really do
|
||||
// anything but to shut down all stuff and returns errors to all
|
||||
// pending replies.
|
||||
conn.Close()
|
||||
conn.calls.finalizeAllWithError(sequenceGen, err)
|
||||
return
|
||||
}
|
||||
// invalid messages are ignored
|
||||
continue
|
||||
}
|
||||
conn.eavesdroppedLck.Lock()
|
||||
if conn.eavesdropped != nil {
|
||||
select {
|
||||
case conn.eavesdropped <- msg:
|
||||
default:
|
||||
}
|
||||
conn.eavesdroppedLck.Unlock()
|
||||
continue
|
||||
}
|
||||
conn.eavesdroppedLck.Unlock()
|
||||
dest, _ := msg.Headers[FieldDestination].value.(string)
|
||||
found := dest == "" ||
|
||||
!conn.names.uniqueNameIsKnown() ||
|
||||
conn.names.isKnownName(dest)
|
||||
if !found {
|
||||
// Eavesdropped a message, but no channel for it is registered.
|
||||
// Ignore it.
|
||||
continue
|
||||
}
|
||||
|
||||
if conn.inInt != nil {
|
||||
conn.inInt(msg)
|
||||
}
|
||||
sequence := sequenceGen.next()
|
||||
switch msg.Type {
|
||||
case TypeError:
|
||||
conn.serialGen.RetireSerial(conn.calls.handleDBusError(sequence, msg))
|
||||
case TypeMethodReply:
|
||||
conn.serialGen.RetireSerial(conn.calls.handleReply(sequence, msg))
|
||||
case TypeSignal:
|
||||
conn.handleSignal(sequence, msg)
|
||||
case TypeMethodCall:
|
||||
go conn.handleCall(msg)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *Conn) handleSignal(sequence Sequence, msg *Message) {
|
||||
iface := msg.Headers[FieldInterface].value.(string)
|
||||
member := msg.Headers[FieldMember].value.(string)
|
||||
// as per http://dbus.freedesktop.org/doc/dbus-specification.html ,
|
||||
// sender is optional for signals.
|
||||
sender, _ := msg.Headers[FieldSender].value.(string)
|
||||
if iface == "org.freedesktop.DBus" && sender == "org.freedesktop.DBus" {
|
||||
if member == "NameLost" {
|
||||
// If we lost the name on the bus, remove it from our
|
||||
// tracking list.
|
||||
name, ok := msg.Body[0].(string)
|
||||
if !ok {
|
||||
panic("Unable to read the lost name")
|
||||
}
|
||||
conn.names.loseName(name)
|
||||
} else if member == "NameAcquired" {
|
||||
// If we acquired the name on the bus, add it to our
|
||||
// tracking list.
|
||||
name, ok := msg.Body[0].(string)
|
||||
if !ok {
|
||||
panic("Unable to read the acquired name")
|
||||
}
|
||||
conn.names.acquireName(name)
|
||||
}
|
||||
}
|
||||
signal := &Signal{
|
||||
Sender: sender,
|
||||
Path: msg.Headers[FieldPath].value.(ObjectPath),
|
||||
Name: iface + "." + member,
|
||||
Body: msg.Body,
|
||||
Sequence: sequence,
|
||||
}
|
||||
conn.signalHandler.DeliverSignal(iface, member, signal)
|
||||
}
|
||||
|
||||
// Names returns the list of all names that are currently owned by this
|
||||
// connection. The slice is always at least one element long, the first element
|
||||
// being the unique name of the connection.
|
||||
func (conn *Conn) Names() []string {
|
||||
return conn.names.listKnownNames()
|
||||
}
|
||||
|
||||
// Object returns the object identified by the given destination name and path.
|
||||
func (conn *Conn) Object(dest string, path ObjectPath) BusObject {
|
||||
return &Object{conn, dest, path}
|
||||
}
|
||||
|
||||
func (conn *Conn) sendMessageAndIfClosed(msg *Message, ifClosed func()) {
|
||||
if msg.serial == 0 {
|
||||
msg.serial = conn.getSerial()
|
||||
}
|
||||
if conn.outInt != nil {
|
||||
conn.outInt(msg)
|
||||
}
|
||||
err := conn.outHandler.sendAndIfClosed(msg, ifClosed)
|
||||
if err != nil {
|
||||
conn.handleSendError(msg, err)
|
||||
} else if msg.Type != TypeMethodCall {
|
||||
conn.serialGen.RetireSerial(msg.serial)
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *Conn) handleSendError(msg *Message, err error) {
|
||||
if msg.Type == TypeMethodCall {
|
||||
conn.calls.handleSendError(msg, err)
|
||||
} else if msg.Type == TypeMethodReply {
|
||||
if _, ok := err.(FormatError); ok {
|
||||
conn.sendError(err, msg.Headers[FieldDestination].value.(string), msg.Headers[FieldReplySerial].value.(uint32))
|
||||
}
|
||||
}
|
||||
conn.serialGen.RetireSerial(msg.serial)
|
||||
}
|
||||
|
||||
// Send sends the given message to the message bus. You usually don't need to
|
||||
// use this; use the higher-level equivalents (Call / Go, Emit and Export)
|
||||
// instead. If msg is a method call and NoReplyExpected is not set, a non-nil
|
||||
// call is returned and the same value is sent to ch (which must be buffered)
|
||||
// once the call is complete. Otherwise, ch is ignored and a Call structure is
|
||||
// returned of which only the Err member is valid.
|
||||
func (conn *Conn) Send(msg *Message, ch chan *Call) *Call {
|
||||
return conn.send(context.Background(), msg, ch)
|
||||
}
|
||||
|
||||
// SendWithContext acts like Send but takes a context
|
||||
func (conn *Conn) SendWithContext(ctx context.Context, msg *Message, ch chan *Call) *Call {
|
||||
return conn.send(ctx, msg, ch)
|
||||
}
|
||||
|
||||
func (conn *Conn) send(ctx context.Context, msg *Message, ch chan *Call) *Call {
|
||||
if ctx == nil {
|
||||
panic("nil context")
|
||||
}
|
||||
if ch == nil {
|
||||
ch = make(chan *Call, 1)
|
||||
} else if cap(ch) == 0 {
|
||||
panic("dbus: unbuffered channel passed to (*Conn).Send")
|
||||
}
|
||||
|
||||
var call *Call
|
||||
ctx, canceler := context.WithCancel(ctx)
|
||||
msg.serial = conn.getSerial()
|
||||
if msg.Type == TypeMethodCall && msg.Flags&FlagNoReplyExpected == 0 {
|
||||
call = new(Call)
|
||||
call.Destination, _ = msg.Headers[FieldDestination].value.(string)
|
||||
call.Path, _ = msg.Headers[FieldPath].value.(ObjectPath)
|
||||
iface, _ := msg.Headers[FieldInterface].value.(string)
|
||||
member, _ := msg.Headers[FieldMember].value.(string)
|
||||
call.Method = iface + "." + member
|
||||
call.Args = msg.Body
|
||||
call.Done = ch
|
||||
call.ctx = ctx
|
||||
call.ctxCanceler = canceler
|
||||
conn.calls.track(msg.serial, call)
|
||||
if ctx.Err() != nil {
|
||||
// short path: don't even send the message if context already cancelled
|
||||
conn.calls.handleSendError(msg, ctx.Err())
|
||||
return call
|
||||
}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
conn.calls.handleSendError(msg, ctx.Err())
|
||||
}()
|
||||
conn.sendMessageAndIfClosed(msg, func() {
|
||||
conn.calls.handleSendError(msg, ErrClosed)
|
||||
canceler()
|
||||
})
|
||||
} else {
|
||||
canceler()
|
||||
call = &Call{Err: nil, Done: ch}
|
||||
ch <- call
|
||||
conn.sendMessageAndIfClosed(msg, func() {
|
||||
call = &Call{Err: ErrClosed}
|
||||
})
|
||||
}
|
||||
return call
|
||||
}
|
||||
|
||||
// sendError creates an error message corresponding to the parameters and sends
|
||||
// it to conn.out.
|
||||
func (conn *Conn) sendError(err error, dest string, serial uint32) {
|
||||
var e *Error
|
||||
switch em := err.(type) {
|
||||
case Error:
|
||||
e = &em
|
||||
case *Error:
|
||||
e = em
|
||||
case DBusError:
|
||||
name, body := em.DBusError()
|
||||
e = NewError(name, body)
|
||||
default:
|
||||
e = MakeFailedError(err)
|
||||
}
|
||||
msg := new(Message)
|
||||
msg.Type = TypeError
|
||||
msg.Headers = make(map[HeaderField]Variant)
|
||||
if dest != "" {
|
||||
msg.Headers[FieldDestination] = MakeVariant(dest)
|
||||
}
|
||||
msg.Headers[FieldErrorName] = MakeVariant(e.Name)
|
||||
msg.Headers[FieldReplySerial] = MakeVariant(serial)
|
||||
msg.Body = e.Body
|
||||
if len(e.Body) > 0 {
|
||||
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(e.Body...))
|
||||
}
|
||||
conn.sendMessageAndIfClosed(msg, nil)
|
||||
}
|
||||
|
||||
// sendReply creates a method reply message corresponding to the parameters and
|
||||
// sends it to conn.out.
|
||||
func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) {
|
||||
msg := new(Message)
|
||||
msg.Type = TypeMethodReply
|
||||
msg.Headers = make(map[HeaderField]Variant)
|
||||
if dest != "" {
|
||||
msg.Headers[FieldDestination] = MakeVariant(dest)
|
||||
}
|
||||
msg.Headers[FieldReplySerial] = MakeVariant(serial)
|
||||
msg.Body = values
|
||||
if len(values) > 0 {
|
||||
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
|
||||
}
|
||||
conn.sendMessageAndIfClosed(msg, nil)
|
||||
}
|
||||
|
||||
// AddMatchSignal registers the given match rule to receive broadcast
|
||||
// signals based on their contents.
|
||||
func (conn *Conn) AddMatchSignal(options ...MatchOption) error {
|
||||
return conn.AddMatchSignalContext(context.Background(), options...)
|
||||
}
|
||||
|
||||
// AddMatchSignalContext acts like AddMatchSignal but takes a context.
|
||||
func (conn *Conn) AddMatchSignalContext(ctx context.Context, options ...MatchOption) error {
|
||||
options = append([]MatchOption{withMatchType("signal")}, options...)
|
||||
return conn.busObj.CallWithContext(
|
||||
ctx,
|
||||
"org.freedesktop.DBus.AddMatch", 0,
|
||||
formatMatchOptions(options),
|
||||
).Store()
|
||||
}
|
||||
|
||||
// RemoveMatchSignal removes the first rule that matches previously registered with AddMatchSignal.
|
||||
func (conn *Conn) RemoveMatchSignal(options ...MatchOption) error {
|
||||
return conn.RemoveMatchSignalContext(context.Background(), options...)
|
||||
}
|
||||
|
||||
// RemoveMatchSignalContext acts like RemoveMatchSignal but takes a context.
|
||||
func (conn *Conn) RemoveMatchSignalContext(ctx context.Context, options ...MatchOption) error {
|
||||
options = append([]MatchOption{withMatchType("signal")}, options...)
|
||||
return conn.busObj.CallWithContext(
|
||||
ctx,
|
||||
"org.freedesktop.DBus.RemoveMatch", 0,
|
||||
formatMatchOptions(options),
|
||||
).Store()
|
||||
}
|
||||
|
||||
// Signal registers the given channel to be passed all received signal messages.
|
||||
//
|
||||
// Multiple of these channels can be registered at the same time. The channel is
|
||||
// closed if the Conn is closed; it should not be closed by the caller before
|
||||
// RemoveSignal was called on it.
|
||||
//
|
||||
// These channels are "overwritten" by Eavesdrop; i.e., if there currently is a
|
||||
// channel for eavesdropped messages, this channel receives all signals, and
|
||||
// none of the channels passed to Signal will receive any signals.
|
||||
//
|
||||
// Panics if the signal handler is not a `SignalRegistrar`.
|
||||
func (conn *Conn) Signal(ch chan<- *Signal) {
|
||||
handler, ok := conn.signalHandler.(SignalRegistrar)
|
||||
if !ok {
|
||||
panic("cannot use this method with a non SignalRegistrar handler")
|
||||
}
|
||||
handler.AddSignal(ch)
|
||||
}
|
||||
|
||||
// RemoveSignal removes the given channel from the list of the registered channels.
|
||||
//
|
||||
// Panics if the signal handler is not a `SignalRegistrar`.
|
||||
func (conn *Conn) RemoveSignal(ch chan<- *Signal) {
|
||||
handler, ok := conn.signalHandler.(SignalRegistrar)
|
||||
if !ok {
|
||||
panic("cannot use this method with a non SignalRegistrar handler")
|
||||
}
|
||||
handler.RemoveSignal(ch)
|
||||
}
|
||||
|
||||
// SupportsUnixFDs returns whether the underlying transport supports passing of
|
||||
// unix file descriptors. If this is false, method calls containing unix file
|
||||
// descriptors will return an error and emitted signals containing them will
|
||||
// not be sent.
|
||||
func (conn *Conn) SupportsUnixFDs() bool {
|
||||
return conn.unixFD
|
||||
}
|
||||
|
||||
// Error represents a D-Bus message of type Error.
|
||||
type Error struct {
|
||||
Name string
|
||||
Body []interface{}
|
||||
}
|
||||
|
||||
func NewError(name string, body []interface{}) *Error {
|
||||
return &Error{name, body}
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
if len(e.Body) >= 1 {
|
||||
s, ok := e.Body[0].(string)
|
||||
if ok {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return e.Name
|
||||
}
|
||||
|
||||
// Signal represents a D-Bus message of type Signal. The name member is given in
|
||||
// "interface.member" notation, e.g. org.freedesktop.D-Bus.NameLost.
|
||||
type Signal struct {
|
||||
Sender string
|
||||
Path ObjectPath
|
||||
Name string
|
||||
Body []interface{}
|
||||
Sequence Sequence
|
||||
}
|
||||
|
||||
// transport is a D-Bus transport.
|
||||
type transport interface {
|
||||
// Read and Write raw data (for example, for the authentication protocol).
|
||||
io.ReadWriteCloser
|
||||
|
||||
// Send the initial null byte used for the EXTERNAL mechanism.
|
||||
SendNullByte() error
|
||||
|
||||
// Returns whether this transport supports passing Unix FDs.
|
||||
SupportsUnixFDs() bool
|
||||
|
||||
// Signal the transport that Unix FD passing is enabled for this connection.
|
||||
EnableUnixFDs()
|
||||
|
||||
// Read / send a message, handling things like Unix FDs.
|
||||
ReadMessage() (*Message, error)
|
||||
SendMessage(*Message) error
|
||||
}
|
||||
|
||||
var (
|
||||
transports = make(map[string]func(string) (transport, error))
|
||||
)
|
||||
|
||||
func getTransport(address string) (transport, error) {
|
||||
var err error
|
||||
var t transport
|
||||
|
||||
addresses := strings.Split(address, ";")
|
||||
for _, v := range addresses {
|
||||
i := strings.IndexRune(v, ':')
|
||||
if i == -1 {
|
||||
err = errors.New("dbus: invalid bus address (no transport)")
|
||||
continue
|
||||
}
|
||||
f := transports[v[:i]]
|
||||
if f == nil {
|
||||
err = errors.New("dbus: invalid bus address (invalid or unsupported transport)")
|
||||
continue
|
||||
}
|
||||
t, err = f(v[i+1:])
|
||||
if err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// getKey gets a key from a the list of keys. Returns "" on error / not found...
|
||||
func getKey(s, key string) string {
|
||||
for _, keyEqualsValue := range strings.Split(s, ",") {
|
||||
keyValue := strings.SplitN(keyEqualsValue, "=", 2)
|
||||
if len(keyValue) == 2 && keyValue[0] == key {
|
||||
val, err := UnescapeBusAddressValue(keyValue[1])
|
||||
if err != nil {
|
||||
// No way to return an error.
|
||||
return ""
|
||||
}
|
||||
return val
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type outputHandler struct {
|
||||
conn *Conn
|
||||
sendLck sync.Mutex
|
||||
closed struct {
|
||||
isClosed bool
|
||||
lck sync.RWMutex
|
||||
}
|
||||
}
|
||||
|
||||
func (h *outputHandler) sendAndIfClosed(msg *Message, ifClosed func()) error {
|
||||
h.closed.lck.RLock()
|
||||
defer h.closed.lck.RUnlock()
|
||||
if h.closed.isClosed {
|
||||
if ifClosed != nil {
|
||||
ifClosed()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
h.sendLck.Lock()
|
||||
defer h.sendLck.Unlock()
|
||||
return h.conn.SendMessage(msg)
|
||||
}
|
||||
|
||||
func (h *outputHandler) close() {
|
||||
h.closed.lck.Lock()
|
||||
defer h.closed.lck.Unlock()
|
||||
h.closed.isClosed = true
|
||||
}
|
||||
|
||||
type serialGenerator struct {
|
||||
lck sync.Mutex
|
||||
nextSerial uint32
|
||||
serialUsed map[uint32]bool
|
||||
}
|
||||
|
||||
func newSerialGenerator() *serialGenerator {
|
||||
return &serialGenerator{
|
||||
serialUsed: map[uint32]bool{0: true},
|
||||
nextSerial: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (gen *serialGenerator) GetSerial() uint32 {
|
||||
gen.lck.Lock()
|
||||
defer gen.lck.Unlock()
|
||||
n := gen.nextSerial
|
||||
for gen.serialUsed[n] {
|
||||
n++
|
||||
}
|
||||
gen.serialUsed[n] = true
|
||||
gen.nextSerial = n + 1
|
||||
return n
|
||||
}
|
||||
|
||||
func (gen *serialGenerator) RetireSerial(serial uint32) {
|
||||
gen.lck.Lock()
|
||||
defer gen.lck.Unlock()
|
||||
delete(gen.serialUsed, serial)
|
||||
}
|
||||
|
||||
type nameTracker struct {
|
||||
lck sync.RWMutex
|
||||
unique string
|
||||
names map[string]struct{}
|
||||
}
|
||||
|
||||
func newNameTracker() *nameTracker {
|
||||
return &nameTracker{names: map[string]struct{}{}}
|
||||
}
|
||||
func (tracker *nameTracker) acquireUniqueConnectionName(name string) {
|
||||
tracker.lck.Lock()
|
||||
defer tracker.lck.Unlock()
|
||||
tracker.unique = name
|
||||
}
|
||||
func (tracker *nameTracker) acquireName(name string) {
|
||||
tracker.lck.Lock()
|
||||
defer tracker.lck.Unlock()
|
||||
tracker.names[name] = struct{}{}
|
||||
}
|
||||
func (tracker *nameTracker) loseName(name string) {
|
||||
tracker.lck.Lock()
|
||||
defer tracker.lck.Unlock()
|
||||
delete(tracker.names, name)
|
||||
}
|
||||
|
||||
func (tracker *nameTracker) uniqueNameIsKnown() bool {
|
||||
tracker.lck.RLock()
|
||||
defer tracker.lck.RUnlock()
|
||||
return tracker.unique != ""
|
||||
}
|
||||
func (tracker *nameTracker) isKnownName(name string) bool {
|
||||
tracker.lck.RLock()
|
||||
defer tracker.lck.RUnlock()
|
||||
_, ok := tracker.names[name]
|
||||
return ok || name == tracker.unique
|
||||
}
|
||||
func (tracker *nameTracker) listKnownNames() []string {
|
||||
tracker.lck.RLock()
|
||||
defer tracker.lck.RUnlock()
|
||||
out := make([]string, 0, len(tracker.names)+1)
|
||||
out = append(out, tracker.unique)
|
||||
for k := range tracker.names {
|
||||
out = append(out, k)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type callTracker struct {
|
||||
calls map[uint32]*Call
|
||||
lck sync.RWMutex
|
||||
}
|
||||
|
||||
func newCallTracker() *callTracker {
|
||||
return &callTracker{calls: map[uint32]*Call{}}
|
||||
}
|
||||
|
||||
func (tracker *callTracker) track(sn uint32, call *Call) {
|
||||
tracker.lck.Lock()
|
||||
tracker.calls[sn] = call
|
||||
tracker.lck.Unlock()
|
||||
}
|
||||
|
||||
func (tracker *callTracker) handleReply(sequence Sequence, msg *Message) uint32 {
|
||||
serial := msg.Headers[FieldReplySerial].value.(uint32)
|
||||
tracker.lck.RLock()
|
||||
_, ok := tracker.calls[serial]
|
||||
tracker.lck.RUnlock()
|
||||
if ok {
|
||||
tracker.finalizeWithBody(serial, sequence, msg.Body)
|
||||
}
|
||||
return serial
|
||||
}
|
||||
|
||||
func (tracker *callTracker) handleDBusError(sequence Sequence, msg *Message) uint32 {
|
||||
serial := msg.Headers[FieldReplySerial].value.(uint32)
|
||||
tracker.lck.RLock()
|
||||
_, ok := tracker.calls[serial]
|
||||
tracker.lck.RUnlock()
|
||||
if ok {
|
||||
name, _ := msg.Headers[FieldErrorName].value.(string)
|
||||
tracker.finalizeWithError(serial, sequence, Error{name, msg.Body})
|
||||
}
|
||||
return serial
|
||||
}
|
||||
|
||||
func (tracker *callTracker) handleSendError(msg *Message, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
tracker.lck.RLock()
|
||||
_, ok := tracker.calls[msg.serial]
|
||||
tracker.lck.RUnlock()
|
||||
if ok {
|
||||
tracker.finalizeWithError(msg.serial, NoSequence, err)
|
||||
}
|
||||
}
|
||||
|
||||
// finalize was the only func that did not strobe Done
|
||||
func (tracker *callTracker) finalize(sn uint32) {
|
||||
tracker.lck.Lock()
|
||||
defer tracker.lck.Unlock()
|
||||
c, ok := tracker.calls[sn]
|
||||
if ok {
|
||||
delete(tracker.calls, sn)
|
||||
c.ContextCancel()
|
||||
}
|
||||
}
|
||||
|
||||
func (tracker *callTracker) finalizeWithBody(sn uint32, sequence Sequence, body []interface{}) {
|
||||
tracker.lck.Lock()
|
||||
c, ok := tracker.calls[sn]
|
||||
if ok {
|
||||
delete(tracker.calls, sn)
|
||||
}
|
||||
tracker.lck.Unlock()
|
||||
if ok {
|
||||
c.Body = body
|
||||
c.ResponseSequence = sequence
|
||||
c.done()
|
||||
}
|
||||
}
|
||||
|
||||
func (tracker *callTracker) finalizeWithError(sn uint32, sequence Sequence, err error) {
|
||||
tracker.lck.Lock()
|
||||
c, ok := tracker.calls[sn]
|
||||
if ok {
|
||||
delete(tracker.calls, sn)
|
||||
}
|
||||
tracker.lck.Unlock()
|
||||
if ok {
|
||||
c.Err = err
|
||||
c.ResponseSequence = sequence
|
||||
c.done()
|
||||
}
|
||||
}
|
||||
|
||||
func (tracker *callTracker) finalizeAllWithError(sequenceGen *sequenceGenerator, err error) {
|
||||
tracker.lck.Lock()
|
||||
closedCalls := make([]*Call, 0, len(tracker.calls))
|
||||
for sn := range tracker.calls {
|
||||
closedCalls = append(closedCalls, tracker.calls[sn])
|
||||
}
|
||||
tracker.calls = map[uint32]*Call{}
|
||||
tracker.lck.Unlock()
|
||||
for _, call := range closedCalls {
|
||||
call.Err = err
|
||||
call.ResponseSequence = sequenceGen.next()
|
||||
call.done()
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
const defaultSystemBusAddress = "unix:path=/opt/local/var/run/dbus/system_bus_socket"
|
||||
|
||||
func getSessionBusPlatformAddress() (string, error) {
|
||||
cmd := exec.Command("launchctl", "getenv", "DBUS_LAUNCHD_SESSION_BUS_SOCKET")
|
||||
b, err := cmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(b) == 0 {
|
||||
return "", errors.New("dbus: couldn't determine address of session bus")
|
||||
}
|
||||
|
||||
return "unix:path=" + string(b[:len(b)-1]), nil
|
||||
}
|
||||
|
||||
func getSystemBusPlatformAddress() string {
|
||||
address := os.Getenv("DBUS_LAUNCHD_SESSION_BUS_SOCKET")
|
||||
if address != "" {
|
||||
return fmt.Sprintf("unix:path=%s", address)
|
||||
}
|
||||
return defaultSystemBusAddress
|
||||
}
|
||||
|
||||
func tryDiscoverDbusSessionBusAddress() string {
|
||||
return ""
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// +build !darwin
|
||||
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var execCommand = exec.Command
|
||||
|
||||
func getSessionBusPlatformAddress() (string, error) {
|
||||
cmd := execCommand("dbus-launch")
|
||||
b, err := cmd.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
i := bytes.IndexByte(b, '=')
|
||||
j := bytes.IndexByte(b, '\n')
|
||||
|
||||
if i == -1 || j == -1 || i > j {
|
||||
return "", errors.New("dbus: couldn't determine address of session bus")
|
||||
}
|
||||
|
||||
env, addr := string(b[0:i]), string(b[i+1:j])
|
||||
os.Setenv(env, addr)
|
||||
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// tryDiscoverDbusSessionBusAddress tries to discover an existing dbus session
|
||||
// and return the value of its DBUS_SESSION_BUS_ADDRESS.
|
||||
// It tries different techniques employed by different operating systems,
|
||||
// returning the first valid address it finds, or an empty string.
|
||||
//
|
||||
// * /run/user/<uid>/bus if this exists, it *is* the bus socket. present on
|
||||
// Ubuntu 18.04
|
||||
// * /run/user/<uid>/dbus-session: if this exists, it can be parsed for the bus
|
||||
// address. present on Ubuntu 16.04
|
||||
//
|
||||
// See https://dbus.freedesktop.org/doc/dbus-launch.1.html
|
||||
func tryDiscoverDbusSessionBusAddress() string {
|
||||
if runtimeDirectory, err := getRuntimeDirectory(); err == nil {
|
||||
|
||||
if runUserBusFile := path.Join(runtimeDirectory, "bus"); fileExists(runUserBusFile) {
|
||||
// if /run/user/<uid>/bus exists, that file itself
|
||||
// *is* the unix socket, so return its path
|
||||
return fmt.Sprintf("unix:path=%s", EscapeBusAddressValue(runUserBusFile))
|
||||
}
|
||||
if runUserSessionDbusFile := path.Join(runtimeDirectory, "dbus-session"); fileExists(runUserSessionDbusFile) {
|
||||
// if /run/user/<uid>/dbus-session exists, it's a
|
||||
// text file // containing the address of the socket, e.g.:
|
||||
// DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-E1c73yNqrG
|
||||
|
||||
if f, err := ioutil.ReadFile(runUserSessionDbusFile); err == nil {
|
||||
fileContent := string(f)
|
||||
|
||||
prefix := "DBUS_SESSION_BUS_ADDRESS="
|
||||
|
||||
if strings.HasPrefix(fileContent, prefix) {
|
||||
address := strings.TrimRight(strings.TrimPrefix(fileContent, prefix), "\n\r")
|
||||
return address
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getRuntimeDirectory() (string, error) {
|
||||
if currentUser, err := user.Current(); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return fmt.Sprintf("/run/user/%s", currentUser.Uid), nil
|
||||
}
|
||||
}
|
||||
|
||||
func fileExists(filename string) bool {
|
||||
_, err := os.Stat(filename)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
//+build !windows,!solaris,!darwin
|
||||
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
const defaultSystemBusAddress = "unix:path=/var/run/dbus/system_bus_socket"
|
||||
|
||||
func getSystemBusPlatformAddress() string {
|
||||
address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS")
|
||||
if address != "" {
|
||||
return address
|
||||
}
|
||||
return defaultSystemBusAddress
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
//+build windows
|
||||
|
||||
package dbus
|
||||
|
||||
import "os"
|
||||
|
||||
const defaultSystemBusAddress = "tcp:host=127.0.0.1,port=12434"
|
||||
|
||||
func getSystemBusPlatformAddress() string {
|
||||
address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS")
|
||||
if address != "" {
|
||||
return address
|
||||
}
|
||||
return defaultSystemBusAddress
|
||||
}
|
||||
+430
@@ -0,0 +1,430 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
byteType = reflect.TypeOf(byte(0))
|
||||
boolType = reflect.TypeOf(false)
|
||||
uint8Type = reflect.TypeOf(uint8(0))
|
||||
int16Type = reflect.TypeOf(int16(0))
|
||||
uint16Type = reflect.TypeOf(uint16(0))
|
||||
intType = reflect.TypeOf(int(0))
|
||||
uintType = reflect.TypeOf(uint(0))
|
||||
int32Type = reflect.TypeOf(int32(0))
|
||||
uint32Type = reflect.TypeOf(uint32(0))
|
||||
int64Type = reflect.TypeOf(int64(0))
|
||||
uint64Type = reflect.TypeOf(uint64(0))
|
||||
float64Type = reflect.TypeOf(float64(0))
|
||||
stringType = reflect.TypeOf("")
|
||||
signatureType = reflect.TypeOf(Signature{""})
|
||||
objectPathType = reflect.TypeOf(ObjectPath(""))
|
||||
variantType = reflect.TypeOf(Variant{Signature{""}, nil})
|
||||
interfacesType = reflect.TypeOf([]interface{}{})
|
||||
interfaceType = reflect.TypeOf((*interface{})(nil)).Elem()
|
||||
unixFDType = reflect.TypeOf(UnixFD(0))
|
||||
unixFDIndexType = reflect.TypeOf(UnixFDIndex(0))
|
||||
errType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
)
|
||||
|
||||
// An InvalidTypeError signals that a value which cannot be represented in the
|
||||
// D-Bus wire format was passed to a function.
|
||||
type InvalidTypeError struct {
|
||||
Type reflect.Type
|
||||
}
|
||||
|
||||
func (e InvalidTypeError) Error() string {
|
||||
return "dbus: invalid type " + e.Type.String()
|
||||
}
|
||||
|
||||
// Store copies the values contained in src to dest, which must be a slice of
|
||||
// pointers. It converts slices of interfaces from src to corresponding structs
|
||||
// in dest. An error is returned if the lengths of src and dest or the types of
|
||||
// their elements don't match.
|
||||
func Store(src []interface{}, dest ...interface{}) error {
|
||||
if len(src) != len(dest) {
|
||||
return errors.New("dbus.Store: length mismatch")
|
||||
}
|
||||
|
||||
for i := range src {
|
||||
if err := storeInterfaces(src[i], dest[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func storeInterfaces(src, dest interface{}) error {
|
||||
return store(reflect.ValueOf(dest), reflect.ValueOf(src))
|
||||
}
|
||||
|
||||
func store(dest, src reflect.Value) error {
|
||||
if dest.Kind() == reflect.Ptr {
|
||||
if dest.IsNil() {
|
||||
dest.Set(reflect.New(dest.Type().Elem()))
|
||||
}
|
||||
return store(dest.Elem(), src)
|
||||
}
|
||||
switch src.Kind() {
|
||||
case reflect.Slice:
|
||||
return storeSlice(dest, src)
|
||||
case reflect.Map:
|
||||
return storeMap(dest, src)
|
||||
default:
|
||||
return storeBase(dest, src)
|
||||
}
|
||||
}
|
||||
|
||||
func storeBase(dest, src reflect.Value) error {
|
||||
return setDest(dest, src)
|
||||
}
|
||||
|
||||
func setDest(dest, src reflect.Value) error {
|
||||
if !isVariant(src.Type()) && isVariant(dest.Type()) {
|
||||
//special conversion for dbus.Variant
|
||||
dest.Set(reflect.ValueOf(MakeVariant(src.Interface())))
|
||||
return nil
|
||||
}
|
||||
if isVariant(src.Type()) && !isVariant(dest.Type()) {
|
||||
src = getVariantValue(src)
|
||||
return store(dest, src)
|
||||
}
|
||||
if !src.Type().ConvertibleTo(dest.Type()) {
|
||||
return fmt.Errorf(
|
||||
"dbus.Store: type mismatch: cannot convert %s to %s",
|
||||
src.Type(), dest.Type())
|
||||
}
|
||||
dest.Set(src.Convert(dest.Type()))
|
||||
return nil
|
||||
}
|
||||
|
||||
func kindsAreCompatible(dest, src reflect.Type) bool {
|
||||
switch {
|
||||
case isVariant(dest):
|
||||
return true
|
||||
case dest.Kind() == reflect.Interface:
|
||||
return true
|
||||
default:
|
||||
return dest.Kind() == src.Kind()
|
||||
}
|
||||
}
|
||||
|
||||
func isConvertibleTo(dest, src reflect.Type) bool {
|
||||
switch {
|
||||
case isVariant(dest):
|
||||
return true
|
||||
case dest.Kind() == reflect.Interface:
|
||||
return true
|
||||
case dest.Kind() == reflect.Slice:
|
||||
return src.Kind() == reflect.Slice &&
|
||||
isConvertibleTo(dest.Elem(), src.Elem())
|
||||
case dest.Kind() == reflect.Ptr:
|
||||
dest = dest.Elem()
|
||||
return isConvertibleTo(dest, src)
|
||||
case dest.Kind() == reflect.Struct:
|
||||
return src == interfacesType || dest.Kind() == src.Kind()
|
||||
default:
|
||||
return src.ConvertibleTo(dest)
|
||||
}
|
||||
}
|
||||
|
||||
func storeMap(dest, src reflect.Value) error {
|
||||
switch {
|
||||
case !kindsAreCompatible(dest.Type(), src.Type()):
|
||||
return fmt.Errorf(
|
||||
"dbus.Store: type mismatch: "+
|
||||
"map: cannot store a value of %s into %s",
|
||||
src.Type(), dest.Type())
|
||||
case isVariant(dest.Type()):
|
||||
return storeMapIntoVariant(dest, src)
|
||||
case dest.Kind() == reflect.Interface:
|
||||
return storeMapIntoInterface(dest, src)
|
||||
case isConvertibleTo(dest.Type().Key(), src.Type().Key()) &&
|
||||
isConvertibleTo(dest.Type().Elem(), src.Type().Elem()):
|
||||
return storeMapIntoMap(dest, src)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"dbus.Store: type mismatch: "+
|
||||
"map: cannot convert a value of %s into %s",
|
||||
src.Type(), dest.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func storeMapIntoVariant(dest, src reflect.Value) error {
|
||||
dv := reflect.MakeMap(src.Type())
|
||||
err := store(dv, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storeBase(dest, dv)
|
||||
}
|
||||
|
||||
func storeMapIntoInterface(dest, src reflect.Value) error {
|
||||
var dv reflect.Value
|
||||
if isVariant(src.Type().Elem()) {
|
||||
//Convert variants to interface{} recursively when converting
|
||||
//to interface{}
|
||||
dv = reflect.MakeMap(
|
||||
reflect.MapOf(src.Type().Key(), interfaceType))
|
||||
} else {
|
||||
dv = reflect.MakeMap(src.Type())
|
||||
}
|
||||
err := store(dv, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storeBase(dest, dv)
|
||||
}
|
||||
|
||||
func storeMapIntoMap(dest, src reflect.Value) error {
|
||||
if dest.IsNil() {
|
||||
dest.Set(reflect.MakeMap(dest.Type()))
|
||||
}
|
||||
keys := src.MapKeys()
|
||||
for _, key := range keys {
|
||||
dkey := key.Convert(dest.Type().Key())
|
||||
dval := reflect.New(dest.Type().Elem()).Elem()
|
||||
err := store(dval, getVariantValue(src.MapIndex(key)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dest.SetMapIndex(dkey, dval)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func storeSlice(dest, src reflect.Value) error {
|
||||
switch {
|
||||
case src.Type() == interfacesType && dest.Kind() == reflect.Struct:
|
||||
//The decoder always decodes structs as slices of interface{}
|
||||
return storeStruct(dest, src)
|
||||
case !kindsAreCompatible(dest.Type(), src.Type()):
|
||||
return fmt.Errorf(
|
||||
"dbus.Store: type mismatch: "+
|
||||
"slice: cannot store a value of %s into %s",
|
||||
src.Type(), dest.Type())
|
||||
case isVariant(dest.Type()):
|
||||
return storeSliceIntoVariant(dest, src)
|
||||
case dest.Kind() == reflect.Interface:
|
||||
return storeSliceIntoInterface(dest, src)
|
||||
case isConvertibleTo(dest.Type().Elem(), src.Type().Elem()):
|
||||
return storeSliceIntoSlice(dest, src)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"dbus.Store: type mismatch: "+
|
||||
"slice: cannot convert a value of %s into %s",
|
||||
src.Type(), dest.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func storeStruct(dest, src reflect.Value) error {
|
||||
if isVariant(dest.Type()) {
|
||||
return storeBase(dest, src)
|
||||
}
|
||||
dval := make([]interface{}, 0, dest.NumField())
|
||||
dtype := dest.Type()
|
||||
for i := 0; i < dest.NumField(); i++ {
|
||||
field := dest.Field(i)
|
||||
ftype := dtype.Field(i)
|
||||
if ftype.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
if ftype.Tag.Get("dbus") == "-" {
|
||||
continue
|
||||
}
|
||||
dval = append(dval, field.Addr().Interface())
|
||||
}
|
||||
if src.Len() != len(dval) {
|
||||
return fmt.Errorf(
|
||||
"dbus.Store: type mismatch: "+
|
||||
"destination struct does not have "+
|
||||
"enough fields need: %d have: %d",
|
||||
src.Len(), len(dval))
|
||||
}
|
||||
return Store(src.Interface().([]interface{}), dval...)
|
||||
}
|
||||
|
||||
func storeSliceIntoVariant(dest, src reflect.Value) error {
|
||||
dv := reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
|
||||
err := store(dv, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storeBase(dest, dv)
|
||||
}
|
||||
|
||||
func storeSliceIntoInterface(dest, src reflect.Value) error {
|
||||
var dv reflect.Value
|
||||
if isVariant(src.Type().Elem()) {
|
||||
//Convert variants to interface{} recursively when converting
|
||||
//to interface{}
|
||||
dv = reflect.MakeSlice(reflect.SliceOf(interfaceType),
|
||||
src.Len(), src.Cap())
|
||||
} else {
|
||||
dv = reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
|
||||
}
|
||||
err := store(dv, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return storeBase(dest, dv)
|
||||
}
|
||||
|
||||
func storeSliceIntoSlice(dest, src reflect.Value) error {
|
||||
if dest.IsNil() || dest.Len() < src.Len() {
|
||||
dest.Set(reflect.MakeSlice(dest.Type(), src.Len(), src.Cap()))
|
||||
} else if dest.Len() > src.Len() {
|
||||
dest.Set(dest.Slice(0, src.Len()))
|
||||
}
|
||||
for i := 0; i < src.Len(); i++ {
|
||||
err := store(dest.Index(i), getVariantValue(src.Index(i)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getVariantValue(in reflect.Value) reflect.Value {
|
||||
if isVariant(in.Type()) {
|
||||
return reflect.ValueOf(in.Interface().(Variant).Value())
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
func isVariant(t reflect.Type) bool {
|
||||
return t == variantType
|
||||
}
|
||||
|
||||
// An ObjectPath is an object path as defined by the D-Bus spec.
|
||||
type ObjectPath string
|
||||
|
||||
// IsValid returns whether the object path is valid.
|
||||
func (o ObjectPath) IsValid() bool {
|
||||
s := string(o)
|
||||
if len(s) == 0 {
|
||||
return false
|
||||
}
|
||||
if s[0] != '/' {
|
||||
return false
|
||||
}
|
||||
if s[len(s)-1] == '/' && len(s) != 1 {
|
||||
return false
|
||||
}
|
||||
// probably not used, but technically possible
|
||||
if s == "/" {
|
||||
return true
|
||||
}
|
||||
split := strings.Split(s[1:], "/")
|
||||
for _, v := range split {
|
||||
if len(v) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, c := range v {
|
||||
if !isMemberChar(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// A UnixFD is a Unix file descriptor sent over the wire. See the package-level
|
||||
// documentation for more information about Unix file descriptor passsing.
|
||||
type UnixFD int32
|
||||
|
||||
// A UnixFDIndex is the representation of a Unix file descriptor in a message.
|
||||
type UnixFDIndex uint32
|
||||
|
||||
// alignment returns the alignment of values of type t.
|
||||
func alignment(t reflect.Type) int {
|
||||
switch t {
|
||||
case variantType:
|
||||
return 1
|
||||
case objectPathType:
|
||||
return 4
|
||||
case signatureType:
|
||||
return 1
|
||||
case interfacesType:
|
||||
return 4
|
||||
}
|
||||
switch t.Kind() {
|
||||
case reflect.Uint8:
|
||||
return 1
|
||||
case reflect.Uint16, reflect.Int16:
|
||||
return 2
|
||||
case reflect.Uint, reflect.Int, reflect.Uint32, reflect.Int32, reflect.String, reflect.Array, reflect.Slice, reflect.Map:
|
||||
return 4
|
||||
case reflect.Uint64, reflect.Int64, reflect.Float64, reflect.Struct:
|
||||
return 8
|
||||
case reflect.Ptr:
|
||||
return alignment(t.Elem())
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
// isKeyType returns whether t is a valid type for a D-Bus dict.
|
||||
func isKeyType(t reflect.Type) bool {
|
||||
switch t.Kind() {
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||
reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64,
|
||||
reflect.String, reflect.Uint, reflect.Int:
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isValidInterface returns whether s is a valid name for an interface.
|
||||
func isValidInterface(s string) bool {
|
||||
if len(s) == 0 || len(s) > 255 || s[0] == '.' {
|
||||
return false
|
||||
}
|
||||
elem := strings.Split(s, ".")
|
||||
if len(elem) < 2 {
|
||||
return false
|
||||
}
|
||||
for _, v := range elem {
|
||||
if len(v) == 0 {
|
||||
return false
|
||||
}
|
||||
if v[0] >= '0' && v[0] <= '9' {
|
||||
return false
|
||||
}
|
||||
for _, c := range v {
|
||||
if !isMemberChar(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isValidMember returns whether s is a valid name for a member.
|
||||
func isValidMember(s string) bool {
|
||||
if len(s) == 0 || len(s) > 255 {
|
||||
return false
|
||||
}
|
||||
i := strings.Index(s, ".")
|
||||
if i != -1 {
|
||||
return false
|
||||
}
|
||||
if s[0] >= '0' && s[0] <= '9' {
|
||||
return false
|
||||
}
|
||||
for _, c := range s {
|
||||
if !isMemberChar(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isMemberChar(c rune) bool {
|
||||
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
|
||||
(c >= 'a' && c <= 'z') || c == '_'
|
||||
}
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type decoder struct {
|
||||
in io.Reader
|
||||
order binary.ByteOrder
|
||||
pos int
|
||||
fds []int
|
||||
}
|
||||
|
||||
// newDecoder returns a new decoder that reads values from in. The input is
|
||||
// expected to be in the given byte order.
|
||||
func newDecoder(in io.Reader, order binary.ByteOrder, fds []int) *decoder {
|
||||
dec := new(decoder)
|
||||
dec.in = in
|
||||
dec.order = order
|
||||
dec.fds = fds
|
||||
return dec
|
||||
}
|
||||
|
||||
// align aligns the input to the given boundary and panics on error.
|
||||
func (dec *decoder) align(n int) {
|
||||
if dec.pos%n != 0 {
|
||||
newpos := (dec.pos + n - 1) & ^(n - 1)
|
||||
empty := make([]byte, newpos-dec.pos)
|
||||
if _, err := io.ReadFull(dec.in, empty); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dec.pos = newpos
|
||||
}
|
||||
}
|
||||
|
||||
// Calls binary.Read(dec.in, dec.order, v) and panics on read errors.
|
||||
func (dec *decoder) binread(v interface{}) {
|
||||
if err := binary.Read(dec.in, dec.order, v); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *decoder) Decode(sig Signature) (vs []interface{}, err error) {
|
||||
defer func() {
|
||||
var ok bool
|
||||
v := recover()
|
||||
if err, ok = v.(error); ok {
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
err = FormatError("unexpected EOF")
|
||||
}
|
||||
}
|
||||
}()
|
||||
vs = make([]interface{}, 0)
|
||||
s := sig.str
|
||||
for s != "" {
|
||||
err, rem := validSingle(s, &depthCounter{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := dec.decode(s[:len(s)-len(rem)], 0)
|
||||
vs = append(vs, v)
|
||||
s = rem
|
||||
}
|
||||
return vs, nil
|
||||
}
|
||||
|
||||
func (dec *decoder) decode(s string, depth int) interface{} {
|
||||
dec.align(alignment(typeFor(s)))
|
||||
switch s[0] {
|
||||
case 'y':
|
||||
var b [1]byte
|
||||
if _, err := dec.in.Read(b[:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dec.pos++
|
||||
return b[0]
|
||||
case 'b':
|
||||
i := dec.decode("u", depth).(uint32)
|
||||
switch {
|
||||
case i == 0:
|
||||
return false
|
||||
case i == 1:
|
||||
return true
|
||||
default:
|
||||
panic(FormatError("invalid value for boolean"))
|
||||
}
|
||||
case 'n':
|
||||
var i int16
|
||||
dec.binread(&i)
|
||||
dec.pos += 2
|
||||
return i
|
||||
case 'i':
|
||||
var i int32
|
||||
dec.binread(&i)
|
||||
dec.pos += 4
|
||||
return i
|
||||
case 'x':
|
||||
var i int64
|
||||
dec.binread(&i)
|
||||
dec.pos += 8
|
||||
return i
|
||||
case 'q':
|
||||
var i uint16
|
||||
dec.binread(&i)
|
||||
dec.pos += 2
|
||||
return i
|
||||
case 'u':
|
||||
var i uint32
|
||||
dec.binread(&i)
|
||||
dec.pos += 4
|
||||
return i
|
||||
case 't':
|
||||
var i uint64
|
||||
dec.binread(&i)
|
||||
dec.pos += 8
|
||||
return i
|
||||
case 'd':
|
||||
var f float64
|
||||
dec.binread(&f)
|
||||
dec.pos += 8
|
||||
return f
|
||||
case 's':
|
||||
length := dec.decode("u", depth).(uint32)
|
||||
b := make([]byte, int(length)+1)
|
||||
if _, err := io.ReadFull(dec.in, b); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dec.pos += int(length) + 1
|
||||
return string(b[:len(b)-1])
|
||||
case 'o':
|
||||
return ObjectPath(dec.decode("s", depth).(string))
|
||||
case 'g':
|
||||
length := dec.decode("y", depth).(byte)
|
||||
b := make([]byte, int(length)+1)
|
||||
if _, err := io.ReadFull(dec.in, b); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dec.pos += int(length) + 1
|
||||
sig, err := ParseSignature(string(b[:len(b)-1]))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sig
|
||||
case 'v':
|
||||
if depth >= 64 {
|
||||
panic(FormatError("input exceeds container depth limit"))
|
||||
}
|
||||
var variant Variant
|
||||
sig := dec.decode("g", depth).(Signature)
|
||||
if len(sig.str) == 0 {
|
||||
panic(FormatError("variant signature is empty"))
|
||||
}
|
||||
err, rem := validSingle(sig.str, &depthCounter{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if rem != "" {
|
||||
panic(FormatError("variant signature has multiple types"))
|
||||
}
|
||||
variant.sig = sig
|
||||
variant.value = dec.decode(sig.str, depth+1)
|
||||
return variant
|
||||
case 'h':
|
||||
idx := dec.decode("u", depth).(uint32)
|
||||
if int(idx) < len(dec.fds) {
|
||||
return UnixFD(dec.fds[idx])
|
||||
}
|
||||
return UnixFDIndex(idx)
|
||||
case 'a':
|
||||
if len(s) > 1 && s[1] == '{' {
|
||||
ksig := s[2:3]
|
||||
vsig := s[3 : len(s)-1]
|
||||
v := reflect.MakeMap(reflect.MapOf(typeFor(ksig), typeFor(vsig)))
|
||||
if depth >= 63 {
|
||||
panic(FormatError("input exceeds container depth limit"))
|
||||
}
|
||||
length := dec.decode("u", depth).(uint32)
|
||||
// Even for empty maps, the correct padding must be included
|
||||
dec.align(8)
|
||||
spos := dec.pos
|
||||
for dec.pos < spos+int(length) {
|
||||
dec.align(8)
|
||||
if !isKeyType(v.Type().Key()) {
|
||||
panic(InvalidTypeError{v.Type()})
|
||||
}
|
||||
kv := dec.decode(ksig, depth+2)
|
||||
vv := dec.decode(vsig, depth+2)
|
||||
v.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv))
|
||||
}
|
||||
return v.Interface()
|
||||
}
|
||||
if depth >= 64 {
|
||||
panic(FormatError("input exceeds container depth limit"))
|
||||
}
|
||||
sig := s[1:]
|
||||
length := dec.decode("u", depth).(uint32)
|
||||
// capacity can be determined only for fixed-size element types
|
||||
var capacity int
|
||||
if s := sigByteSize(sig); s != 0 {
|
||||
capacity = int(length) / s
|
||||
}
|
||||
v := reflect.MakeSlice(reflect.SliceOf(typeFor(sig)), 0, capacity)
|
||||
// Even for empty arrays, the correct padding must be included
|
||||
align := alignment(typeFor(s[1:]))
|
||||
if len(s) > 1 && s[1] == '(' {
|
||||
//Special case for arrays of structs
|
||||
//structs decode as a slice of interface{} values
|
||||
//but the dbus alignment does not match this
|
||||
align = 8
|
||||
}
|
||||
dec.align(align)
|
||||
spos := dec.pos
|
||||
for dec.pos < spos+int(length) {
|
||||
ev := dec.decode(s[1:], depth+1)
|
||||
v = reflect.Append(v, reflect.ValueOf(ev))
|
||||
}
|
||||
return v.Interface()
|
||||
case '(':
|
||||
if depth >= 64 {
|
||||
panic(FormatError("input exceeds container depth limit"))
|
||||
}
|
||||
dec.align(8)
|
||||
v := make([]interface{}, 0)
|
||||
s = s[1 : len(s)-1]
|
||||
for s != "" {
|
||||
err, rem := validSingle(s, &depthCounter{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ev := dec.decode(s[:len(s)-len(rem)], depth+1)
|
||||
v = append(v, ev)
|
||||
s = rem
|
||||
}
|
||||
return v
|
||||
default:
|
||||
panic(SignatureError{Sig: s})
|
||||
}
|
||||
}
|
||||
|
||||
// sigByteSize tries to calculates size of the given signature in bytes.
|
||||
//
|
||||
// It returns zero when it can't, for example when it contains non-fixed size
|
||||
// types such as strings, maps and arrays that require reading of the transmitted
|
||||
// data, for that we would need to implement the unread method for Decoder first.
|
||||
func sigByteSize(sig string) int {
|
||||
var total int
|
||||
for offset := 0; offset < len(sig); {
|
||||
switch sig[offset] {
|
||||
case 'y':
|
||||
total += 1
|
||||
offset += 1
|
||||
case 'n', 'q':
|
||||
total += 2
|
||||
offset += 1
|
||||
case 'b', 'i', 'u', 'h':
|
||||
total += 4
|
||||
offset += 1
|
||||
case 'x', 't', 'd':
|
||||
total += 8
|
||||
offset += 1
|
||||
case '(':
|
||||
i := 1
|
||||
depth := 1
|
||||
for i < len(sig[offset:]) && depth != 0 {
|
||||
if sig[offset+i] == '(' {
|
||||
depth++
|
||||
} else if sig[offset+i] == ')' {
|
||||
depth--
|
||||
}
|
||||
i++
|
||||
}
|
||||
s := sigByteSize(sig[offset+1 : offset+i-1])
|
||||
if s == 0 {
|
||||
return 0
|
||||
}
|
||||
total += s
|
||||
offset += i
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// A FormatError is an error in the wire format.
|
||||
type FormatError string
|
||||
|
||||
func (e FormatError) Error() string {
|
||||
return "dbus: wire format error: " + string(e)
|
||||
}
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func newIntrospectIntf(h *defaultHandler) *exportedIntf {
|
||||
methods := make(map[string]Method)
|
||||
methods["Introspect"] = exportedMethod{
|
||||
reflect.ValueOf(func(msg Message) (string, *Error) {
|
||||
path := msg.Headers[FieldPath].value.(ObjectPath)
|
||||
return h.introspectPath(path), nil
|
||||
}),
|
||||
}
|
||||
return newExportedIntf(methods, true)
|
||||
}
|
||||
|
||||
//NewDefaultHandler returns an instance of the default
|
||||
//call handler. This is useful if you want to implement only
|
||||
//one of the two handlers but not both.
|
||||
//
|
||||
// Deprecated: this is the default value, don't use it, it will be unexported.
|
||||
func NewDefaultHandler() *defaultHandler {
|
||||
h := &defaultHandler{
|
||||
objects: make(map[ObjectPath]*exportedObj),
|
||||
defaultIntf: make(map[string]*exportedIntf),
|
||||
}
|
||||
h.defaultIntf["org.freedesktop.DBus.Introspectable"] = newIntrospectIntf(h)
|
||||
return h
|
||||
}
|
||||
|
||||
type defaultHandler struct {
|
||||
sync.RWMutex
|
||||
objects map[ObjectPath]*exportedObj
|
||||
defaultIntf map[string]*exportedIntf
|
||||
}
|
||||
|
||||
func (h *defaultHandler) PathExists(path ObjectPath) bool {
|
||||
_, ok := h.objects[path]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (h *defaultHandler) introspectPath(path ObjectPath) string {
|
||||
subpath := make(map[string]struct{})
|
||||
var xml bytes.Buffer
|
||||
xml.WriteString("<node>")
|
||||
for obj := range h.objects {
|
||||
p := string(path)
|
||||
if p != "/" {
|
||||
p += "/"
|
||||
}
|
||||
if strings.HasPrefix(string(obj), p) {
|
||||
node_name := strings.Split(string(obj[len(p):]), "/")[0]
|
||||
subpath[node_name] = struct{}{}
|
||||
}
|
||||
}
|
||||
for s := range subpath {
|
||||
xml.WriteString("\n\t<node name=\"" + s + "\"/>")
|
||||
}
|
||||
xml.WriteString("\n</node>")
|
||||
return xml.String()
|
||||
}
|
||||
|
||||
func (h *defaultHandler) LookupObject(path ObjectPath) (ServerObject, bool) {
|
||||
h.RLock()
|
||||
defer h.RUnlock()
|
||||
object, ok := h.objects[path]
|
||||
if ok {
|
||||
return object, ok
|
||||
}
|
||||
|
||||
// If an object wasn't found for this exact path,
|
||||
// look for a matching subtree registration
|
||||
subtreeObject := newExportedObject()
|
||||
path = path[:strings.LastIndex(string(path), "/")]
|
||||
for len(path) > 0 {
|
||||
object, ok = h.objects[path]
|
||||
if ok {
|
||||
for name, iface := range object.interfaces {
|
||||
// Only include this handler if it registered for the subtree
|
||||
if iface.isFallbackInterface() {
|
||||
subtreeObject.interfaces[name] = iface
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
path = path[:strings.LastIndex(string(path), "/")]
|
||||
}
|
||||
|
||||
for name, intf := range h.defaultIntf {
|
||||
if _, exists := subtreeObject.interfaces[name]; exists {
|
||||
continue
|
||||
}
|
||||
subtreeObject.interfaces[name] = intf
|
||||
}
|
||||
|
||||
return subtreeObject, true
|
||||
}
|
||||
|
||||
func (h *defaultHandler) AddObject(path ObjectPath, object *exportedObj) {
|
||||
h.Lock()
|
||||
h.objects[path] = object
|
||||
h.Unlock()
|
||||
}
|
||||
|
||||
func (h *defaultHandler) DeleteObject(path ObjectPath) {
|
||||
h.Lock()
|
||||
delete(h.objects, path)
|
||||
h.Unlock()
|
||||
}
|
||||
|
||||
type exportedMethod struct {
|
||||
reflect.Value
|
||||
}
|
||||
|
||||
func (m exportedMethod) Call(args ...interface{}) ([]interface{}, error) {
|
||||
t := m.Type()
|
||||
|
||||
params := make([]reflect.Value, len(args))
|
||||
for i := 0; i < len(args); i++ {
|
||||
params[i] = reflect.ValueOf(args[i]).Elem()
|
||||
}
|
||||
|
||||
ret := m.Value.Call(params)
|
||||
var err error
|
||||
nilErr := false // The reflection will find almost-nils, let's only pass back clean ones!
|
||||
if t.NumOut() > 0 {
|
||||
if e, ok := ret[t.NumOut()-1].Interface().(*Error); ok { // godbus *Error
|
||||
nilErr = ret[t.NumOut()-1].IsNil()
|
||||
ret = ret[:t.NumOut()-1]
|
||||
err = e
|
||||
} else if ret[t.NumOut()-1].Type().Implements(errType) { // Go error
|
||||
i := ret[t.NumOut()-1].Interface()
|
||||
if i == nil {
|
||||
nilErr = ret[t.NumOut()-1].IsNil()
|
||||
} else {
|
||||
err = i.(error)
|
||||
}
|
||||
ret = ret[:t.NumOut()-1]
|
||||
}
|
||||
}
|
||||
out := make([]interface{}, len(ret))
|
||||
for i, val := range ret {
|
||||
out[i] = val.Interface()
|
||||
}
|
||||
if nilErr || err == nil {
|
||||
//concrete type to interface nil is a special case
|
||||
return out, nil
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (m exportedMethod) NumArguments() int {
|
||||
return m.Value.Type().NumIn()
|
||||
}
|
||||
|
||||
func (m exportedMethod) ArgumentValue(i int) interface{} {
|
||||
return reflect.Zero(m.Type().In(i)).Interface()
|
||||
}
|
||||
|
||||
func (m exportedMethod) NumReturns() int {
|
||||
return m.Value.Type().NumOut()
|
||||
}
|
||||
|
||||
func (m exportedMethod) ReturnValue(i int) interface{} {
|
||||
return reflect.Zero(m.Type().Out(i)).Interface()
|
||||
}
|
||||
|
||||
func newExportedObject() *exportedObj {
|
||||
return &exportedObj{
|
||||
interfaces: make(map[string]*exportedIntf),
|
||||
}
|
||||
}
|
||||
|
||||
type exportedObj struct {
|
||||
mu sync.RWMutex
|
||||
interfaces map[string]*exportedIntf
|
||||
}
|
||||
|
||||
func (obj *exportedObj) LookupInterface(name string) (Interface, bool) {
|
||||
if name == "" {
|
||||
return obj, true
|
||||
}
|
||||
obj.mu.RLock()
|
||||
defer obj.mu.RUnlock()
|
||||
intf, exists := obj.interfaces[name]
|
||||
return intf, exists
|
||||
}
|
||||
|
||||
func (obj *exportedObj) AddInterface(name string, iface *exportedIntf) {
|
||||
obj.mu.Lock()
|
||||
defer obj.mu.Unlock()
|
||||
obj.interfaces[name] = iface
|
||||
}
|
||||
|
||||
func (obj *exportedObj) DeleteInterface(name string) {
|
||||
obj.mu.Lock()
|
||||
defer obj.mu.Unlock()
|
||||
delete(obj.interfaces, name)
|
||||
}
|
||||
|
||||
func (obj *exportedObj) LookupMethod(name string) (Method, bool) {
|
||||
obj.mu.RLock()
|
||||
defer obj.mu.RUnlock()
|
||||
for _, intf := range obj.interfaces {
|
||||
method, exists := intf.LookupMethod(name)
|
||||
if exists {
|
||||
return method, exists
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (obj *exportedObj) isFallbackInterface() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func newExportedIntf(methods map[string]Method, includeSubtree bool) *exportedIntf {
|
||||
return &exportedIntf{
|
||||
methods: methods,
|
||||
includeSubtree: includeSubtree,
|
||||
}
|
||||
}
|
||||
|
||||
type exportedIntf struct {
|
||||
methods map[string]Method
|
||||
|
||||
// Whether or not this export is for the entire subtree
|
||||
includeSubtree bool
|
||||
}
|
||||
|
||||
func (obj *exportedIntf) LookupMethod(name string) (Method, bool) {
|
||||
out, exists := obj.methods[name]
|
||||
return out, exists
|
||||
}
|
||||
|
||||
func (obj *exportedIntf) isFallbackInterface() bool {
|
||||
return obj.includeSubtree
|
||||
}
|
||||
|
||||
//NewDefaultSignalHandler returns an instance of the default
|
||||
//signal handler. This is useful if you want to implement only
|
||||
//one of the two handlers but not both.
|
||||
//
|
||||
// Deprecated: this is the default value, don't use it, it will be unexported.
|
||||
func NewDefaultSignalHandler() *defaultSignalHandler {
|
||||
return &defaultSignalHandler{}
|
||||
}
|
||||
|
||||
type defaultSignalHandler struct {
|
||||
mu sync.RWMutex
|
||||
closed bool
|
||||
signals []*signalChannelData
|
||||
}
|
||||
|
||||
func (sh *defaultSignalHandler) DeliverSignal(intf, name string, signal *Signal) {
|
||||
sh.mu.RLock()
|
||||
defer sh.mu.RUnlock()
|
||||
if sh.closed {
|
||||
return
|
||||
}
|
||||
for _, scd := range sh.signals {
|
||||
scd.deliver(signal)
|
||||
}
|
||||
}
|
||||
|
||||
func (sh *defaultSignalHandler) Terminate() {
|
||||
sh.mu.Lock()
|
||||
defer sh.mu.Unlock()
|
||||
if sh.closed {
|
||||
return
|
||||
}
|
||||
|
||||
for _, scd := range sh.signals {
|
||||
scd.close()
|
||||
close(scd.ch)
|
||||
}
|
||||
sh.closed = true
|
||||
sh.signals = nil
|
||||
}
|
||||
|
||||
func (sh *defaultSignalHandler) AddSignal(ch chan<- *Signal) {
|
||||
sh.mu.Lock()
|
||||
defer sh.mu.Unlock()
|
||||
if sh.closed {
|
||||
return
|
||||
}
|
||||
sh.signals = append(sh.signals, &signalChannelData{
|
||||
ch: ch,
|
||||
done: make(chan struct{}),
|
||||
})
|
||||
}
|
||||
|
||||
func (sh *defaultSignalHandler) RemoveSignal(ch chan<- *Signal) {
|
||||
sh.mu.Lock()
|
||||
defer sh.mu.Unlock()
|
||||
if sh.closed {
|
||||
return
|
||||
}
|
||||
for i := len(sh.signals) - 1; i >= 0; i-- {
|
||||
if ch == sh.signals[i].ch {
|
||||
sh.signals[i].close()
|
||||
copy(sh.signals[i:], sh.signals[i+1:])
|
||||
sh.signals[len(sh.signals)-1] = nil
|
||||
sh.signals = sh.signals[:len(sh.signals)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type signalChannelData struct {
|
||||
wg sync.WaitGroup
|
||||
ch chan<- *Signal
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (scd *signalChannelData) deliver(signal *Signal) {
|
||||
select {
|
||||
case scd.ch <- signal:
|
||||
case <-scd.done:
|
||||
return
|
||||
default:
|
||||
scd.wg.Add(1)
|
||||
go scd.deferredDeliver(signal)
|
||||
}
|
||||
}
|
||||
|
||||
func (scd *signalChannelData) deferredDeliver(signal *Signal) {
|
||||
select {
|
||||
case scd.ch <- signal:
|
||||
case <-scd.done:
|
||||
}
|
||||
scd.wg.Done()
|
||||
}
|
||||
|
||||
func (scd *signalChannelData) close() {
|
||||
close(scd.done)
|
||||
scd.wg.Wait() // wait until all spawned goroutines return
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Package dbus implements bindings to the D-Bus message bus system.
|
||||
|
||||
To use the message bus API, you first need to connect to a bus (usually the
|
||||
session or system bus). The acquired connection then can be used to call methods
|
||||
on remote objects and emit or receive signals. Using the Export method, you can
|
||||
arrange D-Bus methods calls to be directly translated to method calls on a Go
|
||||
value.
|
||||
|
||||
Conversion Rules
|
||||
|
||||
For outgoing messages, Go types are automatically converted to the
|
||||
corresponding D-Bus types. See the official specification at
|
||||
https://dbus.freedesktop.org/doc/dbus-specification.html#type-system for more
|
||||
information on the D-Bus type system. The following types are directly encoded
|
||||
as their respective D-Bus equivalents:
|
||||
|
||||
Go type | D-Bus type
|
||||
------------+-----------
|
||||
byte | BYTE
|
||||
bool | BOOLEAN
|
||||
int16 | INT16
|
||||
uint16 | UINT16
|
||||
int | INT32
|
||||
uint | UINT32
|
||||
int32 | INT32
|
||||
uint32 | UINT32
|
||||
int64 | INT64
|
||||
uint64 | UINT64
|
||||
float64 | DOUBLE
|
||||
string | STRING
|
||||
ObjectPath | OBJECT_PATH
|
||||
Signature | SIGNATURE
|
||||
Variant | VARIANT
|
||||
interface{} | VARIANT
|
||||
UnixFDIndex | UNIX_FD
|
||||
|
||||
Slices and arrays encode as ARRAYs of their element type.
|
||||
|
||||
Maps encode as DICTs, provided that their key type can be used as a key for
|
||||
a DICT.
|
||||
|
||||
Structs other than Variant and Signature encode as a STRUCT containing their
|
||||
exported fields in order. Fields whose tags contain `dbus:"-"` and unexported
|
||||
fields will be skipped.
|
||||
|
||||
Pointers encode as the value they're pointed to.
|
||||
|
||||
Types convertible to one of the base types above will be mapped as the
|
||||
base type.
|
||||
|
||||
Trying to encode any other type or a slice, map or struct containing an
|
||||
unsupported type will result in an InvalidTypeError.
|
||||
|
||||
For incoming messages, the inverse of these rules are used, with the exception
|
||||
of STRUCTs. Incoming STRUCTS are represented as a slice of empty interfaces
|
||||
containing the struct fields in the correct order. The Store function can be
|
||||
used to convert such values to Go structs.
|
||||
|
||||
Unix FD passing
|
||||
|
||||
Handling Unix file descriptors deserves special mention. To use them, you should
|
||||
first check that they are supported on a connection by calling SupportsUnixFDs.
|
||||
If it returns true, all method of Connection will translate messages containing
|
||||
UnixFD's to messages that are accompanied by the given file descriptors with the
|
||||
UnixFD values being substituted by the correct indices. Similarly, the indices
|
||||
of incoming messages are automatically resolved. It shouldn't be necessary to use
|
||||
UnixFDIndex.
|
||||
|
||||
*/
|
||||
package dbus
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// An encoder encodes values to the D-Bus wire format.
|
||||
type encoder struct {
|
||||
out io.Writer
|
||||
fds []int
|
||||
order binary.ByteOrder
|
||||
pos int
|
||||
}
|
||||
|
||||
// NewEncoder returns a new encoder that writes to out in the given byte order.
|
||||
func newEncoder(out io.Writer, order binary.ByteOrder, fds []int) *encoder {
|
||||
enc := newEncoderAtOffset(out, 0, order, fds)
|
||||
return enc
|
||||
}
|
||||
|
||||
// newEncoderAtOffset returns a new encoder that writes to out in the given
|
||||
// byte order. Specify the offset to initialize pos for proper alignment
|
||||
// computation.
|
||||
func newEncoderAtOffset(out io.Writer, offset int, order binary.ByteOrder, fds []int) *encoder {
|
||||
enc := new(encoder)
|
||||
enc.out = out
|
||||
enc.order = order
|
||||
enc.pos = offset
|
||||
enc.fds = fds
|
||||
return enc
|
||||
}
|
||||
|
||||
// Aligns the next output to be on a multiple of n. Panics on write errors.
|
||||
func (enc *encoder) align(n int) {
|
||||
pad := enc.padding(0, n)
|
||||
if pad > 0 {
|
||||
empty := make([]byte, pad)
|
||||
if _, err := enc.out.Write(empty); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
enc.pos += pad
|
||||
}
|
||||
}
|
||||
|
||||
// pad returns the number of bytes of padding, based on current position and additional offset.
|
||||
// and alignment.
|
||||
func (enc *encoder) padding(offset, algn int) int {
|
||||
abs := enc.pos + offset
|
||||
if abs%algn != 0 {
|
||||
newabs := (abs + algn - 1) & ^(algn - 1)
|
||||
return newabs - abs
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Calls binary.Write(enc.out, enc.order, v) and panics on write errors.
|
||||
func (enc *encoder) binwrite(v interface{}) {
|
||||
if err := binary.Write(enc.out, enc.order, v); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Encode encodes the given values to the underlying reader. All written values
|
||||
// are aligned properly as required by the D-Bus spec.
|
||||
func (enc *encoder) Encode(vs ...interface{}) (err error) {
|
||||
defer func() {
|
||||
err, _ = recover().(error)
|
||||
}()
|
||||
for _, v := range vs {
|
||||
enc.encode(reflect.ValueOf(v), 0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// encode encodes the given value to the writer and panics on error. depth holds
|
||||
// the depth of the container nesting.
|
||||
func (enc *encoder) encode(v reflect.Value, depth int) {
|
||||
if depth > 64 {
|
||||
panic(FormatError("input exceeds depth limitation"))
|
||||
}
|
||||
enc.align(alignment(v.Type()))
|
||||
switch v.Kind() {
|
||||
case reflect.Uint8:
|
||||
var b [1]byte
|
||||
b[0] = byte(v.Uint())
|
||||
if _, err := enc.out.Write(b[:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
enc.pos++
|
||||
case reflect.Bool:
|
||||
if v.Bool() {
|
||||
enc.encode(reflect.ValueOf(uint32(1)), depth)
|
||||
} else {
|
||||
enc.encode(reflect.ValueOf(uint32(0)), depth)
|
||||
}
|
||||
case reflect.Int16:
|
||||
enc.binwrite(int16(v.Int()))
|
||||
enc.pos += 2
|
||||
case reflect.Uint16:
|
||||
enc.binwrite(uint16(v.Uint()))
|
||||
enc.pos += 2
|
||||
case reflect.Int, reflect.Int32:
|
||||
if v.Type() == unixFDType {
|
||||
fd := v.Int()
|
||||
idx := len(enc.fds)
|
||||
enc.fds = append(enc.fds, int(fd))
|
||||
enc.binwrite(uint32(idx))
|
||||
} else {
|
||||
enc.binwrite(int32(v.Int()))
|
||||
}
|
||||
enc.pos += 4
|
||||
case reflect.Uint, reflect.Uint32:
|
||||
enc.binwrite(uint32(v.Uint()))
|
||||
enc.pos += 4
|
||||
case reflect.Int64:
|
||||
enc.binwrite(v.Int())
|
||||
enc.pos += 8
|
||||
case reflect.Uint64:
|
||||
enc.binwrite(v.Uint())
|
||||
enc.pos += 8
|
||||
case reflect.Float64:
|
||||
enc.binwrite(v.Float())
|
||||
enc.pos += 8
|
||||
case reflect.String:
|
||||
str := v.String()
|
||||
if !utf8.ValidString(str) {
|
||||
panic(FormatError("input has a not-utf8 char in string"))
|
||||
}
|
||||
if strings.IndexByte(str, byte(0)) != -1 {
|
||||
panic(FormatError("input has a null char('\\000') in string"))
|
||||
}
|
||||
if v.Type() == objectPathType {
|
||||
if !ObjectPath(str).IsValid() {
|
||||
panic(FormatError("invalid object path"))
|
||||
}
|
||||
}
|
||||
enc.encode(reflect.ValueOf(uint32(len(str))), depth)
|
||||
b := make([]byte, v.Len()+1)
|
||||
copy(b, str)
|
||||
b[len(b)-1] = 0
|
||||
n, err := enc.out.Write(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
enc.pos += n
|
||||
case reflect.Ptr:
|
||||
enc.encode(v.Elem(), depth)
|
||||
case reflect.Slice, reflect.Array:
|
||||
// Lookahead offset: 4 bytes for uint32 length (with alignment),
|
||||
// plus alignment for elements.
|
||||
n := enc.padding(0, 4) + 4
|
||||
offset := enc.pos + n + enc.padding(n, alignment(v.Type().Elem()))
|
||||
|
||||
var buf bytes.Buffer
|
||||
bufenc := newEncoderAtOffset(&buf, offset, enc.order, enc.fds)
|
||||
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
bufenc.encode(v.Index(i), depth+1)
|
||||
}
|
||||
|
||||
if buf.Len() > 1<<26 {
|
||||
panic(FormatError("input exceeds array size limitation"))
|
||||
}
|
||||
|
||||
enc.fds = bufenc.fds
|
||||
enc.encode(reflect.ValueOf(uint32(buf.Len())), depth)
|
||||
length := buf.Len()
|
||||
enc.align(alignment(v.Type().Elem()))
|
||||
if _, err := buf.WriteTo(enc.out); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
enc.pos += length
|
||||
case reflect.Struct:
|
||||
switch t := v.Type(); t {
|
||||
case signatureType:
|
||||
str := v.Field(0)
|
||||
enc.encode(reflect.ValueOf(byte(str.Len())), depth)
|
||||
b := make([]byte, str.Len()+1)
|
||||
copy(b, str.String())
|
||||
b[len(b)-1] = 0
|
||||
n, err := enc.out.Write(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
enc.pos += n
|
||||
case variantType:
|
||||
variant := v.Interface().(Variant)
|
||||
enc.encode(reflect.ValueOf(variant.sig), depth+1)
|
||||
enc.encode(reflect.ValueOf(variant.value), depth+1)
|
||||
default:
|
||||
for i := 0; i < v.Type().NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
|
||||
enc.encode(v.Field(i), depth+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
// Maps are arrays of structures, so they actually increase the depth by
|
||||
// 2.
|
||||
if !isKeyType(v.Type().Key()) {
|
||||
panic(InvalidTypeError{v.Type()})
|
||||
}
|
||||
keys := v.MapKeys()
|
||||
// Lookahead offset: 4 bytes for uint32 length (with alignment),
|
||||
// plus 8-byte alignment
|
||||
n := enc.padding(0, 4) + 4
|
||||
offset := enc.pos + n + enc.padding(n, 8)
|
||||
|
||||
var buf bytes.Buffer
|
||||
bufenc := newEncoderAtOffset(&buf, offset, enc.order, enc.fds)
|
||||
for _, k := range keys {
|
||||
bufenc.align(8)
|
||||
bufenc.encode(k, depth+2)
|
||||
bufenc.encode(v.MapIndex(k), depth+2)
|
||||
}
|
||||
enc.fds = bufenc.fds
|
||||
enc.encode(reflect.ValueOf(uint32(buf.Len())), depth)
|
||||
length := buf.Len()
|
||||
enc.align(8)
|
||||
if _, err := buf.WriteTo(enc.out); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
enc.pos += length
|
||||
case reflect.Interface:
|
||||
enc.encode(reflect.ValueOf(MakeVariant(v.Interface())), depth)
|
||||
default:
|
||||
panic(InvalidTypeError{v.Type()})
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package dbus
|
||||
|
||||
import "net/url"
|
||||
|
||||
// EscapeBusAddressValue implements a requirement to escape the values
|
||||
// in D-Bus server addresses, as defined by the D-Bus specification at
|
||||
// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses.
|
||||
func EscapeBusAddressValue(val string) string {
|
||||
toEsc := strNeedsEscape(val)
|
||||
if toEsc == 0 {
|
||||
// Avoid unneeded allocation/copying.
|
||||
return val
|
||||
}
|
||||
|
||||
// Avoid allocation for short paths.
|
||||
var buf [64]byte
|
||||
var out []byte
|
||||
// Every to-be-escaped byte needs 2 extra bytes.
|
||||
required := len(val) + 2*toEsc
|
||||
if required <= len(buf) {
|
||||
out = buf[:required]
|
||||
} else {
|
||||
out = make([]byte, required)
|
||||
}
|
||||
|
||||
j := 0
|
||||
for i := 0; i < len(val); i++ {
|
||||
if ch := val[i]; needsEscape(ch) {
|
||||
// Convert ch to %xx, where xx is hex value.
|
||||
out[j] = '%'
|
||||
out[j+1] = hexchar(ch >> 4)
|
||||
out[j+2] = hexchar(ch & 0x0F)
|
||||
j += 3
|
||||
} else {
|
||||
out[j] = ch
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
return string(out)
|
||||
}
|
||||
|
||||
// UnescapeBusAddressValue unescapes values in D-Bus server addresses,
|
||||
// as defined by the D-Bus specification at
|
||||
// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses.
|
||||
func UnescapeBusAddressValue(val string) (string, error) {
|
||||
// Looks like url.PathUnescape does exactly what is required.
|
||||
return url.PathUnescape(val)
|
||||
}
|
||||
|
||||
// hexchar returns an octal representation of a n, where n < 16.
|
||||
// For invalid values of n, the function panics.
|
||||
func hexchar(n byte) byte {
|
||||
const hex = "0123456789abcdef"
|
||||
|
||||
// For n >= len(hex), runtime will panic.
|
||||
return hex[n]
|
||||
}
|
||||
|
||||
// needsEscape tells if a byte is NOT one of optionally-escaped bytes.
|
||||
func needsEscape(c byte) bool {
|
||||
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' {
|
||||
return false
|
||||
}
|
||||
switch c {
|
||||
case '-', '_', '/', '\\', '.', '*':
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// strNeedsEscape tells how many bytes in the string need escaping.
|
||||
func strNeedsEscape(val string) int {
|
||||
count := 0
|
||||
|
||||
for i := 0; i < len(val); i++ {
|
||||
if needsEscape(val[i]) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
+463
@@ -0,0 +1,463 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMsgInvalidArg = Error{
|
||||
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||
[]interface{}{"Invalid type / number of args"},
|
||||
}
|
||||
ErrMsgNoObject = Error{
|
||||
"org.freedesktop.DBus.Error.NoSuchObject",
|
||||
[]interface{}{"No such object"},
|
||||
}
|
||||
ErrMsgUnknownMethod = Error{
|
||||
"org.freedesktop.DBus.Error.UnknownMethod",
|
||||
[]interface{}{"Unknown / invalid method"},
|
||||
}
|
||||
ErrMsgUnknownInterface = Error{
|
||||
"org.freedesktop.DBus.Error.UnknownInterface",
|
||||
[]interface{}{"Object does not implement the interface"},
|
||||
}
|
||||
)
|
||||
|
||||
func MakeNoObjectError(path ObjectPath) Error {
|
||||
return Error{
|
||||
"org.freedesktop.DBus.Error.NoSuchObject",
|
||||
[]interface{}{fmt.Sprintf("No such object '%s'", string(path))},
|
||||
}
|
||||
}
|
||||
|
||||
func MakeUnknownMethodError(methodName string) Error {
|
||||
return Error{
|
||||
"org.freedesktop.DBus.Error.UnknownMethod",
|
||||
[]interface{}{fmt.Sprintf("Unknown / invalid method '%s'", methodName)},
|
||||
}
|
||||
}
|
||||
|
||||
func MakeUnknownInterfaceError(ifaceName string) Error {
|
||||
return Error{
|
||||
"org.freedesktop.DBus.Error.UnknownInterface",
|
||||
[]interface{}{fmt.Sprintf("Object does not implement the interface '%s'", ifaceName)},
|
||||
}
|
||||
}
|
||||
|
||||
func MakeFailedError(err error) *Error {
|
||||
return &Error{
|
||||
"org.freedesktop.DBus.Error.Failed",
|
||||
[]interface{}{err.Error()},
|
||||
}
|
||||
}
|
||||
|
||||
// Sender is a type which can be used in exported methods to receive the message
|
||||
// sender.
|
||||
type Sender string
|
||||
|
||||
func computeMethodName(name string, mapping map[string]string) string {
|
||||
newname, ok := mapping[name]
|
||||
if ok {
|
||||
name = newname
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func getMethods(in interface{}, mapping map[string]string) map[string]reflect.Value {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
methods := make(map[string]reflect.Value)
|
||||
val := reflect.ValueOf(in)
|
||||
typ := val.Type()
|
||||
for i := 0; i < typ.NumMethod(); i++ {
|
||||
methtype := typ.Method(i)
|
||||
method := val.Method(i)
|
||||
t := method.Type()
|
||||
// only track valid methods must return *Error as last arg
|
||||
// and must be exported
|
||||
if t.NumOut() == 0 ||
|
||||
t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) ||
|
||||
methtype.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
// map names while building table
|
||||
methods[computeMethodName(methtype.Name, mapping)] = method
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
func getAllMethods(in interface{}, mapping map[string]string) map[string]reflect.Value {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
methods := make(map[string]reflect.Value)
|
||||
val := reflect.ValueOf(in)
|
||||
typ := val.Type()
|
||||
for i := 0; i < typ.NumMethod(); i++ {
|
||||
methtype := typ.Method(i)
|
||||
method := val.Method(i)
|
||||
// map names while building table
|
||||
methods[computeMethodName(methtype.Name, mapping)] = method
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
func standardMethodArgumentDecode(m Method, sender string, msg *Message, body []interface{}) ([]interface{}, error) {
|
||||
pointers := make([]interface{}, m.NumArguments())
|
||||
decode := make([]interface{}, 0, len(body))
|
||||
|
||||
for i := 0; i < m.NumArguments(); i++ {
|
||||
tp := reflect.TypeOf(m.ArgumentValue(i))
|
||||
val := reflect.New(tp)
|
||||
pointers[i] = val.Interface()
|
||||
if tp == reflect.TypeOf((*Sender)(nil)).Elem() {
|
||||
val.Elem().SetString(sender)
|
||||
} else if tp == reflect.TypeOf((*Message)(nil)).Elem() {
|
||||
val.Elem().Set(reflect.ValueOf(*msg))
|
||||
} else {
|
||||
decode = append(decode, pointers[i])
|
||||
}
|
||||
}
|
||||
|
||||
if len(decode) != len(body) {
|
||||
return nil, ErrMsgInvalidArg
|
||||
}
|
||||
|
||||
if err := Store(body, decode...); err != nil {
|
||||
return nil, ErrMsgInvalidArg
|
||||
}
|
||||
|
||||
return pointers, nil
|
||||
}
|
||||
|
||||
func (conn *Conn) decodeArguments(m Method, sender string, msg *Message) ([]interface{}, error) {
|
||||
if decoder, ok := m.(ArgumentDecoder); ok {
|
||||
return decoder.DecodeArguments(conn, sender, msg, msg.Body)
|
||||
}
|
||||
return standardMethodArgumentDecode(m, sender, msg, msg.Body)
|
||||
}
|
||||
|
||||
// handleCall handles the given method call (i.e. looks if it's one of the
|
||||
// pre-implemented ones and searches for a corresponding handler if not).
|
||||
func (conn *Conn) handleCall(msg *Message) {
|
||||
name := msg.Headers[FieldMember].value.(string)
|
||||
path := msg.Headers[FieldPath].value.(ObjectPath)
|
||||
ifaceName, _ := msg.Headers[FieldInterface].value.(string)
|
||||
sender, hasSender := msg.Headers[FieldSender].value.(string)
|
||||
serial := msg.serial
|
||||
|
||||
if len(name) == 0 {
|
||||
conn.sendError(ErrMsgUnknownMethod, sender, serial)
|
||||
}
|
||||
|
||||
if ifaceName == "org.freedesktop.DBus.Peer" {
|
||||
switch name {
|
||||
case "Ping":
|
||||
conn.sendReply(sender, serial)
|
||||
case "GetMachineId":
|
||||
conn.sendReply(sender, serial, conn.uuid)
|
||||
default:
|
||||
conn.sendError(MakeUnknownMethodError(name), sender, serial)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
object, ok := conn.handler.LookupObject(path)
|
||||
if !ok {
|
||||
conn.sendError(MakeNoObjectError(path), sender, serial)
|
||||
return
|
||||
}
|
||||
|
||||
iface, exists := object.LookupInterface(ifaceName)
|
||||
if !exists {
|
||||
conn.sendError(MakeUnknownInterfaceError(ifaceName), sender, serial)
|
||||
return
|
||||
}
|
||||
|
||||
m, exists := iface.LookupMethod(name)
|
||||
if !exists {
|
||||
conn.sendError(MakeUnknownMethodError(name), sender, serial)
|
||||
return
|
||||
}
|
||||
args, err := conn.decodeArguments(m, sender, msg)
|
||||
if err != nil {
|
||||
conn.sendError(err, sender, serial)
|
||||
return
|
||||
}
|
||||
|
||||
ret, err := m.Call(args...)
|
||||
if err != nil {
|
||||
conn.sendError(err, sender, serial)
|
||||
return
|
||||
}
|
||||
|
||||
if msg.Flags&FlagNoReplyExpected == 0 {
|
||||
reply := new(Message)
|
||||
reply.Type = TypeMethodReply
|
||||
reply.Headers = make(map[HeaderField]Variant)
|
||||
if hasSender {
|
||||
reply.Headers[FieldDestination] = msg.Headers[FieldSender]
|
||||
}
|
||||
reply.Headers[FieldReplySerial] = MakeVariant(msg.serial)
|
||||
reply.Body = make([]interface{}, len(ret))
|
||||
for i := 0; i < len(ret); i++ {
|
||||
reply.Body[i] = ret[i]
|
||||
}
|
||||
reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...))
|
||||
|
||||
if err := reply.IsValid(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "dbus: dropping invalid reply to %s.%s on obj %s: %s\n", ifaceName, name, path, err)
|
||||
} else {
|
||||
conn.sendMessageAndIfClosed(reply, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Emit emits the given signal on the message bus. The name parameter must be
|
||||
// formatted as "interface.member", e.g., "org.freedesktop.DBus.NameLost".
|
||||
func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) error {
|
||||
i := strings.LastIndex(name, ".")
|
||||
if i == -1 {
|
||||
return errors.New("dbus: invalid method name")
|
||||
}
|
||||
iface := name[:i]
|
||||
member := name[i+1:]
|
||||
msg := new(Message)
|
||||
msg.Type = TypeSignal
|
||||
msg.Headers = make(map[HeaderField]Variant)
|
||||
msg.Headers[FieldInterface] = MakeVariant(iface)
|
||||
msg.Headers[FieldMember] = MakeVariant(member)
|
||||
msg.Headers[FieldPath] = MakeVariant(path)
|
||||
msg.Body = values
|
||||
if len(values) > 0 {
|
||||
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
|
||||
}
|
||||
if err := msg.IsValid(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var closed bool
|
||||
conn.sendMessageAndIfClosed(msg, func() {
|
||||
closed = true
|
||||
})
|
||||
if closed {
|
||||
return ErrClosed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Export registers the given value to be exported as an object on the
|
||||
// message bus.
|
||||
//
|
||||
// If a method call on the given path and interface is received, an exported
|
||||
// method with the same name is called with v as the receiver if the
|
||||
// parameters match and the last return value is of type *Error. If this
|
||||
// *Error is not nil, it is sent back to the caller as an error.
|
||||
// Otherwise, a method reply is sent with the other return values as its body.
|
||||
//
|
||||
// Any parameters with the special type Sender are set to the sender of the
|
||||
// dbus message when the method is called. Parameters of this type do not
|
||||
// contribute to the dbus signature of the method (i.e. the method is exposed
|
||||
// as if the parameters of type Sender were not there).
|
||||
//
|
||||
// Similarly, any parameters with the type Message are set to the raw message
|
||||
// received on the bus. Again, parameters of this type do not contribute to the
|
||||
// dbus signature of the method.
|
||||
//
|
||||
// Every method call is executed in a new goroutine, so the method may be called
|
||||
// in multiple goroutines at once.
|
||||
//
|
||||
// Method calls on the interface org.freedesktop.DBus.Peer will be automatically
|
||||
// handled for every object.
|
||||
//
|
||||
// Passing nil as the first parameter will cause conn to cease handling calls on
|
||||
// the given combination of path and interface.
|
||||
//
|
||||
// Export returns an error if path is not a valid path name.
|
||||
func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
|
||||
return conn.ExportWithMap(v, nil, path, iface)
|
||||
}
|
||||
|
||||
// ExportAll registers all exported methods defined by the given object on
|
||||
// the message bus.
|
||||
//
|
||||
// Unlike Export there is no requirement to have the last parameter as type
|
||||
// *Error. If you want to be able to return error then you can append an error
|
||||
// type parameter to your method signature. If the error returned is not nil,
|
||||
// it is sent back to the caller as an error. Otherwise, a method reply is
|
||||
// sent with the other return values as its body.
|
||||
func (conn *Conn) ExportAll(v interface{}, path ObjectPath, iface string) error {
|
||||
return conn.export(getAllMethods(v, nil), path, iface, false)
|
||||
}
|
||||
|
||||
// ExportWithMap works exactly like Export but provides the ability to remap
|
||||
// method names (e.g. export a lower-case method).
|
||||
//
|
||||
// The keys in the map are the real method names (exported on the struct), and
|
||||
// the values are the method names to be exported on DBus.
|
||||
func (conn *Conn) ExportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
|
||||
return conn.export(getMethods(v, mapping), path, iface, false)
|
||||
}
|
||||
|
||||
// ExportSubtree works exactly like Export but registers the given value for
|
||||
// an entire subtree rather under the root path provided.
|
||||
//
|
||||
// In order to make this useful, one parameter in each of the value's exported
|
||||
// methods should be a Message, in which case it will contain the raw message
|
||||
// (allowing one to get access to the path that caused the method to be called).
|
||||
//
|
||||
// Note that more specific export paths take precedence over less specific. For
|
||||
// example, a method call using the ObjectPath /foo/bar/baz will call a method
|
||||
// exported on /foo/bar before a method exported on /foo.
|
||||
func (conn *Conn) ExportSubtree(v interface{}, path ObjectPath, iface string) error {
|
||||
return conn.ExportSubtreeWithMap(v, nil, path, iface)
|
||||
}
|
||||
|
||||
// ExportSubtreeWithMap works exactly like ExportSubtree but provides the
|
||||
// ability to remap method names (e.g. export a lower-case method).
|
||||
//
|
||||
// The keys in the map are the real method names (exported on the struct), and
|
||||
// the values are the method names to be exported on DBus.
|
||||
func (conn *Conn) ExportSubtreeWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
|
||||
return conn.export(getMethods(v, mapping), path, iface, true)
|
||||
}
|
||||
|
||||
// ExportMethodTable like Export registers the given methods as an object
|
||||
// on the message bus. Unlike Export the it uses a method table to define
|
||||
// the object instead of a native go object.
|
||||
//
|
||||
// The method table is a map from method name to function closure
|
||||
// representing the method. This allows an object exported on the bus to not
|
||||
// necessarily be a native go object. It can be useful for generating exposed
|
||||
// methods on the fly.
|
||||
//
|
||||
// Any non-function objects in the method table are ignored.
|
||||
func (conn *Conn) ExportMethodTable(methods map[string]interface{}, path ObjectPath, iface string) error {
|
||||
return conn.exportMethodTable(methods, path, iface, false)
|
||||
}
|
||||
|
||||
// Like ExportSubtree, but with the same caveats as ExportMethodTable.
|
||||
func (conn *Conn) ExportSubtreeMethodTable(methods map[string]interface{}, path ObjectPath, iface string) error {
|
||||
return conn.exportMethodTable(methods, path, iface, true)
|
||||
}
|
||||
|
||||
func (conn *Conn) exportMethodTable(methods map[string]interface{}, path ObjectPath, iface string, includeSubtree bool) error {
|
||||
var out map[string]reflect.Value
|
||||
if methods != nil {
|
||||
out = make(map[string]reflect.Value)
|
||||
for name, method := range methods {
|
||||
rval := reflect.ValueOf(method)
|
||||
if rval.Kind() != reflect.Func {
|
||||
continue
|
||||
}
|
||||
t := rval.Type()
|
||||
// only track valid methods must return *Error as last arg
|
||||
if t.NumOut() == 0 ||
|
||||
t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) {
|
||||
continue
|
||||
}
|
||||
out[name] = rval
|
||||
}
|
||||
}
|
||||
return conn.export(out, path, iface, includeSubtree)
|
||||
}
|
||||
|
||||
func (conn *Conn) unexport(h *defaultHandler, path ObjectPath, iface string) error {
|
||||
if h.PathExists(path) {
|
||||
obj := h.objects[path]
|
||||
obj.DeleteInterface(iface)
|
||||
if len(obj.interfaces) == 0 {
|
||||
h.DeleteObject(path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// export is the worker function for all exports/registrations.
|
||||
func (conn *Conn) export(methods map[string]reflect.Value, path ObjectPath, iface string, includeSubtree bool) error {
|
||||
h, ok := conn.handler.(*defaultHandler)
|
||||
if !ok {
|
||||
return fmt.Errorf(
|
||||
`dbus: export only allowed on the default handler. Received: %T"`,
|
||||
conn.handler)
|
||||
}
|
||||
|
||||
if !path.IsValid() {
|
||||
return fmt.Errorf(`dbus: Invalid path name: "%s"`, path)
|
||||
}
|
||||
|
||||
// Remove a previous export if the interface is nil
|
||||
if methods == nil {
|
||||
return conn.unexport(h, path, iface)
|
||||
}
|
||||
|
||||
// If this is the first handler for this path, make a new map to hold all
|
||||
// handlers for this path.
|
||||
if !h.PathExists(path) {
|
||||
h.AddObject(path, newExportedObject())
|
||||
}
|
||||
|
||||
exportedMethods := make(map[string]Method)
|
||||
for name, method := range methods {
|
||||
exportedMethods[name] = exportedMethod{method}
|
||||
}
|
||||
|
||||
// Finally, save this handler
|
||||
obj := h.objects[path]
|
||||
obj.AddInterface(iface, newExportedIntf(exportedMethods, includeSubtree))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReleaseName calls org.freedesktop.DBus.ReleaseName and awaits a response.
|
||||
func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) {
|
||||
var r uint32
|
||||
err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return ReleaseNameReply(r), nil
|
||||
}
|
||||
|
||||
// RequestName calls org.freedesktop.DBus.RequestName and awaits a response.
|
||||
func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) {
|
||||
var r uint32
|
||||
err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return RequestNameReply(r), nil
|
||||
}
|
||||
|
||||
// ReleaseNameReply is the reply to a ReleaseName call.
|
||||
type ReleaseNameReply uint32
|
||||
|
||||
const (
|
||||
ReleaseNameReplyReleased ReleaseNameReply = 1 + iota
|
||||
ReleaseNameReplyNonExistent
|
||||
ReleaseNameReplyNotOwner
|
||||
)
|
||||
|
||||
// RequestNameFlags represents the possible flags for a RequestName call.
|
||||
type RequestNameFlags uint32
|
||||
|
||||
const (
|
||||
NameFlagAllowReplacement RequestNameFlags = 1 << iota
|
||||
NameFlagReplaceExisting
|
||||
NameFlagDoNotQueue
|
||||
)
|
||||
|
||||
// RequestNameReply is the reply to a RequestName call.
|
||||
type RequestNameReply uint32
|
||||
|
||||
const (
|
||||
RequestNameReplyPrimaryOwner RequestNameReply = 1 + iota
|
||||
RequestNameReplyInQueue
|
||||
RequestNameReplyExists
|
||||
RequestNameReplyAlreadyOwner
|
||||
)
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
)
|
||||
|
||||
// Get returns the home directory of the current user, which is usually the
|
||||
// value of HOME environment variable. In case it is not set or empty, os/user
|
||||
// package is used.
|
||||
//
|
||||
// If linking statically with cgo enabled against glibc, make sure the
|
||||
// osusergo build tag is used.
|
||||
//
|
||||
// If needing to do nss lookups, do not disable cgo or set osusergo.
|
||||
func getHomeDir() string {
|
||||
homeDir := os.Getenv("HOME")
|
||||
if homeDir != "" {
|
||||
return homeDir
|
||||
}
|
||||
if u, err := user.Current(); err == nil {
|
||||
return u.HomeDir
|
||||
}
|
||||
return "/"
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MatchOption specifies option for dbus routing match rule. Options can be constructed with WithMatch* helpers.
|
||||
// For full list of available options consult
|
||||
// https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules
|
||||
type MatchOption struct {
|
||||
key string
|
||||
value string
|
||||
}
|
||||
|
||||
func formatMatchOptions(options []MatchOption) string {
|
||||
items := make([]string, 0, len(options))
|
||||
for _, option := range options {
|
||||
items = append(items, option.key+"='"+option.value+"'")
|
||||
}
|
||||
return strings.Join(items, ",")
|
||||
}
|
||||
|
||||
// WithMatchOption creates match option with given key and value
|
||||
func WithMatchOption(key, value string) MatchOption {
|
||||
return MatchOption{key, value}
|
||||
}
|
||||
|
||||
// doesn't make sense to export this option because clients can only
|
||||
// subscribe to messages with signal type.
|
||||
func withMatchType(typ string) MatchOption {
|
||||
return WithMatchOption("type", typ)
|
||||
}
|
||||
|
||||
// WithMatchSender sets sender match option.
|
||||
func WithMatchSender(sender string) MatchOption {
|
||||
return WithMatchOption("sender", sender)
|
||||
}
|
||||
|
||||
// WithMatchSender sets interface match option.
|
||||
func WithMatchInterface(iface string) MatchOption {
|
||||
return WithMatchOption("interface", iface)
|
||||
}
|
||||
|
||||
// WithMatchMember sets member match option.
|
||||
func WithMatchMember(member string) MatchOption {
|
||||
return WithMatchOption("member", member)
|
||||
}
|
||||
|
||||
// WithMatchObjectPath creates match option that filters events based on given path
|
||||
func WithMatchObjectPath(path ObjectPath) MatchOption {
|
||||
return WithMatchOption("path", string(path))
|
||||
}
|
||||
|
||||
// WithMatchPathNamespace sets path_namespace match option.
|
||||
func WithMatchPathNamespace(namespace ObjectPath) MatchOption {
|
||||
return WithMatchOption("path_namespace", string(namespace))
|
||||
}
|
||||
|
||||
// WithMatchDestination sets destination match option.
|
||||
func WithMatchDestination(destination string) MatchOption {
|
||||
return WithMatchOption("destination", destination)
|
||||
}
|
||||
|
||||
// WithMatchArg sets argN match option, range of N is 0 to 63.
|
||||
func WithMatchArg(argIdx int, value string) MatchOption {
|
||||
if argIdx < 0 || argIdx > 63 {
|
||||
panic("range of argument index is 0 to 63")
|
||||
}
|
||||
return WithMatchOption("arg"+strconv.Itoa(argIdx), value)
|
||||
}
|
||||
|
||||
// WithMatchArgPath sets argN path match option, range of N is 0 to 63.
|
||||
func WithMatchArgPath(argIdx int, path string) MatchOption {
|
||||
if argIdx < 0 || argIdx > 63 {
|
||||
panic("range of argument index is 0 to 63")
|
||||
}
|
||||
return WithMatchOption("arg"+strconv.Itoa(argIdx)+"path", path)
|
||||
}
|
||||
|
||||
// WithMatchArg0Namespace sets arg0namespace match option.
|
||||
func WithMatchArg0Namespace(arg0Namespace string) MatchOption {
|
||||
return WithMatchOption("arg0namespace", arg0Namespace)
|
||||
}
|
||||
|
||||
// WithMatchEavesdrop sets eavesdrop match option.
|
||||
func WithMatchEavesdrop(eavesdrop bool) MatchOption {
|
||||
return WithMatchOption("eavesdrop", strconv.FormatBool(eavesdrop))
|
||||
}
|
||||
+390
@@ -0,0 +1,390 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const protoVersion byte = 1
|
||||
|
||||
// Flags represents the possible flags of a D-Bus message.
|
||||
type Flags byte
|
||||
|
||||
const (
|
||||
// FlagNoReplyExpected signals that the message is not expected to generate
|
||||
// a reply. If this flag is set on outgoing messages, any possible reply
|
||||
// will be discarded.
|
||||
FlagNoReplyExpected Flags = 1 << iota
|
||||
// FlagNoAutoStart signals that the message bus should not automatically
|
||||
// start an application when handling this message.
|
||||
FlagNoAutoStart
|
||||
// FlagAllowInteractiveAuthorization may be set on a method call
|
||||
// message to inform the receiving side that the caller is prepared
|
||||
// to wait for interactive authorization, which might take a
|
||||
// considerable time to complete. For instance, if this flag is set,
|
||||
// it would be appropriate to query the user for passwords or
|
||||
// confirmation via Polkit or a similar framework.
|
||||
FlagAllowInteractiveAuthorization
|
||||
)
|
||||
|
||||
// Type represents the possible types of a D-Bus message.
|
||||
type Type byte
|
||||
|
||||
const (
|
||||
TypeMethodCall Type = 1 + iota
|
||||
TypeMethodReply
|
||||
TypeError
|
||||
TypeSignal
|
||||
typeMax
|
||||
)
|
||||
|
||||
func (t Type) String() string {
|
||||
switch t {
|
||||
case TypeMethodCall:
|
||||
return "method call"
|
||||
case TypeMethodReply:
|
||||
return "reply"
|
||||
case TypeError:
|
||||
return "error"
|
||||
case TypeSignal:
|
||||
return "signal"
|
||||
}
|
||||
return "invalid"
|
||||
}
|
||||
|
||||
// HeaderField represents the possible byte codes for the headers
|
||||
// of a D-Bus message.
|
||||
type HeaderField byte
|
||||
|
||||
const (
|
||||
FieldPath HeaderField = 1 + iota
|
||||
FieldInterface
|
||||
FieldMember
|
||||
FieldErrorName
|
||||
FieldReplySerial
|
||||
FieldDestination
|
||||
FieldSender
|
||||
FieldSignature
|
||||
FieldUnixFDs
|
||||
fieldMax
|
||||
)
|
||||
|
||||
// An InvalidMessageError describes the reason why a D-Bus message is regarded as
|
||||
// invalid.
|
||||
type InvalidMessageError string
|
||||
|
||||
func (e InvalidMessageError) Error() string {
|
||||
return "dbus: invalid message: " + string(e)
|
||||
}
|
||||
|
||||
// fieldType are the types of the various header fields.
|
||||
var fieldTypes = [fieldMax]reflect.Type{
|
||||
FieldPath: objectPathType,
|
||||
FieldInterface: stringType,
|
||||
FieldMember: stringType,
|
||||
FieldErrorName: stringType,
|
||||
FieldReplySerial: uint32Type,
|
||||
FieldDestination: stringType,
|
||||
FieldSender: stringType,
|
||||
FieldSignature: signatureType,
|
||||
FieldUnixFDs: uint32Type,
|
||||
}
|
||||
|
||||
// requiredFields lists the header fields that are required by the different
|
||||
// message types.
|
||||
var requiredFields = [typeMax][]HeaderField{
|
||||
TypeMethodCall: {FieldPath, FieldMember},
|
||||
TypeMethodReply: {FieldReplySerial},
|
||||
TypeError: {FieldErrorName, FieldReplySerial},
|
||||
TypeSignal: {FieldPath, FieldInterface, FieldMember},
|
||||
}
|
||||
|
||||
// Message represents a single D-Bus message.
|
||||
type Message struct {
|
||||
Type
|
||||
Flags
|
||||
Headers map[HeaderField]Variant
|
||||
Body []interface{}
|
||||
|
||||
serial uint32
|
||||
}
|
||||
|
||||
type header struct {
|
||||
Field byte
|
||||
Variant
|
||||
}
|
||||
|
||||
func DecodeMessageWithFDs(rd io.Reader, fds []int) (msg *Message, err error) {
|
||||
var order binary.ByteOrder
|
||||
var hlength, length uint32
|
||||
var typ, flags, proto byte
|
||||
var headers []header
|
||||
|
||||
b := make([]byte, 1)
|
||||
_, err = rd.Read(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch b[0] {
|
||||
case 'l':
|
||||
order = binary.LittleEndian
|
||||
case 'B':
|
||||
order = binary.BigEndian
|
||||
default:
|
||||
return nil, InvalidMessageError("invalid byte order")
|
||||
}
|
||||
|
||||
dec := newDecoder(rd, order, fds)
|
||||
dec.pos = 1
|
||||
|
||||
msg = new(Message)
|
||||
vs, err := dec.Decode(Signature{"yyyuu"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = Store(vs, &typ, &flags, &proto, &length, &msg.serial); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg.Type = Type(typ)
|
||||
msg.Flags = Flags(flags)
|
||||
|
||||
// get the header length separately because we need it later
|
||||
b = make([]byte, 4)
|
||||
_, err = io.ReadFull(rd, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
binary.Read(bytes.NewBuffer(b), order, &hlength)
|
||||
if hlength+length+16 > 1<<27 {
|
||||
return nil, InvalidMessageError("message is too long")
|
||||
}
|
||||
dec = newDecoder(io.MultiReader(bytes.NewBuffer(b), rd), order, fds)
|
||||
dec.pos = 12
|
||||
vs, err = dec.Decode(Signature{"a(yv)"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = Store(vs, &headers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg.Headers = make(map[HeaderField]Variant)
|
||||
for _, v := range headers {
|
||||
msg.Headers[HeaderField(v.Field)] = v.Variant
|
||||
}
|
||||
|
||||
dec.align(8)
|
||||
body := make([]byte, int(length))
|
||||
if length != 0 {
|
||||
_, err := io.ReadFull(rd, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = msg.IsValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sig, _ := msg.Headers[FieldSignature].value.(Signature)
|
||||
if sig.str != "" {
|
||||
buf := bytes.NewBuffer(body)
|
||||
dec = newDecoder(buf, order, fds)
|
||||
vs, err := dec.Decode(sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg.Body = vs
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeMessage tries to decode a single message in the D-Bus wire format
|
||||
// from the given reader. The byte order is figured out from the first byte.
|
||||
// The possibly returned error can be an error of the underlying reader, an
|
||||
// InvalidMessageError or a FormatError.
|
||||
func DecodeMessage(rd io.Reader) (msg *Message, err error) {
|
||||
return DecodeMessageWithFDs(rd, make([]int, 0))
|
||||
}
|
||||
|
||||
type nullwriter struct{}
|
||||
|
||||
func (nullwriter) Write(p []byte) (cnt int, err error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (msg *Message) CountFds() (int, error) {
|
||||
if len(msg.Body) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
enc := newEncoder(nullwriter{}, nativeEndian, make([]int, 0))
|
||||
err := enc.Encode(msg.Body...)
|
||||
return len(enc.fds), err
|
||||
}
|
||||
|
||||
func (msg *Message) EncodeToWithFDs(out io.Writer, order binary.ByteOrder) (fds []int, err error) {
|
||||
if err := msg.validateHeader(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var vs [7]interface{}
|
||||
switch order {
|
||||
case binary.LittleEndian:
|
||||
vs[0] = byte('l')
|
||||
case binary.BigEndian:
|
||||
vs[0] = byte('B')
|
||||
default:
|
||||
return nil, errors.New("dbus: invalid byte order")
|
||||
}
|
||||
body := new(bytes.Buffer)
|
||||
fds = make([]int, 0)
|
||||
enc := newEncoder(body, order, fds)
|
||||
if len(msg.Body) != 0 {
|
||||
err = enc.Encode(msg.Body...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
vs[1] = msg.Type
|
||||
vs[2] = msg.Flags
|
||||
vs[3] = protoVersion
|
||||
vs[4] = uint32(len(body.Bytes()))
|
||||
vs[5] = msg.serial
|
||||
headers := make([]header, 0, len(msg.Headers))
|
||||
for k, v := range msg.Headers {
|
||||
headers = append(headers, header{byte(k), v})
|
||||
}
|
||||
vs[6] = headers
|
||||
var buf bytes.Buffer
|
||||
enc = newEncoder(&buf, order, enc.fds)
|
||||
err = enc.Encode(vs[:]...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
enc.align(8)
|
||||
body.WriteTo(&buf)
|
||||
if buf.Len() > 1<<27 {
|
||||
return make([]int, 0), InvalidMessageError("message is too long")
|
||||
}
|
||||
if _, err := buf.WriteTo(out); err != nil {
|
||||
return make([]int, 0), err
|
||||
}
|
||||
return enc.fds, nil
|
||||
}
|
||||
|
||||
// EncodeTo encodes and sends a message to the given writer. The byte order must
|
||||
// be either binary.LittleEndian or binary.BigEndian. If the message is not
|
||||
// valid or an error occurs when writing, an error is returned.
|
||||
func (msg *Message) EncodeTo(out io.Writer, order binary.ByteOrder) (err error) {
|
||||
_, err = msg.EncodeToWithFDs(out, order)
|
||||
return err
|
||||
}
|
||||
|
||||
// IsValid checks whether msg is a valid message and returns an
|
||||
// InvalidMessageError or FormatError if it is not.
|
||||
func (msg *Message) IsValid() error {
|
||||
var b bytes.Buffer
|
||||
return msg.EncodeTo(&b, nativeEndian)
|
||||
}
|
||||
|
||||
func (msg *Message) validateHeader() error {
|
||||
if msg.Flags & ^(FlagNoAutoStart|FlagNoReplyExpected|FlagAllowInteractiveAuthorization) != 0 {
|
||||
return InvalidMessageError("invalid flags")
|
||||
}
|
||||
if msg.Type == 0 || msg.Type >= typeMax {
|
||||
return InvalidMessageError("invalid message type")
|
||||
}
|
||||
for k, v := range msg.Headers {
|
||||
if k == 0 || k >= fieldMax {
|
||||
return InvalidMessageError("invalid header")
|
||||
}
|
||||
if reflect.TypeOf(v.value) != fieldTypes[k] {
|
||||
return InvalidMessageError("invalid type of header field")
|
||||
}
|
||||
}
|
||||
for _, v := range requiredFields[msg.Type] {
|
||||
if _, ok := msg.Headers[v]; !ok {
|
||||
return InvalidMessageError("missing required header")
|
||||
}
|
||||
}
|
||||
if path, ok := msg.Headers[FieldPath]; ok {
|
||||
if !path.value.(ObjectPath).IsValid() {
|
||||
return InvalidMessageError("invalid path name")
|
||||
}
|
||||
}
|
||||
if iface, ok := msg.Headers[FieldInterface]; ok {
|
||||
if !isValidInterface(iface.value.(string)) {
|
||||
return InvalidMessageError("invalid interface name")
|
||||
}
|
||||
}
|
||||
if member, ok := msg.Headers[FieldMember]; ok {
|
||||
if !isValidMember(member.value.(string)) {
|
||||
return InvalidMessageError("invalid member name")
|
||||
}
|
||||
}
|
||||
if errname, ok := msg.Headers[FieldErrorName]; ok {
|
||||
if !isValidInterface(errname.value.(string)) {
|
||||
return InvalidMessageError("invalid error name")
|
||||
}
|
||||
}
|
||||
if len(msg.Body) != 0 {
|
||||
if _, ok := msg.Headers[FieldSignature]; !ok {
|
||||
return InvalidMessageError("missing signature")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serial returns the message's serial number. The returned value is only valid
|
||||
// for messages received by eavesdropping.
|
||||
func (msg *Message) Serial() uint32 {
|
||||
return msg.serial
|
||||
}
|
||||
|
||||
// String returns a string representation of a message similar to the format of
|
||||
// dbus-monitor.
|
||||
func (msg *Message) String() string {
|
||||
if err := msg.IsValid(); err != nil {
|
||||
return "<invalid>"
|
||||
}
|
||||
s := msg.Type.String()
|
||||
if v, ok := msg.Headers[FieldSender]; ok {
|
||||
s += " from " + v.value.(string)
|
||||
}
|
||||
if v, ok := msg.Headers[FieldDestination]; ok {
|
||||
s += " to " + v.value.(string)
|
||||
}
|
||||
s += " serial " + strconv.FormatUint(uint64(msg.serial), 10)
|
||||
if v, ok := msg.Headers[FieldReplySerial]; ok {
|
||||
s += " reply_serial " + strconv.FormatUint(uint64(v.value.(uint32)), 10)
|
||||
}
|
||||
if v, ok := msg.Headers[FieldUnixFDs]; ok {
|
||||
s += " unixfds " + strconv.FormatUint(uint64(v.value.(uint32)), 10)
|
||||
}
|
||||
if v, ok := msg.Headers[FieldPath]; ok {
|
||||
s += " path " + string(v.value.(ObjectPath))
|
||||
}
|
||||
if v, ok := msg.Headers[FieldInterface]; ok {
|
||||
s += " interface " + v.value.(string)
|
||||
}
|
||||
if v, ok := msg.Headers[FieldErrorName]; ok {
|
||||
s += " error " + v.value.(string)
|
||||
}
|
||||
if v, ok := msg.Headers[FieldMember]; ok {
|
||||
s += " member " + v.value.(string)
|
||||
}
|
||||
if len(msg.Body) != 0 {
|
||||
s += "\n"
|
||||
}
|
||||
for i, v := range msg.Body {
|
||||
s += " " + MakeVariant(v).String()
|
||||
if i != len(msg.Body)-1 {
|
||||
s += "\n"
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BusObject is the interface of a remote object on which methods can be
|
||||
// invoked.
|
||||
type BusObject interface {
|
||||
Call(method string, flags Flags, args ...interface{}) *Call
|
||||
CallWithContext(ctx context.Context, method string, flags Flags, args ...interface{}) *Call
|
||||
Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call
|
||||
GoWithContext(ctx context.Context, method string, flags Flags, ch chan *Call, args ...interface{}) *Call
|
||||
AddMatchSignal(iface, member string, options ...MatchOption) *Call
|
||||
RemoveMatchSignal(iface, member string, options ...MatchOption) *Call
|
||||
GetProperty(p string) (Variant, error)
|
||||
StoreProperty(p string, value interface{}) error
|
||||
SetProperty(p string, v interface{}) error
|
||||
Destination() string
|
||||
Path() ObjectPath
|
||||
}
|
||||
|
||||
// Object represents a remote object on which methods can be invoked.
|
||||
type Object struct {
|
||||
conn *Conn
|
||||
dest string
|
||||
path ObjectPath
|
||||
}
|
||||
|
||||
// Call calls a method with (*Object).Go and waits for its reply.
|
||||
func (o *Object) Call(method string, flags Flags, args ...interface{}) *Call {
|
||||
return <-o.createCall(context.Background(), method, flags, make(chan *Call, 1), args...).Done
|
||||
}
|
||||
|
||||
// CallWithContext acts like Call but takes a context
|
||||
func (o *Object) CallWithContext(ctx context.Context, method string, flags Flags, args ...interface{}) *Call {
|
||||
return <-o.createCall(ctx, method, flags, make(chan *Call, 1), args...).Done
|
||||
}
|
||||
|
||||
// AddMatchSignal subscribes BusObject to signals from specified interface,
|
||||
// method (member). Additional filter rules can be added via WithMatch* option constructors.
|
||||
// Note: To filter events by object path you have to specify this path via an option.
|
||||
//
|
||||
// Deprecated: use (*Conn) AddMatchSignal instead.
|
||||
func (o *Object) AddMatchSignal(iface, member string, options ...MatchOption) *Call {
|
||||
base := []MatchOption{
|
||||
withMatchType("signal"),
|
||||
WithMatchInterface(iface),
|
||||
WithMatchMember(member),
|
||||
}
|
||||
|
||||
options = append(base, options...)
|
||||
return o.conn.BusObject().Call(
|
||||
"org.freedesktop.DBus.AddMatch",
|
||||
0,
|
||||
formatMatchOptions(options),
|
||||
)
|
||||
}
|
||||
|
||||
// RemoveMatchSignal unsubscribes BusObject from signals from specified interface,
|
||||
// method (member). Additional filter rules can be added via WithMatch* option constructors
|
||||
//
|
||||
// Deprecated: use (*Conn) RemoveMatchSignal instead.
|
||||
func (o *Object) RemoveMatchSignal(iface, member string, options ...MatchOption) *Call {
|
||||
base := []MatchOption{
|
||||
withMatchType("signal"),
|
||||
WithMatchInterface(iface),
|
||||
WithMatchMember(member),
|
||||
}
|
||||
|
||||
options = append(base, options...)
|
||||
return o.conn.BusObject().Call(
|
||||
"org.freedesktop.DBus.RemoveMatch",
|
||||
0,
|
||||
formatMatchOptions(options),
|
||||
)
|
||||
}
|
||||
|
||||
// Go calls a method with the given arguments asynchronously. It returns a
|
||||
// Call structure representing this method call. The passed channel will
|
||||
// return the same value once the call is done. If ch is nil, a new channel
|
||||
// will be allocated. Otherwise, ch has to be buffered or Go will panic.
|
||||
//
|
||||
// If the flags include FlagNoReplyExpected, ch is ignored and a Call structure
|
||||
// is returned with any error in Err and a closed channel in Done containing
|
||||
// the returned Call as it's one entry.
|
||||
//
|
||||
// If the method parameter contains a dot ('.'), the part before the last dot
|
||||
// specifies the interface on which the method is called.
|
||||
func (o *Object) Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call {
|
||||
return o.createCall(context.Background(), method, flags, ch, args...)
|
||||
}
|
||||
|
||||
// GoWithContext acts like Go but takes a context
|
||||
func (o *Object) GoWithContext(ctx context.Context, method string, flags Flags, ch chan *Call, args ...interface{}) *Call {
|
||||
return o.createCall(ctx, method, flags, ch, args...)
|
||||
}
|
||||
|
||||
func (o *Object) createCall(ctx context.Context, method string, flags Flags, ch chan *Call, args ...interface{}) *Call {
|
||||
if ctx == nil {
|
||||
panic("nil context")
|
||||
}
|
||||
iface := ""
|
||||
i := strings.LastIndex(method, ".")
|
||||
if i != -1 {
|
||||
iface = method[:i]
|
||||
}
|
||||
method = method[i+1:]
|
||||
msg := new(Message)
|
||||
msg.Type = TypeMethodCall
|
||||
msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected)
|
||||
msg.Headers = make(map[HeaderField]Variant)
|
||||
msg.Headers[FieldPath] = MakeVariant(o.path)
|
||||
msg.Headers[FieldDestination] = MakeVariant(o.dest)
|
||||
msg.Headers[FieldMember] = MakeVariant(method)
|
||||
if iface != "" {
|
||||
msg.Headers[FieldInterface] = MakeVariant(iface)
|
||||
}
|
||||
msg.Body = args
|
||||
if len(args) > 0 {
|
||||
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(args...))
|
||||
}
|
||||
return o.conn.SendWithContext(ctx, msg, ch)
|
||||
}
|
||||
|
||||
// GetProperty calls org.freedesktop.DBus.Properties.Get on the given
|
||||
// object. The property name must be given in interface.member notation.
|
||||
func (o *Object) GetProperty(p string) (Variant, error) {
|
||||
var result Variant
|
||||
err := o.StoreProperty(p, &result)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// StoreProperty calls org.freedesktop.DBus.Properties.Get on the given
|
||||
// object. The property name must be given in interface.member notation.
|
||||
// It stores the returned property into the provided value.
|
||||
func (o *Object) StoreProperty(p string, value interface{}) error {
|
||||
idx := strings.LastIndex(p, ".")
|
||||
if idx == -1 || idx+1 == len(p) {
|
||||
return errors.New("dbus: invalid property " + p)
|
||||
}
|
||||
|
||||
iface := p[:idx]
|
||||
prop := p[idx+1:]
|
||||
|
||||
return o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop).
|
||||
Store(value)
|
||||
}
|
||||
|
||||
// SetProperty calls org.freedesktop.DBus.Properties.Set on the given
|
||||
// object. The property name must be given in interface.member notation.
|
||||
func (o *Object) SetProperty(p string, v interface{}) error {
|
||||
idx := strings.LastIndex(p, ".")
|
||||
if idx == -1 || idx+1 == len(p) {
|
||||
return errors.New("dbus: invalid property " + p)
|
||||
}
|
||||
|
||||
iface := p[:idx]
|
||||
prop := p[idx+1:]
|
||||
|
||||
return o.Call("org.freedesktop.DBus.Properties.Set", 0, iface, prop, v).Err
|
||||
}
|
||||
|
||||
// Destination returns the destination that calls on (o *Object) are sent to.
|
||||
func (o *Object) Destination() string {
|
||||
return o.dest
|
||||
}
|
||||
|
||||
// Path returns the path that calls on (o *Object") are sent to.
|
||||
func (o *Object) Path() ObjectPath {
|
||||
return o.path
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package dbus
|
||||
|
||||
// Sequence represents the value of a monotonically increasing counter.
|
||||
type Sequence uint64
|
||||
|
||||
const (
|
||||
// NoSequence indicates the absence of a sequence value.
|
||||
NoSequence Sequence = 0
|
||||
)
|
||||
|
||||
// sequenceGenerator represents a monotonically increasing counter.
|
||||
type sequenceGenerator struct {
|
||||
nextSequence Sequence
|
||||
}
|
||||
|
||||
func (generator *sequenceGenerator) next() Sequence {
|
||||
result := generator.nextSequence
|
||||
generator.nextSequence++
|
||||
return result
|
||||
}
|
||||
|
||||
func newSequenceGenerator() *sequenceGenerator {
|
||||
return &sequenceGenerator{nextSequence: 1}
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// NewSequentialSignalHandler returns an instance of a new
|
||||
// signal handler that guarantees sequential processing of signals. It is a
|
||||
// guarantee of this signal handler that signals will be written to
|
||||
// channels in the order they are received on the DBus connection.
|
||||
func NewSequentialSignalHandler() SignalHandler {
|
||||
return &sequentialSignalHandler{}
|
||||
}
|
||||
|
||||
type sequentialSignalHandler struct {
|
||||
mu sync.RWMutex
|
||||
closed bool
|
||||
signals []*sequentialSignalChannelData
|
||||
}
|
||||
|
||||
func (sh *sequentialSignalHandler) DeliverSignal(intf, name string, signal *Signal) {
|
||||
sh.mu.RLock()
|
||||
defer sh.mu.RUnlock()
|
||||
if sh.closed {
|
||||
return
|
||||
}
|
||||
for _, scd := range sh.signals {
|
||||
scd.deliver(signal)
|
||||
}
|
||||
}
|
||||
|
||||
func (sh *sequentialSignalHandler) Terminate() {
|
||||
sh.mu.Lock()
|
||||
defer sh.mu.Unlock()
|
||||
if sh.closed {
|
||||
return
|
||||
}
|
||||
|
||||
for _, scd := range sh.signals {
|
||||
scd.close()
|
||||
close(scd.ch)
|
||||
}
|
||||
sh.closed = true
|
||||
sh.signals = nil
|
||||
}
|
||||
|
||||
func (sh *sequentialSignalHandler) AddSignal(ch chan<- *Signal) {
|
||||
sh.mu.Lock()
|
||||
defer sh.mu.Unlock()
|
||||
if sh.closed {
|
||||
return
|
||||
}
|
||||
sh.signals = append(sh.signals, newSequentialSignalChannelData(ch))
|
||||
}
|
||||
|
||||
func (sh *sequentialSignalHandler) RemoveSignal(ch chan<- *Signal) {
|
||||
sh.mu.Lock()
|
||||
defer sh.mu.Unlock()
|
||||
if sh.closed {
|
||||
return
|
||||
}
|
||||
for i := len(sh.signals) - 1; i >= 0; i-- {
|
||||
if ch == sh.signals[i].ch {
|
||||
sh.signals[i].close()
|
||||
copy(sh.signals[i:], sh.signals[i+1:])
|
||||
sh.signals[len(sh.signals)-1] = nil
|
||||
sh.signals = sh.signals[:len(sh.signals)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type sequentialSignalChannelData struct {
|
||||
ch chan<- *Signal
|
||||
in chan *Signal
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func newSequentialSignalChannelData(ch chan<- *Signal) *sequentialSignalChannelData {
|
||||
scd := &sequentialSignalChannelData{
|
||||
ch: ch,
|
||||
in: make(chan *Signal),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
go scd.bufferSignals()
|
||||
return scd
|
||||
}
|
||||
|
||||
func (scd *sequentialSignalChannelData) bufferSignals() {
|
||||
defer close(scd.done)
|
||||
|
||||
// Ensure that signals are delivered to scd.ch in the same
|
||||
// order they are received from scd.in.
|
||||
var queue []*Signal
|
||||
for {
|
||||
if len(queue) == 0 {
|
||||
signal, ok := <- scd.in
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
queue = append(queue, signal)
|
||||
}
|
||||
select {
|
||||
case scd.ch <- queue[0]:
|
||||
copy(queue, queue[1:])
|
||||
queue[len(queue)-1] = nil
|
||||
queue = queue[:len(queue)-1]
|
||||
case signal, ok := <-scd.in:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
queue = append(queue, signal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (scd *sequentialSignalChannelData) deliver(signal *Signal) {
|
||||
scd.in <- signal
|
||||
}
|
||||
|
||||
func (scd *sequentialSignalChannelData) close() {
|
||||
close(scd.in)
|
||||
// Ensure that bufferSignals() has exited and won't attempt
|
||||
// any future sends on scd.ch
|
||||
<-scd.done
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package dbus
|
||||
|
||||
// Terminator allows a handler to implement a shutdown mechanism that
|
||||
// is called when the connection terminates.
|
||||
type Terminator interface {
|
||||
Terminate()
|
||||
}
|
||||
|
||||
// Handler is the representation of a D-Bus Application.
|
||||
//
|
||||
// The Handler must have a way to lookup objects given
|
||||
// an ObjectPath. The returned object must implement the
|
||||
// ServerObject interface.
|
||||
type Handler interface {
|
||||
LookupObject(path ObjectPath) (ServerObject, bool)
|
||||
}
|
||||
|
||||
// ServerObject is the representation of an D-Bus Object.
|
||||
//
|
||||
// Objects are registered at a path for a given Handler.
|
||||
// The Objects implement D-Bus interfaces. The semantics
|
||||
// of Interface lookup is up to the implementation of
|
||||
// the ServerObject. The ServerObject implementation may
|
||||
// choose to implement empty string as a valid interface
|
||||
// represeting all methods or not per the D-Bus specification.
|
||||
type ServerObject interface {
|
||||
LookupInterface(name string) (Interface, bool)
|
||||
}
|
||||
|
||||
// An Interface is the representation of a D-Bus Interface.
|
||||
//
|
||||
// Interfaces are a grouping of methods implemented by the Objects.
|
||||
// Interfaces are responsible for routing method calls.
|
||||
type Interface interface {
|
||||
LookupMethod(name string) (Method, bool)
|
||||
}
|
||||
|
||||
// A Method represents the exposed methods on D-Bus.
|
||||
type Method interface {
|
||||
// Call requires that all arguments are decoded before being passed to it.
|
||||
Call(args ...interface{}) ([]interface{}, error)
|
||||
NumArguments() int
|
||||
NumReturns() int
|
||||
// ArgumentValue returns a representative value for the argument at position
|
||||
// it should be of the proper type. reflect.Zero would be a good mechanism
|
||||
// to use for this Value.
|
||||
ArgumentValue(position int) interface{}
|
||||
// ReturnValue returns a representative value for the return at position
|
||||
// it should be of the proper type. reflect.Zero would be a good mechanism
|
||||
// to use for this Value.
|
||||
ReturnValue(position int) interface{}
|
||||
}
|
||||
|
||||
// An Argument Decoder can decode arguments using the non-standard mechanism
|
||||
//
|
||||
// If a method implements this interface then the non-standard
|
||||
// decoder will be used.
|
||||
//
|
||||
// Method arguments must be decoded from the message.
|
||||
// The mechanism for doing this will vary based on the
|
||||
// implementation of the method. A normal approach is provided
|
||||
// as part of this library, but may be replaced with
|
||||
// any other decoding scheme.
|
||||
type ArgumentDecoder interface {
|
||||
// To decode the arguments of a method the sender and message are
|
||||
// provided in case the semantics of the implementer provides access
|
||||
// to these as part of the method invocation.
|
||||
DecodeArguments(conn *Conn, sender string, msg *Message, args []interface{}) ([]interface{}, error)
|
||||
}
|
||||
|
||||
// A SignalHandler is responsible for delivering a signal.
|
||||
//
|
||||
// Signal delivery may be changed from the default channel
|
||||
// based approach by Handlers implementing the SignalHandler
|
||||
// interface.
|
||||
type SignalHandler interface {
|
||||
DeliverSignal(iface, name string, signal *Signal)
|
||||
}
|
||||
|
||||
// SignalRegistrar manages signal delivery channels.
|
||||
//
|
||||
// This is an optional set of methods for `SignalHandler`.
|
||||
type SignalRegistrar interface {
|
||||
AddSignal(ch chan<- *Signal)
|
||||
RemoveSignal(ch chan<- *Signal)
|
||||
}
|
||||
|
||||
// A DBusError is used to convert a generic object to a D-Bus error.
|
||||
//
|
||||
// Any custom error mechanism may implement this interface to provide
|
||||
// a custom encoding of the error on D-Bus. By default if a normal
|
||||
// error is returned, it will be encoded as the generic
|
||||
// "org.freedesktop.DBus.Error.Failed" error. By implementing this
|
||||
// interface as well a custom encoding may be provided.
|
||||
type DBusError interface {
|
||||
DBusError() (string, []interface{})
|
||||
}
|
||||
|
||||
// SerialGenerator is responsible for serials generation.
|
||||
//
|
||||
// Different approaches for the serial generation can be used,
|
||||
// maintaining a map guarded with a mutex (the standard way) or
|
||||
// simply increment an atomic counter.
|
||||
type SerialGenerator interface {
|
||||
GetSerial() uint32
|
||||
RetireSerial(serial uint32)
|
||||
}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var sigToType = map[byte]reflect.Type{
|
||||
'y': byteType,
|
||||
'b': boolType,
|
||||
'n': int16Type,
|
||||
'q': uint16Type,
|
||||
'i': int32Type,
|
||||
'u': uint32Type,
|
||||
'x': int64Type,
|
||||
't': uint64Type,
|
||||
'd': float64Type,
|
||||
's': stringType,
|
||||
'g': signatureType,
|
||||
'o': objectPathType,
|
||||
'v': variantType,
|
||||
'h': unixFDIndexType,
|
||||
}
|
||||
|
||||
// Signature represents a correct type signature as specified by the D-Bus
|
||||
// specification. The zero value represents the empty signature, "".
|
||||
type Signature struct {
|
||||
str string
|
||||
}
|
||||
|
||||
// SignatureOf returns the concatenation of all the signatures of the given
|
||||
// values. It panics if one of them is not representable in D-Bus.
|
||||
func SignatureOf(vs ...interface{}) Signature {
|
||||
var s string
|
||||
for _, v := range vs {
|
||||
s += getSignature(reflect.TypeOf(v), &depthCounter{})
|
||||
}
|
||||
return Signature{s}
|
||||
}
|
||||
|
||||
// SignatureOfType returns the signature of the given type. It panics if the
|
||||
// type is not representable in D-Bus.
|
||||
func SignatureOfType(t reflect.Type) Signature {
|
||||
return Signature{getSignature(t, &depthCounter{})}
|
||||
}
|
||||
|
||||
// getSignature returns the signature of the given type and panics on unknown types.
|
||||
func getSignature(t reflect.Type, depth *depthCounter) (sig string) {
|
||||
if !depth.Valid() {
|
||||
panic("container nesting too deep")
|
||||
}
|
||||
defer func() {
|
||||
if len(sig) > 255 {
|
||||
panic("signature exceeds the length limitation")
|
||||
}
|
||||
}()
|
||||
// handle simple types first
|
||||
switch t.Kind() {
|
||||
case reflect.Uint8:
|
||||
return "y"
|
||||
case reflect.Bool:
|
||||
return "b"
|
||||
case reflect.Int16:
|
||||
return "n"
|
||||
case reflect.Uint16:
|
||||
return "q"
|
||||
case reflect.Int, reflect.Int32:
|
||||
if t == unixFDType {
|
||||
return "h"
|
||||
}
|
||||
return "i"
|
||||
case reflect.Uint, reflect.Uint32:
|
||||
if t == unixFDIndexType {
|
||||
return "h"
|
||||
}
|
||||
return "u"
|
||||
case reflect.Int64:
|
||||
return "x"
|
||||
case reflect.Uint64:
|
||||
return "t"
|
||||
case reflect.Float64:
|
||||
return "d"
|
||||
case reflect.Ptr:
|
||||
return getSignature(t.Elem(), depth)
|
||||
case reflect.String:
|
||||
if t == objectPathType {
|
||||
return "o"
|
||||
}
|
||||
return "s"
|
||||
case reflect.Struct:
|
||||
if t == variantType {
|
||||
return "v"
|
||||
} else if t == signatureType {
|
||||
return "g"
|
||||
}
|
||||
var s string
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
|
||||
s += getSignature(t.Field(i).Type, depth.EnterStruct())
|
||||
}
|
||||
}
|
||||
if len(s) == 0 {
|
||||
panic(InvalidTypeError{t})
|
||||
}
|
||||
return "(" + s + ")"
|
||||
case reflect.Array, reflect.Slice:
|
||||
return "a" + getSignature(t.Elem(), depth.EnterArray())
|
||||
case reflect.Map:
|
||||
if !isKeyType(t.Key()) {
|
||||
panic(InvalidTypeError{t})
|
||||
}
|
||||
return "a{" + getSignature(t.Key(), depth.EnterArray().EnterDictEntry()) + getSignature(t.Elem(), depth.EnterArray().EnterDictEntry()) + "}"
|
||||
case reflect.Interface:
|
||||
return "v"
|
||||
}
|
||||
panic(InvalidTypeError{t})
|
||||
}
|
||||
|
||||
// ParseSignature returns the signature represented by this string, or a
|
||||
// SignatureError if the string is not a valid signature.
|
||||
func ParseSignature(s string) (sig Signature, err error) {
|
||||
if len(s) == 0 {
|
||||
return
|
||||
}
|
||||
if len(s) > 255 {
|
||||
return Signature{""}, SignatureError{s, "too long"}
|
||||
}
|
||||
sig.str = s
|
||||
for err == nil && len(s) != 0 {
|
||||
err, s = validSingle(s, &depthCounter{})
|
||||
}
|
||||
if err != nil {
|
||||
sig = Signature{""}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ParseSignatureMust behaves like ParseSignature, except that it panics if s
|
||||
// is not valid.
|
||||
func ParseSignatureMust(s string) Signature {
|
||||
sig, err := ParseSignature(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sig
|
||||
}
|
||||
|
||||
// Empty returns whether the signature is the empty signature.
|
||||
func (s Signature) Empty() bool {
|
||||
return s.str == ""
|
||||
}
|
||||
|
||||
// Single returns whether the signature represents a single, complete type.
|
||||
func (s Signature) Single() bool {
|
||||
err, r := validSingle(s.str, &depthCounter{})
|
||||
return err != nil && r == ""
|
||||
}
|
||||
|
||||
// String returns the signature's string representation.
|
||||
func (s Signature) String() string {
|
||||
return s.str
|
||||
}
|
||||
|
||||
// A SignatureError indicates that a signature passed to a function or received
|
||||
// on a connection is not a valid signature.
|
||||
type SignatureError struct {
|
||||
Sig string
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (e SignatureError) Error() string {
|
||||
return fmt.Sprintf("dbus: invalid signature: %q (%s)", e.Sig, e.Reason)
|
||||
}
|
||||
|
||||
type depthCounter struct {
|
||||
arrayDepth, structDepth, dictEntryDepth int
|
||||
}
|
||||
|
||||
func (cnt *depthCounter) Valid() bool {
|
||||
return cnt.arrayDepth <= 32 && cnt.structDepth <= 32 && cnt.dictEntryDepth <= 32
|
||||
}
|
||||
|
||||
func (cnt depthCounter) EnterArray() *depthCounter {
|
||||
cnt.arrayDepth++
|
||||
return &cnt
|
||||
}
|
||||
|
||||
func (cnt depthCounter) EnterStruct() *depthCounter {
|
||||
cnt.structDepth++
|
||||
return &cnt
|
||||
}
|
||||
|
||||
func (cnt depthCounter) EnterDictEntry() *depthCounter {
|
||||
cnt.dictEntryDepth++
|
||||
return &cnt
|
||||
}
|
||||
|
||||
// Try to read a single type from this string. If it was successful, err is nil
|
||||
// and rem is the remaining unparsed part. Otherwise, err is a non-nil
|
||||
// SignatureError and rem is "". depth is the current recursion depth which may
|
||||
// not be greater than 64 and should be given as 0 on the first call.
|
||||
func validSingle(s string, depth *depthCounter) (err error, rem string) {
|
||||
if s == "" {
|
||||
return SignatureError{Sig: s, Reason: "empty signature"}, ""
|
||||
}
|
||||
if !depth.Valid() {
|
||||
return SignatureError{Sig: s, Reason: "container nesting too deep"}, ""
|
||||
}
|
||||
switch s[0] {
|
||||
case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 's', 'g', 'o', 'v', 'h':
|
||||
return nil, s[1:]
|
||||
case 'a':
|
||||
if len(s) > 1 && s[1] == '{' {
|
||||
i := findMatching(s[1:], '{', '}')
|
||||
if i == -1 {
|
||||
return SignatureError{Sig: s, Reason: "unmatched '{'"}, ""
|
||||
}
|
||||
i++
|
||||
rem = s[i+1:]
|
||||
s = s[2:i]
|
||||
if err, _ = validSingle(s[:1], depth.EnterArray().EnterDictEntry()); err != nil {
|
||||
return err, ""
|
||||
}
|
||||
err, nr := validSingle(s[1:], depth.EnterArray().EnterDictEntry())
|
||||
if err != nil {
|
||||
return err, ""
|
||||
}
|
||||
if nr != "" {
|
||||
return SignatureError{Sig: s, Reason: "too many types in dict"}, ""
|
||||
}
|
||||
return nil, rem
|
||||
}
|
||||
return validSingle(s[1:], depth.EnterArray())
|
||||
case '(':
|
||||
i := findMatching(s, '(', ')')
|
||||
if i == -1 {
|
||||
return SignatureError{Sig: s, Reason: "unmatched ')'"}, ""
|
||||
}
|
||||
rem = s[i+1:]
|
||||
s = s[1:i]
|
||||
for err == nil && s != "" {
|
||||
err, s = validSingle(s, depth.EnterStruct())
|
||||
}
|
||||
if err != nil {
|
||||
rem = ""
|
||||
}
|
||||
return
|
||||
}
|
||||
return SignatureError{Sig: s, Reason: "invalid type character"}, ""
|
||||
}
|
||||
|
||||
func findMatching(s string, left, right rune) int {
|
||||
n := 0
|
||||
for i, v := range s {
|
||||
if v == left {
|
||||
n++
|
||||
} else if v == right {
|
||||
n--
|
||||
}
|
||||
if n == 0 {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// typeFor returns the type of the given signature. It ignores any left over
|
||||
// characters and panics if s doesn't start with a valid type signature.
|
||||
func typeFor(s string) (t reflect.Type) {
|
||||
err, _ := validSingle(s, &depthCounter{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if t, ok := sigToType[s[0]]; ok {
|
||||
return t
|
||||
}
|
||||
switch s[0] {
|
||||
case 'a':
|
||||
if s[1] == '{' {
|
||||
i := strings.LastIndex(s, "}")
|
||||
t = reflect.MapOf(sigToType[s[2]], typeFor(s[3:i]))
|
||||
} else {
|
||||
t = reflect.SliceOf(typeFor(s[1:]))
|
||||
}
|
||||
case '(':
|
||||
t = interfacesType
|
||||
}
|
||||
return
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package dbus
|
||||
|
||||
func (t *unixTransport) SendNullByte() error {
|
||||
_, err := t.Write([]byte{0})
|
||||
return err
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var nativeEndian binary.ByteOrder
|
||||
|
||||
func detectEndianness() binary.ByteOrder {
|
||||
var x uint32 = 0x01020304
|
||||
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
||||
return binary.BigEndian
|
||||
}
|
||||
return binary.LittleEndian
|
||||
}
|
||||
|
||||
func init() {
|
||||
nativeEndian = detectEndianness()
|
||||
}
|
||||
|
||||
type genericTransport struct {
|
||||
io.ReadWriteCloser
|
||||
}
|
||||
|
||||
func (t genericTransport) SendNullByte() error {
|
||||
_, err := t.Write([]byte{0})
|
||||
return err
|
||||
}
|
||||
|
||||
func (t genericTransport) SupportsUnixFDs() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t genericTransport) EnableUnixFDs() {}
|
||||
|
||||
func (t genericTransport) ReadMessage() (*Message, error) {
|
||||
return DecodeMessage(t)
|
||||
}
|
||||
|
||||
func (t genericTransport) SendMessage(msg *Message) error {
|
||||
fds, err := msg.CountFds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fds != 0 {
|
||||
return errors.New("dbus: unix fd passing not enabled")
|
||||
}
|
||||
return msg.EncodeTo(t, nativeEndian)
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
//+build !windows
|
||||
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
)
|
||||
|
||||
func init() {
|
||||
transports["nonce-tcp"] = newNonceTcpTransport
|
||||
}
|
||||
|
||||
func newNonceTcpTransport(keys string) (transport, error) {
|
||||
host := getKey(keys, "host")
|
||||
port := getKey(keys, "port")
|
||||
noncefile := getKey(keys, "noncefile")
|
||||
if host == "" || port == "" || noncefile == "" {
|
||||
return nil, errors.New("dbus: unsupported address (must set host, port and noncefile)")
|
||||
}
|
||||
protocol, err := tcpFamily(keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
socket, err := net.Dial(protocol, net.JoinHostPort(host, port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err := ioutil.ReadFile(noncefile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = socket.Write(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewConn(socket)
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
func init() {
|
||||
transports["tcp"] = newTcpTransport
|
||||
}
|
||||
|
||||
func tcpFamily(keys string) (string, error) {
|
||||
switch getKey(keys, "family") {
|
||||
case "":
|
||||
return "tcp", nil
|
||||
case "ipv4":
|
||||
return "tcp4", nil
|
||||
case "ipv6":
|
||||
return "tcp6", nil
|
||||
default:
|
||||
return "", errors.New("dbus: invalid tcp family (must be ipv4 or ipv6)")
|
||||
}
|
||||
}
|
||||
|
||||
func newTcpTransport(keys string) (transport, error) {
|
||||
host := getKey(keys, "host")
|
||||
port := getKey(keys, "port")
|
||||
if host == "" || port == "" {
|
||||
return nil, errors.New("dbus: unsupported address (must set host and port)")
|
||||
}
|
||||
|
||||
protocol, err := tcpFamily(keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
socket, err := net.Dial(protocol, net.JoinHostPort(host, port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewConn(socket)
|
||||
}
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
//+build !windows,!solaris
|
||||
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type oobReader struct {
|
||||
conn *net.UnixConn
|
||||
oob []byte
|
||||
buf [4096]byte
|
||||
}
|
||||
|
||||
func (o *oobReader) Read(b []byte) (n int, err error) {
|
||||
n, oobn, flags, _, err := o.conn.ReadMsgUnix(b, o.buf[:])
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if flags&syscall.MSG_CTRUNC != 0 {
|
||||
return n, errors.New("dbus: control data truncated (too many fds received)")
|
||||
}
|
||||
o.oob = append(o.oob, o.buf[:oobn]...)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
type unixTransport struct {
|
||||
*net.UnixConn
|
||||
rdr *oobReader
|
||||
hasUnixFDs bool
|
||||
}
|
||||
|
||||
func newUnixTransport(keys string) (transport, error) {
|
||||
var err error
|
||||
|
||||
t := new(unixTransport)
|
||||
abstract := getKey(keys, "abstract")
|
||||
path := getKey(keys, "path")
|
||||
switch {
|
||||
case abstract == "" && path == "":
|
||||
return nil, errors.New("dbus: invalid address (neither path nor abstract set)")
|
||||
case abstract != "" && path == "":
|
||||
t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: "@" + abstract, Net: "unix"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t, nil
|
||||
case abstract == "" && path != "":
|
||||
t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: path, Net: "unix"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return t, nil
|
||||
default:
|
||||
return nil, errors.New("dbus: invalid address (both path and abstract set)")
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
transports["unix"] = newUnixTransport
|
||||
}
|
||||
|
||||
func (t *unixTransport) EnableUnixFDs() {
|
||||
t.hasUnixFDs = true
|
||||
}
|
||||
|
||||
func (t *unixTransport) ReadMessage() (*Message, error) {
|
||||
var (
|
||||
blen, hlen uint32
|
||||
csheader [16]byte
|
||||
headers []header
|
||||
order binary.ByteOrder
|
||||
unixfds uint32
|
||||
)
|
||||
// To be sure that all bytes of out-of-band data are read, we use a special
|
||||
// reader that uses ReadUnix on the underlying connection instead of Read
|
||||
// and gathers the out-of-band data in a buffer.
|
||||
if t.rdr == nil {
|
||||
t.rdr = &oobReader{conn: t.UnixConn}
|
||||
} else {
|
||||
t.rdr.oob = nil
|
||||
}
|
||||
|
||||
// read the first 16 bytes (the part of the header that has a constant size),
|
||||
// from which we can figure out the length of the rest of the message
|
||||
if _, err := io.ReadFull(t.rdr, csheader[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch csheader[0] {
|
||||
case 'l':
|
||||
order = binary.LittleEndian
|
||||
case 'B':
|
||||
order = binary.BigEndian
|
||||
default:
|
||||
return nil, InvalidMessageError("invalid byte order")
|
||||
}
|
||||
// csheader[4:8] -> length of message body, csheader[12:16] -> length of
|
||||
// header fields (without alignment)
|
||||
binary.Read(bytes.NewBuffer(csheader[4:8]), order, &blen)
|
||||
binary.Read(bytes.NewBuffer(csheader[12:]), order, &hlen)
|
||||
if hlen%8 != 0 {
|
||||
hlen += 8 - (hlen % 8)
|
||||
}
|
||||
|
||||
// decode headers and look for unix fds
|
||||
headerdata := make([]byte, hlen+4)
|
||||
copy(headerdata, csheader[12:])
|
||||
if _, err := io.ReadFull(t.rdr, headerdata[4:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dec := newDecoder(bytes.NewBuffer(headerdata), order, make([]int, 0))
|
||||
dec.pos = 12
|
||||
vs, err := dec.Decode(Signature{"a(yv)"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
Store(vs, &headers)
|
||||
for _, v := range headers {
|
||||
if v.Field == byte(FieldUnixFDs) {
|
||||
unixfds, _ = v.Variant.value.(uint32)
|
||||
}
|
||||
}
|
||||
all := make([]byte, 16+hlen+blen)
|
||||
copy(all, csheader[:])
|
||||
copy(all[16:], headerdata[4:])
|
||||
if _, err := io.ReadFull(t.rdr, all[16+hlen:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if unixfds != 0 {
|
||||
if !t.hasUnixFDs {
|
||||
return nil, errors.New("dbus: got unix fds on unsupported transport")
|
||||
}
|
||||
// read the fds from the OOB data
|
||||
scms, err := syscall.ParseSocketControlMessage(t.rdr.oob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(scms) != 1 {
|
||||
return nil, errors.New("dbus: received more than one socket control message")
|
||||
}
|
||||
fds, err := syscall.ParseUnixRights(&scms[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg, err := DecodeMessageWithFDs(bytes.NewBuffer(all), fds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// substitute the values in the message body (which are indices for the
|
||||
// array receiver via OOB) with the actual values
|
||||
for i, v := range msg.Body {
|
||||
switch index := v.(type) {
|
||||
case UnixFDIndex:
|
||||
if uint32(index) >= unixfds {
|
||||
return nil, InvalidMessageError("invalid index for unix fd")
|
||||
}
|
||||
msg.Body[i] = UnixFD(fds[index])
|
||||
case []UnixFDIndex:
|
||||
fdArray := make([]UnixFD, len(index))
|
||||
for k, j := range index {
|
||||
if uint32(j) >= unixfds {
|
||||
return nil, InvalidMessageError("invalid index for unix fd")
|
||||
}
|
||||
fdArray[k] = UnixFD(fds[j])
|
||||
}
|
||||
msg.Body[i] = fdArray
|
||||
}
|
||||
}
|
||||
return msg, nil
|
||||
}
|
||||
return DecodeMessage(bytes.NewBuffer(all))
|
||||
}
|
||||
|
||||
func (t *unixTransport) SendMessage(msg *Message) error {
|
||||
fdcnt, err := msg.CountFds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fdcnt != 0 {
|
||||
if !t.hasUnixFDs {
|
||||
return errors.New("dbus: unix fd passing not enabled")
|
||||
}
|
||||
msg.Headers[FieldUnixFDs] = MakeVariant(uint32(fdcnt))
|
||||
buf := new(bytes.Buffer)
|
||||
fds, err := msg.EncodeToWithFDs(buf, nativeEndian)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oob := syscall.UnixRights(fds...)
|
||||
n, oobn, err := t.UnixConn.WriteMsgUnix(buf.Bytes(), oob, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != buf.Len() || oobn != len(oob) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
} else {
|
||||
if err := msg.EncodeTo(t, nativeEndian); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *unixTransport) SupportsUnixFDs() bool {
|
||||
return true
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
// The UnixCredentials system call is currently only implemented on Linux
|
||||
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
|
||||
// https://golang.org/s/go1.4-syscall
|
||||
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
|
||||
|
||||
// Local implementation of the UnixCredentials system call for DragonFly BSD
|
||||
|
||||
package dbus
|
||||
|
||||
/*
|
||||
#include <sys/ucred.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// http://golang.org/src/pkg/syscall/ztypes_linux_amd64.go
|
||||
// http://golang.org/src/pkg/syscall/ztypes_dragonfly_amd64.go
|
||||
type Ucred struct {
|
||||
Pid int32
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
}
|
||||
|
||||
// http://golang.org/src/pkg/syscall/types_linux.go
|
||||
// http://golang.org/src/pkg/syscall/types_dragonfly.go
|
||||
// https://github.com/DragonFlyBSD/DragonFlyBSD/blob/master/sys/sys/ucred.h
|
||||
const (
|
||||
SizeofUcred = C.sizeof_struct_ucred
|
||||
)
|
||||
|
||||
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
|
||||
func cmsgAlignOf(salen int) int {
|
||||
// From http://golang.org/src/pkg/syscall/sockcmsg_unix.go
|
||||
//salign := sizeofPtr
|
||||
// NOTE: It seems like 64-bit Darwin and DragonFly BSD kernels
|
||||
// still require 32-bit aligned access to network subsystem.
|
||||
//if darwin64Bit || dragonfly64Bit {
|
||||
// salign = 4
|
||||
//}
|
||||
salign := 4
|
||||
return (salen + salign - 1) & ^(salign - 1)
|
||||
}
|
||||
|
||||
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
|
||||
func cmsgData(h *syscall.Cmsghdr) unsafe.Pointer {
|
||||
return unsafe.Pointer(uintptr(unsafe.Pointer(h)) + uintptr(cmsgAlignOf(syscall.SizeofCmsghdr)))
|
||||
}
|
||||
|
||||
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
|
||||
// UnixCredentials encodes credentials into a socket control message
|
||||
// for sending to another process. This can be used for
|
||||
// authentication.
|
||||
func UnixCredentials(ucred *Ucred) []byte {
|
||||
b := make([]byte, syscall.CmsgSpace(SizeofUcred))
|
||||
h := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
|
||||
h.Level = syscall.SOL_SOCKET
|
||||
h.Type = syscall.SCM_CREDS
|
||||
h.SetLen(syscall.CmsgLen(SizeofUcred))
|
||||
*((*Ucred)(cmsgData(h))) = *ucred
|
||||
return b
|
||||
}
|
||||
|
||||
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
|
||||
// ParseUnixCredentials decodes a socket control message that contains
|
||||
// credentials in a Ucred structure. To receive such a message, the
|
||||
// SO_PASSCRED option must be enabled on the socket.
|
||||
func ParseUnixCredentials(m *syscall.SocketControlMessage) (*Ucred, error) {
|
||||
if m.Header.Level != syscall.SOL_SOCKET {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
if m.Header.Type != syscall.SCM_CREDS {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
ucred := *(*Ucred)(unsafe.Pointer(&m.Data[0]))
|
||||
return &ucred, nil
|
||||
}
|
||||
|
||||
func (t *unixTransport) SendNullByte() error {
|
||||
ucred := &Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
|
||||
b := UnixCredentials(ucred)
|
||||
_, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if oobn != len(b) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
// The UnixCredentials system call is currently only implemented on Linux
|
||||
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
|
||||
// https://golang.org/s/go1.4-syscall
|
||||
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
|
||||
|
||||
// Local implementation of the UnixCredentials system call for FreeBSD
|
||||
|
||||
package dbus
|
||||
|
||||
/*
|
||||
const int sizeofPtr = sizeof(void*);
|
||||
#define _WANT_UCRED
|
||||
#include <sys/types.h>
|
||||
#include <sys/ucred.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// http://golang.org/src/pkg/syscall/ztypes_linux_amd64.go
|
||||
// https://golang.org/src/syscall/ztypes_freebsd_amd64.go
|
||||
type Ucred struct {
|
||||
Pid int32
|
||||
Uid uint32
|
||||
Gid uint32
|
||||
}
|
||||
|
||||
// http://golang.org/src/pkg/syscall/types_linux.go
|
||||
// https://golang.org/src/syscall/types_freebsd.go
|
||||
// https://github.com/freebsd/freebsd/blob/master/sys/sys/ucred.h
|
||||
const (
|
||||
SizeofUcred = C.sizeof_struct_ucred
|
||||
)
|
||||
|
||||
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
|
||||
func cmsgAlignOf(salen int) int {
|
||||
salign := C.sizeofPtr
|
||||
|
||||
return (salen + salign - 1) & ^(salign - 1)
|
||||
}
|
||||
|
||||
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
|
||||
func cmsgData(h *syscall.Cmsghdr) unsafe.Pointer {
|
||||
return unsafe.Pointer(uintptr(unsafe.Pointer(h)) + uintptr(cmsgAlignOf(syscall.SizeofCmsghdr)))
|
||||
}
|
||||
|
||||
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
|
||||
// UnixCredentials encodes credentials into a socket control message
|
||||
// for sending to another process. This can be used for
|
||||
// authentication.
|
||||
func UnixCredentials(ucred *Ucred) []byte {
|
||||
b := make([]byte, syscall.CmsgSpace(SizeofUcred))
|
||||
h := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
|
||||
h.Level = syscall.SOL_SOCKET
|
||||
h.Type = syscall.SCM_CREDS
|
||||
h.SetLen(syscall.CmsgLen(SizeofUcred))
|
||||
*((*Ucred)(cmsgData(h))) = *ucred
|
||||
return b
|
||||
}
|
||||
|
||||
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
|
||||
// ParseUnixCredentials decodes a socket control message that contains
|
||||
// credentials in a Ucred structure. To receive such a message, the
|
||||
// SO_PASSCRED option must be enabled on the socket.
|
||||
func ParseUnixCredentials(m *syscall.SocketControlMessage) (*Ucred, error) {
|
||||
if m.Header.Level != syscall.SOL_SOCKET {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
if m.Header.Type != syscall.SCM_CREDS {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
ucred := *(*Ucred)(unsafe.Pointer(&m.Data[0]))
|
||||
return &ucred, nil
|
||||
}
|
||||
|
||||
func (t *unixTransport) SendNullByte() error {
|
||||
ucred := &Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
|
||||
b := UnixCredentials(ucred)
|
||||
_, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if oobn != len(b) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// The UnixCredentials system call is currently only implemented on Linux
|
||||
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
|
||||
// https://golang.org/s/go1.4-syscall
|
||||
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
|
||||
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (t *unixTransport) SendNullByte() error {
|
||||
ucred := &syscall.Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
|
||||
b := syscall.UnixCredentials(ucred)
|
||||
_, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if oobn != len(b) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package dbus
|
||||
|
||||
import "io"
|
||||
|
||||
func (t *unixTransport) SendNullByte() error {
|
||||
n, _, err := t.UnixConn.WriteMsgUnix([]byte{0}, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != 1 {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package dbus
|
||||
|
||||
import "io"
|
||||
|
||||
func (t *unixTransport) SendNullByte() error {
|
||||
n, _, err := t.UnixConn.WriteMsgUnix([]byte{0}, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != 1 {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package dbus
|
||||
|
||||
func (t *unixTransport) SendNullByte() error {
|
||||
_, err := t.Write([]byte{0})
|
||||
return err
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Variant represents the D-Bus variant type.
|
||||
type Variant struct {
|
||||
sig Signature
|
||||
value interface{}
|
||||
}
|
||||
|
||||
// MakeVariant converts the given value to a Variant. It panics if v cannot be
|
||||
// represented as a D-Bus type.
|
||||
func MakeVariant(v interface{}) Variant {
|
||||
return MakeVariantWithSignature(v, SignatureOf(v))
|
||||
}
|
||||
|
||||
// MakeVariantWithSignature converts the given value to a Variant.
|
||||
func MakeVariantWithSignature(v interface{}, s Signature) Variant {
|
||||
return Variant{s, v}
|
||||
}
|
||||
|
||||
// ParseVariant parses the given string as a variant as described at
|
||||
// https://developer.gnome.org/glib/stable/gvariant-text.html. If sig is not
|
||||
// empty, it is taken to be the expected signature for the variant.
|
||||
func ParseVariant(s string, sig Signature) (Variant, error) {
|
||||
tokens := varLex(s)
|
||||
p := &varParser{tokens: tokens}
|
||||
n, err := varMakeNode(p)
|
||||
if err != nil {
|
||||
return Variant{}, err
|
||||
}
|
||||
if sig.str == "" {
|
||||
sig, err = varInfer(n)
|
||||
if err != nil {
|
||||
return Variant{}, err
|
||||
}
|
||||
}
|
||||
v, err := n.Value(sig)
|
||||
if err != nil {
|
||||
return Variant{}, err
|
||||
}
|
||||
return MakeVariant(v), nil
|
||||
}
|
||||
|
||||
// format returns a formatted version of v and whether this string can be parsed
|
||||
// unambiguously.
|
||||
func (v Variant) format() (string, bool) {
|
||||
switch v.sig.str[0] {
|
||||
case 'b', 'i':
|
||||
return fmt.Sprint(v.value), true
|
||||
case 'n', 'q', 'u', 'x', 't', 'd', 'h':
|
||||
return fmt.Sprint(v.value), false
|
||||
case 's':
|
||||
return strconv.Quote(v.value.(string)), true
|
||||
case 'o':
|
||||
return strconv.Quote(string(v.value.(ObjectPath))), false
|
||||
case 'g':
|
||||
return strconv.Quote(v.value.(Signature).str), false
|
||||
case 'v':
|
||||
s, unamb := v.value.(Variant).format()
|
||||
if !unamb {
|
||||
return "<@" + v.value.(Variant).sig.str + " " + s + ">", true
|
||||
}
|
||||
return "<" + s + ">", true
|
||||
case 'y':
|
||||
return fmt.Sprintf("%#x", v.value.(byte)), false
|
||||
}
|
||||
rv := reflect.ValueOf(v.value)
|
||||
switch rv.Kind() {
|
||||
case reflect.Slice:
|
||||
if rv.Len() == 0 {
|
||||
return "[]", false
|
||||
}
|
||||
unamb := true
|
||||
buf := bytes.NewBuffer([]byte("["))
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
// TODO: slooow
|
||||
s, b := MakeVariant(rv.Index(i).Interface()).format()
|
||||
unamb = unamb && b
|
||||
buf.WriteString(s)
|
||||
if i != rv.Len()-1 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
}
|
||||
buf.WriteByte(']')
|
||||
return buf.String(), unamb
|
||||
case reflect.Map:
|
||||
if rv.Len() == 0 {
|
||||
return "{}", false
|
||||
}
|
||||
unamb := true
|
||||
var buf bytes.Buffer
|
||||
kvs := make([]string, rv.Len())
|
||||
for i, k := range rv.MapKeys() {
|
||||
s, b := MakeVariant(k.Interface()).format()
|
||||
unamb = unamb && b
|
||||
buf.Reset()
|
||||
buf.WriteString(s)
|
||||
buf.WriteString(": ")
|
||||
s, b = MakeVariant(rv.MapIndex(k).Interface()).format()
|
||||
unamb = unamb && b
|
||||
buf.WriteString(s)
|
||||
kvs[i] = buf.String()
|
||||
}
|
||||
buf.Reset()
|
||||
buf.WriteByte('{')
|
||||
sort.Strings(kvs)
|
||||
for i, kv := range kvs {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
buf.WriteString(kv)
|
||||
}
|
||||
buf.WriteByte('}')
|
||||
return buf.String(), unamb
|
||||
}
|
||||
return `"INVALID"`, true
|
||||
}
|
||||
|
||||
// Signature returns the D-Bus signature of the underlying value of v.
|
||||
func (v Variant) Signature() Signature {
|
||||
return v.sig
|
||||
}
|
||||
|
||||
// String returns the string representation of the underlying value of v as
|
||||
// described at https://developer.gnome.org/glib/stable/gvariant-text.html.
|
||||
func (v Variant) String() string {
|
||||
s, unamb := v.format()
|
||||
if !unamb {
|
||||
return "@" + v.sig.str + " " + s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Value returns the underlying value of v.
|
||||
func (v Variant) Value() interface{} {
|
||||
return v.value
|
||||
}
|
||||
|
||||
// Store converts the variant into a native go type using the same
|
||||
// mechanism as the "Store" function.
|
||||
func (v Variant) Store(value interface{}) error {
|
||||
return storeInterfaces(v.value, value)
|
||||
}
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Heavily inspired by the lexer from text/template.
|
||||
|
||||
type varToken struct {
|
||||
typ varTokenType
|
||||
val string
|
||||
}
|
||||
|
||||
type varTokenType byte
|
||||
|
||||
const (
|
||||
tokEOF varTokenType = iota
|
||||
tokError
|
||||
tokNumber
|
||||
tokString
|
||||
tokBool
|
||||
tokArrayStart
|
||||
tokArrayEnd
|
||||
tokDictStart
|
||||
tokDictEnd
|
||||
tokVariantStart
|
||||
tokVariantEnd
|
||||
tokComma
|
||||
tokColon
|
||||
tokType
|
||||
tokByteString
|
||||
)
|
||||
|
||||
type varLexer struct {
|
||||
input string
|
||||
start int
|
||||
pos int
|
||||
width int
|
||||
tokens []varToken
|
||||
}
|
||||
|
||||
type lexState func(*varLexer) lexState
|
||||
|
||||
func varLex(s string) []varToken {
|
||||
l := &varLexer{input: s}
|
||||
l.run()
|
||||
return l.tokens
|
||||
}
|
||||
|
||||
func (l *varLexer) accept(valid string) bool {
|
||||
if strings.ContainsRune(valid, l.next()) {
|
||||
return true
|
||||
}
|
||||
l.backup()
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *varLexer) backup() {
|
||||
l.pos -= l.width
|
||||
}
|
||||
|
||||
func (l *varLexer) emit(t varTokenType) {
|
||||
l.tokens = append(l.tokens, varToken{t, l.input[l.start:l.pos]})
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
func (l *varLexer) errorf(format string, v ...interface{}) lexState {
|
||||
l.tokens = append(l.tokens, varToken{
|
||||
tokError,
|
||||
fmt.Sprintf(format, v...),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *varLexer) ignore() {
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
func (l *varLexer) next() rune {
|
||||
var r rune
|
||||
|
||||
if l.pos >= len(l.input) {
|
||||
l.width = 0
|
||||
return -1
|
||||
}
|
||||
r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
|
||||
l.pos += l.width
|
||||
return r
|
||||
}
|
||||
|
||||
func (l *varLexer) run() {
|
||||
for state := varLexNormal; state != nil; {
|
||||
state = state(l)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *varLexer) peek() rune {
|
||||
r := l.next()
|
||||
l.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
func varLexNormal(l *varLexer) lexState {
|
||||
for {
|
||||
r := l.next()
|
||||
switch {
|
||||
case r == -1:
|
||||
l.emit(tokEOF)
|
||||
return nil
|
||||
case r == '[':
|
||||
l.emit(tokArrayStart)
|
||||
case r == ']':
|
||||
l.emit(tokArrayEnd)
|
||||
case r == '{':
|
||||
l.emit(tokDictStart)
|
||||
case r == '}':
|
||||
l.emit(tokDictEnd)
|
||||
case r == '<':
|
||||
l.emit(tokVariantStart)
|
||||
case r == '>':
|
||||
l.emit(tokVariantEnd)
|
||||
case r == ':':
|
||||
l.emit(tokColon)
|
||||
case r == ',':
|
||||
l.emit(tokComma)
|
||||
case r == '\'' || r == '"':
|
||||
l.backup()
|
||||
return varLexString
|
||||
case r == '@':
|
||||
l.backup()
|
||||
return varLexType
|
||||
case unicode.IsSpace(r):
|
||||
l.ignore()
|
||||
case unicode.IsNumber(r) || r == '+' || r == '-':
|
||||
l.backup()
|
||||
return varLexNumber
|
||||
case r == 'b':
|
||||
pos := l.start
|
||||
if n := l.peek(); n == '"' || n == '\'' {
|
||||
return varLexByteString
|
||||
}
|
||||
// not a byte string; try to parse it as a type or bool below
|
||||
l.pos = pos + 1
|
||||
l.width = 1
|
||||
fallthrough
|
||||
default:
|
||||
// either a bool or a type. Try bools first.
|
||||
l.backup()
|
||||
if l.pos+4 <= len(l.input) {
|
||||
if l.input[l.pos:l.pos+4] == "true" {
|
||||
l.pos += 4
|
||||
l.emit(tokBool)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if l.pos+5 <= len(l.input) {
|
||||
if l.input[l.pos:l.pos+5] == "false" {
|
||||
l.pos += 5
|
||||
l.emit(tokBool)
|
||||
continue
|
||||
}
|
||||
}
|
||||
// must be a type.
|
||||
return varLexType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var varTypeMap = map[string]string{
|
||||
"boolean": "b",
|
||||
"byte": "y",
|
||||
"int16": "n",
|
||||
"uint16": "q",
|
||||
"int32": "i",
|
||||
"uint32": "u",
|
||||
"int64": "x",
|
||||
"uint64": "t",
|
||||
"double": "f",
|
||||
"string": "s",
|
||||
"objectpath": "o",
|
||||
"signature": "g",
|
||||
}
|
||||
|
||||
func varLexByteString(l *varLexer) lexState {
|
||||
q := l.next()
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case '\\':
|
||||
if r := l.next(); r != -1 {
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case -1:
|
||||
return l.errorf("unterminated bytestring")
|
||||
case q:
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(tokByteString)
|
||||
return varLexNormal
|
||||
}
|
||||
|
||||
func varLexNumber(l *varLexer) lexState {
|
||||
l.accept("+-")
|
||||
digits := "0123456789"
|
||||
if l.accept("0") {
|
||||
if l.accept("x") {
|
||||
digits = "0123456789abcdefABCDEF"
|
||||
} else {
|
||||
digits = "01234567"
|
||||
}
|
||||
}
|
||||
for strings.ContainsRune(digits, l.next()) {
|
||||
}
|
||||
l.backup()
|
||||
if l.accept(".") {
|
||||
for strings.ContainsRune(digits, l.next()) {
|
||||
}
|
||||
l.backup()
|
||||
}
|
||||
if l.accept("eE") {
|
||||
l.accept("+-")
|
||||
for strings.ContainsRune("0123456789", l.next()) {
|
||||
}
|
||||
l.backup()
|
||||
}
|
||||
if r := l.peek(); unicode.IsLetter(r) {
|
||||
l.next()
|
||||
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
l.emit(tokNumber)
|
||||
return varLexNormal
|
||||
}
|
||||
|
||||
func varLexString(l *varLexer) lexState {
|
||||
q := l.next()
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case '\\':
|
||||
if r := l.next(); r != -1 {
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case -1:
|
||||
return l.errorf("unterminated string")
|
||||
case q:
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(tokString)
|
||||
return varLexNormal
|
||||
}
|
||||
|
||||
func varLexType(l *varLexer) lexState {
|
||||
at := l.accept("@")
|
||||
for {
|
||||
r := l.next()
|
||||
if r == -1 {
|
||||
break
|
||||
}
|
||||
if unicode.IsSpace(r) {
|
||||
l.backup()
|
||||
break
|
||||
}
|
||||
}
|
||||
if at {
|
||||
if _, err := ParseSignature(l.input[l.start+1 : l.pos]); err != nil {
|
||||
return l.errorf("%s", err)
|
||||
}
|
||||
} else {
|
||||
if _, ok := varTypeMap[l.input[l.start:l.pos]]; ok {
|
||||
l.emit(tokType)
|
||||
return varLexNormal
|
||||
}
|
||||
return l.errorf("unrecognized type %q", l.input[l.start:l.pos])
|
||||
}
|
||||
l.emit(tokType)
|
||||
return varLexNormal
|
||||
}
|
||||
+817
@@ -0,0 +1,817 @@
|
||||
package dbus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type varParser struct {
|
||||
tokens []varToken
|
||||
i int
|
||||
}
|
||||
|
||||
func (p *varParser) backup() {
|
||||
p.i--
|
||||
}
|
||||
|
||||
func (p *varParser) next() varToken {
|
||||
if p.i < len(p.tokens) {
|
||||
t := p.tokens[p.i]
|
||||
p.i++
|
||||
return t
|
||||
}
|
||||
return varToken{typ: tokEOF}
|
||||
}
|
||||
|
||||
type varNode interface {
|
||||
Infer() (Signature, error)
|
||||
String() string
|
||||
Sigs() sigSet
|
||||
Value(Signature) (interface{}, error)
|
||||
}
|
||||
|
||||
func varMakeNode(p *varParser) (varNode, error) {
|
||||
var sig Signature
|
||||
|
||||
for {
|
||||
t := p.next()
|
||||
switch t.typ {
|
||||
case tokEOF:
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
case tokError:
|
||||
return nil, errors.New(t.val)
|
||||
case tokNumber:
|
||||
return varMakeNumNode(t, sig)
|
||||
case tokString:
|
||||
return varMakeStringNode(t, sig)
|
||||
case tokBool:
|
||||
if sig.str != "" && sig.str != "b" {
|
||||
return nil, varTypeError{t.val, sig}
|
||||
}
|
||||
b, err := strconv.ParseBool(t.val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return boolNode(b), nil
|
||||
case tokArrayStart:
|
||||
return varMakeArrayNode(p, sig)
|
||||
case tokVariantStart:
|
||||
return varMakeVariantNode(p, sig)
|
||||
case tokDictStart:
|
||||
return varMakeDictNode(p, sig)
|
||||
case tokType:
|
||||
if sig.str != "" {
|
||||
return nil, errors.New("unexpected type annotation")
|
||||
}
|
||||
if t.val[0] == '@' {
|
||||
sig.str = t.val[1:]
|
||||
} else {
|
||||
sig.str = varTypeMap[t.val]
|
||||
}
|
||||
case tokByteString:
|
||||
if sig.str != "" && sig.str != "ay" {
|
||||
return nil, varTypeError{t.val, sig}
|
||||
}
|
||||
b, err := varParseByteString(t.val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return byteStringNode(b), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected %q", t.val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type varTypeError struct {
|
||||
val string
|
||||
sig Signature
|
||||
}
|
||||
|
||||
func (e varTypeError) Error() string {
|
||||
return fmt.Sprintf("dbus: can't parse %q as type %q", e.val, e.sig.str)
|
||||
}
|
||||
|
||||
type sigSet map[Signature]bool
|
||||
|
||||
func (s sigSet) Empty() bool {
|
||||
return len(s) == 0
|
||||
}
|
||||
|
||||
func (s sigSet) Intersect(s2 sigSet) sigSet {
|
||||
r := make(sigSet)
|
||||
for k := range s {
|
||||
if s2[k] {
|
||||
r[k] = true
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (s sigSet) Single() (Signature, bool) {
|
||||
if len(s) == 1 {
|
||||
for k := range s {
|
||||
return k, true
|
||||
}
|
||||
}
|
||||
return Signature{}, false
|
||||
}
|
||||
|
||||
func (s sigSet) ToArray() sigSet {
|
||||
r := make(sigSet, len(s))
|
||||
for k := range s {
|
||||
r[Signature{"a" + k.str}] = true
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
type numNode struct {
|
||||
sig Signature
|
||||
str string
|
||||
val interface{}
|
||||
}
|
||||
|
||||
var numSigSet = sigSet{
|
||||
Signature{"y"}: true,
|
||||
Signature{"n"}: true,
|
||||
Signature{"q"}: true,
|
||||
Signature{"i"}: true,
|
||||
Signature{"u"}: true,
|
||||
Signature{"x"}: true,
|
||||
Signature{"t"}: true,
|
||||
Signature{"d"}: true,
|
||||
}
|
||||
|
||||
func (n numNode) Infer() (Signature, error) {
|
||||
if strings.ContainsAny(n.str, ".e") {
|
||||
return Signature{"d"}, nil
|
||||
}
|
||||
return Signature{"i"}, nil
|
||||
}
|
||||
|
||||
func (n numNode) String() string {
|
||||
return n.str
|
||||
}
|
||||
|
||||
func (n numNode) Sigs() sigSet {
|
||||
if n.sig.str != "" {
|
||||
return sigSet{n.sig: true}
|
||||
}
|
||||
if strings.ContainsAny(n.str, ".e") {
|
||||
return sigSet{Signature{"d"}: true}
|
||||
}
|
||||
return numSigSet
|
||||
}
|
||||
|
||||
func (n numNode) Value(sig Signature) (interface{}, error) {
|
||||
if n.sig.str != "" && n.sig != sig {
|
||||
return nil, varTypeError{n.str, sig}
|
||||
}
|
||||
if n.val != nil {
|
||||
return n.val, nil
|
||||
}
|
||||
return varNumAs(n.str, sig)
|
||||
}
|
||||
|
||||
func varMakeNumNode(tok varToken, sig Signature) (varNode, error) {
|
||||
if sig.str == "" {
|
||||
return numNode{str: tok.val}, nil
|
||||
}
|
||||
num, err := varNumAs(tok.val, sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return numNode{sig: sig, val: num}, nil
|
||||
}
|
||||
|
||||
func varNumAs(s string, sig Signature) (interface{}, error) {
|
||||
isUnsigned := false
|
||||
size := 32
|
||||
switch sig.str {
|
||||
case "n":
|
||||
size = 16
|
||||
case "i":
|
||||
case "x":
|
||||
size = 64
|
||||
case "y":
|
||||
size = 8
|
||||
isUnsigned = true
|
||||
case "q":
|
||||
size = 16
|
||||
isUnsigned = true
|
||||
case "u":
|
||||
isUnsigned = true
|
||||
case "t":
|
||||
size = 64
|
||||
isUnsigned = true
|
||||
case "d":
|
||||
d, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d, nil
|
||||
default:
|
||||
return nil, varTypeError{s, sig}
|
||||
}
|
||||
base := 10
|
||||
if strings.HasPrefix(s, "0x") {
|
||||
base = 16
|
||||
s = s[2:]
|
||||
}
|
||||
if strings.HasPrefix(s, "0") && len(s) != 1 {
|
||||
base = 8
|
||||
s = s[1:]
|
||||
}
|
||||
if isUnsigned {
|
||||
i, err := strconv.ParseUint(s, base, size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var v interface{} = i
|
||||
switch sig.str {
|
||||
case "y":
|
||||
v = byte(i)
|
||||
case "q":
|
||||
v = uint16(i)
|
||||
case "u":
|
||||
v = uint32(i)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
i, err := strconv.ParseInt(s, base, size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var v interface{} = i
|
||||
switch sig.str {
|
||||
case "n":
|
||||
v = int16(i)
|
||||
case "i":
|
||||
v = int32(i)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
type stringNode struct {
|
||||
sig Signature
|
||||
str string // parsed
|
||||
val interface{} // has correct type
|
||||
}
|
||||
|
||||
var stringSigSet = sigSet{
|
||||
Signature{"s"}: true,
|
||||
Signature{"g"}: true,
|
||||
Signature{"o"}: true,
|
||||
}
|
||||
|
||||
func (n stringNode) Infer() (Signature, error) {
|
||||
return Signature{"s"}, nil
|
||||
}
|
||||
|
||||
func (n stringNode) String() string {
|
||||
return n.str
|
||||
}
|
||||
|
||||
func (n stringNode) Sigs() sigSet {
|
||||
if n.sig.str != "" {
|
||||
return sigSet{n.sig: true}
|
||||
}
|
||||
return stringSigSet
|
||||
}
|
||||
|
||||
func (n stringNode) Value(sig Signature) (interface{}, error) {
|
||||
if n.sig.str != "" && n.sig != sig {
|
||||
return nil, varTypeError{n.str, sig}
|
||||
}
|
||||
if n.val != nil {
|
||||
return n.val, nil
|
||||
}
|
||||
switch {
|
||||
case sig.str == "g":
|
||||
return Signature{n.str}, nil
|
||||
case sig.str == "o":
|
||||
return ObjectPath(n.str), nil
|
||||
case sig.str == "s":
|
||||
return n.str, nil
|
||||
default:
|
||||
return nil, varTypeError{n.str, sig}
|
||||
}
|
||||
}
|
||||
|
||||
func varMakeStringNode(tok varToken, sig Signature) (varNode, error) {
|
||||
if sig.str != "" && sig.str != "s" && sig.str != "g" && sig.str != "o" {
|
||||
return nil, fmt.Errorf("invalid type %q for string", sig.str)
|
||||
}
|
||||
s, err := varParseString(tok.val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n := stringNode{str: s}
|
||||
if sig.str == "" {
|
||||
return stringNode{str: s}, nil
|
||||
}
|
||||
n.sig = sig
|
||||
switch sig.str {
|
||||
case "o":
|
||||
n.val = ObjectPath(s)
|
||||
case "g":
|
||||
n.val = Signature{s}
|
||||
case "s":
|
||||
n.val = s
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func varParseString(s string) (string, error) {
|
||||
// quotes are guaranteed to be there
|
||||
s = s[1 : len(s)-1]
|
||||
buf := new(bytes.Buffer)
|
||||
for len(s) != 0 {
|
||||
r, size := utf8.DecodeRuneInString(s)
|
||||
if r == utf8.RuneError && size == 1 {
|
||||
return "", errors.New("invalid UTF-8")
|
||||
}
|
||||
s = s[size:]
|
||||
if r != '\\' {
|
||||
buf.WriteRune(r)
|
||||
continue
|
||||
}
|
||||
r, size = utf8.DecodeRuneInString(s)
|
||||
if r == utf8.RuneError && size == 1 {
|
||||
return "", errors.New("invalid UTF-8")
|
||||
}
|
||||
s = s[size:]
|
||||
switch r {
|
||||
case 'a':
|
||||
buf.WriteRune(0x7)
|
||||
case 'b':
|
||||
buf.WriteRune(0x8)
|
||||
case 'f':
|
||||
buf.WriteRune(0xc)
|
||||
case 'n':
|
||||
buf.WriteRune('\n')
|
||||
case 'r':
|
||||
buf.WriteRune('\r')
|
||||
case 't':
|
||||
buf.WriteRune('\t')
|
||||
case '\n':
|
||||
case 'u':
|
||||
if len(s) < 4 {
|
||||
return "", errors.New("short unicode escape")
|
||||
}
|
||||
r, err := strconv.ParseUint(s[:4], 16, 32)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf.WriteRune(rune(r))
|
||||
s = s[4:]
|
||||
case 'U':
|
||||
if len(s) < 8 {
|
||||
return "", errors.New("short unicode escape")
|
||||
}
|
||||
r, err := strconv.ParseUint(s[:8], 16, 32)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf.WriteRune(rune(r))
|
||||
s = s[8:]
|
||||
default:
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
var boolSigSet = sigSet{Signature{"b"}: true}
|
||||
|
||||
type boolNode bool
|
||||
|
||||
func (boolNode) Infer() (Signature, error) {
|
||||
return Signature{"b"}, nil
|
||||
}
|
||||
|
||||
func (b boolNode) String() string {
|
||||
if b {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func (boolNode) Sigs() sigSet {
|
||||
return boolSigSet
|
||||
}
|
||||
|
||||
func (b boolNode) Value(sig Signature) (interface{}, error) {
|
||||
if sig.str != "b" {
|
||||
return nil, varTypeError{b.String(), sig}
|
||||
}
|
||||
return bool(b), nil
|
||||
}
|
||||
|
||||
type arrayNode struct {
|
||||
set sigSet
|
||||
children []varNode
|
||||
val interface{}
|
||||
}
|
||||
|
||||
func (n arrayNode) Infer() (Signature, error) {
|
||||
for _, v := range n.children {
|
||||
csig, err := varInfer(v)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
return Signature{"a" + csig.str}, nil
|
||||
}
|
||||
return Signature{}, fmt.Errorf("can't infer type for %q", n.String())
|
||||
}
|
||||
|
||||
func (n arrayNode) String() string {
|
||||
s := "["
|
||||
for i, v := range n.children {
|
||||
s += v.String()
|
||||
if i != len(n.children)-1 {
|
||||
s += ", "
|
||||
}
|
||||
}
|
||||
return s + "]"
|
||||
}
|
||||
|
||||
func (n arrayNode) Sigs() sigSet {
|
||||
return n.set
|
||||
}
|
||||
|
||||
func (n arrayNode) Value(sig Signature) (interface{}, error) {
|
||||
if n.set.Empty() {
|
||||
// no type information whatsoever, so this must be an empty slice
|
||||
return reflect.MakeSlice(typeFor(sig.str), 0, 0).Interface(), nil
|
||||
}
|
||||
if !n.set[sig] {
|
||||
return nil, varTypeError{n.String(), sig}
|
||||
}
|
||||
s := reflect.MakeSlice(typeFor(sig.str), len(n.children), len(n.children))
|
||||
for i, v := range n.children {
|
||||
rv, err := v.Value(Signature{sig.str[1:]})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Index(i).Set(reflect.ValueOf(rv))
|
||||
}
|
||||
return s.Interface(), nil
|
||||
}
|
||||
|
||||
func varMakeArrayNode(p *varParser, sig Signature) (varNode, error) {
|
||||
var n arrayNode
|
||||
if sig.str != "" {
|
||||
n.set = sigSet{sig: true}
|
||||
}
|
||||
if t := p.next(); t.typ == tokArrayEnd {
|
||||
return n, nil
|
||||
} else {
|
||||
p.backup()
|
||||
}
|
||||
Loop:
|
||||
for {
|
||||
t := p.next()
|
||||
switch t.typ {
|
||||
case tokEOF:
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
case tokError:
|
||||
return nil, errors.New(t.val)
|
||||
}
|
||||
p.backup()
|
||||
cn, err := varMakeNode(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cset := cn.Sigs(); !cset.Empty() {
|
||||
if n.set.Empty() {
|
||||
n.set = cset.ToArray()
|
||||
} else {
|
||||
nset := cset.ToArray().Intersect(n.set)
|
||||
if nset.Empty() {
|
||||
return nil, fmt.Errorf("can't parse %q with given type information", cn.String())
|
||||
}
|
||||
n.set = nset
|
||||
}
|
||||
}
|
||||
n.children = append(n.children, cn)
|
||||
switch t := p.next(); t.typ {
|
||||
case tokEOF:
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
case tokError:
|
||||
return nil, errors.New(t.val)
|
||||
case tokArrayEnd:
|
||||
break Loop
|
||||
case tokComma:
|
||||
continue
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected %q", t.val)
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
type variantNode struct {
|
||||
n varNode
|
||||
}
|
||||
|
||||
var variantSet = sigSet{
|
||||
Signature{"v"}: true,
|
||||
}
|
||||
|
||||
func (variantNode) Infer() (Signature, error) {
|
||||
return Signature{"v"}, nil
|
||||
}
|
||||
|
||||
func (n variantNode) String() string {
|
||||
return "<" + n.n.String() + ">"
|
||||
}
|
||||
|
||||
func (variantNode) Sigs() sigSet {
|
||||
return variantSet
|
||||
}
|
||||
|
||||
func (n variantNode) Value(sig Signature) (interface{}, error) {
|
||||
if sig.str != "v" {
|
||||
return nil, varTypeError{n.String(), sig}
|
||||
}
|
||||
sig, err := varInfer(n.n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v, err := n.n.Value(sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return MakeVariant(v), nil
|
||||
}
|
||||
|
||||
func varMakeVariantNode(p *varParser, sig Signature) (varNode, error) {
|
||||
n, err := varMakeNode(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t := p.next(); t.typ != tokVariantEnd {
|
||||
return nil, fmt.Errorf("unexpected %q", t.val)
|
||||
}
|
||||
vn := variantNode{n}
|
||||
if sig.str != "" && sig.str != "v" {
|
||||
return nil, varTypeError{vn.String(), sig}
|
||||
}
|
||||
return variantNode{n}, nil
|
||||
}
|
||||
|
||||
type dictEntry struct {
|
||||
key, val varNode
|
||||
}
|
||||
|
||||
type dictNode struct {
|
||||
kset, vset sigSet
|
||||
children []dictEntry
|
||||
val interface{}
|
||||
}
|
||||
|
||||
func (n dictNode) Infer() (Signature, error) {
|
||||
for _, v := range n.children {
|
||||
ksig, err := varInfer(v.key)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
vsig, err := varInfer(v.val)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
return Signature{"a{" + ksig.str + vsig.str + "}"}, nil
|
||||
}
|
||||
return Signature{}, fmt.Errorf("can't infer type for %q", n.String())
|
||||
}
|
||||
|
||||
func (n dictNode) String() string {
|
||||
s := "{"
|
||||
for i, v := range n.children {
|
||||
s += v.key.String() + ": " + v.val.String()
|
||||
if i != len(n.children)-1 {
|
||||
s += ", "
|
||||
}
|
||||
}
|
||||
return s + "}"
|
||||
}
|
||||
|
||||
func (n dictNode) Sigs() sigSet {
|
||||
r := sigSet{}
|
||||
for k := range n.kset {
|
||||
for v := range n.vset {
|
||||
sig := "a{" + k.str + v.str + "}"
|
||||
r[Signature{sig}] = true
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (n dictNode) Value(sig Signature) (interface{}, error) {
|
||||
set := n.Sigs()
|
||||
if set.Empty() {
|
||||
// no type information -> empty dict
|
||||
return reflect.MakeMap(typeFor(sig.str)).Interface(), nil
|
||||
}
|
||||
if !set[sig] {
|
||||
return nil, varTypeError{n.String(), sig}
|
||||
}
|
||||
m := reflect.MakeMap(typeFor(sig.str))
|
||||
ksig := Signature{sig.str[2:3]}
|
||||
vsig := Signature{sig.str[3 : len(sig.str)-1]}
|
||||
for _, v := range n.children {
|
||||
kv, err := v.key.Value(ksig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vv, err := v.val.Value(vsig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv))
|
||||
}
|
||||
return m.Interface(), nil
|
||||
}
|
||||
|
||||
func varMakeDictNode(p *varParser, sig Signature) (varNode, error) {
|
||||
var n dictNode
|
||||
|
||||
if sig.str != "" {
|
||||
if len(sig.str) < 5 {
|
||||
return nil, fmt.Errorf("invalid signature %q for dict type", sig)
|
||||
}
|
||||
ksig := Signature{string(sig.str[2])}
|
||||
vsig := Signature{sig.str[3 : len(sig.str)-1]}
|
||||
n.kset = sigSet{ksig: true}
|
||||
n.vset = sigSet{vsig: true}
|
||||
}
|
||||
if t := p.next(); t.typ == tokDictEnd {
|
||||
return n, nil
|
||||
} else {
|
||||
p.backup()
|
||||
}
|
||||
Loop:
|
||||
for {
|
||||
t := p.next()
|
||||
switch t.typ {
|
||||
case tokEOF:
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
case tokError:
|
||||
return nil, errors.New(t.val)
|
||||
}
|
||||
p.backup()
|
||||
kn, err := varMakeNode(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if kset := kn.Sigs(); !kset.Empty() {
|
||||
if n.kset.Empty() {
|
||||
n.kset = kset
|
||||
} else {
|
||||
n.kset = kset.Intersect(n.kset)
|
||||
if n.kset.Empty() {
|
||||
return nil, fmt.Errorf("can't parse %q with given type information", kn.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
t = p.next()
|
||||
switch t.typ {
|
||||
case tokEOF:
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
case tokError:
|
||||
return nil, errors.New(t.val)
|
||||
case tokColon:
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected %q", t.val)
|
||||
}
|
||||
t = p.next()
|
||||
switch t.typ {
|
||||
case tokEOF:
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
case tokError:
|
||||
return nil, errors.New(t.val)
|
||||
}
|
||||
p.backup()
|
||||
vn, err := varMakeNode(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if vset := vn.Sigs(); !vset.Empty() {
|
||||
if n.vset.Empty() {
|
||||
n.vset = vset
|
||||
} else {
|
||||
n.vset = n.vset.Intersect(vset)
|
||||
if n.vset.Empty() {
|
||||
return nil, fmt.Errorf("can't parse %q with given type information", vn.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
n.children = append(n.children, dictEntry{kn, vn})
|
||||
t = p.next()
|
||||
switch t.typ {
|
||||
case tokEOF:
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
case tokError:
|
||||
return nil, errors.New(t.val)
|
||||
case tokDictEnd:
|
||||
break Loop
|
||||
case tokComma:
|
||||
continue
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected %q", t.val)
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
type byteStringNode []byte
|
||||
|
||||
var byteStringSet = sigSet{
|
||||
Signature{"ay"}: true,
|
||||
}
|
||||
|
||||
func (byteStringNode) Infer() (Signature, error) {
|
||||
return Signature{"ay"}, nil
|
||||
}
|
||||
|
||||
func (b byteStringNode) String() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (b byteStringNode) Sigs() sigSet {
|
||||
return byteStringSet
|
||||
}
|
||||
|
||||
func (b byteStringNode) Value(sig Signature) (interface{}, error) {
|
||||
if sig.str != "ay" {
|
||||
return nil, varTypeError{b.String(), sig}
|
||||
}
|
||||
return []byte(b), nil
|
||||
}
|
||||
|
||||
func varParseByteString(s string) ([]byte, error) {
|
||||
// quotes and b at start are guaranteed to be there
|
||||
b := make([]byte, 0, 1)
|
||||
s = s[2 : len(s)-1]
|
||||
for len(s) != 0 {
|
||||
c := s[0]
|
||||
s = s[1:]
|
||||
if c != '\\' {
|
||||
b = append(b, c)
|
||||
continue
|
||||
}
|
||||
c = s[0]
|
||||
s = s[1:]
|
||||
switch c {
|
||||
case 'a':
|
||||
b = append(b, 0x7)
|
||||
case 'b':
|
||||
b = append(b, 0x8)
|
||||
case 'f':
|
||||
b = append(b, 0xc)
|
||||
case 'n':
|
||||
b = append(b, '\n')
|
||||
case 'r':
|
||||
b = append(b, '\r')
|
||||
case 't':
|
||||
b = append(b, '\t')
|
||||
case 'x':
|
||||
if len(s) < 2 {
|
||||
return nil, errors.New("short escape")
|
||||
}
|
||||
n, err := strconv.ParseUint(s[:2], 16, 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b = append(b, byte(n))
|
||||
s = s[2:]
|
||||
case '0':
|
||||
if len(s) < 3 {
|
||||
return nil, errors.New("short escape")
|
||||
}
|
||||
n, err := strconv.ParseUint(s[:3], 8, 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b = append(b, byte(n))
|
||||
s = s[3:]
|
||||
default:
|
||||
b = append(b, c)
|
||||
}
|
||||
}
|
||||
return append(b, 0), nil
|
||||
}
|
||||
|
||||
func varInfer(n varNode) (Signature, error) {
|
||||
if sig, ok := n.Sigs().Single(); ok {
|
||||
return sig, nil
|
||||
}
|
||||
return n.Infer()
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
# Changelog
|
||||
|
||||
## [1.6.0](https://github.com/google/uuid/compare/v1.5.0...v1.6.0) (2024-01-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add Max UUID constant ([#149](https://github.com/google/uuid/issues/149)) ([c58770e](https://github.com/google/uuid/commit/c58770eb495f55fe2ced6284f93c5158a62e53e3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix typo in version 7 uuid documentation ([#153](https://github.com/google/uuid/issues/153)) ([016b199](https://github.com/google/uuid/commit/016b199544692f745ffc8867b914129ecb47ef06))
|
||||
* Monotonicity in UUIDv7 ([#150](https://github.com/google/uuid/issues/150)) ([a2b2b32](https://github.com/google/uuid/commit/a2b2b32373ff0b1a312b7fdf6d38a977099698a6))
|
||||
|
||||
## [1.5.0](https://github.com/google/uuid/compare/v1.4.0...v1.5.0) (2023-12-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Validate UUID without creating new UUID ([#141](https://github.com/google/uuid/issues/141)) ([9ee7366](https://github.com/google/uuid/commit/9ee7366e66c9ad96bab89139418a713dc584ae29))
|
||||
|
||||
## [1.4.0](https://github.com/google/uuid/compare/v1.3.1...v1.4.0) (2023-10-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* UUIDs slice type with Strings() convenience method ([#133](https://github.com/google/uuid/issues/133)) ([cd5fbbd](https://github.com/google/uuid/commit/cd5fbbdd02f3e3467ac18940e07e062be1f864b4))
|
||||
|
||||
### Fixes
|
||||
|
||||
* Clarify that Parse's job is to parse but not necessarily validate strings. (Documents current behavior)
|
||||
|
||||
## [1.3.1](https://github.com/google/uuid/compare/v1.3.0...v1.3.1) (2023-08-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Use .EqualFold() to parse urn prefixed UUIDs ([#118](https://github.com/google/uuid/issues/118)) ([574e687](https://github.com/google/uuid/commit/574e6874943741fb99d41764c705173ada5293f0))
|
||||
|
||||
## Changelog
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
# How to contribute
|
||||
|
||||
We definitely welcome patches and contribution to this project!
|
||||
|
||||
### Tips
|
||||
|
||||
Commits must be formatted according to the [Conventional Commits Specification](https://www.conventionalcommits.org).
|
||||
|
||||
Always try to include a test case! If it is not possible or not necessary,
|
||||
please explain why in the pull request description.
|
||||
|
||||
### Releasing
|
||||
|
||||
Commits that would precipitate a SemVer change, as described in the Conventional
|
||||
Commits Specification, will trigger [`release-please`](https://github.com/google-github-actions/release-please-action)
|
||||
to create a release candidate pull request. Once submitted, `release-please`
|
||||
will create a release.
|
||||
|
||||
For tips on how to work with `release-please`, see its documentation.
|
||||
|
||||
### Legal requirements
|
||||
|
||||
In order to protect both you and ourselves, you will need to sign the
|
||||
[Contributor License Agreement](https://cla.developers.google.com/clas).
|
||||
|
||||
You may have already signed it for other Google projects.
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
Paul Borman <borman@google.com>
|
||||
bmatsuo
|
||||
shawnps
|
||||
theory
|
||||
jboverfelt
|
||||
dsymonds
|
||||
cd1
|
||||
wallclockbuilder
|
||||
dansouza
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009,2014 Google Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# uuid
|
||||
The uuid package generates and inspects UUIDs based on
|
||||
[RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122)
|
||||
and DCE 1.1: Authentication and Security Services.
|
||||
|
||||
This package is based on the github.com/pborman/uuid package (previously named
|
||||
code.google.com/p/go-uuid). It differs from these earlier packages in that
|
||||
a UUID is a 16 byte array rather than a byte slice. One loss due to this
|
||||
change is the ability to represent an invalid UUID (vs a NIL UUID).
|
||||
|
||||
###### Install
|
||||
```sh
|
||||
go get github.com/google/uuid
|
||||
```
|
||||
|
||||
###### Documentation
|
||||
[](https://pkg.go.dev/github.com/google/uuid)
|
||||
|
||||
Full `go doc` style documentation for the package can be viewed online without
|
||||
installing this package by using the GoDoc site here:
|
||||
http://pkg.go.dev/github.com/google/uuid
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// A Domain represents a Version 2 domain
|
||||
type Domain byte
|
||||
|
||||
// Domain constants for DCE Security (Version 2) UUIDs.
|
||||
const (
|
||||
Person = Domain(0)
|
||||
Group = Domain(1)
|
||||
Org = Domain(2)
|
||||
)
|
||||
|
||||
// NewDCESecurity returns a DCE Security (Version 2) UUID.
|
||||
//
|
||||
// The domain should be one of Person, Group or Org.
|
||||
// On a POSIX system the id should be the users UID for the Person
|
||||
// domain and the users GID for the Group. The meaning of id for
|
||||
// the domain Org or on non-POSIX systems is site defined.
|
||||
//
|
||||
// For a given domain/id pair the same token may be returned for up to
|
||||
// 7 minutes and 10 seconds.
|
||||
func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
|
||||
uuid, err := NewUUID()
|
||||
if err == nil {
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
|
||||
uuid[9] = byte(domain)
|
||||
binary.BigEndian.PutUint32(uuid[0:], id)
|
||||
}
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
|
||||
// domain with the id returned by os.Getuid.
|
||||
//
|
||||
// NewDCESecurity(Person, uint32(os.Getuid()))
|
||||
func NewDCEPerson() (UUID, error) {
|
||||
return NewDCESecurity(Person, uint32(os.Getuid()))
|
||||
}
|
||||
|
||||
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
|
||||
// domain with the id returned by os.Getgid.
|
||||
//
|
||||
// NewDCESecurity(Group, uint32(os.Getgid()))
|
||||
func NewDCEGroup() (UUID, error) {
|
||||
return NewDCESecurity(Group, uint32(os.Getgid()))
|
||||
}
|
||||
|
||||
// Domain returns the domain for a Version 2 UUID. Domains are only defined
|
||||
// for Version 2 UUIDs.
|
||||
func (uuid UUID) Domain() Domain {
|
||||
return Domain(uuid[9])
|
||||
}
|
||||
|
||||
// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
|
||||
// UUIDs.
|
||||
func (uuid UUID) ID() uint32 {
|
||||
return binary.BigEndian.Uint32(uuid[0:4])
|
||||
}
|
||||
|
||||
func (d Domain) String() string {
|
||||
switch d {
|
||||
case Person:
|
||||
return "Person"
|
||||
case Group:
|
||||
return "Group"
|
||||
case Org:
|
||||
return "Org"
|
||||
}
|
||||
return fmt.Sprintf("Domain%d", int(d))
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package uuid generates and inspects UUIDs.
|
||||
//
|
||||
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
|
||||
// Services.
|
||||
//
|
||||
// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
|
||||
// maps or compared directly.
|
||||
package uuid
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"hash"
|
||||
)
|
||||
|
||||
// Well known namespace IDs and UUIDs
|
||||
var (
|
||||
NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
|
||||
Nil UUID // empty UUID, all zeros
|
||||
|
||||
// The Max UUID is special form of UUID that is specified to have all 128 bits set to 1.
|
||||
Max = UUID{
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
}
|
||||
)
|
||||
|
||||
// NewHash returns a new UUID derived from the hash of space concatenated with
|
||||
// data generated by h. The hash should be at least 16 byte in length. The
|
||||
// first 16 bytes of the hash are used to form the UUID. The version of the
|
||||
// UUID will be the lower 4 bits of version. NewHash is used to implement
|
||||
// NewMD5 and NewSHA1.
|
||||
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
||||
h.Reset()
|
||||
h.Write(space[:]) //nolint:errcheck
|
||||
h.Write(data) //nolint:errcheck
|
||||
s := h.Sum(nil)
|
||||
var uuid UUID
|
||||
copy(uuid[:], s)
|
||||
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
||||
return uuid
|
||||
}
|
||||
|
||||
// NewMD5 returns a new MD5 (Version 3) UUID based on the
|
||||
// supplied name space and data. It is the same as calling:
|
||||
//
|
||||
// NewHash(md5.New(), space, data, 3)
|
||||
func NewMD5(space UUID, data []byte) UUID {
|
||||
return NewHash(md5.New(), space, data, 3)
|
||||
}
|
||||
|
||||
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
|
||||
// supplied name space and data. It is the same as calling:
|
||||
//
|
||||
// NewHash(sha1.New(), space, data, 5)
|
||||
func NewSHA1(space UUID, data []byte) UUID {
|
||||
return NewHash(sha1.New(), space, data, 5)
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "fmt"
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (uuid UUID) MarshalText() ([]byte, error) {
|
||||
var js [36]byte
|
||||
encodeHex(js[:], uuid)
|
||||
return js[:], nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (uuid *UUID) UnmarshalText(data []byte) error {
|
||||
id, err := ParseBytes(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*uuid = id
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (uuid UUID) MarshalBinary() ([]byte, error) {
|
||||
return uuid[:], nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (uuid *UUID) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != 16 {
|
||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
||||
}
|
||||
copy(uuid[:], data)
|
||||
return nil
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
nodeMu sync.Mutex
|
||||
ifname string // name of interface being used
|
||||
nodeID [6]byte // hardware for version 1 UUIDs
|
||||
zeroID [6]byte // nodeID with only 0's
|
||||
)
|
||||
|
||||
// NodeInterface returns the name of the interface from which the NodeID was
|
||||
// derived. The interface "user" is returned if the NodeID was set by
|
||||
// SetNodeID.
|
||||
func NodeInterface() string {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
return ifname
|
||||
}
|
||||
|
||||
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
|
||||
// If name is "" then the first usable interface found will be used or a random
|
||||
// Node ID will be generated. If a named interface cannot be found then false
|
||||
// is returned.
|
||||
//
|
||||
// SetNodeInterface never fails when name is "".
|
||||
func SetNodeInterface(name string) bool {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
return setNodeInterface(name)
|
||||
}
|
||||
|
||||
func setNodeInterface(name string) bool {
|
||||
iname, addr := getHardwareInterface(name) // null implementation for js
|
||||
if iname != "" && addr != nil {
|
||||
ifname = iname
|
||||
copy(nodeID[:], addr)
|
||||
return true
|
||||
}
|
||||
|
||||
// We found no interfaces with a valid hardware address. If name
|
||||
// does not specify a specific interface generate a random Node ID
|
||||
// (section 4.1.6)
|
||||
if name == "" {
|
||||
ifname = "random"
|
||||
randomBits(nodeID[:])
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
|
||||
// if not already set.
|
||||
func NodeID() []byte {
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
if nodeID == zeroID {
|
||||
setNodeInterface("")
|
||||
}
|
||||
nid := nodeID
|
||||
return nid[:]
|
||||
}
|
||||
|
||||
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
|
||||
// of id are used. If id is less than 6 bytes then false is returned and the
|
||||
// Node ID is not set.
|
||||
func SetNodeID(id []byte) bool {
|
||||
if len(id) < 6 {
|
||||
return false
|
||||
}
|
||||
defer nodeMu.Unlock()
|
||||
nodeMu.Lock()
|
||||
copy(nodeID[:], id)
|
||||
ifname = "user"
|
||||
return true
|
||||
}
|
||||
|
||||
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
|
||||
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) NodeID() []byte {
|
||||
var node [6]byte
|
||||
copy(node[:], uuid[10:])
|
||||
return node[:]
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build js
|
||||
|
||||
package uuid
|
||||
|
||||
// getHardwareInterface returns nil values for the JS version of the code.
|
||||
// This removes the "net" dependency, because it is not used in the browser.
|
||||
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
|
||||
func getHardwareInterface(name string) (string, []byte) { return "", nil }
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !js
|
||||
|
||||
package uuid
|
||||
|
||||
import "net"
|
||||
|
||||
var interfaces []net.Interface // cached list of interfaces
|
||||
|
||||
// getHardwareInterface returns the name and hardware address of interface name.
|
||||
// If name is "" then the name and hardware address of one of the system's
|
||||
// interfaces is returned. If no interfaces are found (name does not exist or
|
||||
// there are no interfaces) then "", nil is returned.
|
||||
//
|
||||
// Only addresses of at least 6 bytes are returned.
|
||||
func getHardwareInterface(name string) (string, []byte) {
|
||||
if interfaces == nil {
|
||||
var err error
|
||||
interfaces, err = net.Interfaces()
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
for _, ifs := range interfaces {
|
||||
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
|
||||
return ifs.Name, ifs.HardwareAddr
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
// Copyright 2021 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var jsonNull = []byte("null")
|
||||
|
||||
// NullUUID represents a UUID that may be null.
|
||||
// NullUUID implements the SQL driver.Scanner interface so
|
||||
// it can be used as a scan destination:
|
||||
//
|
||||
// var u uuid.NullUUID
|
||||
// err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&u)
|
||||
// ...
|
||||
// if u.Valid {
|
||||
// // use u.UUID
|
||||
// } else {
|
||||
// // NULL value
|
||||
// }
|
||||
//
|
||||
type NullUUID struct {
|
||||
UUID UUID
|
||||
Valid bool // Valid is true if UUID is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the SQL driver.Scanner interface.
|
||||
func (nu *NullUUID) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
nu.UUID, nu.Valid = Nil, false
|
||||
return nil
|
||||
}
|
||||
|
||||
err := nu.UUID.Scan(value)
|
||||
if err != nil {
|
||||
nu.Valid = false
|
||||
return err
|
||||
}
|
||||
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (nu NullUUID) Value() (driver.Value, error) {
|
||||
if !nu.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
// Delegate to UUID Value function
|
||||
return nu.UUID.Value()
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (nu NullUUID) MarshalBinary() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return nu.UUID[:], nil
|
||||
}
|
||||
|
||||
return []byte(nil), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (nu *NullUUID) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != 16 {
|
||||
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
||||
}
|
||||
copy(nu.UUID[:], data)
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (nu NullUUID) MarshalText() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return nu.UUID.MarshalText()
|
||||
}
|
||||
|
||||
return jsonNull, nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (nu *NullUUID) UnmarshalText(data []byte) error {
|
||||
id, err := ParseBytes(data)
|
||||
if err != nil {
|
||||
nu.Valid = false
|
||||
return err
|
||||
}
|
||||
nu.UUID = id
|
||||
nu.Valid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (nu NullUUID) MarshalJSON() ([]byte, error) {
|
||||
if nu.Valid {
|
||||
return json.Marshal(nu.UUID)
|
||||
}
|
||||
|
||||
return jsonNull, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (nu *NullUUID) UnmarshalJSON(data []byte) error {
|
||||
if bytes.Equal(data, jsonNull) {
|
||||
*nu = NullUUID{}
|
||||
return nil // valid null UUID
|
||||
}
|
||||
err := json.Unmarshal(data, &nu.UUID)
|
||||
nu.Valid = err == nil
|
||||
return err
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Scan implements sql.Scanner so UUIDs can be read from databases transparently.
|
||||
// Currently, database types that map to string and []byte are supported. Please
|
||||
// consult database-specific driver documentation for matching types.
|
||||
func (uuid *UUID) Scan(src interface{}) error {
|
||||
switch src := src.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
|
||||
case string:
|
||||
// if an empty UUID comes from a table, we return a null UUID
|
||||
if src == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// see Parse for required string format
|
||||
u, err := Parse(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Scan: %v", err)
|
||||
}
|
||||
|
||||
*uuid = u
|
||||
|
||||
case []byte:
|
||||
// if an empty UUID comes from a table, we return a null UUID
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// assumes a simple slice of bytes if 16 bytes
|
||||
// otherwise attempts to parse
|
||||
if len(src) != 16 {
|
||||
return uuid.Scan(string(src))
|
||||
}
|
||||
copy((*uuid)[:], src)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value implements sql.Valuer so that UUIDs can be written to databases
|
||||
// transparently. Currently, UUIDs map to strings. Please consult
|
||||
// database-specific driver documentation for matching types.
|
||||
func (uuid UUID) Value() (driver.Value, error) {
|
||||
return uuid.String(), nil
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
|
||||
// 1582.
|
||||
type Time int64
|
||||
|
||||
const (
|
||||
lillian = 2299160 // Julian day of 15 Oct 1582
|
||||
unix = 2440587 // Julian day of 1 Jan 1970
|
||||
epoch = unix - lillian // Days between epochs
|
||||
g1582 = epoch * 86400 // seconds between epochs
|
||||
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
|
||||
)
|
||||
|
||||
var (
|
||||
timeMu sync.Mutex
|
||||
lasttime uint64 // last time we returned
|
||||
clockSeq uint16 // clock sequence for this run
|
||||
|
||||
timeNow = time.Now // for testing
|
||||
)
|
||||
|
||||
// UnixTime converts t the number of seconds and nanoseconds using the Unix
|
||||
// epoch of 1 Jan 1970.
|
||||
func (t Time) UnixTime() (sec, nsec int64) {
|
||||
sec = int64(t - g1582ns100)
|
||||
nsec = (sec % 10000000) * 100
|
||||
sec /= 10000000
|
||||
return sec, nsec
|
||||
}
|
||||
|
||||
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
|
||||
// clock sequence as well as adjusting the clock sequence as needed. An error
|
||||
// is returned if the current time cannot be determined.
|
||||
func GetTime() (Time, uint16, error) {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
return getTime()
|
||||
}
|
||||
|
||||
func getTime() (Time, uint16, error) {
|
||||
t := timeNow()
|
||||
|
||||
// If we don't have a clock sequence already, set one.
|
||||
if clockSeq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
now := uint64(t.UnixNano()/100) + g1582ns100
|
||||
|
||||
// If time has gone backwards with this clock sequence then we
|
||||
// increment the clock sequence
|
||||
if now <= lasttime {
|
||||
clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
|
||||
}
|
||||
lasttime = now
|
||||
return Time(now), clockSeq, nil
|
||||
}
|
||||
|
||||
// ClockSequence returns the current clock sequence, generating one if not
|
||||
// already set. The clock sequence is only used for Version 1 UUIDs.
|
||||
//
|
||||
// The uuid package does not use global static storage for the clock sequence or
|
||||
// the last time a UUID was generated. Unless SetClockSequence is used, a new
|
||||
// random clock sequence is generated the first time a clock sequence is
|
||||
// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
|
||||
func ClockSequence() int {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
return clockSequence()
|
||||
}
|
||||
|
||||
func clockSequence() int {
|
||||
if clockSeq == 0 {
|
||||
setClockSequence(-1)
|
||||
}
|
||||
return int(clockSeq & 0x3fff)
|
||||
}
|
||||
|
||||
// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to
|
||||
// -1 causes a new sequence to be generated.
|
||||
func SetClockSequence(seq int) {
|
||||
defer timeMu.Unlock()
|
||||
timeMu.Lock()
|
||||
setClockSequence(seq)
|
||||
}
|
||||
|
||||
func setClockSequence(seq int) {
|
||||
if seq == -1 {
|
||||
var b [2]byte
|
||||
randomBits(b[:]) // clock sequence
|
||||
seq = int(b[0])<<8 | int(b[1])
|
||||
}
|
||||
oldSeq := clockSeq
|
||||
clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
|
||||
if oldSeq != clockSeq {
|
||||
lasttime = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
|
||||
// uuid. The time is only defined for version 1, 2, 6 and 7 UUIDs.
|
||||
func (uuid UUID) Time() Time {
|
||||
var t Time
|
||||
switch uuid.Version() {
|
||||
case 6:
|
||||
time := binary.BigEndian.Uint64(uuid[:8]) // Ignore uuid[6] version b0110
|
||||
t = Time(time)
|
||||
case 7:
|
||||
time := binary.BigEndian.Uint64(uuid[:8])
|
||||
t = Time((time>>16)*10000 + g1582ns100)
|
||||
default: // forward compatible
|
||||
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
|
||||
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
|
||||
t = Time(time)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ClockSequence returns the clock sequence encoded in uuid.
|
||||
// The clock sequence is only well defined for version 1 and 2 UUIDs.
|
||||
func (uuid UUID) ClockSequence() int {
|
||||
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// randomBits completely fills slice b with random data.
|
||||
func randomBits(b []byte) {
|
||||
if _, err := io.ReadFull(rander, b); err != nil {
|
||||
panic(err.Error()) // rand should never fail
|
||||
}
|
||||
}
|
||||
|
||||
// xvalues returns the value of a byte as a hexadecimal digit or 255.
|
||||
var xvalues = [256]byte{
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
|
||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||
}
|
||||
|
||||
// xtob converts hex characters x1 and x2 into a byte.
|
||||
func xtob(x1, x2 byte) (byte, bool) {
|
||||
b1 := xvalues[x1]
|
||||
b2 := xvalues[x2]
|
||||
return (b1 << 4) | b2, b1 != 255 && b2 != 255
|
||||
}
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
// Copyright 2018 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
|
||||
// 4122.
|
||||
type UUID [16]byte
|
||||
|
||||
// A Version represents a UUID's version.
|
||||
type Version byte
|
||||
|
||||
// A Variant represents a UUID's variant.
|
||||
type Variant byte
|
||||
|
||||
// Constants returned by Variant.
|
||||
const (
|
||||
Invalid = Variant(iota) // Invalid UUID
|
||||
RFC4122 // The variant specified in RFC4122
|
||||
Reserved // Reserved, NCS backward compatibility.
|
||||
Microsoft // Reserved, Microsoft Corporation backward compatibility.
|
||||
Future // Reserved for future definition.
|
||||
)
|
||||
|
||||
const randPoolSize = 16 * 16
|
||||
|
||||
var (
|
||||
rander = rand.Reader // random function
|
||||
poolEnabled = false
|
||||
poolMu sync.Mutex
|
||||
poolPos = randPoolSize // protected with poolMu
|
||||
pool [randPoolSize]byte // protected with poolMu
|
||||
)
|
||||
|
||||
type invalidLengthError struct{ len int }
|
||||
|
||||
func (err invalidLengthError) Error() string {
|
||||
return fmt.Sprintf("invalid UUID length: %d", err.len)
|
||||
}
|
||||
|
||||
// IsInvalidLengthError is matcher function for custom error invalidLengthError
|
||||
func IsInvalidLengthError(err error) bool {
|
||||
_, ok := err.(invalidLengthError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Parse decodes s into a UUID or returns an error if it cannot be parsed. Both
|
||||
// the standard UUID forms defined in RFC 4122
|
||||
// (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) are decoded. In addition,
|
||||
// Parse accepts non-standard strings such as the raw hex encoding
|
||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx and 38 byte "Microsoft style" encodings,
|
||||
// e.g. {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}. Only the middle 36 bytes are
|
||||
// examined in the latter case. Parse should not be used to validate strings as
|
||||
// it parses non-standard encodings as indicated above.
|
||||
func Parse(s string) (UUID, error) {
|
||||
var uuid UUID
|
||||
switch len(s) {
|
||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36:
|
||||
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36 + 9:
|
||||
if !strings.EqualFold(s[:9], "urn:uuid:") {
|
||||
return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
|
||||
}
|
||||
s = s[9:]
|
||||
|
||||
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
case 36 + 2:
|
||||
s = s[1:]
|
||||
|
||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
case 32:
|
||||
var ok bool
|
||||
for i := range uuid {
|
||||
uuid[i], ok = xtob(s[i*2], s[i*2+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
return uuid, nil
|
||||
default:
|
||||
return uuid, invalidLengthError{len(s)}
|
||||
}
|
||||
// s is now at least 36 bytes long
|
||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
for i, x := range [16]int{
|
||||
0, 2, 4, 6,
|
||||
9, 11,
|
||||
14, 16,
|
||||
19, 21,
|
||||
24, 26, 28, 30, 32, 34,
|
||||
} {
|
||||
v, ok := xtob(s[x], s[x+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
uuid[i] = v
|
||||
}
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
|
||||
func ParseBytes(b []byte) (UUID, error) {
|
||||
var uuid UUID
|
||||
switch len(b) {
|
||||
case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if !bytes.EqualFold(b[:9], []byte("urn:uuid:")) {
|
||||
return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
|
||||
}
|
||||
b = b[9:]
|
||||
case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
b = b[1:]
|
||||
case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
var ok bool
|
||||
for i := 0; i < 32; i += 2 {
|
||||
uuid[i/2], ok = xtob(b[i], b[i+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
return uuid, nil
|
||||
default:
|
||||
return uuid, invalidLengthError{len(b)}
|
||||
}
|
||||
// s is now at least 36 bytes long
|
||||
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
for i, x := range [16]int{
|
||||
0, 2, 4, 6,
|
||||
9, 11,
|
||||
14, 16,
|
||||
19, 21,
|
||||
24, 26, 28, 30, 32, 34,
|
||||
} {
|
||||
v, ok := xtob(b[x], b[x+1])
|
||||
if !ok {
|
||||
return uuid, errors.New("invalid UUID format")
|
||||
}
|
||||
uuid[i] = v
|
||||
}
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// MustParse is like Parse but panics if the string cannot be parsed.
|
||||
// It simplifies safe initialization of global variables holding compiled UUIDs.
|
||||
func MustParse(s string) UUID {
|
||||
uuid, err := Parse(s)
|
||||
if err != nil {
|
||||
panic(`uuid: Parse(` + s + `): ` + err.Error())
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// FromBytes creates a new UUID from a byte slice. Returns an error if the slice
|
||||
// does not have a length of 16. The bytes are copied from the slice.
|
||||
func FromBytes(b []byte) (uuid UUID, err error) {
|
||||
err = uuid.UnmarshalBinary(b)
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
// Must returns uuid if err is nil and panics otherwise.
|
||||
func Must(uuid UUID, err error) UUID {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// Validate returns an error if s is not a properly formatted UUID in one of the following formats:
|
||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||
// It returns an error if the format is invalid, otherwise nil.
|
||||
func Validate(s string) error {
|
||||
switch len(s) {
|
||||
// Standard UUID format
|
||||
case 36:
|
||||
|
||||
// UUID with "urn:uuid:" prefix
|
||||
case 36 + 9:
|
||||
if !strings.EqualFold(s[:9], "urn:uuid:") {
|
||||
return fmt.Errorf("invalid urn prefix: %q", s[:9])
|
||||
}
|
||||
s = s[9:]
|
||||
|
||||
// UUID enclosed in braces
|
||||
case 36 + 2:
|
||||
if s[0] != '{' || s[len(s)-1] != '}' {
|
||||
return fmt.Errorf("invalid bracketed UUID format")
|
||||
}
|
||||
s = s[1 : len(s)-1]
|
||||
|
||||
// UUID without hyphens
|
||||
case 32:
|
||||
for i := 0; i < len(s); i += 2 {
|
||||
_, ok := xtob(s[i], s[i+1])
|
||||
if !ok {
|
||||
return errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return invalidLengthError{len(s)}
|
||||
}
|
||||
|
||||
// Check for standard UUID format
|
||||
if len(s) == 36 {
|
||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||
return errors.New("invalid UUID format")
|
||||
}
|
||||
for _, x := range []int{0, 2, 4, 6, 9, 11, 14, 16, 19, 21, 24, 26, 28, 30, 32, 34} {
|
||||
if _, ok := xtob(s[x], s[x+1]); !ok {
|
||||
return errors.New("invalid UUID format")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
// , or "" if uuid is invalid.
|
||||
func (uuid UUID) String() string {
|
||||
var buf [36]byte
|
||||
encodeHex(buf[:], uuid)
|
||||
return string(buf[:])
|
||||
}
|
||||
|
||||
// URN returns the RFC 2141 URN form of uuid,
|
||||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
|
||||
func (uuid UUID) URN() string {
|
||||
var buf [36 + 9]byte
|
||||
copy(buf[:], "urn:uuid:")
|
||||
encodeHex(buf[9:], uuid)
|
||||
return string(buf[:])
|
||||
}
|
||||
|
||||
func encodeHex(dst []byte, uuid UUID) {
|
||||
hex.Encode(dst, uuid[:4])
|
||||
dst[8] = '-'
|
||||
hex.Encode(dst[9:13], uuid[4:6])
|
||||
dst[13] = '-'
|
||||
hex.Encode(dst[14:18], uuid[6:8])
|
||||
dst[18] = '-'
|
||||
hex.Encode(dst[19:23], uuid[8:10])
|
||||
dst[23] = '-'
|
||||
hex.Encode(dst[24:], uuid[10:])
|
||||
}
|
||||
|
||||
// Variant returns the variant encoded in uuid.
|
||||
func (uuid UUID) Variant() Variant {
|
||||
switch {
|
||||
case (uuid[8] & 0xc0) == 0x80:
|
||||
return RFC4122
|
||||
case (uuid[8] & 0xe0) == 0xc0:
|
||||
return Microsoft
|
||||
case (uuid[8] & 0xe0) == 0xe0:
|
||||
return Future
|
||||
default:
|
||||
return Reserved
|
||||
}
|
||||
}
|
||||
|
||||
// Version returns the version of uuid.
|
||||
func (uuid UUID) Version() Version {
|
||||
return Version(uuid[6] >> 4)
|
||||
}
|
||||
|
||||
func (v Version) String() string {
|
||||
if v > 15 {
|
||||
return fmt.Sprintf("BAD_VERSION_%d", v)
|
||||
}
|
||||
return fmt.Sprintf("VERSION_%d", v)
|
||||
}
|
||||
|
||||
func (v Variant) String() string {
|
||||
switch v {
|
||||
case RFC4122:
|
||||
return "RFC4122"
|
||||
case Reserved:
|
||||
return "Reserved"
|
||||
case Microsoft:
|
||||
return "Microsoft"
|
||||
case Future:
|
||||
return "Future"
|
||||
case Invalid:
|
||||
return "Invalid"
|
||||
}
|
||||
return fmt.Sprintf("BadVariant%d", int(v))
|
||||
}
|
||||
|
||||
// SetRand sets the random number generator to r, which implements io.Reader.
|
||||
// If r.Read returns an error when the package requests random data then
|
||||
// a panic will be issued.
|
||||
//
|
||||
// Calling SetRand with nil sets the random number generator to the default
|
||||
// generator.
|
||||
func SetRand(r io.Reader) {
|
||||
if r == nil {
|
||||
rander = rand.Reader
|
||||
return
|
||||
}
|
||||
rander = r
|
||||
}
|
||||
|
||||
// EnableRandPool enables internal randomness pool used for Random
|
||||
// (Version 4) UUID generation. The pool contains random bytes read from
|
||||
// the random number generator on demand in batches. Enabling the pool
|
||||
// may improve the UUID generation throughput significantly.
|
||||
//
|
||||
// Since the pool is stored on the Go heap, this feature may be a bad fit
|
||||
// for security sensitive applications.
|
||||
//
|
||||
// Both EnableRandPool and DisableRandPool are not thread-safe and should
|
||||
// only be called when there is no possibility that New or any other
|
||||
// UUID Version 4 generation function will be called concurrently.
|
||||
func EnableRandPool() {
|
||||
poolEnabled = true
|
||||
}
|
||||
|
||||
// DisableRandPool disables the randomness pool if it was previously
|
||||
// enabled with EnableRandPool.
|
||||
//
|
||||
// Both EnableRandPool and DisableRandPool are not thread-safe and should
|
||||
// only be called when there is no possibility that New or any other
|
||||
// UUID Version 4 generation function will be called concurrently.
|
||||
func DisableRandPool() {
|
||||
poolEnabled = false
|
||||
defer poolMu.Unlock()
|
||||
poolMu.Lock()
|
||||
poolPos = randPoolSize
|
||||
}
|
||||
|
||||
// UUIDs is a slice of UUID types.
|
||||
type UUIDs []UUID
|
||||
|
||||
// Strings returns a string slice containing the string form of each UUID in uuids.
|
||||
func (uuids UUIDs) Strings() []string {
|
||||
var uuidStrs = make([]string, len(uuids))
|
||||
for i, uuid := range uuids {
|
||||
uuidStrs[i] = uuid.String()
|
||||
}
|
||||
return uuidStrs
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
|
||||
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
||||
// be set NewUUID returns nil. If clock sequence has not been set by
|
||||
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||
// return the current NewUUID returns nil and an error.
|
||||
//
|
||||
// In most cases, New should be used.
|
||||
func NewUUID() (UUID, error) {
|
||||
var uuid UUID
|
||||
now, seq, err := GetTime()
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
timeLow := uint32(now & 0xffffffff)
|
||||
timeMid := uint16((now >> 32) & 0xffff)
|
||||
timeHi := uint16((now >> 48) & 0x0fff)
|
||||
timeHi |= 0x1000 // Version 1
|
||||
|
||||
binary.BigEndian.PutUint32(uuid[0:], timeLow)
|
||||
binary.BigEndian.PutUint16(uuid[4:], timeMid)
|
||||
binary.BigEndian.PutUint16(uuid[6:], timeHi)
|
||||
binary.BigEndian.PutUint16(uuid[8:], seq)
|
||||
|
||||
nodeMu.Lock()
|
||||
if nodeID == zeroID {
|
||||
setNodeInterface("")
|
||||
}
|
||||
copy(uuid[10:], nodeID[:])
|
||||
nodeMu.Unlock()
|
||||
|
||||
return uuid, nil
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "io"
|
||||
|
||||
// New creates a new random UUID or panics. New is equivalent to
|
||||
// the expression
|
||||
//
|
||||
// uuid.Must(uuid.NewRandom())
|
||||
func New() UUID {
|
||||
return Must(NewRandom())
|
||||
}
|
||||
|
||||
// NewString creates a new random UUID and returns it as a string or panics.
|
||||
// NewString is equivalent to the expression
|
||||
//
|
||||
// uuid.New().String()
|
||||
func NewString() string {
|
||||
return Must(NewRandom()).String()
|
||||
}
|
||||
|
||||
// NewRandom returns a Random (Version 4) UUID.
|
||||
//
|
||||
// The strength of the UUIDs is based on the strength of the crypto/rand
|
||||
// package.
|
||||
//
|
||||
// Uses the randomness pool if it was enabled with EnableRandPool.
|
||||
//
|
||||
// A note about uniqueness derived from the UUID Wikipedia entry:
|
||||
//
|
||||
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
|
||||
// hit by a meteorite is estimated to be one chance in 17 billion, that
|
||||
// means the probability is about 0.00000000006 (6 × 10−11),
|
||||
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
|
||||
// year and having one duplicate.
|
||||
func NewRandom() (UUID, error) {
|
||||
if !poolEnabled {
|
||||
return NewRandomFromReader(rander)
|
||||
}
|
||||
return newRandomFromPool()
|
||||
}
|
||||
|
||||
// NewRandomFromReader returns a UUID based on bytes read from a given io.Reader.
|
||||
func NewRandomFromReader(r io.Reader) (UUID, error) {
|
||||
var uuid UUID
|
||||
_, err := io.ReadFull(r, uuid[:])
|
||||
if err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
func newRandomFromPool() (UUID, error) {
|
||||
var uuid UUID
|
||||
poolMu.Lock()
|
||||
if poolPos == randPoolSize {
|
||||
_, err := io.ReadFull(rander, pool[:])
|
||||
if err != nil {
|
||||
poolMu.Unlock()
|
||||
return Nil, err
|
||||
}
|
||||
poolPos = 0
|
||||
}
|
||||
copy(uuid[:], pool[poolPos:(poolPos+16)])
|
||||
poolPos += 16
|
||||
poolMu.Unlock()
|
||||
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||
return uuid, nil
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// Copyright 2023 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// UUID version 6 is a field-compatible version of UUIDv1, reordered for improved DB locality.
|
||||
// It is expected that UUIDv6 will primarily be used in contexts where there are existing v1 UUIDs.
|
||||
// Systems that do not involve legacy UUIDv1 SHOULD consider using UUIDv7 instead.
|
||||
//
|
||||
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#uuidv6
|
||||
//
|
||||
// NewV6 returns a Version 6 UUID based on the current NodeID and clock
|
||||
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
||||
// be set NewV6 set NodeID is random bits automatically . If clock sequence has not been set by
|
||||
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||
// return the current NewV6 returns Nil and an error.
|
||||
func NewV6() (UUID, error) {
|
||||
var uuid UUID
|
||||
now, seq, err := GetTime()
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
/*
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| time_high |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| time_mid | time_low_and_version |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|clk_seq_hi_res | clk_seq_low | node (0-1) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| node (2-5) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
|
||||
binary.BigEndian.PutUint64(uuid[0:], uint64(now))
|
||||
binary.BigEndian.PutUint16(uuid[8:], seq)
|
||||
|
||||
uuid[6] = 0x60 | (uuid[6] & 0x0F)
|
||||
uuid[8] = 0x80 | (uuid[8] & 0x3F)
|
||||
|
||||
nodeMu.Lock()
|
||||
if nodeID == zeroID {
|
||||
setNodeInterface("")
|
||||
}
|
||||
copy(uuid[10:], nodeID[:])
|
||||
nodeMu.Unlock()
|
||||
|
||||
return uuid, nil
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
// Copyright 2023 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// UUID version 7 features a time-ordered value field derived from the widely
|
||||
// implemented and well known Unix Epoch timestamp source,
|
||||
// the number of milliseconds seconds since midnight 1 Jan 1970 UTC, leap seconds excluded.
|
||||
// As well as improved entropy characteristics over versions 1 or 6.
|
||||
//
|
||||
// see https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-03#name-uuid-version-7
|
||||
//
|
||||
// Implementations SHOULD utilize UUID version 7 over UUID version 1 and 6 if possible.
|
||||
//
|
||||
// NewV7 returns a Version 7 UUID based on the current time(Unix Epoch).
|
||||
// Uses the randomness pool if it was enabled with EnableRandPool.
|
||||
// On error, NewV7 returns Nil and an error
|
||||
func NewV7() (UUID, error) {
|
||||
uuid, err := NewRandom()
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
makeV7(uuid[:])
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// NewV7FromReader returns a Version 7 UUID based on the current time(Unix Epoch).
|
||||
// it use NewRandomFromReader fill random bits.
|
||||
// On error, NewV7FromReader returns Nil and an error.
|
||||
func NewV7FromReader(r io.Reader) (UUID, error) {
|
||||
uuid, err := NewRandomFromReader(r)
|
||||
if err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
makeV7(uuid[:])
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// makeV7 fill 48 bits time (uuid[0] - uuid[5]), set version b0111 (uuid[6])
|
||||
// uuid[8] already has the right version number (Variant is 10)
|
||||
// see function NewV7 and NewV7FromReader
|
||||
func makeV7(uuid []byte) {
|
||||
/*
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| unix_ts_ms |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| unix_ts_ms | ver | rand_a (12 bit seq) |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|var| rand_b |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| rand_b |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*/
|
||||
_ = uuid[15] // bounds check
|
||||
|
||||
t, s := getV7Time()
|
||||
|
||||
uuid[0] = byte(t >> 40)
|
||||
uuid[1] = byte(t >> 32)
|
||||
uuid[2] = byte(t >> 24)
|
||||
uuid[3] = byte(t >> 16)
|
||||
uuid[4] = byte(t >> 8)
|
||||
uuid[5] = byte(t)
|
||||
|
||||
uuid[6] = 0x70 | (0x0F & byte(s>>8))
|
||||
uuid[7] = byte(s)
|
||||
}
|
||||
|
||||
// lastV7time is the last time we returned stored as:
|
||||
//
|
||||
// 52 bits of time in milliseconds since epoch
|
||||
// 12 bits of (fractional nanoseconds) >> 8
|
||||
var lastV7time int64
|
||||
|
||||
const nanoPerMilli = 1000000
|
||||
|
||||
// getV7Time returns the time in milliseconds and nanoseconds / 256.
|
||||
// The returned (milli << 12 + seq) is guarenteed to be greater than
|
||||
// (milli << 12 + seq) returned by any previous call to getV7Time.
|
||||
func getV7Time() (milli, seq int64) {
|
||||
timeMu.Lock()
|
||||
defer timeMu.Unlock()
|
||||
|
||||
nano := timeNow().UnixNano()
|
||||
milli = nano / nanoPerMilli
|
||||
// Sequence number is between 0 and 3906 (nanoPerMilli>>8)
|
||||
seq = (nano - milli*nanoPerMilli) >> 8
|
||||
now := milli<<12 + seq
|
||||
if now <= lastV7time {
|
||||
now = lastV7time + 1
|
||||
milli = now >> 12
|
||||
seq = now & 0xfff
|
||||
}
|
||||
lastV7time = now
|
||||
return milli, seq
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 - Jinzhu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
# Inflection
|
||||
|
||||
Inflection pluralizes and singularizes English nouns
|
||||
|
||||
[](https://app.wercker.com/project/byKey/f8c7432b097d1f4ce636879670be0930)
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```go
|
||||
inflection.Plural("person") => "people"
|
||||
inflection.Plural("Person") => "People"
|
||||
inflection.Plural("PERSON") => "PEOPLE"
|
||||
inflection.Plural("bus") => "buses"
|
||||
inflection.Plural("BUS") => "BUSES"
|
||||
inflection.Plural("Bus") => "Buses"
|
||||
|
||||
inflection.Singular("people") => "person"
|
||||
inflection.Singular("People") => "Person"
|
||||
inflection.Singular("PEOPLE") => "PERSON"
|
||||
inflection.Singular("buses") => "bus"
|
||||
inflection.Singular("BUSES") => "BUS"
|
||||
inflection.Singular("Buses") => "Bus"
|
||||
|
||||
inflection.Plural("FancyPerson") => "FancyPeople"
|
||||
inflection.Singular("FancyPeople") => "FancyPerson"
|
||||
```
|
||||
|
||||
## Register Rules
|
||||
|
||||
Standard rules are from Rails's ActiveSupport (https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflections.rb)
|
||||
|
||||
If you want to register more rules, follow:
|
||||
|
||||
```
|
||||
inflection.AddUncountable("fish")
|
||||
inflection.AddIrregular("person", "people")
|
||||
inflection.AddPlural("(bu)s$", "${1}ses") # "bus" => "buses" / "BUS" => "BUSES" / "Bus" => "Buses"
|
||||
inflection.AddSingular("(bus)(es)?$", "${1}") # "buses" => "bus" / "Buses" => "Bus" / "BUSES" => "BUS"
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
You can help to make the project better, check out [http://gorm.io/contribute.html](http://gorm.io/contribute.html) for things you can do.
|
||||
|
||||
## Author
|
||||
|
||||
**jinzhu**
|
||||
|
||||
* <http://github.com/jinzhu>
|
||||
* <wosmvp@gmail.com>
|
||||
* <http://twitter.com/zhangjinzhu>
|
||||
|
||||
## License
|
||||
|
||||
Released under the [MIT License](http://www.opensource.org/licenses/MIT).
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
Package inflection pluralizes and singularizes English nouns.
|
||||
|
||||
inflection.Plural("person") => "people"
|
||||
inflection.Plural("Person") => "People"
|
||||
inflection.Plural("PERSON") => "PEOPLE"
|
||||
|
||||
inflection.Singular("people") => "person"
|
||||
inflection.Singular("People") => "Person"
|
||||
inflection.Singular("PEOPLE") => "PERSON"
|
||||
|
||||
inflection.Plural("FancyPerson") => "FancydPeople"
|
||||
inflection.Singular("FancyPeople") => "FancydPerson"
|
||||
|
||||
Standard rules are from Rails's ActiveSupport (https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflections.rb)
|
||||
|
||||
If you want to register more rules, follow:
|
||||
|
||||
inflection.AddUncountable("fish")
|
||||
inflection.AddIrregular("person", "people")
|
||||
inflection.AddPlural("(bu)s$", "${1}ses") # "bus" => "buses" / "BUS" => "BUSES" / "Bus" => "Buses"
|
||||
inflection.AddSingular("(bus)(es)?$", "${1}") # "buses" => "bus" / "Buses" => "Bus" / "BUSES" => "BUS"
|
||||
*/
|
||||
package inflection
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type inflection struct {
|
||||
regexp *regexp.Regexp
|
||||
replace string
|
||||
}
|
||||
|
||||
// Regular is a regexp find replace inflection
|
||||
type Regular struct {
|
||||
find string
|
||||
replace string
|
||||
}
|
||||
|
||||
// Irregular is a hard replace inflection,
|
||||
// containing both singular and plural forms
|
||||
type Irregular struct {
|
||||
singular string
|
||||
plural string
|
||||
}
|
||||
|
||||
// RegularSlice is a slice of Regular inflections
|
||||
type RegularSlice []Regular
|
||||
|
||||
// IrregularSlice is a slice of Irregular inflections
|
||||
type IrregularSlice []Irregular
|
||||
|
||||
var pluralInflections = RegularSlice{
|
||||
{"([a-z])$", "${1}s"},
|
||||
{"s$", "s"},
|
||||
{"^(ax|test)is$", "${1}es"},
|
||||
{"(octop|vir)us$", "${1}i"},
|
||||
{"(octop|vir)i$", "${1}i"},
|
||||
{"(alias|status)$", "${1}es"},
|
||||
{"(bu)s$", "${1}ses"},
|
||||
{"(buffal|tomat)o$", "${1}oes"},
|
||||
{"([ti])um$", "${1}a"},
|
||||
{"([ti])a$", "${1}a"},
|
||||
{"sis$", "ses"},
|
||||
{"(?:([^f])fe|([lr])f)$", "${1}${2}ves"},
|
||||
{"(hive)$", "${1}s"},
|
||||
{"([^aeiouy]|qu)y$", "${1}ies"},
|
||||
{"(x|ch|ss|sh)$", "${1}es"},
|
||||
{"(matr|vert|ind)(?:ix|ex)$", "${1}ices"},
|
||||
{"^(m|l)ouse$", "${1}ice"},
|
||||
{"^(m|l)ice$", "${1}ice"},
|
||||
{"^(ox)$", "${1}en"},
|
||||
{"^(oxen)$", "${1}"},
|
||||
{"(quiz)$", "${1}zes"},
|
||||
}
|
||||
|
||||
var singularInflections = RegularSlice{
|
||||
{"s$", ""},
|
||||
{"(ss)$", "${1}"},
|
||||
{"(n)ews$", "${1}ews"},
|
||||
{"([ti])a$", "${1}um"},
|
||||
{"((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$", "${1}sis"},
|
||||
{"(^analy)(sis|ses)$", "${1}sis"},
|
||||
{"([^f])ves$", "${1}fe"},
|
||||
{"(hive)s$", "${1}"},
|
||||
{"(tive)s$", "${1}"},
|
||||
{"([lr])ves$", "${1}f"},
|
||||
{"([^aeiouy]|qu)ies$", "${1}y"},
|
||||
{"(s)eries$", "${1}eries"},
|
||||
{"(m)ovies$", "${1}ovie"},
|
||||
{"(c)ookies$", "${1}ookie"},
|
||||
{"(x|ch|ss|sh)es$", "${1}"},
|
||||
{"^(m|l)ice$", "${1}ouse"},
|
||||
{"(bus)(es)?$", "${1}"},
|
||||
{"(o)es$", "${1}"},
|
||||
{"(shoe)s$", "${1}"},
|
||||
{"(cris|test)(is|es)$", "${1}is"},
|
||||
{"^(a)x[ie]s$", "${1}xis"},
|
||||
{"(octop|vir)(us|i)$", "${1}us"},
|
||||
{"(alias|status)(es)?$", "${1}"},
|
||||
{"^(ox)en", "${1}"},
|
||||
{"(vert|ind)ices$", "${1}ex"},
|
||||
{"(matr)ices$", "${1}ix"},
|
||||
{"(quiz)zes$", "${1}"},
|
||||
{"(database)s$", "${1}"},
|
||||
}
|
||||
|
||||
var irregularInflections = IrregularSlice{
|
||||
{"person", "people"},
|
||||
{"man", "men"},
|
||||
{"child", "children"},
|
||||
{"sex", "sexes"},
|
||||
{"move", "moves"},
|
||||
{"mombie", "mombies"},
|
||||
}
|
||||
|
||||
var uncountableInflections = []string{"equipment", "information", "rice", "money", "species", "series", "fish", "sheep", "jeans", "police"}
|
||||
|
||||
var compiledPluralMaps []inflection
|
||||
var compiledSingularMaps []inflection
|
||||
|
||||
func compile() {
|
||||
compiledPluralMaps = []inflection{}
|
||||
compiledSingularMaps = []inflection{}
|
||||
for _, uncountable := range uncountableInflections {
|
||||
inf := inflection{
|
||||
regexp: regexp.MustCompile("^(?i)(" + uncountable + ")$"),
|
||||
replace: "${1}",
|
||||
}
|
||||
compiledPluralMaps = append(compiledPluralMaps, inf)
|
||||
compiledSingularMaps = append(compiledSingularMaps, inf)
|
||||
}
|
||||
|
||||
for _, value := range irregularInflections {
|
||||
infs := []inflection{
|
||||
inflection{regexp: regexp.MustCompile(strings.ToUpper(value.singular) + "$"), replace: strings.ToUpper(value.plural)},
|
||||
inflection{regexp: regexp.MustCompile(strings.Title(value.singular) + "$"), replace: strings.Title(value.plural)},
|
||||
inflection{regexp: regexp.MustCompile(value.singular + "$"), replace: value.plural},
|
||||
}
|
||||
compiledPluralMaps = append(compiledPluralMaps, infs...)
|
||||
}
|
||||
|
||||
for _, value := range irregularInflections {
|
||||
infs := []inflection{
|
||||
inflection{regexp: regexp.MustCompile(strings.ToUpper(value.plural) + "$"), replace: strings.ToUpper(value.singular)},
|
||||
inflection{regexp: regexp.MustCompile(strings.Title(value.plural) + "$"), replace: strings.Title(value.singular)},
|
||||
inflection{regexp: regexp.MustCompile(value.plural + "$"), replace: value.singular},
|
||||
}
|
||||
compiledSingularMaps = append(compiledSingularMaps, infs...)
|
||||
}
|
||||
|
||||
for i := len(pluralInflections) - 1; i >= 0; i-- {
|
||||
value := pluralInflections[i]
|
||||
infs := []inflection{
|
||||
inflection{regexp: regexp.MustCompile(strings.ToUpper(value.find)), replace: strings.ToUpper(value.replace)},
|
||||
inflection{regexp: regexp.MustCompile(value.find), replace: value.replace},
|
||||
inflection{regexp: regexp.MustCompile("(?i)" + value.find), replace: value.replace},
|
||||
}
|
||||
compiledPluralMaps = append(compiledPluralMaps, infs...)
|
||||
}
|
||||
|
||||
for i := len(singularInflections) - 1; i >= 0; i-- {
|
||||
value := singularInflections[i]
|
||||
infs := []inflection{
|
||||
inflection{regexp: regexp.MustCompile(strings.ToUpper(value.find)), replace: strings.ToUpper(value.replace)},
|
||||
inflection{regexp: regexp.MustCompile(value.find), replace: value.replace},
|
||||
inflection{regexp: regexp.MustCompile("(?i)" + value.find), replace: value.replace},
|
||||
}
|
||||
compiledSingularMaps = append(compiledSingularMaps, infs...)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
compile()
|
||||
}
|
||||
|
||||
// AddPlural adds a plural inflection
|
||||
func AddPlural(find, replace string) {
|
||||
pluralInflections = append(pluralInflections, Regular{find, replace})
|
||||
compile()
|
||||
}
|
||||
|
||||
// AddSingular adds a singular inflection
|
||||
func AddSingular(find, replace string) {
|
||||
singularInflections = append(singularInflections, Regular{find, replace})
|
||||
compile()
|
||||
}
|
||||
|
||||
// AddIrregular adds an irregular inflection
|
||||
func AddIrregular(singular, plural string) {
|
||||
irregularInflections = append(irregularInflections, Irregular{singular, plural})
|
||||
compile()
|
||||
}
|
||||
|
||||
// AddUncountable adds an uncountable inflection
|
||||
func AddUncountable(values ...string) {
|
||||
uncountableInflections = append(uncountableInflections, values...)
|
||||
compile()
|
||||
}
|
||||
|
||||
// GetPlural retrieves the plural inflection values
|
||||
func GetPlural() RegularSlice {
|
||||
plurals := make(RegularSlice, len(pluralInflections))
|
||||
copy(plurals, pluralInflections)
|
||||
return plurals
|
||||
}
|
||||
|
||||
// GetSingular retrieves the singular inflection values
|
||||
func GetSingular() RegularSlice {
|
||||
singulars := make(RegularSlice, len(singularInflections))
|
||||
copy(singulars, singularInflections)
|
||||
return singulars
|
||||
}
|
||||
|
||||
// GetIrregular retrieves the irregular inflection values
|
||||
func GetIrregular() IrregularSlice {
|
||||
irregular := make(IrregularSlice, len(irregularInflections))
|
||||
copy(irregular, irregularInflections)
|
||||
return irregular
|
||||
}
|
||||
|
||||
// GetUncountable retrieves the uncountable inflection values
|
||||
func GetUncountable() []string {
|
||||
uncountables := make([]string, len(uncountableInflections))
|
||||
copy(uncountables, uncountableInflections)
|
||||
return uncountables
|
||||
}
|
||||
|
||||
// SetPlural sets the plural inflections slice
|
||||
func SetPlural(inflections RegularSlice) {
|
||||
pluralInflections = inflections
|
||||
compile()
|
||||
}
|
||||
|
||||
// SetSingular sets the singular inflections slice
|
||||
func SetSingular(inflections RegularSlice) {
|
||||
singularInflections = inflections
|
||||
compile()
|
||||
}
|
||||
|
||||
// SetIrregular sets the irregular inflections slice
|
||||
func SetIrregular(inflections IrregularSlice) {
|
||||
irregularInflections = inflections
|
||||
compile()
|
||||
}
|
||||
|
||||
// SetUncountable sets the uncountable inflections slice
|
||||
func SetUncountable(inflections []string) {
|
||||
uncountableInflections = inflections
|
||||
compile()
|
||||
}
|
||||
|
||||
// Plural converts a word to its plural form
|
||||
func Plural(str string) string {
|
||||
for _, inflection := range compiledPluralMaps {
|
||||
if inflection.regexp.MatchString(str) {
|
||||
return inflection.regexp.ReplaceAllString(str, inflection.replace)
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// Singular converts a word to its singular form
|
||||
func Singular(str string) string {
|
||||
for _, inflection := range compiledSingularMaps {
|
||||
if inflection.regexp.MatchString(str) {
|
||||
return inflection.regexp.ReplaceAllString(str, inflection.replace)
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
box: golang
|
||||
|
||||
build:
|
||||
steps:
|
||||
- setup-go-workspace
|
||||
|
||||
# Gets the dependencies
|
||||
- script:
|
||||
name: go get
|
||||
code: |
|
||||
go get
|
||||
|
||||
# Build the project
|
||||
- script:
|
||||
name: go build
|
||||
code: |
|
||||
go build ./...
|
||||
|
||||
# Test the project
|
||||
- script:
|
||||
name: go test
|
||||
code: |
|
||||
go test ./...
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
guard 'gotest' do
|
||||
watch(%r{\.go$})
|
||||
end
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-NOW Jinzhu <wosmvp@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
## Now
|
||||
|
||||
Now is a time toolkit for golang
|
||||
|
||||
[](https://goreportcard.com/report/github.com/jinzhu/now)
|
||||
[](https://github.com/jinzhu/now/actions)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
go get -u github.com/jinzhu/now
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Calculating time based on current time
|
||||
|
||||
```go
|
||||
import "github.com/jinzhu/now"
|
||||
|
||||
time.Now() // 2013-11-18 17:51:49.123456789 Mon
|
||||
|
||||
now.BeginningOfMinute() // 2013-11-18 17:51:00 Mon
|
||||
now.BeginningOfHour() // 2013-11-18 17:00:00 Mon
|
||||
now.BeginningOfDay() // 2013-11-18 00:00:00 Mon
|
||||
now.BeginningOfWeek() // 2013-11-17 00:00:00 Sun
|
||||
now.BeginningOfMonth() // 2013-11-01 00:00:00 Fri
|
||||
now.BeginningOfQuarter() // 2013-10-01 00:00:00 Tue
|
||||
now.BeginningOfYear() // 2013-01-01 00:00:00 Tue
|
||||
|
||||
now.EndOfMinute() // 2013-11-18 17:51:59.999999999 Mon
|
||||
now.EndOfHour() // 2013-11-18 17:59:59.999999999 Mon
|
||||
now.EndOfDay() // 2013-11-18 23:59:59.999999999 Mon
|
||||
now.EndOfWeek() // 2013-11-23 23:59:59.999999999 Sat
|
||||
now.EndOfMonth() // 2013-11-30 23:59:59.999999999 Sat
|
||||
now.EndOfQuarter() // 2013-12-31 23:59:59.999999999 Tue
|
||||
now.EndOfYear() // 2013-12-31 23:59:59.999999999 Tue
|
||||
|
||||
now.WeekStartDay = time.Monday // Set Monday as first day, default is Sunday
|
||||
now.EndOfWeek() // 2013-11-24 23:59:59.999999999 Sun
|
||||
```
|
||||
|
||||
Calculating time based on another time
|
||||
|
||||
```go
|
||||
t := time.Date(2013, 02, 18, 17, 51, 49, 123456789, time.Now().Location())
|
||||
now.With(t).EndOfMonth() // 2013-02-28 23:59:59.999999999 Thu
|
||||
```
|
||||
|
||||
Calculating time based on configuration
|
||||
|
||||
```go
|
||||
location, err := time.LoadLocation("Asia/Shanghai")
|
||||
|
||||
myConfig := &now.Config{
|
||||
WeekStartDay: time.Monday,
|
||||
TimeLocation: location,
|
||||
TimeFormats: []string{"2006-01-02 15:04:05"},
|
||||
}
|
||||
|
||||
t := time.Date(2013, 11, 18, 17, 51, 49, 123456789, time.Now().Location()) // // 2013-11-18 17:51:49.123456789 Mon
|
||||
myConfig.With(t).BeginningOfWeek() // 2013-11-18 00:00:00 Mon
|
||||
|
||||
myConfig.Parse("2002-10-12 22:14:01") // 2002-10-12 22:14:01
|
||||
myConfig.Parse("2002-10-12 22:14") // returns error 'can't parse string as time: 2002-10-12 22:14'
|
||||
```
|
||||
|
||||
### Monday/Sunday
|
||||
|
||||
Don't be bothered with the `WeekStartDay` setting, you can use `Monday`, `Sunday`
|
||||
|
||||
```go
|
||||
now.Monday() // 2013-11-18 00:00:00 Mon
|
||||
now.Monday("17:44") // 2013-11-18 17:44:00 Mon
|
||||
now.Sunday() // 2013-11-24 00:00:00 Sun (Next Sunday)
|
||||
now.Sunday("18:19:24") // 2013-11-24 18:19:24 Sun (Next Sunday)
|
||||
now.EndOfSunday() // 2013-11-24 23:59:59.999999999 Sun (End of next Sunday)
|
||||
|
||||
t := time.Date(2013, 11, 24, 17, 51, 49, 123456789, time.Now().Location()) // 2013-11-24 17:51:49.123456789 Sun
|
||||
now.With(t).Monday() // 2013-11-18 00:00:00 Mon (Last Monday if today is Sunday)
|
||||
now.With(t).Monday("17:44") // 2013-11-18 17:44:00 Mon (Last Monday if today is Sunday)
|
||||
now.With(t).Sunday() // 2013-11-24 00:00:00 Sun (Beginning Of Today if today is Sunday)
|
||||
now.With(t).Sunday("18:19:24") // 2013-11-24 18:19:24 Sun (Beginning Of Today if today is Sunday)
|
||||
now.With(t).EndOfSunday() // 2013-11-24 23:59:59.999999999 Sun (End of Today if today is Sunday)
|
||||
```
|
||||
|
||||
### Parse String to Time
|
||||
|
||||
```go
|
||||
time.Now() // 2013-11-18 17:51:49.123456789 Mon
|
||||
|
||||
// Parse(string) (time.Time, error)
|
||||
t, err := now.Parse("2017") // 2017-01-01 00:00:00, nil
|
||||
t, err := now.Parse("2017-10") // 2017-10-01 00:00:00, nil
|
||||
t, err := now.Parse("2017-10-13") // 2017-10-13 00:00:00, nil
|
||||
t, err := now.Parse("1999-12-12 12") // 1999-12-12 12:00:00, nil
|
||||
t, err := now.Parse("1999-12-12 12:20") // 1999-12-12 12:20:00, nil
|
||||
t, err := now.Parse("1999-12-12 12:20:21") // 1999-12-12 12:20:21, nil
|
||||
t, err := now.Parse("10-13") // 2013-10-13 00:00:00, nil
|
||||
t, err := now.Parse("12:20") // 2013-11-18 12:20:00, nil
|
||||
t, err := now.Parse("12:20:13") // 2013-11-18 12:20:13, nil
|
||||
t, err := now.Parse("14") // 2013-11-18 14:00:00, nil
|
||||
t, err := now.Parse("99:99") // 2013-11-18 12:20:00, Can't parse string as time: 99:99
|
||||
|
||||
// MustParse must parse string to time or it will panic
|
||||
now.MustParse("2013-01-13") // 2013-01-13 00:00:00
|
||||
now.MustParse("02-17") // 2013-02-17 00:00:00
|
||||
now.MustParse("2-17") // 2013-02-17 00:00:00
|
||||
now.MustParse("8") // 2013-11-18 08:00:00
|
||||
now.MustParse("2002-10-12 22:14") // 2002-10-12 22:14:00
|
||||
now.MustParse("99:99") // panic: Can't parse string as time: 99:99
|
||||
```
|
||||
|
||||
Extend `now` to support more formats is quite easy, just update `now.TimeFormats` with other time layouts, e.g:
|
||||
|
||||
```go
|
||||
now.TimeFormats = append(now.TimeFormats, "02 Jan 2006 15:04")
|
||||
```
|
||||
|
||||
Please send me pull requests if you want a format to be supported officially
|
||||
|
||||
## Contributing
|
||||
|
||||
You can help to make the project better, check out [http://gorm.io/contribute.html](http://gorm.io/contribute.html) for things you can do.
|
||||
|
||||
# Author
|
||||
|
||||
**jinzhu**
|
||||
|
||||
* <http://github.com/jinzhu>
|
||||
* <wosmvp@gmail.com>
|
||||
* <http://twitter.com/zhangjinzhu>
|
||||
|
||||
## License
|
||||
|
||||
Released under the [MIT License](http://www.opensource.org/licenses/MIT).
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
// Package now is a time toolkit for golang.
|
||||
//
|
||||
// More details README here: https://github.com/jinzhu/now
|
||||
//
|
||||
// import "github.com/jinzhu/now"
|
||||
//
|
||||
// now.BeginningOfMinute() // 2013-11-18 17:51:00 Mon
|
||||
// now.BeginningOfDay() // 2013-11-18 00:00:00 Mon
|
||||
// now.EndOfDay() // 2013-11-18 23:59:59.999999999 Mon
|
||||
package now
|
||||
|
||||
import "time"
|
||||
|
||||
// WeekStartDay set week start day, default is sunday
|
||||
var WeekStartDay = time.Sunday
|
||||
|
||||
// TimeFormats default time formats will be parsed as
|
||||
var TimeFormats = []string{
|
||||
"2006", "2006-1", "2006-1-2", "2006-1-2 15", "2006-1-2 15:4", "2006-1-2 15:4:5", "1-2",
|
||||
"15:4:5", "15:4", "15",
|
||||
"15:4:5 Jan 2, 2006 MST", "2006-01-02 15:04:05.999999999 -0700 MST", "2006-01-02T15:04:05Z0700", "2006-01-02T15:04:05Z07",
|
||||
"2006.1.2", "2006.1.2 15:04:05", "2006.01.02", "2006.01.02 15:04:05", "2006.01.02 15:04:05.999999999",
|
||||
"1/2/2006", "1/2/2006 15:4:5", "2006/01/02", "20060102", "2006/01/02 15:04:05",
|
||||
time.ANSIC, time.UnixDate, time.RubyDate, time.RFC822, time.RFC822Z, time.RFC850,
|
||||
time.RFC1123, time.RFC1123Z, time.RFC3339, time.RFC3339Nano,
|
||||
time.Kitchen, time.Stamp, time.StampMilli, time.StampMicro, time.StampNano,
|
||||
}
|
||||
|
||||
// Config configuration for now package
|
||||
type Config struct {
|
||||
WeekStartDay time.Weekday
|
||||
TimeLocation *time.Location
|
||||
TimeFormats []string
|
||||
}
|
||||
|
||||
// DefaultConfig default config
|
||||
var DefaultConfig *Config
|
||||
|
||||
// New initialize Now based on configuration
|
||||
func (config *Config) With(t time.Time) *Now {
|
||||
return &Now{Time: t, Config: config}
|
||||
}
|
||||
|
||||
// Parse parse string to time based on configuration
|
||||
func (config *Config) Parse(strs ...string) (time.Time, error) {
|
||||
if config.TimeLocation == nil {
|
||||
return config.With(time.Now()).Parse(strs...)
|
||||
} else {
|
||||
return config.With(time.Now().In(config.TimeLocation)).Parse(strs...)
|
||||
}
|
||||
}
|
||||
|
||||
// MustParse must parse string to time or will panic
|
||||
func (config *Config) MustParse(strs ...string) time.Time {
|
||||
if config.TimeLocation == nil {
|
||||
return config.With(time.Now()).MustParse(strs...)
|
||||
} else {
|
||||
return config.With(time.Now().In(config.TimeLocation)).MustParse(strs...)
|
||||
}
|
||||
}
|
||||
|
||||
// Now now struct
|
||||
type Now struct {
|
||||
time.Time
|
||||
*Config
|
||||
}
|
||||
|
||||
// With initialize Now with time
|
||||
func With(t time.Time) *Now {
|
||||
config := DefaultConfig
|
||||
if config == nil {
|
||||
config = &Config{
|
||||
WeekStartDay: WeekStartDay,
|
||||
TimeFormats: TimeFormats,
|
||||
}
|
||||
}
|
||||
|
||||
return &Now{Time: t, Config: config}
|
||||
}
|
||||
|
||||
// New initialize Now with time
|
||||
func New(t time.Time) *Now {
|
||||
return With(t)
|
||||
}
|
||||
|
||||
// BeginningOfMinute beginning of minute
|
||||
func BeginningOfMinute() time.Time {
|
||||
return With(time.Now()).BeginningOfMinute()
|
||||
}
|
||||
|
||||
// BeginningOfHour beginning of hour
|
||||
func BeginningOfHour() time.Time {
|
||||
return With(time.Now()).BeginningOfHour()
|
||||
}
|
||||
|
||||
// BeginningOfDay beginning of day
|
||||
func BeginningOfDay() time.Time {
|
||||
return With(time.Now()).BeginningOfDay()
|
||||
}
|
||||
|
||||
// BeginningOfWeek beginning of week
|
||||
func BeginningOfWeek() time.Time {
|
||||
return With(time.Now()).BeginningOfWeek()
|
||||
}
|
||||
|
||||
// BeginningOfMonth beginning of month
|
||||
func BeginningOfMonth() time.Time {
|
||||
return With(time.Now()).BeginningOfMonth()
|
||||
}
|
||||
|
||||
// BeginningOfQuarter beginning of quarter
|
||||
func BeginningOfQuarter() time.Time {
|
||||
return With(time.Now()).BeginningOfQuarter()
|
||||
}
|
||||
|
||||
// BeginningOfYear beginning of year
|
||||
func BeginningOfYear() time.Time {
|
||||
return With(time.Now()).BeginningOfYear()
|
||||
}
|
||||
|
||||
// EndOfMinute end of minute
|
||||
func EndOfMinute() time.Time {
|
||||
return With(time.Now()).EndOfMinute()
|
||||
}
|
||||
|
||||
// EndOfHour end of hour
|
||||
func EndOfHour() time.Time {
|
||||
return With(time.Now()).EndOfHour()
|
||||
}
|
||||
|
||||
// EndOfDay end of day
|
||||
func EndOfDay() time.Time {
|
||||
return With(time.Now()).EndOfDay()
|
||||
}
|
||||
|
||||
// EndOfWeek end of week
|
||||
func EndOfWeek() time.Time {
|
||||
return With(time.Now()).EndOfWeek()
|
||||
}
|
||||
|
||||
// EndOfMonth end of month
|
||||
func EndOfMonth() time.Time {
|
||||
return With(time.Now()).EndOfMonth()
|
||||
}
|
||||
|
||||
// EndOfQuarter end of quarter
|
||||
func EndOfQuarter() time.Time {
|
||||
return With(time.Now()).EndOfQuarter()
|
||||
}
|
||||
|
||||
// EndOfYear end of year
|
||||
func EndOfYear() time.Time {
|
||||
return With(time.Now()).EndOfYear()
|
||||
}
|
||||
|
||||
// Monday monday
|
||||
|
||||
func Monday(strs ...string) time.Time {
|
||||
return With(time.Now()).Monday(strs...)
|
||||
}
|
||||
|
||||
// Sunday sunday
|
||||
func Sunday(strs ...string) time.Time {
|
||||
return With(time.Now()).Sunday(strs...)
|
||||
}
|
||||
|
||||
// EndOfSunday end of sunday
|
||||
func EndOfSunday() time.Time {
|
||||
return With(time.Now()).EndOfSunday()
|
||||
}
|
||||
|
||||
// Quarter returns the yearly quarter
|
||||
func Quarter() uint {
|
||||
return With(time.Now()).Quarter()
|
||||
}
|
||||
|
||||
// Parse parse string to time
|
||||
func Parse(strs ...string) (time.Time, error) {
|
||||
return With(time.Now()).Parse(strs...)
|
||||
}
|
||||
|
||||
// ParseInLocation parse string to time in location
|
||||
func ParseInLocation(loc *time.Location, strs ...string) (time.Time, error) {
|
||||
return With(time.Now().In(loc)).Parse(strs...)
|
||||
}
|
||||
|
||||
// MustParse must parse string to time or will panic
|
||||
func MustParse(strs ...string) time.Time {
|
||||
return With(time.Now()).MustParse(strs...)
|
||||
}
|
||||
|
||||
// MustParseInLocation must parse string to time in location or will panic
|
||||
func MustParseInLocation(loc *time.Location, strs ...string) time.Time {
|
||||
return With(time.Now().In(loc)).MustParse(strs...)
|
||||
}
|
||||
|
||||
// Between check now between the begin, end time or not
|
||||
func Between(time1, time2 string) bool {
|
||||
return With(time.Now()).Between(time1, time2)
|
||||
}
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
package now
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BeginningOfMinute beginning of minute
|
||||
func (now *Now) BeginningOfMinute() time.Time {
|
||||
return now.Truncate(time.Minute)
|
||||
}
|
||||
|
||||
// BeginningOfHour beginning of hour
|
||||
func (now *Now) BeginningOfHour() time.Time {
|
||||
y, m, d := now.Date()
|
||||
return time.Date(y, m, d, now.Time.Hour(), 0, 0, 0, now.Time.Location())
|
||||
}
|
||||
|
||||
// BeginningOfDay beginning of day
|
||||
func (now *Now) BeginningOfDay() time.Time {
|
||||
y, m, d := now.Date()
|
||||
return time.Date(y, m, d, 0, 0, 0, 0, now.Time.Location())
|
||||
}
|
||||
|
||||
// BeginningOfWeek beginning of week
|
||||
func (now *Now) BeginningOfWeek() time.Time {
|
||||
t := now.BeginningOfDay()
|
||||
weekday := int(t.Weekday())
|
||||
|
||||
if now.WeekStartDay != time.Sunday {
|
||||
weekStartDayInt := int(now.WeekStartDay)
|
||||
|
||||
if weekday < weekStartDayInt {
|
||||
weekday = weekday + 7 - weekStartDayInt
|
||||
} else {
|
||||
weekday = weekday - weekStartDayInt
|
||||
}
|
||||
}
|
||||
return t.AddDate(0, 0, -weekday)
|
||||
}
|
||||
|
||||
// BeginningOfMonth beginning of month
|
||||
func (now *Now) BeginningOfMonth() time.Time {
|
||||
y, m, _ := now.Date()
|
||||
return time.Date(y, m, 1, 0, 0, 0, 0, now.Location())
|
||||
}
|
||||
|
||||
// BeginningOfQuarter beginning of quarter
|
||||
func (now *Now) BeginningOfQuarter() time.Time {
|
||||
month := now.BeginningOfMonth()
|
||||
offset := (int(month.Month()) - 1) % 3
|
||||
return month.AddDate(0, -offset, 0)
|
||||
}
|
||||
|
||||
// BeginningOfHalf beginning of half year
|
||||
func (now *Now) BeginningOfHalf() time.Time {
|
||||
month := now.BeginningOfMonth()
|
||||
offset := (int(month.Month()) - 1) % 6
|
||||
return month.AddDate(0, -offset, 0)
|
||||
}
|
||||
|
||||
// BeginningOfYear BeginningOfYear beginning of year
|
||||
func (now *Now) BeginningOfYear() time.Time {
|
||||
y, _, _ := now.Date()
|
||||
return time.Date(y, time.January, 1, 0, 0, 0, 0, now.Location())
|
||||
}
|
||||
|
||||
// EndOfMinute end of minute
|
||||
func (now *Now) EndOfMinute() time.Time {
|
||||
return now.BeginningOfMinute().Add(time.Minute - time.Nanosecond)
|
||||
}
|
||||
|
||||
// EndOfHour end of hour
|
||||
func (now *Now) EndOfHour() time.Time {
|
||||
return now.BeginningOfHour().Add(time.Hour - time.Nanosecond)
|
||||
}
|
||||
|
||||
// EndOfDay end of day
|
||||
func (now *Now) EndOfDay() time.Time {
|
||||
y, m, d := now.Date()
|
||||
return time.Date(y, m, d, 23, 59, 59, int(time.Second-time.Nanosecond), now.Location())
|
||||
}
|
||||
|
||||
// EndOfWeek end of week
|
||||
func (now *Now) EndOfWeek() time.Time {
|
||||
return now.BeginningOfWeek().AddDate(0, 0, 7).Add(-time.Nanosecond)
|
||||
}
|
||||
|
||||
// EndOfMonth end of month
|
||||
func (now *Now) EndOfMonth() time.Time {
|
||||
return now.BeginningOfMonth().AddDate(0, 1, 0).Add(-time.Nanosecond)
|
||||
}
|
||||
|
||||
// EndOfQuarter end of quarter
|
||||
func (now *Now) EndOfQuarter() time.Time {
|
||||
return now.BeginningOfQuarter().AddDate(0, 3, 0).Add(-time.Nanosecond)
|
||||
}
|
||||
|
||||
// EndOfHalf end of half year
|
||||
func (now *Now) EndOfHalf() time.Time {
|
||||
return now.BeginningOfHalf().AddDate(0, 6, 0).Add(-time.Nanosecond)
|
||||
}
|
||||
|
||||
// EndOfYear end of year
|
||||
func (now *Now) EndOfYear() time.Time {
|
||||
return now.BeginningOfYear().AddDate(1, 0, 0).Add(-time.Nanosecond)
|
||||
}
|
||||
|
||||
// Monday monday
|
||||
/*
|
||||
func (now *Now) Monday() time.Time {
|
||||
t := now.BeginningOfDay()
|
||||
weekday := int(t.Weekday())
|
||||
if weekday == 0 {
|
||||
weekday = 7
|
||||
}
|
||||
return t.AddDate(0, 0, -weekday+1)
|
||||
}
|
||||
*/
|
||||
|
||||
func (now *Now) Monday(strs ...string) time.Time {
|
||||
var parseTime time.Time
|
||||
var err error
|
||||
if len(strs) > 0 {
|
||||
parseTime, err = now.Parse(strs...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
parseTime = now.BeginningOfDay()
|
||||
}
|
||||
weekday := int(parseTime.Weekday())
|
||||
if weekday == 0 {
|
||||
weekday = 7
|
||||
}
|
||||
return parseTime.AddDate(0, 0, -weekday+1)
|
||||
}
|
||||
|
||||
func (now *Now) Sunday(strs ...string) time.Time {
|
||||
var parseTime time.Time
|
||||
var err error
|
||||
if len(strs) > 0 {
|
||||
parseTime, err = now.Parse(strs...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
parseTime = now.BeginningOfDay()
|
||||
}
|
||||
weekday := int(parseTime.Weekday())
|
||||
if weekday == 0 {
|
||||
weekday = 7
|
||||
}
|
||||
return parseTime.AddDate(0, 0, (7 - weekday))
|
||||
}
|
||||
|
||||
// EndOfSunday end of sunday
|
||||
func (now *Now) EndOfSunday() time.Time {
|
||||
return New(now.Sunday()).EndOfDay()
|
||||
}
|
||||
|
||||
// Quarter returns the yearly quarter
|
||||
func (now *Now) Quarter() uint {
|
||||
return (uint(now.Month())-1)/3 + 1
|
||||
}
|
||||
|
||||
func (now *Now) parseWithFormat(str string, location *time.Location) (t time.Time, err error) {
|
||||
for _, format := range now.TimeFormats {
|
||||
t, err = time.ParseInLocation(format, str, location)
|
||||
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = errors.New("Can't parse string as time: " + str)
|
||||
return
|
||||
}
|
||||
|
||||
var hasTimeRegexp = regexp.MustCompile(`(\s+|^\s*|T)\d{1,2}((:\d{1,2})*|((:\d{1,2}){2}\.(\d{3}|\d{6}|\d{9})))(\s*$|[Z+-])`) // match 15:04:05, 15:04:05.000, 15:04:05.000000 15, 2017-01-01 15:04, 2021-07-20T00:59:10Z, 2021-07-20T00:59:10+08:00, 2021-07-20T00:00:10-07:00 etc
|
||||
var onlyTimeRegexp = regexp.MustCompile(`^\s*\d{1,2}((:\d{1,2})*|((:\d{1,2}){2}\.(\d{3}|\d{6}|\d{9})))\s*$`) // match 15:04:05, 15, 15:04:05.000, 15:04:05.000000, etc
|
||||
|
||||
// Parse parse string to time
|
||||
func (now *Now) Parse(strs ...string) (t time.Time, err error) {
|
||||
var (
|
||||
setCurrentTime bool
|
||||
parseTime []int
|
||||
currentLocation = now.Location()
|
||||
onlyTimeInStr = true
|
||||
currentTime = formatTimeToList(now.Time)
|
||||
)
|
||||
|
||||
for _, str := range strs {
|
||||
hasTimeInStr := hasTimeRegexp.MatchString(str) // match 15:04:05, 15
|
||||
onlyTimeInStr = hasTimeInStr && onlyTimeInStr && onlyTimeRegexp.MatchString(str)
|
||||
if t, err = now.parseWithFormat(str, currentLocation); err == nil {
|
||||
location := t.Location()
|
||||
parseTime = formatTimeToList(t)
|
||||
|
||||
for i, v := range parseTime {
|
||||
// Don't reset hour, minute, second if current time str including time
|
||||
if hasTimeInStr && i <= 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
// If value is zero, replace it with current time
|
||||
if v == 0 {
|
||||
if setCurrentTime {
|
||||
parseTime[i] = currentTime[i]
|
||||
}
|
||||
} else {
|
||||
setCurrentTime = true
|
||||
}
|
||||
|
||||
// if current time only includes time, should change day, month to current time
|
||||
if onlyTimeInStr {
|
||||
if i == 4 || i == 5 {
|
||||
parseTime[i] = currentTime[i]
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t = time.Date(parseTime[6], time.Month(parseTime[5]), parseTime[4], parseTime[3], parseTime[2], parseTime[1], parseTime[0], location)
|
||||
currentTime = formatTimeToList(t)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MustParse must parse string to time or it will panic
|
||||
func (now *Now) MustParse(strs ...string) (t time.Time) {
|
||||
t, err := now.Parse(strs...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Between check time between the begin, end time or not
|
||||
func (now *Now) Between(begin, end string) bool {
|
||||
beginTime := now.MustParse(begin)
|
||||
endTime := now.MustParse(end)
|
||||
return now.After(beginTime) && now.Before(endTime)
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package now
|
||||
|
||||
import "time"
|
||||
|
||||
func formatTimeToList(t time.Time) []int {
|
||||
hour, min, sec := t.Clock()
|
||||
year, month, day := t.Date()
|
||||
return []int{t.Nanosecond(), sec, min, hour, day, int(month), year}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
coverage:
|
||||
status:
|
||||
project: off
|
||||
patch: off
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user