From e99f32ded8baef9fe070d3b6382841bfa4a28a30 Mon Sep 17 00:00:00 2001 From: Hadi <112569860+anotherhadi@users.noreply.github.com> Date: Thu, 20 Aug 2026 19:51:33 +0200 Subject: [PATCH] Print on exit -> copy to clipboard Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com> --- internal/ui/actionmodal.go | 6 ++-- internal/ui/keys.go | 6 ++-- internal/ui/model.go | 68 +++++++++++++++++++++++++++++--------- main.go | 13 +------- 4 files changed, 60 insertions(+), 33 deletions(-) diff --git a/internal/ui/actionmodal.go b/internal/ui/actionmodal.go index d8fdf13..740e93e 100644 --- a/internal/ui/actionmodal.go +++ b/internal/ui/actionmodal.go @@ -1,6 +1,7 @@ package ui import ( + "fmt" "strings" "charm.land/bubbles/v2/key" @@ -50,7 +51,8 @@ func (a actionModal) Update(msg tea.Msg) (tea.Model, tea.Cmd) { it := item.(actionItem) if it.nixos { rule := guard.NixOSRule(a.dev, it.status) - return a, tea.Batch(modal.Close(), func() tea.Msg { return nixRuleMsg{rule: rule} }) + key := a.dev.VidPid + return a, tea.Batch(modal.Close(), func() tea.Msg { return nixRuleMsg{key: key, rule: rule} }) } return a, tea.Batch(modal.Close(), doAction(a.dev.ID, it.fn, it.permanent)) } @@ -74,7 +76,7 @@ func (a actionModal) View() tea.View { hintStyle := lipgloss.NewStyle().Foreground(style.S.Muted) parts := []string{a.list.View(), ""} if a.rulesManaged { - parts = append(parts, hintStyle.Render("[NixOS: perm rules printed on exit]")) + parts = append(parts, hintStyle.Render(fmt.Sprintf("[NixOS: perm rules queued — press %s to copy]", listKeys.CopyRules.Help().Key))) } parts = append(parts, hintStyle.Render("↑↓ navigate enter confirm esc cancel")) return tea.NewView(strings.Join(parts, "\n")) diff --git a/internal/ui/keys.go b/internal/ui/keys.go index b9d573a..176c42c 100644 --- a/internal/ui/keys.go +++ b/internal/ui/keys.go @@ -18,7 +18,7 @@ type listKeyMap struct { AllowAll key.Binding AllowAllPerm key.Binding - PrintAll key.Binding + CopyRules key.Binding Up key.Binding Down key.Binding @@ -32,7 +32,7 @@ func (k listKeyMap) globalBindings() []key.Binding { return []key.Binding{ k.Open, k.Filter, k.Refresh, k.Quit, k.Allow, k.AllowPerm, k.Block, k.BlockPerm, k.Reject, k.RejectPerm, - k.AllowAll, k.AllowAllPerm, k.PrintAll, + k.AllowAll, k.AllowAllPerm, k.CopyRules, k.Up, k.Down, k.GoToStart, k.GoToEnd, k.PrevPage, k.NextPage, } } @@ -52,7 +52,7 @@ var listKeys = listKeyMap{ RejectPerm: key.NewBinding(key.WithKeys("E"), key.WithHelp("E", "reject (perm)")), AllowAll: key.NewBinding(key.WithKeys("w"), key.WithHelp("w", "allow all")), AllowAllPerm: key.NewBinding(key.WithKeys("W"), key.WithHelp("W", "allow all (perm)")), - PrintAll: key.NewBinding(key.WithKeys("p"), key.WithHelp("p", "print all rules")), + CopyRules: key.NewBinding(key.WithKeys("y"), key.WithHelp("y", "copy rules")), Up: key.NewBinding(key.WithKeys("up", "k"), key.WithHelp("↑/k", "up")), Down: key.NewBinding(key.WithKeys("down", "j"), key.WithHelp("↓/j", "down")), diff --git a/internal/ui/model.go b/internal/ui/model.go index 2714078..63e0440 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -54,14 +54,6 @@ type Model struct { fatalShown bool } -func (m Model) PendingRules() []string { - rules := make([]string, len(m.pendingRules)) - for i, r := range m.pendingRules { - rules[i] = r.rule - } - return rules -} - func New() Model { l := bubbles.NewList(nil, 0, 0) l.SetDelegate(deviceDelegate{}) @@ -261,8 +253,15 @@ func (m Model) updateList(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { return m, queueNixOSRules(m.visibleDevices(), guard.Allowed) } return m, doBulkAction(m.visibleDeviceIDs(), guard.AllowDevice, true) - case key.Matches(msg, listKeys.PrintAll): - return m, queueCurrentStateRules(m.visibleDevices()) + case key.Matches(msg, listKeys.CopyRules): + toCopy := m.pendingRules + if len(toCopy) == 0 { + toCopy = mergeCurrentStateRules(nil, m.visibleDevices()) + } + cmd := copyRulesCmd(toCopy, m.rulesManaged) + m.pendingRules = nil + m.resizeList() + return m, cmd } if hasSelection { if cmd := m.deviceActionCmd(msg, dev); cmd != nil { @@ -392,7 +391,7 @@ func (m Model) renderPendingRulesLine() string { noun = "rules" } label := infoLabelStyle.Render("Pending rules") - return label + warnStyle.Render(fmt.Sprintf("%d %s queued (printed on exit)", count, noun)) + return label + warnStyle.Render(fmt.Sprintf("%d %s queued (press %s to copy)", count, noun, listKeys.CopyRules.Help().Key)) } func (m Model) listHeight() int { @@ -488,14 +487,51 @@ func queueNixOSRules(devices []guard.Device, status guard.Status) tea.Cmd { return tea.Batch(cmds...) } -func queueCurrentStateRules(devices []guard.Device) tea.Cmd { - cmds := make([]tea.Cmd, len(devices)) - for i, d := range devices { +func mergeCurrentStateRules(pending []pendingRule, devices []guard.Device) []pendingRule { + for _, d := range devices { key := d.VidPid rule := guard.NixOSRule(d, d.Status) - cmds[i] = func() tea.Msg { return nixRuleMsg{key: key, rule: rule} } + if i := slices.IndexFunc(pending, func(r pendingRule) bool { return r.key == key }); i >= 0 { + pending[i].rule = rule + } else { + pending = append(pending, pendingRule{key: key, rule: rule}) + } } - return tea.Batch(cmds...) + return pending +} + +func copyRulesCmd(pending []pendingRule, nixos bool) tea.Cmd { + if len(pending) == 0 { + return notification.Show("Copy rules", "No rules to copy.", notification.Warning) + } + rules := make([]string, len(pending)) + for i, r := range pending { + rules[i] = r.rule + } + text := formatRulesForClipboard(rules, nixos) + noun := "rule" + if len(rules) > 1 { + noun = "rules" + } + msg := fmt.Sprintf("%d %s copied to clipboard.", len(rules), noun) + return tea.Batch( + tea.SetClipboard(text), + notification.Show("Copy rules", msg, notification.Success), + ) +} + +func formatRulesForClipboard(rules []string, nixos bool) string { + if !nixos { + return strings.Join(rules, "\n") + } + var b strings.Builder + b.WriteString("# Add to your NixOS configuration:\n") + b.WriteString("services.usbguard.rules = lib.mkAfter ''\n") + for _, r := range rules { + b.WriteString(" " + r + "\n") + } + b.WriteString("'';") + return b.String() } type actionBinding struct { diff --git a/main.go b/main.go index f6ac443..66ada4e 100644 --- a/main.go +++ b/main.go @@ -23,19 +23,8 @@ func main() { } p := tea.NewProgram(ui.New()) - m, err := p.Run() - if err != nil { + if _, err := p.Run(); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } - if fm, ok := m.(ui.Model); ok { - if rules := fm.PendingRules(); len(rules) > 0 { - fmt.Println("# Add to your NixOS configuration:") - fmt.Println("services.usbguard.rules = lib.mkAfter ''") - for _, rule := range rules { - fmt.Println(" ", rule) - } - fmt.Println("'';") - } - } }