modules

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 1, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package modules implements all the modules. Copyright (C) 2022 Kevin Lyda <[email protected]>

Package modules implements all the modules. Copyright (C) 2022 Kevin Lyda <[email protected]>

Package modules provides the core module system for i3going-on. Copyright (C) 2022 Kevin Lyda <[email protected]>

Package modules implements all the modules. Copyright (C) 2022 Kevin Lyda <[email protected]>

Package modules implements all the modules. Copyright (C) 2022 Kevin Lyda <[email protected]>

Package modules implements all the modules. Copyright (C) 2022 Kevin Lyda <[email protected]>

Package modules implements all the modules. Copyright (C) 2022 Kevin Lyda <[email protected]>

Package modules implements all the modules. Copyright (C) 2022 Kevin Lyda <[email protected]>

Package modules implements all the modules. Copyright (C) 2022 Kevin Lyda <[email protected]>

Package modules implements all the modules. Copyright (C) 2022 Kevin Lyda <[email protected]>

Package modules implements all the modules. Copyright (C) 2022 Kevin Lyda <[email protected]>

Package modules implements all the modules. Copyright (C) 2022 Kevin Lyda <[email protected]>

Package modules implements all the modules. Copyright (C) 2022 Kevin Lyda <[email protected]>

Package modules implements all the modules. Copyright (C) 2022 Kevin Lyda <[email protected]>

Package modules implements all the modules. Copyright (C) 2022 Kevin Lyda <[email protected]>

Package modules implements all the modules. Copyright (C) 2022 Kevin Lyda <[email protected]>

Package modules implements all the modules. Copyright (C) 2022 Kevin Lyda <[email protected]>

Package modules implements all the modules. Copyright (C) 2022 Kevin Lyda <[email protected]>

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ClearRegistry

func ClearRegistry()

ClearRegistry removes all registered module types. This is primarily useful for testing.

func ExecuteModuleClick

func ExecuteModuleClick(ctx context.Context, module ModuleV2, click Click) error

ExecuteModuleClick is a helper that handles click execution for modules. It checks if the module implements ActionProvider and uses that if available, otherwise falls back to calling HandleClick directly.

Example

ExampleExecuteModuleClick demonstrates the helper function.

package main

import (
	"context"
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	textMod := modules.NewTextModuleV2(modules.TextModuleConfig{
		ID:      "test",
		Text:    "Test",
		OnClick: "echo 'test'",
	})

	ctx := context.Background()
	click := modules.Click{Name: "test", Button: 1}

	// This automatically uses ActionProvider if available
	err := modules.ExecuteModuleClick(ctx, textMod, click)
	fmt.Printf("Executed: %v\n", err == nil)

}
Output:

Executed: true

func GetRegisteredTypes

func GetRegisteredTypes() []string

GetRegisteredTypes returns a slice of all registered module type names. This is useful for error messages, help text, and validation.

Example

ExampleGetRegisteredTypes demonstrates listing available module types.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	types := modules.GetRegisteredTypes()

	// The text-v2 module should be registered
	found := false
	for _, t := range types {
		if t == "text-v2" {
			found = true
			break
		}
	}

	fmt.Printf("text-v2 registered: %v\n", found)

}
Output:

text-v2 registered: true

func HandleClickEvent

func HandleClickEvent(ctx context.Context, modules []ModuleV2, click Click) error

HandleClickEvent routes a click event to the appropriate module. Returns an error if the module is not found or if handling fails.

func IsRegistered

func IsRegistered(moduleType string) bool

IsRegistered checks if a module type is registered.

func ParseConfigBool

func ParseConfigBool(config map[string]any, key string, defaultValue bool) bool

ParseConfigBool extracts a boolean value from the config map.

func ParseConfigFloat

func ParseConfigFloat(config map[string]any, key string, defaultValue float64) float64

ParseConfigFloat extracts a float64 value from the config map.

func ParseConfigInt

func ParseConfigInt(config map[string]any, key string, defaultValue int) int

ParseConfigInt extracts an integer value from the config map.

func ParseConfigString

func ParseConfigString(config map[string]any, key, defaultValue string) string

ParseConfigValue is a helper function for extracting typed values from config maps. It handles type assertions and provides sensible defaults.

Example usage:

text := ParseConfigString(config, "text", "default text")
color := ParseConfigString(config, "color", "")

func Register

func Register(moduleType string, factory ModuleFactory)

Register registers a new module type with the given factory function. This should be called during package initialization (in init() functions).

Example usage:

func init() {
    modules.Register("text", NewTextModuleFromConfig)
}

If a module type is registered multiple times, the last registration wins.

Example

ExampleRegister demonstrates registering a custom module.

package main

import (
	"context"
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	// Define a simple custom module factory
	customFactory := func(id string, config map[string]any) (modules.ModuleV2, error) {
		text := modules.ParseConfigString(config, "message", "Custom module!")
		return modules.NewTextModuleV2(modules.TextModuleConfig{
			ID:   id,
			Text: text,
		}), nil
	}

	// Register the custom module type
	modules.Register("custom", customFactory)

	// Now we can create instances of it
	mod, _ := modules.Create("custom", "custom-1", map[string]any{
		"message": "This is custom",
	})

	ctx := context.Background()
	block, _ := mod.Render(ctx)
	fmt.Printf("Custom module: %s\n", block.FullText)

	// Clean up for other tests
	modules.Unregister("custom")

}
Output:

Custom module: This is custom

func StartAll

func StartAll(ctx context.Context, modules []ModuleV2) error

StartAll starts all modules in the slice. If any module fails to start, an error is returned immediately and previously started modules are stopped.

func StopAll

func StopAll(modules []ModuleV2) error

StopAll stops all modules in the slice. All modules are stopped even if some return errors. Returns the first error encountered, if any.

func Unregister

func Unregister(moduleType string)

Unregister removes a module type from the registry. This is primarily useful for testing.

Types

type Action

type Action interface {
	// Execute performs the action.
	// The context can be used for cancellation and timeout.
	// The click parameter provides context about what was clicked.
	Execute(ctx context.Context, click Click) error
}

Action represents an action that can be executed in response to a click event. Actions are returned by modules' HandleClick methods and executed by the bar.

Using actions instead of direct execution provides: - Type safety (no raw shell commands) - Testability (can mock actions) - Composability (chain multiple actions) - In-process operations (refresh, state changes)

type ActionFunc

type ActionFunc func(ctx context.Context, click Click) error

ActionFunc is a function adapter that implements the Action interface. This allows using simple functions as actions without defining new types.

Example

ExampleActionFunc demonstrates using a function as an action.

package main

import (
	"context"
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	// Create an action from a function
	action := modules.ActionFunc(func(ctx context.Context, click modules.Click) error {
		fmt.Printf("Clicked module: %s with button %d\n", click.Name, click.Button)
		return nil
	})

	ctx := context.Background()
	click := modules.Click{Name: "my-module", Button: 1}

	action.Execute(ctx, click)

}
Output:

Clicked module: my-module with button 1

func (ActionFunc) Execute

func (f ActionFunc) Execute(ctx context.Context, click Click) error

Execute calls the function.

type ActionProvider

type ActionProvider interface {
	// GetClickAction returns an Action to execute for the given click event.
	// Returns nil if no action should be taken.
	GetClickAction(click Click) (Action, error)
}

ActionProvider is an optional interface that modules can implement to use the action system instead of executing commands directly in HandleClick.

Modules implementing this interface return Action objects which can then be executed, tested, or composed by the bar.

Example:

func (m *MyModule) GetClickAction(click Click) (Action, error) {
    return &ShellAction{Command: "echo clicked!"}, nil
}

type BaseModule

type BaseModule struct{}

BaseModule provides a default implementation of lifecycle methods that can be embedded in module implementations to reduce boilerplate.

Example usage:

type MyModule struct {
    BaseModule
    id string
    // ... other fields
}

func (m *MyModule) ID() string { return m.id }
func (m *MyModule) Type() string { return "mymodule" }
func (m *MyModule) Render(ctx context.Context) (Block, error) {
    // ... implementation
}

func (*BaseModule) HandleClick

func (b *BaseModule) HandleClick(ctx context.Context, click Click) error

