Skip to content

Commit

Permalink
refactoring, improved code structure, improved layout and style, flags
Browse files Browse the repository at this point in the history
  • Loading branch information
stigoleg committed Dec 19, 2024
1 parent f3ec1a1 commit 034e4c9
Show file tree
Hide file tree
Showing 14 changed files with 466 additions and 155 deletions.
30 changes: 20 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ A lightweight, cross-platform utility to prevent your system from going to sleep

## Features


- 🔄 Configurable keep-alive duration
- 🔄 Configurable keep-alive duration with flexible time format
- 💻 Cross-platform support (macOS, Windows, Linux)
- ⚡ Lightweight and efficient
- 🎯 Simple and intuitive to use
Expand Down Expand Up @@ -51,31 +50,44 @@ sudo mv keepalive /usr/local/bin/

## Usage

1. Start the application:
### Command-Line Options

```
Flags:
-d, --duration string Duration to keep system alive (e.g., "2h30m" or "150")
-v, --version Show version information
-h, --help Show help message
```

The duration can be specified in two formats:
- As a time duration (e.g., "2h30m", "1h", "45m")
- As minutes (e.g., "150" for 2.5 hours)

### Interactive Mode

1. Start the application without flags to enter interactive mode:
```bash
keepalive
```

2. Use arrow keys (↑/↓) or j/k to navigate the menu
3. Press Enter to select an option
4. When entering minutes, use numbers only (e.g., "150" for 2.5 hours)
5. Press q or Esc to quit
4. Press q or Esc to quit

## How It Works

Keep-Alive uses platform-specific APIs to prevent your system from entering sleep mode:

- **macOS**: Uses the `caffeinate` command to prevent system and display sleep
- **Windows**: Uses SetThreadExecutionState to prevent system sleep
- **Windows**: Uses SetThreadExecutionState API to prevent system sleep
- **Linux**: Uses systemd-inhibit to prevent the system from going idle/sleep

The application provides three main options:
1. Keep system awake indefinitely
2. Keep system awake for X minutes (enter the number of minutes)
2. Keep system awake for a specified duration
3. Quit the application

When running with a timer, the application shows a countdown of the remaining time. You can stop the keep-alive at any time by pressing Enter to return to the menu or q/Esc to quit the application.

## Dependencies

