Energy Go Framework

repository·main·Indexed 20 days ago

https://github.com/energye/energy

A Go framework for building cross-platform desktop applications using a hybrid approach of native LCL components and web-based rendering engines, including CEF, Webview2, and WebKit2. It supports a Go + Web hybrid architecture with bidirectional IPC and provides tools like Energy Designer for visual project creation.

Tokens
6.1K
Snippets
16
Records
27
Agent score
70%

What's inside Energy

  1. What is Energy: Core Concepts and Rendering Engines

    main

    Energy is a cross-platform desktop application framework for Go built using LCL, CEF, and Webview (Webview2, Webkit2). It offers three distinct rendering modes that can be used independently or in a hybrid architecture:

    1. LCL Native Mode: Uses LCL native UI components. This is lightweight and follows the system's native visual style. It provides over 100+ native GUI components (buttons, tables, tree views, etc.).
    2. Webview Hybrid Mode: Uses system runtimes (WebView2 on Windows, WebKit2 on Linux/macOS). This allows for a Go backend (window management, system calls, file I/O) and a Web frontend (HTML/CSS/JS using Vue, React, Angular, etc.).
    3. CEF Mode: Uses the full Chromium Embedded Framework (CEF3) for complete browser capabilities.

    Key features include:

    • IPC: High-performance, event-driven bidirectional communication between Go and the Web frontend with automatic type conversion.
    • Local Resource Loading: Supports custom protocols to read local files or go:embed resources directly without an HTTP server.
    • NO CGO: Optional pure Go development mode to simplify compilation environments.
  2. How to interact with COM objects in pure Go

    main

    To interact with Windows COM (Component Object Model) objects without using CGO, you must manually bridge the gap between Go and the raw memory layout of COM interfaces. This involves three main steps:

    1. Locate Headers: Download the Windows SDK (via Visual Studio Installer with "Desktop development with C++") to find the necessary .h files. These files contain the VTable (Virtual Method Table) definitions you need to replicate.
    2. Define VTables in Go: Create Go structs that match the memory layout of the C VTables. A COM object is structured as a parent struct where the first field is a pointer to a VTable struct.
    3. Invoke Methods: Use the syscall package, specifically syscall.SyscallN, to call the methods via their uintptr addresses in the VTable.
    type Object struct {
      lpvtbl *ObjectVtbl
    }
    type ObjectVtbl struct {
      MethodOne uintptr
      MethdoTwo uintptr
      //...
    }
    
    func (v *Object) One() error {
      hr, _, _ := syscall.SyscallN(uintptr(v))
      if hr != ole.S_OK {
        return ole.NewError(hr)
      }
      return nil
    }
  3. Compare toast and wintoast packages

    main

    The go-toast repository provides two distinct packages for interacting with Windows toast notifications:

    1. toast: A high-level wrapper designed for ease of use and common notification patterns.
    2. wintoast: A lower-level API providing more direct access to the Windows Runtime COM API features.
  4. How Energy's rendering engines work

    main

    Energy provides three distinct rendering engines that can be used together or independently depending on your application's needs:

    • LCL Native Controls: Lightweight, system-native GUI components (buttons, grids, tree views, etc.) that provide a native look and feel.
    • Webview System Runtime: Uses the operating system's built-in browser engine (WebView2 on Windows, WebKit2 on Linux/macOS). This has no extra dependencies.
    • CEF (Chromium Embedded Framework): A full Chromium browser component for complete browser capabilities.

    Applications typically use a Go + Web hybrid architecture where the Go backend manages windowing, system calls, and file I/O, while the Web frontend (Vue, React, Angular, etc.) handles the UI rendering via high-performance bidirectional IPC.

  5. Report a security vulnerability privately

    main

    If you discover a security vulnerability in the go-ole package, do not disclose it as a public issue. To allow the maintainers time to develop a patch before public exposure, report the vulnerability privately via a security advisory.

    Note that this project is maintained by volunteers on a reasonable-effort basis. Please allow at least 90 days for the team to work on a fix before public disclosure.

  6. Install github.com/go-ole/go-ole

    main

    To use the Go OLE bindings for Windows COM, you can install the package using go get. To verify the installation and experiment with the library, you can run the provided Excel example program.

    Note that this library uses shared libraries instead of cgo to interface with Windows COM.

    go get github.com/go-ole/go-ole
    cd /path/to/go-ole/
    go test
    
    cd /path/to/go-ole/example/excel
    go run excel.go
  7. Set up the Energy development environment

    main

    To develop applications with Energy, you need the following prerequisites:

    1. Golang: Version 1.20 or higher.
    2. Energy development environment: This includes either a [CEF] runtime or a [System Runtime] (Webview2/Webkit2), along with the libenergy runtime.

    You can also use Energy Designer to create projects visually by dragging and dropping components, which then generates maintainable Go source code.

  8. Handle notification actions and inputs with callbacks

    main

    You can create interactive notifications by adding Inputs and Actions to a toast.Notification. To respond to user interactions (like clicking an action button or submitting an input), you must register a global callback using toast.SetActivationCallback before pushing the notification.

    Components

    • Inputs: Text fields or selection menus. Use toast.Input to define these. Selections within an input allow for dropdown-style choices using toast.InputSelection.
    • Actions: Buttons that trigger specific logic. Use toast.Action with a Type (e.g., toast.Foreground) and Arguments to identify the intent.
    • Callback: toast.SetActivationCallback accepts a function with the signature func(args string, data []UserData). The args string contains the action arguments, and data contains the values from any inputs provided.
    // Set the callback that receives the data from the notification.
    // Any data from actions or inputs will be accessible here. 
    toast.SetActivationCallback(func(args string, data []UserData) {
        fmt.Printf("args: %q, data: %v\n", args, data)
    })
    
    n := toast.Notification{
        AppID: "My cool app",
        Title: "Title",
        Body: "Body", 
    }
    
    n.Inputs = append(n.Inputs, toast.Input{
    	ID:          "reply-to:john-doe",
    	Title:       "Reply",
    	Placeholder: "Reply to John Doe",
    })
    
    n.Inputs = append(n.Inputs, toast.Input{
    	ID:          "select-action",
    	Title:       "Selection Action",
    	Placeholder: "Pick an action to perform",
    	Selections: []toast.InputSelection{
    		{
    			ID:      "1",
    			Content: "do thing one",
    		},
    		{
    			ID:      "2",
    			Content: "do thing two",
    		},
    		{
    			ID:      "3",
    			Content: "do thing three",
    		},
    	},
    })
    
    n.Actions = append(n.Actions, toast.Action{
    	Type:      toast.Foreground,
    	Content:   "Send",
    	Arguments: "send",
    })
    
    n.Actions = append(n.Actions, toast.Action{
    	Type:      toast.Foreground,
    	Content:   "Close",
    	Arguments: "close",
    })
    
    err := n.Push()
  9. How to implement a COM object in pure Go (no cgo!)

    main

    Implementing a COM object in Go requires manual memory management to prevent the Go Garbage Collector from interfering with raw memory that Windows expects to be stable.

    Key Requirements:

    • Manual Allocation: Use syscall.NewProc to load GlobalAlloc and GlobalFree from kernel32.dll. Use these to allocate memory for both the object and its VTable.
    • Callbacks: Use syscall.NewCallback to convert a Go function into a C-callable function pointer.
    • Lifecycle Warning: Memory allocated via NewCallback is never released by the OS, and there is a limit of 1024 callbacks. Therefore, you should allocate these callbacks once (e.g., in an init() function or as package globals).
    • Memory Layout: The object must be a struct where the first field (lpvtbl) points to a VTable struct containing uintptr function pointers.
    // Initialize our kernel functions. 
    var (
      kernel32   = windows.NewLazySystemDLL("kernel32.dll")
      procMalloc = kernel32.NewProc("GlobalAlloc")
      procFree   = kernel32.NewProc("GlobalFree")
    )
    
    // malloc allocates raw memory using the Windows kernel.
    func malloc(size uintptr) unsafe.Pointer {
    	hr, _, _ := procMalloc.Call(uintptr(GMEM_FIXED|GMEM_ZEROINIT), uintptr(size))
    	if hr == 0 {
    		return nil
    	}
    	return unsafe.Pointer(hr)
    }
    
    // Object defines our object layout.
    type Object struct {
      lpvtbl *ObjectVtbl 
    }
    
    type ObjectVtbl struct {
      MethodOne   uintptr
      MethodTwo   uintptr
      MethodThree uintptr
    }
    
    // Callbacks must be allocated once as globals.
    var (
      methodOne = syscall.NewCallback(func(this *Object) uintptr {
        fmt.Printf("methodOne invoked\n")
        return uintptr(0)
      })
    )
    
    func NewObject() *Object {
      obj := (*Object)(malloc(unsafe.Sizeof(Object{})))
      vtbl := (*ObjectVtbl)(malloc(unsafe.Sizeof(ObjectVtbl{})))
    
      vtbl.MethodOne = methodOne
      // ...
      
      obj.lpvtbl = vtbl
      return obj
    }
  10. Instantiate WinRT COM objects using RoGetActivationFactory

    main

    WinRT uses class strings (instead of GUIDs) to identify objects, which are mapped to GUIDs at runtime. To instantiate a WinRT object:

    1. Initialize the Windows Runtime using ole.RoInitialize(0).
    2. Use ole.RoGetActivationFactory with the target class string and the expected interface GUID.
    3. Perform an unsafe.Pointer cast from the returned factory object to your specific Go interface definition to access its methods.
    ole.RoInitialize(0)
    
    CLSID_ToastNotification := "Windows.UI.Notifications.ToastNotification"
    IID_IToastNotificationFactory := ole.NewGUID("{50AC103F-D235-4598-BBEF-98FE4D1A3AD4}")
    
    factoryObject, err := ole.RoGetActivationFactory(CLSID_ToastNotification, IID_ToastNotificationFactory)
    if err != nil {
    	return nil, fmt.Errorf("getting activation factory: %w", err)
    }
    
    // Unsafe cast to the specific interface type
    factory := (*IToastNotificationFactory)(unsafe.Pointer(factoryObject))
    notification, err := factory.CreateToastNotification(xml)