HandleClick is a no-op default implementation. Modules that don't handle clicks can embed BaseModule and not implement this.

func (*BaseModule) Start

func (b *BaseModule) Start(ctx context.Context) error

Start is a no-op default implementation.

func (*BaseModule) Stop

func (b *BaseModule) Stop() error

Stop is a no-op default implementation.

type BatteryModuleConfig

type BatteryModuleConfig struct {
	// ID is the unique identifier for this module instance.
	// If not specified, defaults to "battery".
	ID string `yaml:"id"`

	// Battery is the battery index (0 for first battery, 1 for second, etc.).
	// Defaults to 0.
	Battery int `yaml:"battery"`

	// WarnCmd is a command to execute when battery is below 20% and discharging.
	// Example: ["notify-send", "Low battery!"]
	WarnCmd []string `yaml:"warncmd"`

	// OnClick is a shell command to execute when the module is clicked.
	OnClick string `yaml:"on-click"`

	// Icon is an optional prefix to display before the battery status.
	// Example: "🔋", "BAT:", etc.
	Icon string `yaml:"icon"`

	// Status labels (customize how battery states are displayed)
	StatusEmpty    string `yaml:"status-emp"`
	StatusCharging string `yaml:"status-chr"`
	StatusBattery  string `yaml:"status-bat"`
	StatusUnknown  string `yaml:"status-unk"`
	StatusFull     string `yaml:"status-full"`
	OnError        string `yaml:"on-error"`

	// Colors for different battery levels
	ColorOK    string `yaml:"color-ok"`
	Color20    string `yaml:"color-20"`
	Color10    string `yaml:"color-10"`
	ColorError string `yaml:"color-error"`
}

BatteryModuleConfig holds configuration for creating a BatteryModuleV2.

type BatteryModuleV2

type BatteryModuleV2 struct {
	BaseModule
	// contains filtered or unexported fields
}

BatteryModuleV2 displays battery status with percentage, state, and time remaining. This is a reimplementation of BatteryMod using the new ModuleV2 interface.

Example

ExampleBatteryModuleV2 demonstrates creating a battery module. Note: This example may not work on systems without a battery.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	battMod := modules.NewBatteryModuleV2(modules.BatteryModuleConfig{
		ID:      "my-battery",
		Battery: 0,
	})

	fmt.Printf("ID: %s\n", battMod.ID())
	fmt.Printf("Type: %s\n", battMod.Type())

}
Output:

ID: my-battery
Type: battery
Example (Customization)

ExampleBatteryModuleV2_customization demonstrates customizing battery display.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	battMod := modules.NewBatteryModuleV2(modules.BatteryModuleConfig{
		ID:             "custom-battery",
		StatusCharging: "⚡",
		StatusBattery:  "🔋",
		StatusFull:     "✓",
		ColorOK:        "#00ff00",
		Color20:        "#ff9900",
		Color10:        "#ff0000",
	})

	fmt.Printf("ID: %s\n", battMod.ID())

}
Output:

ID: custom-battery
Example (Registry)

ExampleBatteryModuleV2_registry demonstrates creating via registry.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	mod, err := modules.Create("battery-v2", "bat0", map[string]any{
		"battery":    0,
		"status-chr": "CHR",
		"color-ok":   "#00ff00",
	})
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Type: %s\n", mod.Type())

}
Output:

Type: battery
Example (Skip)

ExampleBatteryModuleV2_skip demonstrates how the battery module automatically hides itself when no battery is present.

// Create modules for a desktop system without a battery
modules := []ModuleV2{
	NewTextModuleV2(TextModuleConfig{
		ID:   "label",
		Text: "Desktop",
	}),
	NewBatteryModuleV2(BatteryModuleConfig{
		ID:      "battery",
		Battery: 0, // Will skip if no battery present
	}),
	NewDateModuleV2(DateModuleConfig{
		ID:     "clock",
		Format: "15:04:05",
	}),
}

// When rendered, if no battery is present, only the text and date modules will appear
ctx := context.Background()
blocks := RenderAll(ctx, modules)

// On a desktop without a battery, we'll only get 2 blocks
// The battery module automatically skips itself
_ = blocks
Example (Skip)

ExampleBatteryModuleV2_skip demonstrates the automatic skip feature. When no battery is present, the module automatically hides itself.

package main

import (
	"context"
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	// Create a setup with multiple modules including battery
	mods := []modules.ModuleV2{
		modules.NewTextModuleV2(modules.TextModuleConfig{
			ID:   "label",
			Text: "System:",
		}),
		modules.NewBatteryModuleV2(modules.BatteryModuleConfig{
			ID:      "battery",
			Battery: 99, // Non-existent battery to demonstrate skip
		}),
		modules.NewDateModuleV2(modules.DateModuleConfig{
			ID:     "clock",
			Format: "15:04",
		}),
	}

	// Render all modules
	ctx := context.Background()
	blocks := modules.RenderAll(ctx, mods)

	// We only get 2 blocks (label + clock)
	// The battery module automatically skips itself when battery doesn't exist
	fmt.Printf("Modules defined: 3\n")
	fmt.Printf("Blocks rendered: %d\n", len(blocks))

}
Output:

Modules defined: 3
Blocks rendered: 2
Example (WithIcon)

ExampleBatteryModuleV2_withIcon demonstrates using the battery module with an icon.

// Create a battery module with a custom icon
battery := NewBatteryModuleV2(BatteryModuleConfig{
	ID:   "my-battery",
	Icon: "⚡",
})

// If a battery is present, the icon will be prefixed to the status:
// ⚡BAT 75.0% 2h30m
_ = battery
Example (WithIcon)

ExampleBatteryModuleV2_withIcon demonstrates the new icon feature. The icon is prepended to the battery status text.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	battMod := modules.NewBatteryModuleV2(modules.BatteryModuleConfig{
		ID:             "icon-battery",
		Icon:           "🔋",
		StatusCharging: "CHR",
		StatusBattery:  "BAT",
	})

	fmt.Printf("ID: %s\n", battMod.ID())
	// When rendered (if battery exists), output would be: "🔋BAT 75.0% 2h30m"

}
Output:

ID: icon-battery

func NewBatteryModuleV2

func NewBatteryModuleV2(cfg BatteryModuleConfig) *BatteryModuleV2

NewBatteryModuleV2 creates a new battery module with the given configuration.

func (*BatteryModuleV2) GetClickAction

func (b *BatteryModuleV2) GetClickAction(click Click) (Action, error)

GetClickAction returns an Action to execute when this module is clicked. This implements the ActionProvider interface.

func (*BatteryModuleV2) HandleClick

func (b *BatteryModuleV2) HandleClick(ctx context.Context, click Click) error

HandleClick executes the configured shell command if one is set. This method is kept for backward compatibility. New code should use GetClickAction() instead.

func (*BatteryModuleV2) ID

func (b *BatteryModuleV2) ID() string

ID returns the unique identifier for this module instance.

func (*BatteryModuleV2) Render

func (b *BatteryModuleV2) Render(ctx context.Context) (Block, error)

Render returns the current battery status as a status block. It reads battery information, calculates time remaining based on discharge rate, and applies color coding based on battery level.

func (*BatteryModuleV2) Type

func (b *BatteryModuleV2) Type() string

Type returns the module type identifier.

type Block

type Block struct {
	Name      string `json:"name"`                 // Unique identifier for click events
	FullText  string `json:"full_text"`            // Main text to display
	ShortText string `json:"short_text,omitempty"` // Optional short text when space is limited
	Color     string `json:"color,omitempty"`      // Text color (hex format: #RRGGBB)
	Urgent    bool   `json:"urgent,omitempty"`     // Whether to highlight as urgent
	Separator bool   `json:"separator,omitempty"`  // Whether to show separator after this block

	// Skip indicates this block should not be rendered in the status bar.
	// This is useful for modules that are conditionally displayed (e.g., battery
	// module when no battery is present). This field is not part of the JSON output.
	Skip bool `json:"-"`
}

Block represents a single status bar element in the i3bar protocol. This struct encapsulates all the fields that can be rendered in a status block.

Example (Skip)

ExampleBlock_skip demonstrates the Skip field in Block.

