mirror of
https://github.com/Cian-H/dotfiles.git
synced 2025-12-22 19:31:57 +00:00
Overhaul kb cycle util
This commit is contained in:
Binary file not shown.
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
const valueToChange = "kb_layout"
|
const valueToChange = "kb_layout"
|
||||||
|
|
||||||
|
// readLayouts reads the available keyboard layouts from a file.
|
||||||
func readLayouts(layoutFile string) ([]string, error) {
|
func readLayouts(layoutFile string) ([]string, error) {
|
||||||
file, err := os.Open(layoutFile)
|
file, err := os.Open(layoutFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -31,16 +32,24 @@ func readLayouts(layoutFile string) ([]string, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// To prevent index out of range error in toggleLine function
|
// To prevent index out of range error in toggleLine function,
|
||||||
layouts = append(layouts, layouts[0]) // Cycle back to the first layout
|
// we add the first layout to the end to create a cycle.
|
||||||
|
if len(layouts) > 0 {
|
||||||
|
layouts = append(layouts, layouts[0])
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("no layouts found in %s", layoutFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return layouts, nil
|
return layouts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isTargetLine checks if a line contains the configuration key we want to change.
|
||||||
func isTargetLine(line string) bool {
|
func isTargetLine(line string) bool {
|
||||||
return strings.Contains(line, valueToChange)
|
return strings.Contains(line, valueToChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readCurrentConfig reads all lines from the configuration file.
|
||||||
func readCurrentConfig(configFile string) ([]string, error) {
|
func readCurrentConfig(configFile string) ([]string, error) {
|
||||||
file, err := os.Open(configFile)
|
file, err := os.Open(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -54,13 +63,10 @@ func readCurrentConfig(configFile string) ([]string, error) {
|
|||||||
lines = append(lines, scanner.Text())
|
lines = append(lines, scanner.Text())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
return lines, scanner.Err()
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeNewConfig writes the updated lines back to the configuration file.
|
||||||
func writeNewConfig(lines []string, configFile string) error {
|
func writeNewConfig(lines []string, configFile string) error {
|
||||||
file, err := os.Create(configFile)
|
file, err := os.Create(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -68,39 +74,55 @@ func writeNewConfig(lines []string, configFile string) error {
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
|
writer := bufio.NewWriter(file)
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
_, err := file.WriteString(line + "\n")
|
if _, err := writer.WriteString(line + "\n"); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return writer.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toggleLine finds the current layout in a line, and replaces it with the next one from the list.
|
||||||
func toggleLine(line string, layouts []string) string {
|
func toggleLine(line string, layouts []string) string {
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
|
|
||||||
|
// Preserve indentation
|
||||||
indentLength := len(line) - len(strings.TrimLeft(line, " \t"))
|
indentLength := len(line) - len(strings.TrimLeft(line, " \t"))
|
||||||
builder.Grow(len(line)) // Preallocate enough space to avoid multiple allocations
|
|
||||||
|
|
||||||
builder.WriteString(strings.Repeat(" ", indentLength))
|
builder.WriteString(strings.Repeat(" ", indentLength))
|
||||||
|
|
||||||
|
// Separate code from comments
|
||||||
codeAndComments := strings.SplitN(line, "#", 2)
|
codeAndComments := strings.SplitN(line, "#", 2)
|
||||||
code := strings.TrimSpace(codeAndComments[0])
|
code := strings.TrimSpace(codeAndComments[0])
|
||||||
tokens := strings.Fields(code)
|
tokens := strings.Fields(code)
|
||||||
currentLayout := tokens[len(tokens)-1]
|
currentLayout := tokens[len(tokens)-1]
|
||||||
index := (strings.Index(strings.Join(layouts, " "), currentLayout)/len(currentLayout) + 1) % len(layouts)
|
|
||||||
tokens[len(tokens)-1] = layouts[index]
|
// Find the index of the current layout and get the next one
|
||||||
|
currentIndex := -1
|
||||||
|
for i, layout := range layouts {
|
||||||
|
if layout == currentLayout {
|
||||||
|
currentIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The last element is a duplicate of the first, so (currentIndex+1) is always safe.
|
||||||
|
if currentIndex != -1 {
|
||||||
|
tokens[len(tokens)-1] = layouts[currentIndex+1]
|
||||||
|
}
|
||||||
|
|
||||||
builder.WriteString(strings.Join(tokens, " "))
|
builder.WriteString(strings.Join(tokens, " "))
|
||||||
|
|
||||||
|
// Re-append the comment if it exists
|
||||||
if len(codeAndComments) > 1 {
|
if len(codeAndComments) > 1 {
|
||||||
builder.WriteString(" # ")
|
builder.WriteString(" # ")
|
||||||
builder.WriteString(codeAndComments[1])
|
builder.WriteString(strings.TrimSpace(codeAndComments[1]))
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.String()
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cycleKbLayout orchestrates the entire process of reading, modifying, and writing the config.
|
||||||
func cycleKbLayout(configFile, layoutFile string) error {
|
func cycleKbLayout(configFile, layoutFile string) error {
|
||||||
layouts, err := readLayouts(layoutFile)
|
layouts, err := readLayouts(layoutFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -123,26 +145,60 @@ func cycleKbLayout(configFile, layoutFile string) error {
|
|||||||
return writeNewConfig(newLines, configFile)
|
return writeNewConfig(newLines, configFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expandPath resolves file paths by handling the '~' shorthand for the home directory
|
||||||
|
// and converting relative paths to absolute paths based on the executable's location.
|
||||||
|
func expandPath(path string) (string, error) {
|
||||||
|
// --- Step 1: Handle tilde expansion ---
|
||||||
|
if strings.HasPrefix(path, "~") {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("could not get user home directory: %w", err)
|
||||||
|
}
|
||||||
|
// Replace "~" with the actual home directory path.
|
||||||
|
// filepath.Join correctly handles path separators.
|
||||||
|
path = filepath.Join(homeDir, path[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Step 2: Handle relative paths ---
|
||||||
|
// If the path is still not absolute after potential tilde expansion,
|
||||||
|
// join it with the executable's directory.
|
||||||
|
if !filepath.IsAbs(path) {
|
||||||
|
exeDir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("could not get executable directory: %w", err)
|
||||||
|
}
|
||||||
|
path = filepath.Join(exeDir, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Define flags for the input files
|
// Define flags for the input files
|
||||||
var configFile = flag.String("c", "inputs.conf", "Path to the configuration file")
|
var configFile = flag.String("c", "inputs.conf", "Path to the configuration file (e.g., ~/.config/hypr/inputs.conf)")
|
||||||
var layoutFile = flag.String("l", "kb_layouts.txt", "Path to the keyboard layout file")
|
var layoutFile = flag.String("l", "kb_layouts.txt", "Path to the keyboard layout file (e.g., ~/.config/hypr/kb_layouts.txt)")
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Parse command-line flags
|
// Parse command-line flags
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// Determine the absolute paths based on provided flags
|
// Resolve the full, absolute path for the config file, handling '~'
|
||||||
hyprDir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
configFilePath, err := expandPath(*configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error getting directory: %v", err)
|
fmt.Printf("Error processing config file path: %v\n", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
configFilePath := filepath.Join(hyprDir, *configFile)
|
// Resolve the full, absolute path for the layout file, handling '~'
|
||||||
layoutFilePath := filepath.Join(hyprDir, *layoutFile)
|
layoutFilePath, err := expandPath(*layoutFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error processing layout file path: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Call the cycleKbLayout function with the paths from the flags
|
// Call the main logic with the fully resolved paths
|
||||||
if err := cycleKbLayout(configFilePath, layoutFilePath); err != nil {
|
if err := cycleKbLayout(configFilePath, layoutFilePath); err != nil {
|
||||||
fmt.Println("Error cycling keyboard layout: %v", err)
|
fmt.Printf("Error cycling keyboard layout: %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
module cycle_kb_layout
|
module cycle_kb_layout
|
||||||
|
|
||||||
go 1.21.8
|
go 1.24.4
|
||||||
|
|||||||
Reference in New Issue
Block a user