### Runtime Dependencies
Expand All @@ -101,8 +113,6 @@ cd keep-alive
go build -o keepalive ./cmd/keepalive
```



## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.
Expand Down
30 changes: 25 additions & 5 deletions cmd/keepalive/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,47 @@ package main

import (
"log"
"os"

"keepalive/internal/config"
"keepalive/internal/ui"

tea "github.com/charmbracelet/bubbletea"
)

const appVersion = "1.0.2"

func main() {
cfg, err := config.ParseFlags(appVersion)
if err != nil {
log.Fatal(err)
}

f, err := tea.LogToFile("debug.log", "debug")
if err != nil {
log.Fatal(err)
}
defer f.Close()

var model ui.Model
if cfg.Duration > 0 {
model = ui.InitialModelWithDuration(cfg.Duration)
p := tea.NewProgram(
model,
tea.WithAltScreen(),
tea.WithMouseCellMotion(),
)
if _, err := p.Run(); err != nil {
log.Fatal(err)
}
return
}

model = ui.InitialModel()
p := tea.NewProgram(
ui.InitialModel(),
model,
tea.WithAltScreen(),
tea.WithInput(os.Stdin),
tea.WithOutput(os.Stdout),
tea.WithMouseCellMotion(),
)

if _, err := p.Run(); err != nil {
log.Fatal(err)
}
Expand Down
Binary file modified docs/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 6 additions & 1 deletion docs/demo.tape
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Set Framerate 60
Set TypingSpeed 0.1
Set PlaybackSpeed 0.5
Set Shell "bash"
Set FontSize 32
Set FontSize 26
Set Width 1200
Set Height 600

Expand All @@ -27,4 +27,9 @@ Type "20"
Sleep 500ms
Enter
Sleep 15s
Enter
Sleep 1s
Type "j"
Sleep 1s
Enter

54 changes: 54 additions & 0 deletions internal/config/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package config

import (
"flag"
"fmt"
"os"
"keepalive/internal/ui"
"keepalive/internal/util"
)

type Config struct {
Duration int
ShowVersion bool
}

func ParseFlags(version string) (*Config, error) {
flags := flag.NewFlagSet("keepalive", flag.ExitOnError)
flags.Usage = func() {
model := ui.InitialModel()
model.ShowHelp = true
fmt.Print(model.View())
}

duration := flags.String("duration", "", "Duration to keep system alive (e.g., \"2h30m\")")
flags.StringVar(duration, "d", "", "Duration to keep system alive (e.g., \"2h30m\")")
showVersion := flags.Bool("version", false, "Show version information")
flags.BoolVar(showVersion, "v", false, "Show version information")

if err := flags.Parse(os.Args[1:]); err != nil {
if err == flag.ErrHelp {
os.Exit(0)
}
return nil, err
}

if *showVersion {
fmt.Printf("Keep-Alive Version: %s\n", version)
os.Exit(0)
}

var minutes int
if *duration != "" {
d, err := util.ParseDuration(*duration)
if err != nil {
return nil, fmt.Errorf("error parsing duration: %v", err)
}
minutes = int(d.Minutes())
}

return &Config{
Duration: minutes,
ShowVersion: *showVersion,
}, nil
}
110 changes: 40 additions & 70 deletions internal/keepalive/keepalive.go
Original file line number Diff line number Diff line change
@@ -1,111 +1,81 @@
package keepalive

import (
"context"
"errors"
"os/exec"
"runtime"
"sync"
"time"
)

// Keeper manages the keep-alive state across platforms.
// Keeper manages the system's keep-alive state
type Keeper struct {
mu sync.Mutex
cancel context.CancelFunc
platformStop func() error
running bool
running bool
mu sync.Mutex
timer *time.Timer
}

// StartIndefinite keeps the system awake indefinitely using platform-specific methods.
// IsRunning returns whether the keep-alive is currently active
func (k *Keeper) IsRunning() bool {
k.mu.Lock()
defer k.mu.Unlock()
return k.running
}

// StartIndefinite starts keeping the system alive indefinitely
func (k *Keeper) StartIndefinite() error {
k.mu.Lock()
defer k.mu.Unlock()

if k.running {
return nil
return errors.New("keep-alive already running")
}

switch runtime.GOOS {
case "windows":
// Windows-specific logic in keepalive_windows.go
if err := setWindowsKeepAlive(); err != nil {
return err
}
k.platformStop = stopWindowsKeepAlive

case "darwin":
ctx, cancel := context.WithCancel(context.Background())
cmd := exec.CommandContext(ctx, "caffeinate", "-dims")
if err := cmd.Start(); err != nil {
cancel()
return err
}
k.cancel = func() {
cancel()
_ = cmd.Wait()
}
k.platformStop = func() error { return nil }

case "linux":
ctx, cancel := context.WithCancel(context.Background())
// systemd-inhibit prevents the system from going idle/sleep.
cmd := exec.CommandContext(ctx, "systemd-inhibit", "--what=idle", "--mode=block", "bash", "-c", "while true; do sleep 3600; done")
if err := cmd.Start(); err != nil {
cancel()
return err
}
k.cancel = func() {
cancel()
_ = cmd.Wait()
}
k.platformStop = func() error { return nil }

default:
return errors.New("unsupported platform")
if err := setWindowsKeepAlive(); err != nil {
return err
}

k.running = true
return nil
}

// StartTimed keeps the system awake for the specified number of minutes, then stops.
func (k *Keeper) StartTimed(minutes int) error {
if minutes <= 0 {
return errors.New("minutes must be > 0")
// StartTimed starts keeping the system alive for the specified duration
func (k *Keeper) StartTimed(d time.Duration) error {
k.mu.Lock()
defer k.mu.Unlock()

if k.running {
return errors.New("keep-alive already running")
}
if err := k.StartIndefinite(); err != nil {

if err := setWindowsKeepAlive(); err != nil {
return err
}

go func() {
time.Sleep(time.Duration(minutes) * time.Minute)
_ = k.Stop()
}()
k.running = true
k.timer = time.AfterFunc(d, func() {
k.Stop()
})

return nil
}

// Stop stops keeping the system awake, restoring normal behavior.
// Stop stops keeping the system alive
func (k *Keeper) Stop() error {
k.mu.Lock()
defer k.mu.Unlock()

if !k.running {
return nil
}
if k.cancel != nil {
k.cancel()

if k.timer != nil {
k.timer.Stop()
k.timer = nil
}
if k.platformStop != nil {
if err := k.platformStop(); err != nil {
return err
}

if err := stopWindowsKeepAlive(); err != nil {
return err
}

k.running = false
return nil
}

// IsRunning returns whether the system is currently being kept awake.
func (k *Keeper) IsRunning() bool {
k.mu.Lock()
defer k.mu.Unlock()
return k.running
}
Loading

0 comments on commit 034e4c9

Please sign in to comment.