// A module can signal it should not be rendered by setting Skip=true
block := Block{
	Name:     "conditional-module",
	FullText: "This won't be shown",
	Skip:     true, // This block will be filtered out by RenderAll
}

// When this block is included in RenderAll output, it will be omitted
_ = block

func RenderAll

func RenderAll(ctx context.Context, modules []ModuleV2) []Block

RenderAll renders all modules and returns a slice of Blocks. This is useful for rendering the entire status bar at once.

If a module fails to render, an error block is returned in its place so that the status bar can continue to function.

Modules that return blocks with Skip=true are omitted from the result. This allows modules to conditionally hide themselves (e.g., battery module when no battery is present).

Example

ExampleRenderAll demonstrates rendering multiple modules at once.

package main

import (
	"context"
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	ctx := context.Background()

	// Create several text modules
	mods := []modules.ModuleV2{
		modules.NewTextModuleV2(modules.TextModuleConfig{
			ID:   "text1",
			Text: "First",
		}),
		modules.NewTextModuleV2(modules.TextModuleConfig{
			ID:   "text2",
			Text: "Second",
		}),
		modules.NewTextModuleV2(modules.TextModuleConfig{
			ID:   "text3",
			Text: "Third",
		}),
	}

	// Render them all at once
	blocks := modules.RenderAll(ctx, mods)

	for i, block := range blocks {
		fmt.Printf("Block %d: %s\n", i+1, block.FullText)
	}

}
Output:

Block 1: First
Block 2: Second
Block 3: Third

type CPUModuleConfig

type CPUModuleConfig struct {
	ID              string `yaml:"id"`
	Format          string `yaml:"format"`
	Icon            string `yaml:"icon"`
	ShowTemp        bool   `yaml:"show-temp"`
	TempZone        string `yaml:"temp-zone"`
	WarnPercent     int    `yaml:"warn-percent"`
	CriticalPercent int    `yaml:"critical-percent"`
	ColorOK         string `yaml:"color-ok"`
	ColorWarn       string `yaml:"color-warn"`
	ColorCritical   string `yaml:"color-critical"`
	OnClick         string `yaml:"on-click"`
}

CPUModuleConfig holds configuration for creating a CPUModuleV2.

type CPUModuleV2

type CPUModuleV2 struct {
	BaseModule
	// contains filtered or unexported fields
}

CPUModuleV2 displays CPU usage and optionally temperature.

Example

ExampleCPUModuleV2 demonstrates creating a CPU module.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	cpuMod := modules.NewCPUModuleV2(modules.CPUModuleConfig{
		ID:     "my-cpu",
		Format: "percent",
	})

	fmt.Printf("ID: %s\n", cpuMod.ID())
	fmt.Printf("Type: %s\n", cpuMod.Type())

}
Output:

ID: my-cpu
Type: cpu
Example (Customization)

ExampleCPUModuleV2_customization demonstrates customizing CPU display.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	cpuMod := modules.NewCPUModuleV2(modules.CPUModuleConfig{
		ID:              "custom-cpu",
		Format:          "with-temp",
		ShowTemp:        true,
		Icon:            "CPU:",
		WarnPercent:     60,
		CriticalPercent: 80,
		ColorOK:         "#00ff00",
		ColorWarn:       "#ffaa00",
		ColorCritical:   "#ff0000",
	})

	fmt.Printf("ID: %s\n", cpuMod.ID())

}
Output:

ID: custom-cpu
Example (Registry)

ExampleCPUModuleV2_registry demonstrates creating via registry.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	mod, err := modules.Create("cpu-v2", "processor", map[string]any{
		"format":           "percent",
		"show-temp":        true,
		"warn-percent":     70,
		"critical-percent": 90,
	})
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Type: %s\n", mod.Type())

}
Output:

Type: cpu

func NewCPUModuleV2

func NewCPUModuleV2(cfg CPUModuleConfig) *CPUModuleV2

NewCPUModuleV2 creates a new CPU module with the given configuration.

func (*CPUModuleV2) GetClickAction

func (c *CPUModuleV2) GetClickAction(_ Click) (Action, error)

GetClickAction returns an Action to execute when this module is clicked.

func (*CPUModuleV2) ID

func (c *CPUModuleV2) ID() string

ID returns the unique identifier for this module instance.

func (*CPUModuleV2) Render

func (c *CPUModuleV2) Render(_ context.Context) (Block, error)

Render returns the current CPU usage as a status block.

func (*CPUModuleV2) Type

func (c *CPUModuleV2) Type() string

Type returns the module type identifier.

type ChainAction

type ChainAction struct {
	Actions []Action // Actions to execute in order
}

ChainAction executes multiple actions in sequence. If any action fails, execution stops and the error is returned.

Example

ExampleChainAction demonstrates chaining multiple actions.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	action := &modules.ChainAction{
		Actions: []modules.Action{
			&modules.ShellAction{Command: "echo 'First'"},
			&modules.ShellAction{Command: "echo 'Second'"},
			&modules.ShellAction{Command: "echo 'Third'"},
		},
	}

	fmt.Printf("Chain has %d actions\n", len(action.Actions))

}
Output:

Chain has 3 actions

func (*ChainAction) Execute

func (a *ChainAction) Execute(ctx context.Context, click Click) error

Execute runs all actions in sequence.

type Click

type Click struct {
	Name      string   `json:"name"`
	Button    int64    `json:"button"`
	Modifiers []string `json:"modifiers"`
	X         int64    `json:"x"`
	Y         int64    `json:"y"`
	RelativeX int64    `json:"relative_x"`
	RelativeY int64    `json:"relative_y"`
	Width     int64    `json:"width"`
	Height    int64    `json:"height"`
}

type ColorRule

type ColorRule struct {
	Pattern string `yaml:"pattern"`
	Color   string `yaml:"color"`
}

ColorRule maps a regex pattern to a color.

type DateModuleConfig

type DateModuleConfig struct {
	// ID is the unique identifier for this module instance.
	// If not specified, defaults to "date".
	ID string `yaml:"id"`

	// Format is the Go time format string (e.g., "15:04:05" or "2006-01-02 15:04").
	// If not specified, defaults to "06/01/02 15:04".
	// See https://golang.org/pkg/time/#Time.Format for format syntax.
	Format string `yaml:"format"`

	// BlinkChar is a character to blink on/off each render cycle.
	// For example, set to ":" to make colons in "15:04:05" blink.
	// Optional - if not specified, no blinking occurs.
	BlinkChar string `yaml:"blink-char"`

	// OnClick is a shell command to execute when the module is clicked.
	// Optional - if not specified, clicks do nothing.
	OnClick string `yaml:"on-click"`
}

DateModuleConfig holds configuration for creating a DateModuleV2.

type DateModuleV2

type DateModuleV2 struct {
	BaseModule
	// contains filtered or unexported fields
}

DateModuleV2 displays the current date and time with optional blinking. This is a reimplementation of DateMod using the new ModuleV2 interface.

Example

ExampleDateModuleV2 demonstrates creating and using a date module.

package main

import (
	"context"
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	dateMod := modules.NewDateModuleV2(modules.DateModuleConfig{
		ID:     "my-date",
		Format: "15:04:05", // Show time only
	})

	ctx := context.Background()

	// Start and render
	dateMod.Start(ctx)
	defer dateMod.Stop()

	block, _ := dateMod.Render(ctx)

	fmt.Printf("ID: %s\n", block.Name)
	fmt.Printf("Type: %s\n", dateMod.Type())
	// Note: We can't check the actual time in the test, but we can verify structure
	fmt.Printf("Has time: %t\n", len(block.FullText) > 0)

}
Output:

ID: my-date
Type: date
Has time: true
Example (Blinking)

ExampleDateModuleV2_blinking demonstrates the blinking feature.

package main

import (
	"context"
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	dateMod := modules.NewDateModuleV2(modules.DateModuleConfig{
		ID:        "blink-date",
		Format:    "15:04:05",
		BlinkChar: ":", // Make colons blink
	})

	ctx := context.Background()

	// First render - colons visible
	block1, _ := dateMod.Render(ctx)
	hasColons1 := len(block1.FullText) > 0 && (block1.FullText[2:3] == ":" || block1.FullText[2:3] == " ")

	// Second render - colons invisible (replaced with spaces)
	block2, _ := dateMod.Render(ctx)
	hasColons2 := len(block2.FullText) > 0 && (block2.FullText[2:3] == ":" || block2.FullText[2:3] == " ")

	fmt.Printf("Blinking works: %t\n", hasColons1 || hasColons2)

}
Output:

