package main import ( "bufio" "context" "log" "os" "os/exec" "os/signal" "strings" "sync" "syscall" "time" ) func main() { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() cmd := exec.CommandContext(ctx, "dbus-monitor", "--session", "type='signal',interface='org.gnome.ScreenSaver'") cmdReader, err := cmd.StdoutPipe() if err != nil { log.Fatalln("cmd.StdoutPipe:", err) } defer func() { _ = cmdReader.Close() }() cmd.Stderr = os.Stderr var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() _ = cmd.Run() }() screenSaverCh := make(chan bool, 1) defer close(screenSaverCh) wg.Add(1) go func() { defer wg.Done() scanner := bufio.NewScanner(cmdReader) for scanner.Scan() { select { case <-ctx.Done(): return default: if !strings.Contains(scanner.Text(), "member=ActiveChanged") { continue } if !scanner.Scan() { continue } screenSaverCh <- strings.Contains(scanner.Text(), "boolean true") } } }() wg.Add(1) go func() { defer wg.Done() isScreenSaverActive := false ticker := time.NewTicker(30 * time.Minute) defer ticker.Stop() for { select { case <-ctx.Done(): return case b := <-screenSaverCh: isScreenSaverActive = b case <-ticker.C: if isScreenSaverActive { continue } _ = sendNotification(ctx) } } }() wg.Wait() log.Println("DONE") } func sendNotification(ctx context.Context) error { return exec.CommandContext(ctx, "notify-send", "title", "description").Run() }