diff --git a/README.md b/README.md index c9a494a9..352130d7 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,7 @@ Features in *italics* are in the master branch, but not in the latest release. - Ability to stream content instead of downloading it first - Highlighting of preformatted code blocks that list a language in the alt text - *Search in pages with Ctrl-F* +- *Run custom commands using the current or selected URL as an argument* ## Usage & Configuration diff --git a/command/command.go b/command/command.go new file mode 100644 index 00000000..cb10a064 --- /dev/null +++ b/command/command.go @@ -0,0 +1,22 @@ +package command + +import ( + "os/exec" + "strings" +) + +// RunCommand runs `command`, replacing the string "${url}" with `url`. +func RunCommand(command string, url string) (string, error) { + cmdWithURL := strings.ReplaceAll(command, "${url}", url) + cmdSplit := strings.SplitN(cmdWithURL, " ", 2) + if len(cmdSplit) > 1 { + if err := exec.Command(cmdSplit[0], cmdSplit[1]).Start(); err != nil { + return "", err + } + return "Ran command " + cmdSplit[0] + " with args " + cmdSplit[1], nil + } + if err := exec.Command(cmdWithURL).Start(); err != nil { + return "", err + } + return "Ran command " + cmdWithURL, nil +} diff --git a/config/config.go b/config/config.go index ae67c207..f4655d65 100644 --- a/config/config.go +++ b/config/config.go @@ -207,6 +207,16 @@ func Init() error { viper.SetDefault("a-general.page_max_time", 10) viper.SetDefault("a-general.scrollbar", "auto") viper.SetDefault("a-general.underline", true) + viper.SetDefault("commands.command1", "") + viper.SetDefault("commands.command2", "") + viper.SetDefault("commands.command3", "") + viper.SetDefault("commands.command4", "") + viper.SetDefault("commands.command5", "") + viper.SetDefault("commands.command6", "") + viper.SetDefault("commands.command7", "") + viper.SetDefault("commands.command8", "") + viper.SetDefault("commands.command9", "") + viper.SetDefault("commands.command0", "") viper.SetDefault("keybindings.bind_reload", []string{"R", "Ctrl-R"}) viper.SetDefault("keybindings.bind_home", "Backspace") viper.SetDefault("keybindings.bind_bookmarks", "Ctrl-B") @@ -250,6 +260,26 @@ func Init() error { viper.SetDefault("keybindings.bind_tab8", "*") viper.SetDefault("keybindings.bind_tab9", "(") viper.SetDefault("keybindings.bind_tab0", ")") + viper.SetDefault("keybindings.bind_command1", "Alt-!") + viper.SetDefault("keybindings.bind_command2", "Alt-@") + viper.SetDefault("keybindings.bind_command3", "Alt-#") + viper.SetDefault("keybindings.bind_command4", "Alt-$") + viper.SetDefault("keybindings.bind_command5", "Alt-%") + viper.SetDefault("keybindings.bind_command6", "Alt-^") + viper.SetDefault("keybindings.bind_command7", "Alt-&") + viper.SetDefault("keybindings.bind_command8", "Alt-*") + viper.SetDefault("keybindings.bind_command9", "Alt-(") + viper.SetDefault("keybindings.bind_command0", "Alt-)") + viper.SetDefault("keybindings.bind_command_target1", "Alt-1") + viper.SetDefault("keybindings.bind_command_target2", "Alt-2") + viper.SetDefault("keybindings.bind_command_target3", "Alt-3") + viper.SetDefault("keybindings.bind_command_target4", "Alt-4") + viper.SetDefault("keybindings.bind_command_target5", "Alt-5") + viper.SetDefault("keybindings.bind_command_target6", "Alt-6") + viper.SetDefault("keybindings.bind_command_target7", "Alt-7") + viper.SetDefault("keybindings.bind_command_target8", "Alt-8") + viper.SetDefault("keybindings.bind_command_target9", "Alt-9") + viper.SetDefault("keybindings.bind_command_target0", "Alt-0") viper.SetDefault("keybindings.bind_copy_page_url", "C") viper.SetDefault("keybindings.bind_copy_target_url", "c") viper.SetDefault("keybindings.bind_beginning", []string{"Home", "g"}) diff --git a/config/default.go b/config/default.go index 0502de6c..341d4463 100644 --- a/config/default.go +++ b/config/default.go @@ -112,6 +112,26 @@ underline = true # Same as [auth.certs] but the path is to the client key file. +[commands] +# Define up to 10 custom commands to execute on the corresponding hotkey press. +# Commands are run in a new process and will not terminate when Amfora is closed. +# If you need your command to accept additional input, it is recommended to open +# a GUI or use a terminal multiplexer like screen or tmux. The string ${url} will +# be replaced with the current or selected URL. Note that pipes and redirections +# are not allowed, if these are needed then you should set up a script. Use only +# absolute paths and/or reference executables in your $PATH. +# command1 = "my-script -a -b -c ${url}" +# command2 = "" +# command3 = "" +# command4 = "" +# command5 = "" +# command6 = "" +# command7 = "" +# command8 = "" +# command9 = "" +# command0 = "" + + [keybindings] # If you have a non-US keyboard, use bind_tab1 through bind_tab0 to # setup the shift-number bindings: Eg, for US keyboards (the default): @@ -159,6 +179,32 @@ underline = true # bind_link9 = "9" # bind_link0 = "0" +# The bind_command[0-9] options are for the command hotkeys. This will pass the URL of the +# current tab as an argument. +# bind_command1 = "Alt-!" +# bind_command2 = "Alt-@" +# bind_command3 = "Alt-#" +# bind_command4 = "Alt-$" +# bind_command5 = "Alt-%" +# bind_command6 = "Alt-^" +# bind_command7 = "Alt-&" +# bind_command8 = "Alt-*" +# bind_command9 = "Alt-(" +# bind_command0 = "Alt-)" + +# The bind_commandtarget[0-9] options are for command hotkeys that operate on the currently +# highlighted link. This will pass the URL of the highlighted link as an argument. +# bind_command_target1 = "Alt-1" +# bind_command_target2 = "Alt-2" +# bind_command_target3 = "Alt-3" +# bind_command_target4 = "Alt-4" +# bind_command_target5 = "Alt-5" +# bind_command_target6 = "Alt-6" +# bind_command_target7 = "Alt-7" +# bind_command_target8 = "Alt-8" +# bind_command_target9 = "Alt-9" +# bind_command_target0 = "Alt-0" + # All keybindings: # # bind_bottom diff --git a/config/keybindings.go b/config/keybindings.go index 10d49f05..f9933da7 100644 --- a/config/keybindings.go +++ b/config/keybindings.go @@ -8,33 +8,55 @@ import ( "github.com/spf13/viper" ) -// NOTE: CmdLink[1-90] and CmdTab[1-90] need to be in-order and consecutive -// This property is used to simplify key handling in display/display.go +// NOTE: CmdLink[1-90], CmdTab[1-90], CmdCommand[1-90], and +// CmdCommandTarget[1-90] need to be in-order and consecutive. This +// property is used to simplify key handling in display/display.go +// and display/tab.go type Command int const ( - CmdInvalid Command = 0 - CmdLink1 = 1 - CmdLink2 = 2 - CmdLink3 = 3 - CmdLink4 = 4 - CmdLink5 = 5 - CmdLink6 = 6 - CmdLink7 = 7 - CmdLink8 = 8 - CmdLink9 = 9 - CmdLink0 = 10 - CmdTab1 = 11 - CmdTab2 = 12 - CmdTab3 = 13 - CmdTab4 = 14 - CmdTab5 = 15 - CmdTab6 = 16 - CmdTab7 = 17 - CmdTab8 = 18 - CmdTab9 = 19 - CmdTab0 = 20 - CmdBottom = iota + CmdInvalid Command = 0 + CmdLink1 = 1 + CmdLink2 = 2 + CmdLink3 = 3 + CmdLink4 = 4 + CmdLink5 = 5 + CmdLink6 = 6 + CmdLink7 = 7 + CmdLink8 = 8 + CmdLink9 = 9 + CmdLink0 = 10 + CmdTab1 = 11 + CmdTab2 = 12 + CmdTab3 = 13 + CmdTab4 = 14 + CmdTab5 = 15 + CmdTab6 = 16 + CmdTab7 = 17 + CmdTab8 = 18 + CmdTab9 = 19 + CmdTab0 = 20 + CmdCommand1 = 21 + CmdCommand2 = 22 + CmdCommand3 = 23 + CmdCommand4 = 24 + CmdCommand5 = 25 + CmdCommand6 = 26 + CmdCommand7 = 27 + CmdCommand8 = 28 + CmdCommand9 = 29 + CmdCommand0 = 30 + CmdCommandTarget1 = 31 + CmdCommandTarget2 = 32 + CmdCommandTarget3 = 33 + CmdCommandTarget4 = 34 + CmdCommandTarget5 = 35 + CmdCommandTarget6 = 36 + CmdCommandTarget7 = 37 + CmdCommandTarget8 = 38 + CmdCommandTarget9 = 39 + CmdCommandTarget0 = 40 + CmdBottom = iota CmdEdit CmdHome CmdBookmarks @@ -203,9 +225,29 @@ func KeyInit() { CmdBeginning: "keybindings.bind_beginning", CmdEnd: "keybindings.bind_end", CmdURLHandlerOpen: "keybindings.bind_url_handler_open", - CmdSearch: "keybindings.bind_search", - CmdNextMatch: "keybindings.bind_next_match", - CmdPrevMatch: "keybindings.bind_prev_match", + CmdCommand1: "keybindings.bind_command1", + CmdCommand2: "keybindings.bind_command2", + CmdCommand3: "keybindings.bind_command3", + CmdCommand4: "keybindings.bind_command4", + CmdCommand5: "keybindings.bind_command5", + CmdCommand6: "keybindings.bind_command6", + CmdCommand7: "keybindings.bind_command7", + CmdCommand8: "keybindings.bind_command8", + CmdCommand9: "keybindings.bind_command9", + CmdCommand0: "keybindings.bind_command0", + CmdCommandTarget1: "keybindings.bind_command_target1", + CmdCommandTarget2: "keybindings.bind_command_target2", + CmdCommandTarget3: "keybindings.bind_command_target3", + CmdCommandTarget4: "keybindings.bind_command_target4", + CmdCommandTarget5: "keybindings.bind_command_target5", + CmdCommandTarget6: "keybindings.bind_command_target6", + CmdCommandTarget7: "keybindings.bind_command_target7", + CmdCommandTarget8: "keybindings.bind_command_target8", + CmdCommandTarget9: "keybindings.bind_command_target9", + CmdCommandTarget0: "keybindings.bind_command_target0", + CmdSearch: "keybindings.bind_search", + CmdNextMatch: "keybindings.bind_next_match", + CmdPrevMatch: "keybindings.bind_prev_match", } // This is split off to allow shift_numbers to override bind_tab[1-90] // (This is needed for older configs so that the default bind_tab values diff --git a/default-config.toml b/default-config.toml index 5184a8bc..ea0985f5 100644 --- a/default-config.toml +++ b/default-config.toml @@ -109,6 +109,26 @@ underline = true # Same as [auth.certs] but the path is to the client key file. +[commands] +# Define up to 10 custom commands to execute on the corresponding hotkey press. +# Commands are run in a new process and will not terminate when Amfora is closed. +# If you need your command to accept additional input, it is recommended to open +# a GUI or use a terminal multiplexer like screen or tmux. The string ${url} will +# be replaced with the current or selected URL. Note that pipes and redirections +# are not allowed, if these are needed then you should set up a script. Use only +# absolute paths and/or reference executables in your $PATH. +# command1 = "my-script -a -b -c ${url}" +# command2 = "" +# command3 = "" +# command4 = "" +# command5 = "" +# command6 = "" +# command7 = "" +# command8 = "" +# command9 = "" +# command0 = "" + + [keybindings] # If you have a non-US keyboard, use bind_tab1 through bind_tab0 to # setup the shift-number bindings: Eg, for US keyboards (the default): @@ -156,6 +176,32 @@ underline = true # bind_link9 = "9" # bind_link0 = "0" +# The bind_command[0-9] options are for the command hotkeys. This will pass the URL of the +# current tab as an argument. +# bind_command1 = "Alt-!" +# bind_command2 = "Alt-@" +# bind_command3 = "Alt-#" +# bind_command4 = "Alt-$" +# bind_command5 = "Alt-%" +# bind_command6 = "Alt-^" +# bind_command7 = "Alt-&" +# bind_command8 = "Alt-*" +# bind_command9 = "Alt-(" +# bind_command0 = "Alt-)" + +# The bind_commandtarget[0-9] options are for command hotkeys that operate on the currently +# highlighted link. This will pass the URL of the highlighted link as an argument. +# bind_command_target1 = "Alt-1" +# bind_command_target2 = "Alt-2" +# bind_command_target3 = "Alt-3" +# bind_command_target4 = "Alt-4" +# bind_command_target5 = "Alt-5" +# bind_command_target6 = "Alt-6" +# bind_command_target7 = "Alt-7" +# bind_command_target8 = "Alt-8" +# bind_command_target9 = "Alt-9" +# bind_command_target0 = "Alt-0" + # All keybindings: # # bind_bottom diff --git a/display/command.go b/display/command.go new file mode 100644 index 00000000..535b9903 --- /dev/null +++ b/display/command.go @@ -0,0 +1,35 @@ +package display + +import ( + "strconv" + + "github.com/makeworld-the-better-one/amfora/command" + "github.com/spf13/viper" +) + +// CustomCommand runs custom commands as defined in the app configuration. +// Commands are zero-indexed, so 0 is command1 and 9 is command0 (10). +func CustomCommand(num int, url string) { + if num < 0 { + num = 0 + } + num++ + if num > 9 { + num = 0 + } + + cmd := viper.GetString("commands.command" + strconv.Itoa(num)) + if len(cmd) > 0 { + msg, err := command.RunCommand(cmd, url) + if err != nil { + go Error("Command Error", err.Error()) + return + } + go Info(msg) + } else { + go Error("Command Error", "Command "+strconv.Itoa(num)+" not defined") + return + } + + App.Draw() +} diff --git a/display/help.go b/display/help.go index 66562dc1..0f065ae3 100644 --- a/display/help.go +++ b/display/help.go @@ -49,6 +49,10 @@ var helpCells = strings.TrimSpace( "%s\tSave the current page to your downloads.\n" + "%s\tView subscriptions\n" + "%s\tAdd or update a subscription\n" + + "%s\tExecute a custom command using the current page URL as an argument.\n" + + "\t(Default: Alt-Shift-NUMBER)\n" + + "%s\tExecute a custom command using the selected URL as an argument.\n" + + "\t(Default: Alt-NUMBER)\n" + "%s\tSearch the page content for a string\n" + "%s\tFind next search match\n" + "%s\tFind previous search match\n" + @@ -82,6 +86,10 @@ func helpInit() { strings.Split(config.GetKeyBinding(config.CmdTab9), ",")[0]) linkKeys := fmt.Sprintf("%s to %s", strings.Split(config.GetKeyBinding(config.CmdLink1), ",")[0], strings.Split(config.GetKeyBinding(config.CmdLink0), ",")[0]) + commandKeys := fmt.Sprintf("%s to %s", strings.Split(config.GetKeyBinding(config.CmdCommand1), ",")[0], + strings.Split(config.GetKeyBinding(config.CmdCommand0), ",")[0]) + commandTargetKeys := fmt.Sprintf("%s to %s", strings.Split(config.GetKeyBinding(config.CmdCommandTarget1), ",")[0], + strings.Split(config.GetKeyBinding(config.CmdCommandTarget0), ",")[0]) helpCells = fmt.Sprintf(helpCells, config.GetKeyBinding(config.CmdMoveLeft), @@ -113,6 +121,8 @@ func helpInit() { config.GetKeyBinding(config.CmdSave), config.GetKeyBinding(config.CmdSub), config.GetKeyBinding(config.CmdAddSub), + commandKeys, + commandTargetKeys, config.GetKeyBinding(config.CmdSearch), config.GetKeyBinding(config.CmdNextMatch), config.GetKeyBinding(config.CmdPrevMatch), diff --git a/display/tab.go b/display/tab.go index 7c1e15ff..bf452d6a 100644 --- a/display/tab.go +++ b/display/tab.go @@ -241,6 +241,29 @@ func makeNewTab() *tab { return nil } } + // Command key: 1-9, 0, COMMAND1 + if cmd >= config.CmdCommand1 && cmd <= config.CmdCommand0 { + commandNum := int(cmd - config.CmdCommand1) + tabURL := tabs[curTab].page.URL + CustomCommand(commandNum, tabURL) + return nil + } + // Target link command key: 1-9, 0, COMMANDTARGET1-COMMANDTARGET10 + if cmd >= config.CmdCommandTarget1 && cmd <= config.CmdCommandTarget0 { + commandNum := int(cmd - config.CmdCommandTarget1) + currentURL := t.page.URL + selectedURL := t.highlightedURL() + + if selectedURL == "" { + return nil + } + u, _ := url.Parse(currentURL) + parsedURL, err := u.Parse(selectedURL) + if err == nil { + CustomCommand(commandNum, parsedURL.String()) + } + return nil + } // Scrolling stuff // Copied in scrollTo