Blinking works: true
Example (Registry)

ExampleDateModuleV2_registry demonstrates creating via registry.

package main

import (
	"context"
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	mod, err := modules.Create("date-v2", "my-date", map[string]any{
		"format": "2006-01-02",
	})
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	ctx := context.Background()
	block, _ := mod.Render(ctx)

	fmt.Printf("Type: %s\n", mod.Type())
	fmt.Printf("Has date: %t\n", len(block.FullText) > 0)

}
Output:

Type: date
Has date: true

func NewDateModuleV2

func NewDateModuleV2(cfg DateModuleConfig) *DateModuleV2

NewDateModuleV2 creates a new date module with the given configuration.

func (*DateModuleV2) GetClickAction

func (d *DateModuleV2) GetClickAction(click Click) (Action, error)

GetClickAction returns an Action to execute when this module is clicked. This implements the ActionProvider interface.

func (*DateModuleV2) HandleClick

func (d *DateModuleV2) HandleClick(ctx context.Context, click Click) error

HandleClick executes the configured shell command if one is set. This method is kept for backward compatibility. New code should use GetClickAction() instead.

func (*DateModuleV2) ID

func (d *DateModuleV2) ID() string

ID returns the unique identifier for this module instance.

func (*DateModuleV2) Render

func (d *DateModuleV2) Render(ctx context.Context) (Block, error)

Render returns the current date/time as a status block. If blinkChar is configured, it will toggle the visibility of that character each time Render is called, creating a blinking effect.

func (*DateModuleV2) SetBlinkChar

func (d *DateModuleV2) SetBlinkChar(char string)

SetBlinkChar updates the blinking character.

func (*DateModuleV2) SetFormat

func (d *DateModuleV2) SetFormat(format string)

SetFormat updates the time format string. This demonstrates dynamic reconfiguration.

func (*DateModuleV2) Type

func (d *DateModuleV2) Type() string

Type returns the module type identifier.

type DiskModuleConfig

type DiskModuleConfig struct {
	ID              string `yaml:"id"`
	Path            string `yaml:"path"`
	Format          string `yaml:"format"`
	Icon            string `yaml:"icon"`
	WarnPercent     int    `yaml:"warn-percent"`
	CriticalPercent int    `yaml:"critical-percent"`
	ColorOK         string `yaml:"color-ok"`
	ColorWarn       string `yaml:"color-warn"`
	ColorCritical   string `yaml:"color-critical"`
	OnClick         string `yaml:"on-click"`
}

DiskModuleConfig holds configuration for creating a DiskModuleV2.

type DiskModuleV2

type DiskModuleV2 struct {
	BaseModule
	// contains filtered or unexported fields
}

DiskModuleV2 displays disk space usage for a mounted partition.

Example

ExampleDiskModuleV2 demonstrates creating a disk usage module.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	diskMod := modules.NewDiskModuleV2(modules.DiskModuleConfig{
		ID:     "my-disk",
		Path:   "/",
		Format: "both",
	})

	fmt.Printf("ID: %s\n", diskMod.ID())
	fmt.Printf("Type: %s\n", diskMod.Type())

}
Output:

ID: my-disk
Type: disk
Example (Customization)

ExampleDiskModuleV2_customization demonstrates customizing disk module display.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	diskMod := modules.NewDiskModuleV2(modules.DiskModuleConfig{
		ID:              "custom-disk",
		Path:            "/home",
		Format:          "percent",
		Icon:            "🗄️",
		WarnPercent:     75,
		CriticalPercent: 85,
		ColorOK:         "#00ff00",
		ColorWarn:       "#ff9900",
		ColorCritical:   "#ff0000",
	})

	fmt.Printf("ID: %s\n", diskMod.ID())

}
Output:

ID: custom-disk
Example (Registry)

ExampleDiskModuleV2_registry demonstrates creating via registry.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	mod, err := modules.Create("disk-v2", "root-disk", map[string]any{
		"path":   "/",
		"format": "short",
	})
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Type: %s\n", mod.Type())

}
Output:

Type: disk

func NewDiskModuleV2

func NewDiskModuleV2(cfg DiskModuleConfig) *DiskModuleV2

NewDiskModuleV2 creates a new disk module with the given configuration.

func (*DiskModuleV2) GetClickAction

func (d *DiskModuleV2) GetClickAction(_ Click) (Action, error)

GetClickAction returns an Action to execute when this module is clicked.

func (*DiskModuleV2) ID

func (d *DiskModuleV2) ID() string

ID returns the unique identifier for this module instance.

func (*DiskModuleV2) Render

func (d *DiskModuleV2) Render(_ context.Context) (Block, error)

Render returns the current disk usage as a status block.

func (*DiskModuleV2) Type

func (d *DiskModuleV2) Type() string

Type returns the module type identifier.

type LoadModuleConfig

type LoadModuleConfig struct {
	ID                string  `yaml:"id"`
	Format            string  `yaml:"format"`
	Icon              string  `yaml:"icon"`
	ColorOK           string  `yaml:"color-ok"`
	ColorWarn         string  `yaml:"color-warn"`
	ColorCritical     string  `yaml:"color-critical"`
	WarnThreshold     float64 `yaml:"warn-threshold"`
	CriticalThreshold float64 `yaml:"critical-threshold"`
	OnClick           string  `yaml:"on-click"`
}

LoadModuleConfig holds configuration for creating a LoadModuleV2.

type LoadModuleV2

type LoadModuleV2 struct {
	BaseModule
	// contains filtered or unexported fields
}

LoadModuleV2 displays system load averages.

Example

ExampleLoadModuleV2 demonstrates creating a load module.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	loadMod := modules.NewLoadModuleV2(modules.LoadModuleConfig{
		ID:     "my-load",
		Format: "all",
	})

	fmt.Printf("ID: %s\n", loadMod.ID())
	fmt.Printf("Type: %s\n", loadMod.Type())

}
Output:

ID: my-load
Type: load
Example (Customization)

ExampleLoadModuleV2_customization demonstrates customizing load display.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	loadMod := modules.NewLoadModuleV2(modules.LoadModuleConfig{
		ID:                "custom-load",
		Format:            "1min",
		Icon:              "CPU:",
		WarnThreshold:     0.5,
		CriticalThreshold: 0.8,
		ColorOK:           "#00ff00",
		ColorWarn:         "#ffaa00",
		ColorCritical:     "#ff0000",
	})

	fmt.Printf("ID: %s\n", loadMod.ID())

}
Output:

ID: custom-load
Example (Registry)

ExampleLoadModuleV2_registry demonstrates creating via registry.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	mod, err := modules.Create("load-v2", "system-load", map[string]any{
		"format":             "1-5",
		"warn-threshold":     0.7,
		"critical-threshold": 0.9,
	})
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Type: %s\n", mod.Type())

}
Output:

Type: load

func NewLoadModuleV2

func NewLoadModuleV2(cfg LoadModuleConfig) *LoadModuleV2

NewLoadModuleV2 creates a new load module with the given configuration.

func (*LoadModuleV2) GetClickAction

func (l *LoadModuleV2) GetClickAction(click Click) (Action, error)

GetClickAction returns an Action to execute when this module is clicked.

func (*LoadModuleV2) ID

func (l *LoadModuleV2) ID() string

ID returns the unique identifier for this module instance.

func (*LoadModuleV2) Render

func (l *LoadModuleV2) Render(ctx context.Context) (Block, error)

Render returns the current system load as a status block.

func (*LoadModuleV2) Type

func (l *LoadModuleV2) Type() string

Type returns the module type identifier.

type M

type M ModuleEntry

M is an intermediate type to avoid name collisions.

type MemoryModuleConfig

type MemoryModuleConfig struct {
	ID              string `yaml:"id"`
	Format          string `yaml:"format"`
	Icon            string `yaml:"icon"`
	WarnPercent     int    `yaml:"warn-percent"`
	CriticalPercent int    `yaml:"critical-percent"`
	ColorOK         string `yaml:"color-ok"`
	ColorWarn       string `yaml:"color-warn"`
	ColorCritical   string `yaml:"color-critical"`
	OnClick         string `yaml:"on-click"`
}

MemoryModuleConfig holds configuration for creating a MemoryModuleV2.

type MemoryModuleV2

type MemoryModuleV2 struct {
	BaseModule
	// contains filtered or unexported fields
}

MemoryModuleV2 displays system memory usage.

Example

ExampleMemoryModuleV2 demonstrates creating a memory module.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	memMod := modules.NewMemoryModuleV2(modules.MemoryModuleConfig{
		ID:     "my-memory",
		Format: "both",
	})

	fmt.Printf("ID: %s\n", memMod.ID())
	fmt.Printf("Type: %s\n", memMod.Type())

}
Output:

ID: my-memory
Type: memory
Example (Customization)

ExampleMemoryModuleV2_customization demonstrates customizing memory display.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	memMod := modules.NewMemoryModuleV2(modules.MemoryModuleConfig{
		ID:              "custom-memory",
		Format:          "percent",
		Icon:            "RAM:",
		WarnPercent:     70,
		CriticalPercent: 85,
		ColorOK:         "#00ff00",
		ColorWarn:       "#ffaa00",
		ColorCritical:   "#ff0000",
	})

	fmt.Printf("ID: %s\n", memMod.ID())

}
Output:

ID: custom-memory
Example (Registry)

ExampleMemoryModuleV2_registry demonstrates creating via registry.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	mod, err := modules.Create("memory-v2", "ram", map[string]any{
		"format":           "short",
		"warn-percent":     80,
		"critical-percent": 90,
	})
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Type: %s\n", mod.Type())

}
Output:

Type: memory

func NewMemoryModuleV2

func NewMemoryModuleV2(cfg MemoryModuleConfig) *MemoryModuleV2

NewMemoryModuleV2 creates a new memory module with the given configuration.

func (*MemoryModuleV2) GetClickAction

func (m *MemoryModuleV2) GetClickAction(click Click) (Action, error)

GetClickAction returns an Action to execute when this module is clicked.

func (*MemoryModuleV2) ID

func (m *MemoryModuleV2) ID() string

ID returns the unique identifier for this module instance.

func (*MemoryModuleV2) Render

func (m *MemoryModuleV2) Render(ctx context.Context) (Block, error)

Render returns the current memory usage as a status block.

func (*MemoryModuleV2) Type

func (m *MemoryModuleV2) Type() string

Type returns the module type identifier.

type MicrophoneModuleConfig

type MicrophoneModuleConfig struct {
	ID           string `yaml:"id"`
	Source       string `yaml:"source"`
	Format       string `yaml:"format"`
	Step         int    `yaml:"step"`
	IconMuted    string `yaml:"icon-muted"`
	Icon         string `yaml:"icon"`
	ColorMuted   string `yaml:"color-muted"`
	ColorNormal  string `yaml:"color-normal"`
	OnClick      string `yaml:"on-click"`
	OnRightClick string `yaml:"on-right-click"`
}

MicrophoneModuleConfig holds configuration for creating a MicrophoneModuleV2.

type MicrophoneModuleV2

type MicrophoneModuleV2 struct {
	BaseModule
	// contains filtered or unexported fields
}

MicrophoneModuleV2 displays system volume level and mute status.

func NewMicrophoneModuleV2

func NewMicrophoneModuleV2(cfg MicrophoneModuleConfig) *MicrophoneModuleV2

NewMicrophoneModuleV2 creates a new volume module with the given configuration.

func (*MicrophoneModuleV2) GetClickAction

func (v *MicrophoneModuleV2) GetClickAction(click Click) (Action, error)

GetClickAction returns an Action to execute when this module is clicked.

func (*MicrophoneModuleV2) ID

func (v *MicrophoneModuleV2) ID() string

ID returns the unique identifier for this module instance.

func (*MicrophoneModuleV2) Render

func (v *MicrophoneModuleV2) Render(ctx context.Context) (Block, error)

Render returns the current volume status as a status block.

func (*MicrophoneModuleV2) Type

func (v *MicrophoneModuleV2) Type() string

Type returns the module type identifier.

type ModuleEntry

type ModuleEntry struct {
	Module  string `yaml:"module"`
	Name    string `yaml:"name"`
	OnClick string `yaml:"on-click"`
	// contains filtered or unexported fields
}

ModuleEntry is the YAML configuration entry for a module. It handles parsing and creates the appropriate ModuleV2 implementation.

func NewErrorEntry

func NewErrorEntry(name string, err error) ModuleEntry

NewErrorEntry creates a ModuleEntry that displays an error message.

func (*ModuleEntry) HandleClick

func (m *ModuleEntry) HandleClick(ctx context.Context, click Click) error

HandleClick handles a click event on this module.

func (*ModuleEntry) ID

func (m *ModuleEntry) ID() string

ID returns the module's unique identifier.

func (*ModuleEntry) Render

func (m *ModuleEntry) Render(ctx context.Context) (Block, error)

Render returns the module's current state as a Block.

func (*ModuleEntry) Start

func (m *ModuleEntry) Start(ctx context.Context) error

Start initializes the module.

func (*ModuleEntry) Stop

func (m *ModuleEntry) Stop() error

Stop cleans up the module.

func (*ModuleEntry) UnmarshalYAML

func (m *ModuleEntry) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML parses the YAML config and creates the ModuleV2 implementation.

type ModuleFactory

type ModuleFactory func(id string, config map[string]any) (ModuleV2, error)

ModuleFactory is a function that creates a new ModuleV2 instance from configuration. The id parameter is the unique instance identifier for this module. The config parameter is a map of configuration values parsed from YAML.

Example factory function:

func NewTextModuleFromConfig(id string, config map[string]any) (ModuleV2, error) {
    cfg := TextModuleConfig{ID: id}
    // Parse config map into cfg struct...
    return NewTextModuleV2(cfg), nil
}

type ModuleV2

type ModuleV2 interface {
	// ID returns the unique instance identifier for this module.
	// This is used for routing click events to the correct module instance.
	// Example: "battery0", "date", "my-custom-text"
	ID() string

	// Type returns the module type identifier.
	// This indicates what kind of module this is.
	// Example: "battery", "date", "text", "network"
	Type() string

	// Start initializes the module and any resources it needs.
	// This is called once when the module is created, before any Render calls.
	// Use this to set up background goroutines, open files, etc.
	//
	// The context will be cancelled when the module should shut down.
	// Long-running operations should respect context cancellation.
	Start(ctx context.Context) error

	// Stop cleans up any resources held by the module.
	// This is called when the module is being removed or the program is exiting.
	// Close files, stop goroutines, release memory, etc.
	Stop() error

	// Render generates the current status bar block for this module.
	// This is called periodically (e.g., every 5 seconds) to update the display.
	//
	// The returned Block contains all the information needed to display
	// this module in the status bar (text, color, etc.).
	//
	// Render should be fast and non-blocking. Heavy operations should be
	// done in Start() or a background goroutine, with Render just returning
	// the current cached state.
	Render(ctx context.Context) (Block, error)

	// HandleClick processes a click event for this module.
	// This is called when the user clicks on this module's status bar entry.
	//
	// The Click parameter contains all information about the click:
	// button number, modifiers, coordinates, etc.
	//
	// Return an error if the click cannot be handled.
	HandleClick(ctx context.Context, click Click) error
}

ModuleV2 is the new, cleaner module interface that will eventually replace the Module struct + ParamsInterface pattern.

This interface provides: - Clear lifecycle management (Start/Stop) - Context support for cancellation and timeouts - Structured rendering with Block type instead of raw JSON strings - Per-module click handling instead of global routing

Modules can be migrated incrementally from the old ParamsInterface to this new interface without breaking existing functionality.

func Create

func Create(moduleType, id string, config map[string]any) (ModuleV2, error)

Create creates a new module instance of the given type. The id parameter is the unique instance identifier. The config parameter is a map of configuration values.

Returns an error if: - The module type is not registered - The factory function returns an error

Example usage:

mod, err := modules.Create("text", "my-text", map[string]any{
    "text": "Hello!",
    "color": "#00ff00",
})
Example

ExampleCreate demonstrates using the module registry to create modules.

package main

import (
	"context"
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	// Create a text module using the registry
	mod, err := modules.Create("text-v2", "my-text", map[string]any{
		"text":  "Registry test",
		"color": "#ff00ff",
	})
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Use the module
	ctx := context.Background()
	block, _ := mod.Render(ctx)

	fmt.Printf("Type: %s\n", mod.Type())
	fmt.Printf("ID: %s\n", mod.ID())
	fmt.Printf("Text: %s\n", block.FullText)
	fmt.Printf("Color: %s\n", block.Color)

}
Output:

Type: text
ID: my-text
Text: Registry test
Color: #ff00ff

func FindModuleByID

func FindModuleByID(modules []ModuleV2, id string) ModuleV2

FindModuleByID searches for a module with the given ID. Returns nil if no module is found.

func NewBatteryModuleV2FromConfig

func NewBatteryModuleV2FromConfig(id string, config map[string]any) (ModuleV2, error)

NewBatteryModuleV2FromConfig is a factory function that creates a BatteryModuleV2 from a configuration map. This is used by the module registry.

func NewCPUModuleV2FromConfig

func NewCPUModuleV2FromConfig(id string, config map[string]any) (ModuleV2, error)

NewCPUModuleV2FromConfig is a factory function that creates a CPUModuleV2 from a configuration map. This is used by the module registry.

func NewDateModuleV2FromConfig

func NewDateModuleV2FromConfig(id string, config map[string]any) (ModuleV2, error)

NewDateModuleV2FromConfig is a factory function that creates a DateModuleV2 from a configuration map. This is used by the module registry.

func NewDiskModuleV2FromConfig

func NewDiskModuleV2FromConfig(id string, config map[string]any) (ModuleV2, error)

NewDiskModuleV2FromConfig is a factory function that creates a DiskModuleV2 from a configuration map. This is used by the module registry.

func NewLoadModuleV2FromConfig

func NewLoadModuleV2FromConfig(id string, config map[string]any) (ModuleV2, error)

NewLoadModuleV2FromConfig is a factory function that creates a LoadModuleV2 from a configuration map. This is used by the module registry.

func NewMemoryModuleV2FromConfig

func NewMemoryModuleV2FromConfig(id string, config map[string]any) (ModuleV2, error)

NewMemoryModuleV2FromConfig is a factory function that creates a MemoryModuleV2 from a configuration map. This is used by the module registry.

func NewMicrophoneModuleV2FromConfig

func NewMicrophoneModuleV2FromConfig(id string, config map[string]any) (ModuleV2, error)

NewMicrophoneModuleV2FromConfig is a factory function that creates a MicrophoneModuleV2 from a configuration map. This is used by the module registry.

func NewNetworkModuleV2FromConfig

func NewNetworkModuleV2FromConfig(id string, config map[string]any) (ModuleV2, error)

NewNetworkModuleV2FromConfig is a factory function that creates a NetworkModuleV2 from a configuration map. This is used by the module registry.

func NewScriptModuleV2FromConfig

func NewScriptModuleV2FromConfig(id string, config map[string]any) (ModuleV2, error)

NewScriptModuleV2FromConfig is a factory function that creates a ScriptModuleV2 from a configuration map. This is used by the module registry.

func NewTextModuleV2FromConfig

func NewTextModuleV2FromConfig(id string, config map[string]any) (ModuleV2, error)

NewTextModuleV2FromConfig is a factory function that creates a TextModuleV2 from a configuration map. This is used by the module registry.

func NewVolumeModuleV2FromConfig

func NewVolumeModuleV2FromConfig(id string, config map[string]any) (ModuleV2, error)

NewVolumeModuleV2FromConfig is a factory function that creates a VolumeModuleV2 from a configuration map. This is used by the module registry.

type NetworkModuleConfig

type NetworkModuleConfig struct {
	ID                string `yaml:"id"`
	Interface         string `yaml:"interface"`
	ShowIP            bool   `yaml:"show-ip"`
	ShowTraffic       bool   `yaml:"show-traffic"`
	Format            string `yaml:"format"`
	IconConnected     string `yaml:"icon-connected"`
	IconDisconnected  string `yaml:"icon-disconnected"`
	ColorConnected    string `yaml:"color-connected"`
	ColorDisconnected string `yaml:"color-disconnected"`
	OnClick           string `yaml:"on-click"`
}

NetworkModuleConfig holds configuration for creating a NetworkModuleV2.

type NetworkModuleV2

type NetworkModuleV2 struct {
	BaseModule
	// contains filtered or unexported fields
}

NetworkModuleV2 displays network interface status and traffic.

Example

ExampleNetworkModuleV2 demonstrates creating a network interface module.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	netMod := modules.NewNetworkModuleV2(modules.NetworkModuleConfig{
		ID:          "my-network",
		Interface:   "eth0",
		ShowIP:      true,
		ShowTraffic: true,
	})

	fmt.Printf("ID: %s\n", netMod.ID())
	fmt.Printf("Type: %s\n", netMod.Type())

}
Output:

ID: my-network
Type: network
Example (AutoDetection)

ExampleNetworkModuleV2_autoDetection demonstrates using auto-detection.

// Create a network module with auto-detection
// This will automatically find the first active network interface
netMod := NewNetworkModuleV2(NetworkModuleConfig{
	ID:          "auto-network",
	Interface:   "auto", // Auto-detect any interface
	ShowIP:      true,
	ShowTraffic: true,
})

// Start the module to resolve the interface
ctx := context.Background()
netMod.Start(ctx)

// The module will now use whatever interface it detected
// Output depends on the system, so we just verify it was created
_ = netMod
Example (AutoEthernet)

ExampleNetworkModuleV2_autoEthernet demonstrates ethernet auto-detection.

// Create a network module that auto-detects ethernet interfaces
// Perfect for desktop systems with wired connections
netMod := NewNetworkModuleV2(NetworkModuleConfig{
	ID:          "ethernet",
	Interface:   "auto-ethernet", // Auto-detect first ethernet interface
	ShowIP:      true,
	ShowTraffic: true,
})

ctx := context.Background()
netMod.Start(ctx)

// Will find interfaces like: eth0, enp3s0, eno1, etc.
_ = netMod
Example (AutoWireless)

ExampleNetworkModuleV2_autoWireless demonstrates wireless auto-detection.

// Create a network module that auto-detects wireless interfaces
// Perfect for laptops with WiFi
netMod := NewNetworkModuleV2(NetworkModuleConfig{
	ID:          "wifi",
	Interface:   "auto-wireless", // Auto-detect first wireless interface
	ShowIP:      true,
	ShowTraffic: true,
})

ctx := context.Background()
netMod.Start(ctx)

// Will find interfaces like: wlan0, wlp2s0, wlo1, etc.
_ = netMod
Example (Customization)

ExampleNetworkModuleV2_customization demonstrates customizing network module.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	netMod := modules.NewNetworkModuleV2(modules.NetworkModuleConfig{
		ID:                "wifi",
		Interface:         "wlan0",
		ShowIP:            true,
		ShowTraffic:       true,
		Format:            "long",
		IconConnected:     "📶",
		IconDisconnected:  "❌",
		ColorConnected:    "#00ff00",
		ColorDisconnected: "#ff0000",
	})

	fmt.Printf("ID: %s\n", netMod.ID())

}
Output:

ID: wifi
Example (PortableConfig)

ExampleNetworkModuleV2_portableConfig demonstrates a portable config.

// This configuration works across different machines without modification
// Each machine will auto-detect its own network interface

// For a laptop that might use ethernet OR wifi:
netMod1 := NewNetworkModuleV2(NetworkModuleConfig{
	ID:        "network",
	Interface: "auto", // Uses whatever is active
})

// For a desktop that always uses ethernet:
netMod2 := NewNetworkModuleV2(NetworkModuleConfig{
	ID:        "network",
	Interface: "auto-ethernet", // Only looks for wired
})

// For a mobile device that only has wifi:
netMod3 := NewNetworkModuleV2(NetworkModuleConfig{
	ID:        "network",
	Interface: "auto-wireless", // Only looks for wireless
})

ctx := context.Background()
netMod1.Start(ctx)
netMod2.Start(ctx)
netMod3.Start(ctx)

// All three modules will work on their respective systems
_, _, _ = netMod1, netMod2, netMod3
Example (Registry)

ExampleNetworkModuleV2_registry demonstrates creating via registry.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	mod, err := modules.Create("network-v2", "eth", map[string]any{
		"interface":    "eth0",
		"show-ip":      true,
		"show-traffic": true,
	})
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Type: %s\n", mod.Type())

}
Output:

Type: network

func NewNetworkModuleV2

func NewNetworkModuleV2(cfg NetworkModuleConfig) *NetworkModuleV2

NewNetworkModuleV2 creates a new network module with the given configuration.

func (*NetworkModuleV2) GetClickAction

func (n *NetworkModuleV2) GetClickAction(_ Click) (Action, error)

GetClickAction returns an Action to execute when this module is clicked.

func (*NetworkModuleV2) ID

func (n *NetworkModuleV2) ID() string

ID returns the unique identifier for this module instance.

func (*NetworkModuleV2) Render

func (n *NetworkModuleV2) Render(_ context.Context) (Block, error)

Render returns the current network status as a status block.

func (*NetworkModuleV2) Start

func (n *NetworkModuleV2) Start(_ context.Context) error

Start resolves auto-detection interface names and initializes the module.

func (*NetworkModuleV2) Type

func (n *NetworkModuleV2) Type() string

Type returns the module type identifier.

type NoOpAction

type NoOpAction struct{}

NoOpAction does nothing. Useful as a placeholder or for testing.

func (*NoOpAction) Execute

func (a *NoOpAction) Execute(ctx context.Context, click Click) error

Execute does nothing and always succeeds.

type NotifyAction

type NotifyAction struct {
	Summary string // Title of the notification
	Body    string // Message body
	Icon    string // Optional icon name or path
}

NotifyAction shows a desktop notification using notify-send. This is useful for providing feedback without launching complex external programs.

Example

ExampleNotifyAction demonstrates showing a desktop notification.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	action := &modules.NotifyAction{
		Summary: "Test Notification",
		Body:    "This is a test",
		Icon:    "dialog-information",
	}

	// Note: This may not work in test environments without a display
	fmt.Printf("Action type: %T\n", action)

}
Output:

Action type: *modules.NotifyAction

func (*NotifyAction) Execute

func (a *NotifyAction) Execute(ctx context.Context, click Click) error

Execute shows the desktop notification using notify-send command.

type ParallelAction

type ParallelAction struct {
	Actions []Action // Actions to execute concurrently
}

ParallelAction executes multiple actions concurrently. All actions are started, and errors from any are collected and returned.

Example

ExampleParallelAction demonstrates running actions concurrently.

package main

import (
	"context"
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	action := &modules.ParallelAction{
		Actions: []modules.Action{
			&modules.ShellAction{Command: "echo 'Action 1'"},
			&modules.ShellAction{Command: "echo 'Action 2'"},
			&modules.ShellAction{Command: "echo 'Action 3'"},
		},
	}

	ctx := context.Background()
	click := modules.Click{Name: "test", Button: 1}

	err := action.Execute(ctx, click)
	fmt.Printf("All executed: %v\n", err == nil)

}
Output:

All executed: true

func (*ParallelAction) Execute

func (a *ParallelAction) Execute(ctx context.Context, click Click) error

Execute runs all actions concurrently and waits for all to complete.

type Params

type Params struct {
	*M     `yaml:",inline"`
	Params yaml.Node `yaml:"params"`
}

Params is the structure for module specific params.

type RefreshAction

type RefreshAction struct {
	ModuleID string // ID of the module to refresh
}

RefreshAction triggers an immediate refresh of a specific module. This is useful when a click should update the display immediately rather than waiting for the next refresh cycle.

func (*RefreshAction) Execute

func (a *RefreshAction) Execute(ctx context.Context, click Click) error

Execute refreshes the specified module. Note: This requires integration with the bar's event loop. For now, it's a placeholder that does nothing.

type ScriptModuleConfig

type ScriptModuleConfig struct {
	ID           string      `yaml:"id"`
	Command      string      `yaml:"command"`
	ColorRules   []ColorRule `yaml:"color-rules"`
	DefaultColor string      `yaml:"default-color"`
	OnError      string      `yaml:"on-error"`
	OnClick      string      `yaml:"on-click"`
}

ScriptModuleConfig holds configuration for creating a ScriptModuleV2.

type ScriptModuleV2

type ScriptModuleV2 struct {
	BaseModule
	// contains filtered or unexported fields
}

ScriptModuleV2 executes a command and displays its output with conditional coloring.

Example

ExampleScriptModuleV2 demonstrates creating a script module.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	scriptMod := modules.NewScriptModuleV2(modules.ScriptModuleConfig{
		ID:           "my-script",
		Command:      "echo 'Hello from script'",
		DefaultColor: "#00ff00",
	})

	fmt.Printf("ID: %s\n", scriptMod.ID())
	fmt.Printf("Type: %s\n", scriptMod.Type())

}
Output:

ID: my-script
Type: script
Example (ColorRules)

ExampleScriptModuleV2_colorRules demonstrates using color rules.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	scriptMod := modules.NewScriptModuleV2(modules.ScriptModuleConfig{
		ID:      "status-script",
		Command: "echo 'OK: All systems go'",
		ColorRules: []modules.ColorRule{
			{Pattern: "^OK:", Color: "#00ff00"},
			{Pattern: "^WARN:", Color: "#ffa500"},
			{Pattern: "^ERROR:", Color: "#ff0000"},
		},
		DefaultColor: "#ffffff",
		OnError:      "Script failed",
	})

	fmt.Printf("ID: %s\n", scriptMod.ID())
	fmt.Printf("Type: %s\n", scriptMod.Type())

}
Output:

ID: status-script
Type: script
Example (Registry)

ExampleScriptModuleV2_registry demonstrates creating via registry.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	mod, err := modules.Create("script-v2", "uptime", map[string]any{
		"command":       "uptime",
		"default-color": "#00ff00",
		"on-error":      "Uptime unavailable",
	})
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Type: %s\n", mod.Type())

}
Output:

Type: script

func NewScriptModuleV2

func NewScriptModuleV2(cfg ScriptModuleConfig) *ScriptModuleV2

NewScriptModuleV2 creates a new script module with the given configuration.

func (*ScriptModuleV2) GetClickAction

func (s *ScriptModuleV2) GetClickAction(click Click) (Action, error)

GetClickAction returns an Action to execute when this module is clicked.

func (*ScriptModuleV2) ID

func (s *ScriptModuleV2) ID() string

ID returns the unique identifier for this module instance.

func (*ScriptModuleV2) Render

func (s *ScriptModuleV2) Render(ctx context.Context) (Block, error)

Render executes the script and returns its output as a status block.

func (*ScriptModuleV2) Type

func (s *ScriptModuleV2) Type() string

Type returns the module type identifier.

type ShellAction

type ShellAction struct {
	Command string            // The shell command to execute
	Env     map[string]string // Optional environment variables
}

ShellAction executes a shell command. This is the most common action type for launching external programs.

Example

ExampleShellAction demonstrates executing a shell command.

package main

import (
	"context"
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	action := &modules.ShellAction{
		Command: "echo 'Hello from action!'",
	}

	ctx := context.Background()
	click := modules.Click{Name: "test", Button: 1}

	err := action.Execute(ctx, click)
	fmt.Printf("Executed: %v\n", err == nil)

}
Output:

Executed: true

func (*ShellAction) Execute

func (a *ShellAction) Execute(ctx context.Context, click Click) error

Execute runs the shell command in a subprocess. The command runs asynchronously (doesn't block).

type TextModuleConfig

type TextModuleConfig struct {
	// ID is the unique identifier for this module instance.
	// If not specified, defaults to "text".
	ID string `yaml:"id"`

	// Text is the string to display in the status bar.
	// If not specified, defaults to "Hello world!".
	Text string `yaml:"text"`

	// Color is the text color in hex format (#RRGGBB).
	// If not specified, uses the default bar color.
	Color string `yaml:"color"`

	// OnClick is a shell command to execute when the module is clicked.
	// Optional - if not specified, clicks do nothing.
	OnClick string `yaml:"on-click"`
}

TextModuleConfig holds configuration for creating a TextModuleV2.

type TextModuleV2

type TextModuleV2 struct {
	BaseModule
	// contains filtered or unexported fields
}

TextModuleV2 is a reimplementation of TextMod using the new ModuleV2 interface. This serves as an example of how to write modules using the new interface.

The text module displays static text with optional color. It's the simplest module and serves as a good template for new modules.

Example

ExampleTextModuleV2 demonstrates how to create and use the new TextModuleV2.

package main

import (
	"context"
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	// Create a new text module with custom configuration
	textMod := modules.NewTextModuleV2(modules.TextModuleConfig{
		ID:      "my-text",
		Text:    "Hello from V2!",
		Color:   "#00ff00",
		OnClick: "echo 'clicked!'",
	})

	// Start the module (not required for text module, but good practice)
	ctx := context.Background()
	if err := textMod.Start(ctx); err != nil {
		fmt.Printf("Error starting module: %v\n", err)
		return
	}
	defer textMod.Stop()

	// Render the module
	block, err := textMod.Render(ctx)
	if err != nil {
		fmt.Printf("Error rendering: %v\n", err)
		return
	}

	fmt.Printf("ID: %s\n", block.Name)
	fmt.Printf("Text: %s\n", block.FullText)
	fmt.Printf("Color: %s\n", block.Color)

}
Output:

ID: my-text
Text: Hello from V2!
Color: #00ff00
Example (Defaults)

ExampleTextModuleV2_defaults demonstrates using default values.

package main

import (
	"context"
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	// Create with minimal configuration - uses defaults
	textMod := modules.NewTextModuleV2(modules.TextModuleConfig{})

	ctx := context.Background()
	block, _ := textMod.Render(ctx)

	fmt.Printf("Default text: %s\n", block.FullText)

}
Output:

Default text: Hello world!
Example (Dynamic)

ExampleTextModuleV2_dynamic demonstrates updating module state.

package main

import (
	"context"
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	textMod := modules.NewTextModuleV2(modules.TextModuleConfig{
		ID:   "dynamic",
		Text: "Initial text",
	})

	ctx := context.Background()

	// Render initial state
	block, _ := textMod.Render(ctx)
	fmt.Printf("Before: %s\n", block.FullText)

	// Update the text dynamically
	textMod.SetText("Updated text")

	// Render again
	block, _ = textMod.Render(ctx)
	fmt.Printf("After: %s\n", block.FullText)

}
Output:

Before: Initial text
After: Updated text

func NewTextModuleV2

func NewTextModuleV2(cfg TextModuleConfig) *TextModuleV2

NewTextModuleV2 creates a new text module with the given configuration.

func (*TextModuleV2) GetClickAction

func (t *TextModuleV2) GetClickAction(click Click) (Action, error)

GetClickAction returns an Action to execute when this module is clicked. This implements the ActionProvider interface, which provides better testability and composability than HandleClick.

Example

ExampleTextModuleV2_GetClickAction demonstrates the ActionProvider interface.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	textMod := modules.NewTextModuleV2(modules.TextModuleConfig{
		ID:      "clickable-text",
		Text:    "Click me!",
		OnClick: "echo 'Clicked!'",
	})

	click := modules.Click{Name: "clickable-text", Button: 1}

	// Get the action without executing it
	action, err := textMod.GetClickAction(click)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Has action: %v\n", action != nil)
	fmt.Printf("Action type: %T\n", action)

}
Output:

Has action: true
Action type: *modules.ShellAction

func (*TextModuleV2) HandleClick

func (t *TextModuleV2) HandleClick(ctx context.Context, click Click) error

HandleClick executes the configured shell command if one is set. This method is kept for backward compatibility. New code should use GetClickAction() instead.

func (*TextModuleV2) ID

func (t *TextModuleV2) ID() string

ID returns the unique identifier for this module instance.

func (*TextModuleV2) Render

func (t *TextModuleV2) Render(ctx context.Context) (Block, error)

Render returns the current status block for this module. For the text module, this is very simple - just return the configured text and color.

func (*TextModuleV2) SetColor

func (t *TextModuleV2) SetColor(color string)

SetColor updates the text color.

func (*TextModuleV2) SetText

func (t *TextModuleV2) SetText(text string)

SetText updates the displayed text. This demonstrates how modules can expose methods to update their state.

func (*TextModuleV2) Type

func (t *TextModuleV2) Type() string

Type returns the module type identifier.

type VolumeModuleConfig

type VolumeModuleConfig struct {
	ID           string `yaml:"id"`
	Sink         string `yaml:"sink"`
	Format       string `yaml:"format"`
	Step         int    `yaml:"step"`
	IconMuted    string `yaml:"icon-muted"`
	IconLow      string `yaml:"icon-low"`
	IconMedium   string `yaml:"icon-medium"`
	IconHigh     string `yaml:"icon-high"`
	ColorMuted   string `yaml:"color-muted"`
	ColorNormal  string `yaml:"color-normal"`
	OnClick      string `yaml:"on-click"`
	OnRightClick string `yaml:"on-right-click"`
}

VolumeModuleConfig holds configuration for creating a VolumeModuleV2.

type VolumeModuleV2

type VolumeModuleV2 struct {
	BaseModule
	// contains filtered or unexported fields
}

VolumeModuleV2 displays system volume level and mute status.

Example

ExampleVolumeModuleV2 demonstrates creating a volume module.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	volMod := modules.NewVolumeModuleV2(modules.VolumeModuleConfig{
		ID:     "my-volume",
		Format: "icon-percent",
	})

	fmt.Printf("ID: %s\n", volMod.ID())
	fmt.Printf("Type: %s\n", volMod.Type())

}
Output:

ID: my-volume
Type: volume
Example (Customization)

ExampleVolumeModuleV2_customization demonstrates customizing volume display.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	volMod := modules.NewVolumeModuleV2(modules.VolumeModuleConfig{
		ID:         "custom-volume",
		Format:     "icon-only",
		IconMuted:  "MUTE",
		IconLow:    "LOW",
		IconMedium: "MED",
		IconHigh:   "HIGH",
		ColorMuted: "#ff0000",
	})

	fmt.Printf("ID: %s\n", volMod.ID())

}
Output:

ID: custom-volume
Example (Registry)

ExampleVolumeModuleV2_registry demonstrates creating via registry.

package main

import (
	"fmt"

	"git.lyda.ie/kevin/i3going-on/modules"
)

func main() {
	mod, err := modules.Create("volume-v2", "vol", map[string]any{
		"format": "icon-percent",
		"sink":   "@DEFAULT_SINK@",
	})
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Type: %s\n", mod.Type())

}
Output:

Type: volume

func NewVolumeModuleV2

func NewVolumeModuleV2(cfg VolumeModuleConfig) *VolumeModuleV2

NewVolumeModuleV2 creates a new volume module with the given configuration.

func (*VolumeModuleV2) GetClickAction

func (v *VolumeModuleV2) GetClickAction(click Click) (Action, error)

GetClickAction returns an Action to execute when this module is clicked.

func (*VolumeModuleV2) ID

func (v *VolumeModuleV2) ID() string

ID returns the unique identifier for this module instance.

func (*VolumeModuleV2) Render

func (v *VolumeModuleV2) Render(ctx context.Context) (Block, error)

Render returns the current volume status as a status block.

func (*VolumeModuleV2) Type

func (v *VolumeModuleV2) Type() string

Type returns the module type identifier.

Source Files

  • action.go
  • battery_v2.go
  • block.go
  • clicks.go
  • cpu_v2.go
  • date_v2.go
  • disk_v2.go
  • helpers.go
  • load_v2.go
  • memory_v2.go
  • microphone_v2.go
  • module_v2.go
  • modules.go
  • network_v2.go
  • registry.go
  • script_v2.go
  • text_v2.go
  • volume_v2.go

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL