aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/c_defs.go84
-rw-r--r--src/c_exec.go164
-rw-r--r--src/c_hardflip.go68
-rw-r--r--src/c_init.go132
-rw-r--r--src/c_ldirs.go135
-rw-r--r--src/c_lhosts.go144
-rw-r--r--src/c_litems.go221
-rw-r--r--src/c_parse.go112
-rw-r--r--src/c_utils.go107
-rw-r--r--src/i_events.go349
-rw-r--r--src/i_host.go131
-rw-r--r--src/i_info.go343
-rw-r--r--src/i_ui.go365
13 files changed, 2355 insertions, 0 deletions
diff --git a/src/c_defs.go b/src/c_defs.go
new file mode 100644
index 0000000..4145613
--- /dev/null
+++ b/src/c_defs.go
@@ -0,0 +1,84 @@
+/*
+ * ========================
+ * ===== ===============
+ * ====== ================
+ * ====== ================
+ * ====== ==== ==== ==
+ * ====== === == = =
+ * ====== === = == =
+ * = === === = == ====
+ * = === === = == = =
+ * == ===== ==== ==
+ * ========================
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2023-2024, Joe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the organization nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * hardflip: src/c_defs.go
+ * Wed Jan 10 17:11:42 2024
+ * Joe
+ *
+ * constants
+ */
+
+package main
+
+const (
+ W = 0
+ H = 1
+ ERROR_MSG = 0
+ ERROR_ERR = 1
+ DATA_DIR_NAME = "hardflip"
+ VERSION = "v0.1"
+)
+
+const (
+ NORMAL_KEYS_HINTS = `[x](a)dd/(i)nsert host -
+[x](m)kdir -
+[x](s)earch -
+[x](?) help`
+ DELETE_KEYS_HINTS = `(q)uit -
+(y)es -
+(n)o`
+ ERROR_KEYS_HINTS = "[Enter] Ok"
+)
+
+const (
+ NORMAL_MODE = 0
+ DELETE_MODE = 1
+ LOAD_MODE = 2
+ ERROR_MODE = 3
+)
+
+var (
+ HOST_ICONS = [2]string{" ", " "}
+ DIRS_ICONS = [2]string{" ", " "}
+)
diff --git a/src/c_exec.go b/src/c_exec.go
new file mode 100644
index 0000000..5c13f72
--- /dev/null
+++ b/src/c_exec.go
@@ -0,0 +1,164 @@
+/*
+ * ========================
+ * ===== ===============
+ * ====== ================
+ * ====== ================
+ * ====== ==== ==== ==
+ * ====== === == = =
+ * ====== === = == =
+ * = === === = == ====
+ * = === === = == = =
+ * == ===== ==== ==
+ * ========================
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2023-2024, Joe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the organization nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * hardflip: src/c_exec.go
+ * Thu Jan 18 18:57:42 2024
+ * Joe
+ *
+ * exec the command at some point
+ */
+
+package main
+
+import (
+ "os"
+ "os/exec"
+ "strconv"
+)
+
+func c_exec_cmd(cmd_fmt []string) {
+ cmd := exec.Command(cmd_fmt[0], cmd_fmt[1:]...)
+
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ cmd.Run()
+}
+
+func c_format_ssh_jump(host *HostNode) string {
+ jump_fmt := "-oProxyCommand=ssh"
+ if len(host.JumpPriv) > 0 {
+ jump_fmt += " -i " + host.JumpPriv
+ }
+ if host.JumpPort != 0 {
+ jump_fmt += " -p " + strconv.Itoa(int(host.JumpPort))
+ }
+ if len(host.JumpUser) == 0 {
+ jump_fmt += " root"
+ } else {
+ jump_fmt += " " + host.JumpUser
+ }
+ jump_fmt += "@" + host.Jump + " -W %h:%p"
+ return jump_fmt
+}
+
+func c_format_ssh(host *HostNode) []string {
+ cmd_fmt := []string{"ssh"}
+
+ if len(host.Priv) > 0 {
+ cmd_fmt = append(cmd_fmt, "-i", host.Priv)
+ }
+ if len(host.Jump) > 0 {
+ cmd_fmt = append(cmd_fmt, c_format_ssh_jump(host))
+ }
+ if host.Port != 0 {
+ cmd_fmt = append(cmd_fmt, "-p", strconv.Itoa(int(host.Port)))
+ }
+ cmd_fmt = append(cmd_fmt, host.User + "@" + host.Host)
+ return cmd_fmt
+}
+
+func c_format_rdp(host *HostNode) []string {
+ cmd_fmt := []string{"xfreerdp"}
+
+ cmd_fmt = append(cmd_fmt,
+ "/v:" + host.Host,
+ "/u:" + host.User)
+ if len(host.Domain) > 0 {
+ cmd_fmt = append(cmd_fmt, "/d:" + host.Domain)
+ }
+ if len(host.Pass) > 0 {
+ cmd_fmt = append(cmd_fmt, "/p:" + host.Pass)
+ }
+ if host.Port != 0 {
+ cmd_fmt = append(cmd_fmt, "/port:" + strconv.Itoa(int(host.Port)))
+ }
+ if host.Dynamic == true {
+ cmd_fmt = append(cmd_fmt, "/dynamic-resolution")
+ }
+ if host.Quality == 0 {
+ cmd_fmt = append(cmd_fmt,
+ "-aero", "-menu-anims", "-window-drag", "-wallpaper",
+ "-decorations", "-fonts", "-themes",
+ "/bpp:8", "/compression-level:2")
+ } else if host.Quality == 1 {
+ } else {
+ cmd_fmt = append(cmd_fmt,
+ "+aero", "+menu-anims", "+window-drag",
+ "+decorations", "+fonts", "+themes", "/gfx:RFX", "/rfx", "/gdi:hw",
+ "/bpp:32")
+ }
+ cmd_fmt = append(cmd_fmt,
+ "/size:" + strconv.Itoa(int(host.Width)) +
+ "x" + strconv.Itoa(int(host.Height)))
+ return cmd_fmt
+}
+
+func c_format_cmd(host *HostNode, term string) {
+ var cmd_fmt []string
+
+ switch host.Protocol {
+ case 0:
+ cmd_fmt = c_format_ssh(host)
+ case 1:
+ cmd_fmt = c_format_rdp(host)
+ default:
+ c_die("type not found", nil)
+ }
+ if len(term) > 0 {
+ // TODO: setsid
+ if term == "$TERMINAL" {
+ term = os.Getenv("TERMINAL")
+ }
+ cmd_fmt = append([]string{"setsid", term, "-e"}, cmd_fmt...)
+ }
+ c_exec_cmd(cmd_fmt)
+}
+
+func c_exec(host *HostNode, term string) {
+ if host == nil {
+ return
+ }
+ c_format_cmd(host, term)
+}
diff --git a/src/c_hardflip.go b/src/c_hardflip.go
new file mode 100644
index 0000000..e4aec6c
--- /dev/null
+++ b/src/c_hardflip.go
@@ -0,0 +1,68 @@
+/*
+ * ========================
+ * ===== ===============
+ * ====== ================
+ * ====== ================
+ * ====== ==== ==== ==
+ * ====== === == = =
+ * ====== === = == =
+ * = === === = == ====
+ * = === === = == = =
+ * == ===== ==== ==
+ * ========================
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2023-2024, Joe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the organization nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * hardflip: src/c_hardflip.go
+ * Thu Jan 18 12:31:08 2024
+ * Joe
+ *
+ * the main
+ */
+
+package main
+
+// the main data structure, holds up everything important
+type HardData struct {
+ litems *ItemsList
+ ldirs *DirsList
+ ui HardUI
+ opts HardOpts
+ data_dir string
+ folds map[*DirsNode]*ItemsList
+}
+
+func main() {
+ data_dir := c_get_data_dir()
+ opts := HardOpts{true, true, false, ""}
+ i_ui(data_dir, opts)
+}
diff --git a/src/c_init.go b/src/c_init.go
new file mode 100644
index 0000000..6355d94
--- /dev/null
+++ b/src/c_init.go
@@ -0,0 +1,132 @@
+/*
+ * ========================
+ * ===== ===============
+ * ====== ================
+ * ====== ================
+ * ====== ==== ==== ==
+ * ====== === == = =
+ * ====== === = == =
+ * = === === = == ====
+ * = === === = == = =
+ * == ===== ==== ==
+ * ========================
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2023-2024, Joe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the organization nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * hardflip: src/c_init.go
+ * Thu Jan 18 16:23:10 2024
+ * Joe
+ *
+ * init functions
+ */
+
+package main
+
+import (
+ "os"
+ "path/filepath"
+)
+
+type HardOpts struct {
+ Icon bool
+ Loop bool
+ Perc bool
+ Term string
+}
+
+// this function recurses into the specified root directory in order to load
+// every yaml file into memory
+func c_recurse_data_dir(dir, root string, opts HardOpts,
+ ldirs *DirsList,
+ name string, parent *DirsNode, depth uint16,
+ ui *HardUI) {
+ files, err := os.ReadDir(root + dir)
+ if err != nil {
+ c_die("could not read data directory", err)
+ }
+ dir_node := DirsNode{
+ name,
+ parent,
+ depth,
+ &HostList{},
+ nil,
+ }
+ ldirs.add_back(&dir_node)
+ i_draw_load_ui(ui)
+ for _, file := range files {
+ filename := file.Name()
+ if file.IsDir() == true {
+ c_recurse_data_dir(dir + filename + "/", root, opts, ldirs,
+ file.Name(), &dir_node, depth + 1, ui)
+ } else if filepath.Ext(filename) == ".yml" {
+ host_node := c_read_yaml_file(root + dir + filename)
+ if host_node == nil {
+ return
+ }
+ host_node.Filename = filename
+ host_node.Parent = &dir_node
+ dir_node.lhost.add_back(host_node)
+ i_draw_load_ui(ui)
+ }
+ }
+}
+
+func c_load_data_dir(dir string, opts HardOpts, ui *HardUI) *DirsList {
+ ldirs := DirsList{}
+
+ c_recurse_data_dir("", dir + "/", opts, &ldirs, "", nil, 1, ui)
+ return &ldirs
+}
+
+// fills litems sorting with dirs last
+// other sorting algos are concievable
+// this func also sets the root folder to unfolded as it may never be folded
+// this func also sets the default litems.curr
+func c_load_litems(ldirs *DirsList) *ItemsList {
+ litems := ItemsList{}
+
+ for ptr := ldirs.head; ptr != nil; ptr = ptr.next {
+ item := ItemsNode{ Dirs: ptr, Host: nil }
+ litems.add_back(&item)
+ for ptr := ptr.lhost.head; ptr != nil; ptr = ptr.next {
+ item := ItemsNode{ Dirs: nil, Host: ptr }
+ litems.add_back(&item)
+ }
+ }
+ litems.head = litems.head.next
+ if litems.head != nil {
+ litems.head.prev = nil
+ }
+ litems.curr = litems.head
+ litems.draw = litems.head
+ return &litems
+}
diff --git a/src/c_ldirs.go b/src/c_ldirs.go
new file mode 100644
index 0000000..b2978f3
--- /dev/null
+++ b/src/c_ldirs.go
@@ -0,0 +1,135 @@
+/*
+ * ========================
+ * ===== ===============
+ * ====== ================
+ * ====== ================
+ * ====== ==== ==== ==
+ * ====== === == = =
+ * ====== === = == =
+ * = === === = == ====
+ * = === === = == = =
+ * == ===== ==== ==
+ * ========================
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2023-2024, Joe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the organization nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * hardflip: src/c_ldirs.go
+ * Thu Jan 11 18:35:31 2024
+ * Joe
+ *
+ * the directories linked list
+ */
+
+package main
+
+type DirsNode struct {
+ Name string
+ Parent *DirsNode
+ Depth uint16
+ lhost *HostList
+ next *DirsNode
+}
+
+type DirsList struct {
+ head *DirsNode
+ last *DirsNode
+}
+
+// adds a directory node to the list
+func (ldirs *DirsList) add_back(node *DirsNode) {
+ if ldirs.head == nil {
+ ldirs.head = node
+ ldirs.last = ldirs.head
+ return
+ }
+ last := ldirs.last
+ last.next = node
+ ldirs.last = last.next
+}
+
+// removes a dir node from the list
+func (ldirs *DirsList) del(dir *DirsNode) {
+ if ldirs.head == nil {
+ return
+ }
+ if ldirs.head == dir {
+ ldirs.head = ldirs.head.next
+ if ldirs.head == nil {
+ ldirs.last = nil
+ return
+ }
+ return
+ }
+ if ldirs.last == dir {
+ ptr := ldirs.head
+ for ptr.next != nil {
+ ptr = ptr.next
+ }
+ ldirs.last = ptr
+ ldirs.last.next = nil
+ return
+ }
+ ptr := ldirs.head
+ for ptr.next != nil && ptr.next != dir {
+ ptr = ptr.next
+ }
+ if ptr.next == dir {
+ ptr.next = ptr.next.next
+ }
+}
+
+// returns a string with the full path of the dir
+func (dir *DirsNode) path() string {
+ var path string
+
+ if dir == nil {
+ return ""
+ }
+ curr := dir
+ for curr != nil {
+ path = curr.Name + "/" + path
+ curr = curr.Parent
+ }
+ return path
+}
+
+func (ldirs *DirsList) prev(dir *DirsNode) *DirsNode {
+ if ldirs.head == dir {
+ return dir
+ }
+ for ptr := ldirs.head; ptr != nil; ptr = ptr.next {
+ if ptr.next == dir {
+ return ptr
+ }
+ }
+ return nil
+}
diff --git a/src/c_lhosts.go b/src/c_lhosts.go
new file mode 100644
index 0000000..654da13
--- /dev/null
+++ b/src/c_lhosts.go
@@ -0,0 +1,144 @@
+/*
+ * ========================
+ * ===== ===============
+ * ====== ================
+ * ====== ================
+ * ====== ==== ==== ==
+ * ====== === == = =
+ * ====== === = == =
+ * = === === = == ====
+ * = === === = == = =
+ * == ===== ==== ==
+ * ========================
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2023-2024, Joe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the organization nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * hardflip: src/c_lhosts.go
+ * Tue Jan 09 12:59:11 2024
+ * Joe
+ *
+ * the hosts linked list
+ */
+
+package main
+
+// 0: ssh
+// 1: rdp
+type HostNode struct {
+ Protocol int8 `yaml:"type"`
+ Name string `yaml:"name"`
+ Host string `yaml:"host"`
+ Port uint16 `yaml:"port"`
+ User string `yaml:"user"`
+ Pass string `yaml:"pass"`
+ Priv string `yaml:"priv"`
+ Jump string `yaml:"jump"`
+ JumpPort uint16 `yaml:"jump_port"`
+ JumpUser string `yaml:"jump_user"`
+ JumpPass string `yaml:"jump_pass"`
+ JumpPriv string `yaml:"jump_priv"`
+ Quality uint8 `yaml:"quality"`
+ Domain string `yaml:"domain"`
+ Width uint16 `yaml:"width"`
+ Height uint16 `yaml:"height"`
+ Dynamic bool `yaml:"dynamic"`
+ Note string `yaml:"note"`
+ Filename string
+ Parent *DirsNode
+ next *HostNode
+}
+
+type HostList struct {
+ head *HostNode
+ last *HostNode
+}
+
+// adds a host node to the list
+func (lhost *HostList) add_back(node *HostNode) {
+ if lhost.head == nil {
+ lhost.head = node
+ lhost.last = lhost.head
+ return
+ }
+ last := lhost.last
+ last.next = node
+ lhost.last = last.next
+}
+
+// removes a host node from the list
+func (lhost *HostList) del(host *HostNode) {
+ if lhost.head == nil {
+ return
+ }
+ if lhost.head == host {
+ lhost.head = lhost.head.next
+ if lhost.head == nil {
+ lhost.last = nil
+ return
+ }
+ return
+ }
+ if lhost.last == host {
+ ptr := lhost.head
+ for ptr.next != nil {
+ ptr = ptr.next
+ }
+ lhost.last = ptr
+ lhost.last.next = nil
+ return
+ }
+ ptr := lhost.head
+ for ptr.next != nil && ptr.next != host {
+ ptr = ptr.next
+ }
+ if ptr.next == host {
+ ptr.next = ptr.next.next
+ }
+}
+
+func (lhost *HostList) count() int {
+ curr := lhost.head
+ var count int
+
+ for count = 0; curr != nil; count++ {
+ curr = curr.next
+ }
+ return count
+}
+
+func (host *HostNode) protocol_str() string {
+ switch host.Protocol {
+ case 0: return "SSH"
+ case 1: return "RDP"
+ default: return ""
+ }
+}
diff --git a/src/c_litems.go b/src/c_litems.go
new file mode 100644
index 0000000..229df41
--- /dev/null
+++ b/src/c_litems.go
@@ -0,0 +1,221 @@
+/*
+ * ========================
+ * ===== ===============
+ * ====== ================
+ * ====== ================
+ * ====== ==== ==== ==
+ * ====== === == = =
+ * ====== === = == =
+ * = === === = == ====
+ * = === === = == = =
+ * == ===== ==== ==
+ * ========================
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2023-2024, Joe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the organization nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * hardflip: src/c_litems.go
+ * Mon Jan 15 17:21:57 2024
+ * Joe
+ *
+ * the dir and hosts linked list
+ */
+
+package main
+
+type ItemsNode struct {
+ ID int
+ Dirs *DirsNode
+ Host *HostNode
+ prev *ItemsNode
+ next *ItemsNode
+}
+
+type ItemsList struct {
+ head *ItemsNode
+ last *ItemsNode
+ curr *ItemsNode
+ draw *ItemsNode
+}
+
+// adds an item node to the list
+func (litems *ItemsList) add_back(node *ItemsNode) {
+ if litems.head == nil {
+ litems.head = node
+ litems.last = litems.head
+ return
+ }
+ last := litems.last
+ node.ID = last.ID + 1
+ node.prev = last
+ last.next = node
+ litems.last = last.next
+}
+
+// removes an item node from the list and resets the ids
+func (litems *ItemsList) del(item *ItemsNode) {
+ if litems.head == nil {
+ return
+ }
+ if litems.head == item {
+ litems.head = litems.head.next
+ if litems.head == nil {
+ litems.last, litems.curr, litems.draw = nil, nil, nil
+ return
+ }
+ litems.head.prev = nil
+ litems.curr, litems.draw = litems.head, litems.head
+ for ptr := litems.head; ptr != nil; ptr = ptr.next {
+ ptr.ID -= 1
+ }
+ return
+ }
+ if litems.last == item {
+ litems.last = litems.last.prev
+ litems.last.next = nil
+ litems.curr = litems.last
+ if litems.draw == item {
+ litems.draw = litems.last
+ }
+ return
+ }
+ ptr := litems.head
+ for ptr.next != nil && ptr.next != item {
+ ptr = ptr.next
+ }
+ if ptr.next == item {
+ ptr.next = ptr.next.next
+ ptr.next.prev = ptr
+ }
+ for ptr := ptr.next; ptr != nil; ptr = ptr.next {
+ ptr.ID -= 1
+ }
+}
+
+// sets litems.curr to be used
+func (litems *ItemsList) sel(id int) {
+ curr := litems.head
+
+ if curr == nil {
+ litems.curr = nil
+ }
+ for curr.next != nil && curr.ID != id {
+ curr = curr.next
+ }
+ if curr.ID != id {
+ litems.curr = nil
+ }
+ litems.curr = curr
+}
+
+func (item *ItemsNode) is_dir() bool {
+ if item.Dirs == nil {
+ return false
+ }
+ return true
+}
+
+func (litems *ItemsList) inc(jump int) {
+ new_item := litems.curr
+
+ if new_item == nil || jump == 0 {
+ return
+ } else if jump == +1 {
+ if new_item.next != nil {
+ new_item = new_item.next
+ }
+ } else if jump == -1 {
+ if new_item.prev != nil {
+ new_item = new_item.prev
+ }
+ } else {
+ for i := 0; jump > +1 && new_item.next != nil && i < jump; i++ {
+ new_item = new_item.next
+ }
+ for i := 0; jump < -1 && new_item.prev != nil && i > jump; i-- {
+ new_item = new_item.prev
+ }
+ }
+ litems.curr = new_item
+}
+
+// returns the previous dir
+func (item *ItemsNode) prev_dir() *ItemsNode {
+ for ptr := item.prev; ptr != nil; ptr = ptr.prev {
+ if ptr.is_dir() == true {
+ return ptr
+ }
+ }
+ return nil
+}
+
+// returns the next dir
+func (item *ItemsNode) next_dir() *ItemsNode {
+ for ptr := item.next; ptr != nil; ptr = ptr.next {
+ if ptr.is_dir() == true {
+ return ptr
+ }
+ }
+ return nil
+}
+
+// returns the next directory in line with the same or lower depth
+func (item *ItemsNode) get_next_level() *ItemsNode {
+ if item == nil || item.Dirs == nil {
+ return nil
+ }
+ dir := item.Dirs
+ ptr := dir.next
+ for ptr != nil && ptr.Depth > dir.Depth {
+ ptr = ptr.next
+ }
+ item_ptr := item
+ for item_ptr != nil {
+ if item_ptr.is_dir() == false {
+ item_ptr = item_ptr.next
+ continue
+ }
+ if item_ptr.Dirs == ptr {
+ return item_ptr
+ }
+ item_ptr = item_ptr.next
+ }
+ return nil
+}
+
+func (litems *ItemsList) reset_id() {
+ if litems.head != nil {
+ litems.head.ID = 1
+ }
+ for ptr := litems.head; ptr != nil && ptr.next != nil; ptr = ptr.next {
+ ptr.next.ID = ptr.ID + 1
+ }
+}
diff --git a/src/c_parse.go b/src/c_parse.go
new file mode 100644
index 0000000..61d4dfc
--- /dev/null
+++ b/src/c_parse.go
@@ -0,0 +1,112 @@
+/*
+ * ========================
+ * ===== ===============
+ * ====== ================
+ * ====== ================
+ * ====== ==== ==== ==
+ * ====== === == = =
+ * ====== === = == =
+ * = === === = == ====
+ * = === === = == = =
+ * == ===== ==== ==
+ * ========================
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2023-2024, Joe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the organization nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * hardflip: src/c_parse.go
+ * Fri, 15 Dec 2023 10:02:29 +0100
+ * Joe
+ *
+ * parsing of the global data
+ */
+
+package main
+
+import (
+ "os"
+
+ "gopkg.in/yaml.v3"
+)
+
+func c_read_yaml_file(file string) *HostNode {
+ var host HostNode
+ yaml_file, err := os.ReadFile(file)
+
+ if err != nil {
+ // TODO: remove all c_dies with c_err_mode
+ c_die("error reading file " + file, err)
+ }
+ if err = yaml.Unmarshal(yaml_file, &host); err != nil {
+ c_die("error reading yaml file " + file, err)
+ }
+ if len(host.Name) == 0 {
+ return nil
+ }
+ if len(host.Host) == 0 {
+ return nil
+ }
+ if host.Protocol == 0 {
+ if host.Port == 0 {
+ host.Port = 22
+ }
+ if len(host.User) == 0 {
+ host.User = "root"
+ }
+ if len(host.Jump) > 0 {
+ if host.JumpPort == 0 {
+ host.JumpPort = 22
+ }
+ if len(host.JumpUser) == 0 {
+ host.JumpUser = "root"
+ }
+ }
+ } else if host.Protocol == 1 {
+ if len(host.User) == 0 {
+ host.User = "Administrator"
+ }
+ if host.Port == 0 {
+ host.Port = 3389
+ }
+ if host.Width == 0 {
+ host.Width = 1600
+ }
+ if host.Height == 0 {
+ host.Height = 1200
+ }
+ } else if host.Protocol > 1 {
+ return nil
+ }
+ if host.Quality > 2 {
+ host.Quality = 2
+ }
+ return &host
+}
diff --git a/src/c_utils.go b/src/c_utils.go
new file mode 100644
index 0000000..72761d8
--- /dev/null
+++ b/src/c_utils.go
@@ -0,0 +1,107 @@
+/*
+ * ========================
+ * ===== ===============
+ * ====== ================
+ * ====== ================
+ * ====== ==== ==== ==
+ * ====== === == = =
+ * ====== === = == =
+ * = === === = == ====
+ * = === === = == = =
+ * == ===== ==== ==
+ * ========================
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2023-2024, Joe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the organization nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * hardflip: src/c_utils.go
+ * Wed Dec 20 10:50:12 2023
+ * Joe
+ *
+ * core funcs
+ */
+
+package main
+
+import (
+ "fmt"
+ "os"
+)
+
+// this function will go get the data folder and try to create it if it does
+// not exist
+// the first path being checked is $XDG_DATA_HOME then $HOME/.local/share
+// it returns the full data directory path
+func c_get_data_dir() string {
+ var ptr *string
+ var home string
+ if home = os.Getenv("HOME"); len(home) == 0 {
+ c_die("env variable HOME not defined", nil)
+ }
+ xdg_home := os.Getenv("XDG_DATA_HOME")
+
+ if len(xdg_home) > 0 {
+ ptr = &xdg_home
+ } else {
+ ptr = &home
+ *ptr += "/.local/share"
+ }
+ *ptr += "/" + DATA_DIR_NAME
+ if _, err := os.Stat(*ptr); os.IsNotExist(err) {
+ if err := os.MkdirAll(*ptr, os.ModePerm); err != nil {
+ c_die("could not create path " + *ptr, err)
+ }
+ }
+ return *ptr
+}
+
+// c_die displays an error string to the stderr fd and exits the program
+// with the return code 1
+// it takes an optional err argument of the error type as a complement of
+// information
+func c_die(str string, err error) {
+ fmt.Fprintf(os.Stderr, "error: %s", str)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, ": %v\n", err)
+ }
+ fmt.Fprintf(os.Stderr, "\n")
+ os.Exit(1)
+}
+
+func c_error_mode(msg string, err error, ui *HardUI) {
+ ui.mode = ERROR_MODE
+ err_str := ""
+ if err != nil {
+ err_str = fmt.Sprintf("%v", err)
+ }
+ ui.err[ERROR_MSG] = msg
+ ui.err[ERROR_ERR] = err_str
+}
diff --git a/src/i_events.go b/src/i_events.go
new file mode 100644
index 0000000..526b4a4
--- /dev/null
+++ b/src/i_events.go
@@ -0,0 +1,349 @@
+/*
+ * ========================
+ * ===== ===============
+ * ====== ================
+ * ====== ================
+ * ====== ==== ==== ==
+ * ====== === == = =
+ * ====== === = == =
+ * = === === = == ====
+ * = === === = == = =
+ * == ===== ==== ==
+ * ========================
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2023-2024, Joe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the organization nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * hardflip: src/i_events.go
+ * Fri Jan 19 19:25:52 2024
+ * Joe
+ *
+ * events in the code
+ */
+
+package main
+
+import (
+ "os"
+
+ "github.com/gdamore/tcell/v2"
+ "golang.org/x/term"
+)
+
+func i_list_follow_cursor(litems *ItemsList, ui *HardUI) {
+ if litems.draw == nil || litems.curr == nil {
+ return
+ }
+ scrolloff := 4
+ if litems.last.ID - (ui.dim[H] - 4) <= litems.draw.ID {
+ scrolloff = 0
+ }
+ virt_id := litems.curr.ID - (ui.dim[H] - 4) + scrolloff
+ for litems.draw.ID < virt_id &&
+ litems.draw.next != nil {
+ litems.draw = litems.draw.next
+ }
+ scrolloff = 4
+ for litems.draw.ID > litems.curr.ID - scrolloff &&
+ litems.draw.prev != nil {
+ litems.draw = litems.draw.prev
+ }
+}
+
+func i_set_unfold(data *HardData, item *ItemsNode) {
+ delete(data.folds, item.Dirs)
+ data.litems.reset_id()
+}
+
+func i_unfold_dir(data *HardData, item *ItemsNode) {
+ if item == nil || item.Dirs == nil {
+ return
+ }
+ fold := data.folds[item.Dirs]
+ if fold == nil {
+ return
+ }
+ start, end := fold.head, fold.last
+ // last empty dir
+ if start == nil && end == nil {
+ i_set_unfold(data, item)
+ return
+ }
+ // single empty dir
+ if start == item && end == end {
+ i_set_unfold(data, item)
+ return
+ }
+ if data.litems.last == item {
+ data.litems.last = end
+ }
+ // non-emtpy dir
+ start.prev = item
+ end.next = item.next
+ if item.next != nil {
+ item.next.prev = end
+ }
+ item.next = start
+ i_set_unfold(data, item)
+}
+
+func i_set_fold(data *HardData, curr, start, end *ItemsNode) {
+ folds := data.folds
+ tmp := ItemsList{
+ start,
+ end,
+ nil,
+ nil,
+ }
+
+ folds[curr.Dirs] = &tmp
+ data.litems.reset_id()
+}
+
+func i_fold_dir(data *HardData, item *ItemsNode) {
+ if item == nil || item.Dirs == nil {
+ return
+ }
+ var start, end *ItemsNode
+ start = item.next
+ // last dir + empty
+ if start == nil {
+ i_set_fold(data, item, nil, nil)
+ return
+ }
+ // empty dir
+ if start.Dirs != nil && start.Dirs.Depth <= item.Dirs.Depth {
+ i_set_fold(data, item, item, item)
+ return
+ }
+ // non-empty dir
+ start.prev = nil
+ end = start
+ next_dir := item.get_next_level()
+ // this is the end
+ if next_dir == nil {
+ item.next = nil
+ end = data.litems.last
+ end.next = nil
+ data.litems.last = item
+ i_set_fold(data, item, start, end)
+ return
+ }
+ // this is not the end
+ end = next_dir.prev
+ end.next = nil
+ item.next = next_dir
+ next_dir.prev = item
+ i_set_fold(data, item, start, end)
+}
+
+func i_reload_data(data *HardData) {
+ data.data_dir = c_get_data_dir()
+ g_load_count = -1
+ data.ldirs, data.litems = i_load_ui(data.data_dir, data.opts, &data.ui)
+ // FIX: must input to start reloading for some reason
+ data.folds = make(map[*DirsNode]*ItemsList)
+}
+
+func i_delete_dir(data *HardData) error {
+ if data.litems.curr == nil || data.litems.curr.Dirs == nil {
+ return nil
+ }
+ curr := data.litems.curr
+ dir_path := data.data_dir + data.litems.curr.Dirs.path()
+ if err := os.RemoveAll(dir_path); err != nil {
+ c_error_mode("can't remove " + dir_path, err, &data.ui)
+ return err
+ }
+ if data.folds[curr.Dirs] == nil {
+ i_fold_dir(data, curr)
+ }
+ delete(data.folds, curr.Dirs)
+ if curr == data.litems.head {
+ data.litems.head = curr.next
+ if curr.next != nil {
+ curr.next.prev = nil
+ }
+ if data.litems.draw == curr {
+ data.litems.draw = curr.next
+ }
+ } else {
+ curr.prev.next = curr.next
+ }
+ if curr.next != nil {
+ curr.next.prev = curr.prev
+ data.litems.curr = curr.next
+ } else {
+ data.litems.last = curr.prev
+ data.litems.curr = curr.prev
+ }
+ data.litems.reset_id()
+ return nil
+}
+
+func i_delete_host(data *HardData) error {
+ if data.litems.curr == nil {
+ return nil
+ }
+ if data.litems.curr.is_dir() == true {
+ return i_delete_dir(data)
+ }
+ host := data.litems.curr.Host
+ if host == nil {
+ return nil
+ }
+ file_path := data.data_dir + host.Parent.path() + host.Filename
+
+ if err := os.Remove(file_path); err != nil {
+ c_error_mode("can't remove " + file_path, err, &data.ui)
+ return err
+ }
+ tmp := data.litems.curr.prev
+ host.Parent.lhost.del(host)
+ data.litems.del(data.litems.curr)
+ if tmp == nil {
+ tmp = data.litems.head
+ }
+ data.litems.curr = tmp
+ return nil
+}
+
+// screen events such as keypresses
+func i_events(data *HardData) {
+ var err error
+ ui := &data.ui
+ event := ui.s.PollEvent()
+ switch event := event.(type) {
+ case *tcell.EventResize:
+ ui.dim[W], ui.dim[H], _ = term.GetSize(0)
+ i_list_follow_cursor(data.litems, ui)
+ ui.s.Sync()
+ case *tcell.EventKey:
+ switch ui.mode {
+ case NORMAL_MODE:
+ if event.Key() == tcell.KeyCtrlC ||
+ event.Rune() == 'q' {
+ ui.s.Fini()
+ os.Exit(0)
+ } else if event.Rune() == 'j' ||
+ event.Key() == tcell.KeyDown {
+ data.litems.inc(+1)
+ } else if event.Rune() == 'k' ||
+ event.Key() == tcell.KeyUp {
+ data.litems.inc(-1)
+ } else if event.Key() == tcell.KeyCtrlD ||
+ event.Key() == tcell.KeyPgDn {
+ data.litems.inc(+(ui.dim[H] / 3))
+ } else if event.Key() == tcell.KeyCtrlU ||
+ event.Key() == tcell.KeyPgUp {
+ data.litems.inc(-(ui.dim[H] / 3))
+ } else if event.Key() == tcell.KeyCtrlF {
+ // TODO: maybe keymap these
+ } else if event.Key() == tcell.KeyCtrlB {
+ // TODO: maybe keymap these
+ } else if event.Rune() == '}' ||
+ event.Rune() == ']' {
+ if next := data.litems.curr.next_dir(); next != nil {
+ data.litems.curr = next
+ }
+ } else if event.Rune() == '{' ||
+ event.Rune() == '[' {
+ if prev := data.litems.curr.prev_dir(); prev != nil {
+ data.litems.curr = prev
+ }
+ } else if event.Rune() == 'g' ||
+ event.Key() == tcell.KeyHome {
+ data.litems.curr = data.litems.head
+ data.litems.draw = data.litems.head
+ } else if event.Rune() == 'G' ||
+ event.Key() == tcell.KeyEnd {
+ data.litems.curr = data.litems.last
+ } else if event.Rune() == 'D' &&
+ data.ldirs.head != nil {
+ ui.mode = DELETE_MODE
+ } else if event.Rune() == 'l' ||
+ event.Key() == tcell.KeyEnter {
+ if data.litems.curr == nil {
+ break
+ } else if data.litems.curr.is_dir() == false {
+ ui.s.Fini()
+ c_exec(data.litems.curr.Host, data.opts.Term)
+ if data.opts.Loop == false {
+ os.Exit(0)
+ } else {
+ if ui.s, err = tcell.NewScreen(); err != nil {
+ c_die("view", err)
+ }
+ if err := ui.s.Init(); err != nil {
+ c_die("view", err)
+ }
+ ui.s.SetStyle(ui.def_style)
+ }
+ } else if data.litems.curr.Dirs != nil &&
+ data.folds[data.litems.curr.Dirs] == nil {
+ i_fold_dir(data, data.litems.curr)
+ } else {
+ i_unfold_dir(data, data.litems.curr)
+ }
+ } else if event.Rune() == ' ' {
+ if data.litems.curr == nil ||
+ data.litems.curr.is_dir() == false {
+ break
+ }
+ if data.litems.curr.Dirs != nil &&
+ data.folds[data.litems.curr.Dirs] == nil {
+ i_fold_dir(data, data.litems.curr)
+ } else {
+ i_unfold_dir(data, data.litems.curr)
+ }
+ } else if event.Key() == tcell.KeyCtrlR {
+ i_reload_data(data)
+ }
+ i_list_follow_cursor(data.litems, ui)
+ case DELETE_MODE:
+ if event.Key() == tcell.KeyEscape ||
+ event.Key() == tcell.KeyCtrlC ||
+ event.Rune() == 'q' ||
+ event.Rune() == 'n' {
+ ui.mode = NORMAL_MODE
+ } else if event.Key() == tcell.KeyEnter ||
+ event.Rune() == 'y' {
+ if err := i_delete_host(data); err == nil {
+ ui.mode = NORMAL_MODE
+ }
+ }
+ case ERROR_MODE:
+ if event.Key() == tcell.KeyEnter {
+ ui.mode = NORMAL_MODE
+ }
+ }
+ }
+}
diff --git a/src/i_host.go b/src/i_host.go
new file mode 100644
index 0000000..0ff0eb2
--- /dev/null
+++ b/src/i_host.go
@@ -0,0 +1,131 @@
+/*
+ * ========================
+ * ===== ===============
+ * ====== ================
+ * ====== ================
+ * ====== ==== ==== ==
+ * ====== === == = =
+ * ====== === = == =
+ * = === === = == ====
+ * = === === = == = =
+ * == ===== ==== ==
+ * ========================
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2023-2024, Joe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the organization nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * hardflip: src/i_host.go
+ * Fri Jan 19 18:44:09 2024
+ * Joe
+ *
+ * interfacing hosts
+ */
+
+package main
+
+func i_host_panel_dirs(ui HardUI, icons bool, dir_icon uint8,
+ dir *DirsNode, curr *DirsNode, line int) {
+ style := ui.dir_style
+ if dir == curr {
+ style = style.Reverse(true)
+ }
+ text := ""
+ for i := 0; i < int(dir.Depth) - 2; i++ {
+ text += " "
+ }
+ if icons == true {
+ text += DIRS_ICONS[dir_icon]
+ }
+ text += dir.Name
+ spaces := ""
+ for i := 0; i < (ui.dim[W] / 3) - len(text) + 1; i++ {
+ spaces += " "
+ }
+ text += spaces
+ i_draw_text(ui.s,
+ 1, line, ui.dim[W] / 3, line,
+ style, text)
+}
+
+func i_host_panel_host(ui HardUI, icons bool,
+ depth uint16, host *HostNode, curr *HostNode, line int) {
+ style := ui.def_style
+ if host == curr {
+ style = style.Reverse(true)
+ }
+ text := ""
+ for i := 0; i < int(depth + 1) - 2; i++ {
+ text += " "
+ }
+ if icons == true {
+ text += HOST_ICONS[int(host.Protocol)]
+ }
+ text += host.Name
+ spaces := ""
+ for i := 0; i < (ui.dim[W] / 3) - len(text) + 1; i++ {
+ spaces += " "
+ }
+ text += spaces
+ i_draw_text(ui.s,
+ 1, line, ui.dim[W] / 3, line,
+ style, text)
+}
+
+func i_draw_host_panel(ui HardUI, icons bool, litems *ItemsList, data *HardData) {
+ i_draw_box(ui.s, 0, 0,
+ ui.dim[W] / 3, ui.dim[H] - 2,
+ " Hosts ", false)
+ line := 1
+ if litems == nil || litems.head == nil {
+ return
+ }
+ for ptr := litems.draw; ptr != nil && line < ui.dim[H] - 2; ptr = ptr.next {
+ if ptr.is_dir() == false && ptr.Host != nil {
+ i_host_panel_host(ui,
+ icons,
+ ptr.Host.Parent.Depth,
+ ptr.Host,
+ litems.curr.Host,
+ line)
+ line++
+ } else if ptr.Dirs != nil {
+ var dir_icon uint8
+ if data.folds[ptr.Dirs] != nil {
+ dir_icon = 1
+ }
+ i_host_panel_dirs(ui, icons, dir_icon,
+ ptr.Dirs,
+ litems.curr.Dirs,
+ line)
+ line++
+ }
+ }
+}
diff --git a/src/i_info.go b/src/i_info.go
new file mode 100644
index 0000000..271f1d0
--- /dev/null
+++ b/src/i_info.go
@@ -0,0 +1,343 @@
+/*
+ * ========================
+ * ===== ===============
+ * ====== ================
+ * ====== ================
+ * ====== ==== ==== ==
+ * ====== === == = =
+ * ====== === = == =
+ * = === === = == ====
+ * = === === = == = =
+ * == ===== ==== ==
+ * ========================
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2023-2024, Joe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the organization nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * hardflip: src/i_info.go
+ * Fri Jan 19 18:44:13 2024
+ * Joe
+ *
+ * interfacing informations about items
+ */
+
+package main
+
+import (
+ "strconv"
+
+ "github.com/gdamore/tcell/v2"
+)
+
+func i_info_panel_dirs(ui HardUI, dir *DirsNode) {
+ line := 2
+ if line > ui.dim[H] - 3 {
+ return
+ }
+
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 3, line, ui.dim[W] - 2, line,
+ ui.title_style, "Name: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 9, line, ui.dim[W] - 2, line,
+ ui.def_style, dir.Name)
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 3, line, ui.dim[W] - 2, line,
+ ui.title_style, "Type: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 9, line, ui.dim[W] - 2, line,
+ ui.def_style, "Directory")
+ if line += 2; line > ui.dim[H] - 3 {
+ return
+ }
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 3, line, ui.dim[W] - 2, line,
+ ui.title_style, "Path: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 9, line, ui.dim[W] - 2, line,
+ ui.def_style, dir.path())
+}
+
+func i_info_panel_host(ui HardUI, host *HostNode) {
+ host_type := host.protocol_str()
+ line := 2
+ if line > ui.dim[H] - 3 {
+ return
+ }
+ // name, type
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 3, line, ui.dim[W] - 2, line,
+ ui.title_style, "Name: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 9, line, ui.dim[W] - 2, line,
+ ui.def_style, host.Name)
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 3, line, ui.dim[W] - 2, line,
+ ui.title_style, "Type: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 9, line, ui.dim[W] - 2, line,
+ ui.def_style, host_type)
+ if line += 2; line > ui.dim[H] - 3 {
+ return
+ }
+ if line > ui.dim[H] - 3 {
+ return
+ }
+ // host, port
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 3, line, ui.dim[W] - 2, line,
+ ui.title_style, "Host: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 9, line, ui.dim[W] - 2, line,
+ ui.def_style, host.Host)
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 3, line, ui.dim[W] - 2, line,
+ ui.title_style, "Port: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 9, line, ui.dim[W] - 2, line,
+ ui.def_style, strconv.Itoa(int(host.Port)))
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ // RDP shit
+ if host.Protocol == 1 {
+ if len(host.Domain) > 0 {
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 3, line, ui.dim[W] - 2, line,
+ ui.title_style, "Domain: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 11, line, ui.dim[W] - 2, line,
+ ui.def_style, host.Domain)
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ }
+ }
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ // user infos
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 3, line, ui.dim[W] - 2, line,
+ ui.title_style, "User: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 9, line, ui.dim[W] - 2, line,
+ ui.def_style, host.User)
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ if len(host.Pass) > 0 {
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 3, line, ui.dim[W] - 2, line,
+ ui.title_style, "Pass: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 9, line, ui.dim[W] - 2, line,
+ ui.def_style, "***")
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ }
+ if host.Protocol == 0 && len(host.Priv) > 0 {
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 3, line, ui.dim[W] - 2, line,
+ ui.title_style, "Privkey: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 12, line, ui.dim[W] - 2, line,
+ ui.def_style, host.Priv)
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ }
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ // jump
+ if host.Protocol == 0 && len(host.Jump) > 0 {
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 3, line, ui.dim[W] - 2, line,
+ ui.title_style, "Jump settings: ")
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 4, line, ui.dim[W] - 2, line,
+ ui.title_style, "Host: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 10, line, ui.dim[W] - 2, line,
+ ui.def_style, host.Jump)
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 4, line, ui.dim[W] - 2, line,
+ ui.title_style, "Port: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 10, line, ui.dim[W] - 2, line,
+ ui.def_style, strconv.Itoa(int(host.JumpPort)))
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 4, line, ui.dim[W] - 2, line,
+ ui.title_style, "User: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 10, line, ui.dim[W] - 2, line,
+ ui.def_style, host.JumpUser)
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ if len(host.JumpPass) > 0 {
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 4, line, ui.dim[W] - 2, line,
+ ui.title_style, "Pass: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 10, line, ui.dim[W] - 2, line,
+ ui.def_style, "***")
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ }
+ if host.Protocol == 0 && len(host.JumpPriv) > 0 {
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 4, line, ui.dim[W] - 2, line,
+ ui.title_style, "Privkey: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 13, line, ui.dim[W] - 2, line,
+ ui.def_style, host.JumpPriv)
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ }
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ }
+ // RDP shit
+ if host.Protocol == 1 {
+ qual := [3]string{"Low", "Medium", "High"}
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 3, line, ui.dim[W] - 2, line,
+ ui.title_style, "Screen size: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 16, line, ui.dim[W] - 2, line,
+ ui.def_style,
+ strconv.Itoa(int(host.Width)) + "x" +
+ strconv.Itoa(int(host.Height)))
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 3, line, ui.dim[W] - 2, line,
+ ui.title_style, "Dynamic window: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 19, line, ui.dim[W] - 2, line,
+ ui.def_style, strconv.FormatBool(host.Dynamic))
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 3, line, ui.dim[W] - 2, line,
+ ui.title_style, "Quality: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 12, line, ui.dim[W] - 2, line,
+ ui.def_style, qual[host.Quality])
+ line += 1
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ }
+ // note
+ if len(host.Note) > 0 {
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 3, line, ui.dim[W] - 2, line,
+ ui.title_style, "Note: ")
+ i_draw_text(ui.s,
+ (ui.dim[W] / 3) + 9, line, ui.dim[W] - 2, line,
+ ui.def_style, host.Note)
+ if line += 1; line > ui.dim[H] - 3 {
+ return
+ }
+ }
+}
+
+func i_draw_info_panel(ui HardUI, percent bool, litems *ItemsList) {
+ i_draw_box(ui.s, (ui.dim[W] / 3), 0,
+ ui.dim[W] - 1, ui.dim[H] - 2,
+ " Infos ", false)
+ ui.s.SetContent(ui.dim[W] / 3, 0, tcell.RuneTTee, nil, ui.def_style)
+ ui.s.SetContent(ui.dim[W] / 3, ui.dim[H] - 2,
+ tcell.RuneBTee, nil, ui.def_style)
+ if litems == nil {
+ return
+ }
+ // number display
+ if litems.head != nil {
+ text := " " + strconv.Itoa(litems.curr.ID) + " of " +
+ strconv.Itoa(int(litems.last.ID)) + " "
+ if percent == true {
+ text += "- " +
+ strconv.Itoa(litems.curr.ID * 100 / litems.last.ID) + "% "
+ }
+ i_draw_text(ui.s,
+ (ui.dim[W] - 1) - len(text) - 1,
+ ui.dim[H] - 2,
+ (ui.dim[W] - 1) - 1,
+ ui.dim[H] - 2,
+ ui.def_style,
+ text)
+ } else {
+ text := " 0 hosts "
+ i_draw_text(ui.s,
+ (ui.dim[W] - 1) - len(text) - 1,
+ ui.dim[H] - 2,
+ (ui.dim[W] - 1) - 1,
+ ui.dim[H] - 2,
+ ui.def_style,
+ text)
+ }
+ // panel
+ if litems.head == nil {
+ return
+ } else if litems.curr.is_dir() == true {
+ i_info_panel_dirs(ui, litems.curr.Dirs)
+ } else {
+ i_info_panel_host(ui, litems.curr.Host)
+ }
+}
diff --git a/src/i_ui.go b/src/i_ui.go
new file mode 100644
index 0000000..05e5141
--- /dev/null
+++ b/src/i_ui.go
@@ -0,0 +1,365 @@
+/*
+ * ========================
+ * ===== ===============
+ * ====== ================
+ * ====== ================
+ * ====== ==== ==== ==
+ * ====== === == = =
+ * ====== === = == =
+ * = === === = == ====
+ * = === === = == = =
+ * == ===== ==== ==
+ * ========================
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2023-2024, Joe
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the organization nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * hardflip: src/i_ui.go
+ * Fri Jan 19 19:23:24 2024
+ * Joe
+ *
+ * interfacing with the user
+ */
+
+package main
+
+import (
+ "os"
+ "strconv"
+
+ "github.com/gdamore/tcell/v2"
+ "golang.org/x/term"
+)
+
+type HardUI struct {
+ s tcell.Screen
+ mode uint8
+ def_style tcell.Style
+ dir_style tcell.Style
+ title_style tcell.Style
+ dim [2]int
+ err [2]string
+}
+
+func i_left_right(text_len int, ui *HardUI) (int, int) {
+ left := (ui.dim[W] / 2) - text_len / 2
+ right := ui.dim[W] - 1
+ if left < 1 {
+ left = 1
+ }
+ if right >= ui.dim[W] - 1 {
+ right = ui.dim[W] - 1
+ }
+ return left, right
+}
+
+func i_draw_text(s tcell.Screen,
+ x1, y1, x2, y2 int,
+ style tcell.Style, text string) {
+ row := y1
+ col := x1
+ for _, r := range []rune(text) {
+ s.SetContent(col, row, r, nil, style)
+ col++
+ if col >= x2 {
+ row++
+ col = x1
+ }
+ if row > y2 {
+ break
+ }
+ }
+}
+
+func i_draw_box(s tcell.Screen, x1, y1, x2, y2 int, title string, fill bool) {
+ style := tcell.StyleDefault.
+ Background(tcell.ColorReset).
+ Foreground(tcell.ColorReset)
+
+ if y2 < y1 {
+ y1, y2 = y2, y1
+ }
+ if x2 < x1 {
+ x1, x2 = x2, x1
+ }
+ // Draw borders
+ for col := x1; col <= x2; col++ {
+ s.SetContent(col, y1, tcell.RuneHLine, nil, style)
+ s.SetContent(col, y2, tcell.RuneHLine, nil, style)
+ }
+ for row := y1 + 1; row < y2; row++ {
+ s.SetContent(x1, row, tcell.RuneVLine, nil, style)
+ s.SetContent(x2, row, tcell.RuneVLine, nil, style)
+ }
+ // Only draw corners if necessary
+ if y1 != y2 && x1 != x2 {
+ s.SetContent(x1, y1, tcell.RuneULCorner, nil, style)
+ s.SetContent(x2, y1, tcell.RuneURCorner, nil, style)
+ s.SetContent(x1, y2, tcell.RuneLLCorner, nil, style)
+ s.SetContent(x2, y2, tcell.RuneLRCorner, nil, style)
+ }
+ if fill == true {
+ for y := y1 + 1; y < y2; y++ {
+ for x := x1 + 1; x < x2; x++ {
+ s.SetContent(x, y, ' ', nil, style)
+ }
+ }
+ }
+ i_draw_text(s, x1 + 1, y1, x2 - 1, y1, style, title)
+}
+
+func i_draw_msg(s tcell.Screen, lines int, dim [2]int, title string) {
+ style := tcell.StyleDefault.
+ Background(tcell.ColorReset).
+ Foreground(tcell.ColorReset)
+
+ lines += 1
+ if lines < 0 {
+ return
+ }
+ if lines > dim[H] - 2 {
+ lines = dim[H] - 2
+ }
+ for row := dim[H] - 2 - lines; row < dim[H] - 2; row++ {
+ s.SetContent(0, row, tcell.RuneVLine, nil, style)
+ s.SetContent(dim[W] - 1, row, tcell.RuneVLine, nil, style)
+ }
+ for col := 1; col < dim[W] - 1; col++ {
+ s.SetContent(col, dim[H] - 2 - lines, tcell.RuneHLine, nil, style)
+ s.SetContent(col, dim[H] - 2, tcell.RuneHLine, nil, style)
+ }
+ s.SetContent(0, dim[H] - 2 - lines, tcell.RuneULCorner, nil, style)
+ s.SetContent(dim[W] - 1, dim[H] - 2 - lines, tcell.RuneURCorner, nil, style)
+ s.SetContent(0, dim[H] - 2, tcell.RuneLLCorner, nil, style)
+ s.SetContent(dim[W] - 1, dim[H] - 2, tcell.RuneLRCorner, nil, style)
+ // s.SetContent(dim[W] / 3, dim[H] - 2 - lines, tcell.RuneBTee, nil, style)
+ // s.SetContent(0, dim[H] - 2 - lines, tcell.RuneLTee, nil, style)
+ // s.SetContent(dim[W] - 1, dim[H] - 2 - lines, tcell.RuneRTee, nil, style)
+ for y := dim[H] - 2 - lines + 1; y < dim[H] - 2; y++ {
+ for x := 1; x < dim[W] - 1; x++ {
+ s.SetContent(x, y, ' ', nil, style)
+ }
+ }
+ i_draw_text(s, 1, dim[H] - 2 - lines, len(title) + 2, dim[H] - 2 - lines,
+ style, title)
+}
+
+func i_draw_bottom_text(ui HardUI) {
+ text := ""
+
+ switch ui.mode {
+ case NORMAL_MODE:
+ text = NORMAL_KEYS_HINTS
+ case DELETE_MODE:
+ text = DELETE_KEYS_HINTS
+ case LOAD_MODE:
+ text = ""
+ case ERROR_MODE:
+ text = ERROR_KEYS_HINTS
+ }
+ i_draw_text(ui.s,
+ 0, ui.dim[H] - 1, ui.dim[W], ui.dim[H] - 1,
+ ui.def_style.Dim(true), text)
+ i_draw_text(ui.s,
+ ui.dim[W] - 5, ui.dim[H] - 1, ui.dim[W], ui.dim[H] - 1,
+ ui.def_style.Dim(true), " " + VERSION)
+}
+
+func i_draw_zhosts_box(ui HardUI) {
+ i_draw_msg(ui.s, 1, ui.dim, " No hosts ")
+ text := "Hosts list empty. Add hosts/folders by pressing (a/m)"
+ left, right := i_left_right(len(text), &ui)
+ i_draw_text(ui.s, left, ui.dim[H] - 2 - 1, right, ui.dim[H] - 2 - 1,
+ ui.def_style, text)
+}
+
+func i_draw_delete_msg(ui HardUI, item *ItemsNode) {
+ var text string
+ var file string
+
+ if item.is_dir() == true {
+ text = "Really delete this directory and all of its content?"
+ file = item.Dirs.path()
+ } else {
+ host := item.Host
+ text = "Really delete this host?"
+ file = host.Parent.path() + host.Filename
+ }
+ file = file[1:]
+ i_draw_msg(ui.s, 2, ui.dim, " Delete ")
+ left, right := i_left_right(len(text), &ui)
+ line := ui.dim[H] - 2 - 2
+ i_draw_text(ui.s, left, line, right, line, ui.def_style, text)
+ left, right = i_left_right(len(file), &ui)
+ line += 1
+ i_draw_text(ui.s,
+ left, line, right, line,
+ ui.def_style.Bold(true), file)
+}
+
+func i_draw_error_msg(ui HardUI) {
+ lines := 2
+ if len(ui.err[ERROR_ERR]) == 0 {
+ lines = 1
+ }
+ i_draw_msg(ui.s, lines, ui.dim, " Delete ")
+ left, right := i_left_right(len(ui.err[ERROR_MSG]), &ui)
+ line := ui.dim[H] - 2 - 2
+ if len(ui.err[ERROR_ERR]) == 0 {
+ line += 1
+ }
+ i_draw_text(ui.s, left, line, right, line, ui.def_style, ui.err[ERROR_MSG])
+ if len(ui.err[ERROR_ERR]) > 0 {
+ left, right = i_left_right(len(ui.err[ERROR_ERR]), &ui)
+ line += 1
+ i_draw_text(ui.s, left, line, right, line,
+ ui.def_style, ui.err[ERROR_ERR])
+ }
+}
+
+func i_draw_scrollhint(ui HardUI, litems *ItemsList) {
+ if litems.head == nil {
+ return
+ }
+ h := ui.dim[H] - 4
+ last := litems.last.ID
+ if last <= h {
+ return
+ }
+ draw_id := litems.draw.ID
+ if draw_id > 1 {
+ ui.s.SetContent(0, 1,
+ '▲',
+ nil, ui.def_style)
+ }
+ if last - draw_id > h {
+ ui.s.SetContent(0, ui.dim[H] - 3,
+ '▼',
+ nil, ui.def_style)
+ return
+ }
+}
+
+// HACK: fuck global vars but do we have the choice really
+var g_load_count int = -1
+
+func i_draw_load_ui(ui *HardUI) {
+ g_load_count += 1
+ if g_load_count % 1000 != 0 {
+ return
+ }
+ ui.s.Clear()
+ i_draw_host_panel(*ui, false, nil, nil)
+ i_draw_info_panel(*ui, false, nil)
+ i_draw_msg(ui.s, 1, ui.dim, " Loading ")
+ text := "Loading " + strconv.Itoa(g_load_count) + " hosts"
+ left, right := i_left_right(len(text), ui)
+ i_draw_text(ui.s,
+ left, ui.dim[H] - 2 - 1, right, ui.dim[H] - 2 - 1, ui.def_style, text)
+ i_draw_text(ui.s,
+ ui.dim[W] - 5, ui.dim[H] - 1, ui.dim[W], ui.dim[H] - 1,
+ ui.def_style.Dim(true), " " + VERSION)
+ ui.s.Show()
+ event := ui.s.PollEvent()
+ ui.s.PostEvent(event)
+ switch event := event.(type) {
+ case *tcell.EventResize:
+ ui.dim[W], ui.dim[H], _ = term.GetSize(0)
+ ui.s.Sync()
+ case *tcell.EventKey:
+ if event.Key() == tcell.KeyCtrlC ||
+ event.Rune() == 'q' {
+ ui.s.Fini()
+ os.Exit(0)
+ }
+ }
+}
+
+func i_load_ui(data_dir string,
+ opts HardOpts,
+ ui *HardUI) (*DirsList, *ItemsList) {
+ ui.mode = LOAD_MODE
+ ldirs := c_load_data_dir(data_dir, opts, ui)
+ litems := c_load_litems(ldirs)
+ ui.mode = NORMAL_MODE
+ return ldirs, litems
+}
+
+func i_ui(data_dir string, opts HardOpts) {
+ ui := HardUI{}
+ var err error
+
+ ui.s, err = tcell.NewScreen()
+ if err != nil {
+ c_die("view", err)
+ }
+ if err := ui.s.Init(); err != nil {
+ c_die("view", err)
+ }
+ ui.def_style = tcell.StyleDefault.
+ Background(tcell.ColorReset).
+ Foreground(tcell.ColorReset)
+ ui.dir_style = tcell.StyleDefault.
+ Background(tcell.ColorReset).
+ Foreground(tcell.ColorBlue).Dim(true).Bold(true)
+ ui.title_style = tcell.StyleDefault.
+ Background(tcell.ColorReset).
+ Foreground(tcell.ColorBlue).Dim(true).Bold(true)
+ ui.s.SetStyle(ui.def_style)
+ ui.dim[W], ui.dim[H], _ = term.GetSize(0)
+ ldirs, litems := i_load_ui(data_dir, opts, &ui)
+ data := HardData{
+ litems,
+ ldirs,
+ ui,
+ opts,
+ data_dir,
+ make(map[*DirsNode]*ItemsList),
+ }
+ for {
+ data.ui.s.Clear()
+ i_draw_bottom_text(data.ui)
+ i_draw_host_panel(data.ui, data.opts.Icon, data.litems, &data)
+ i_draw_info_panel(data.ui, data.opts.Perc, data.litems)
+ i_draw_scrollhint(data.ui, data.litems)
+ if data.litems.head == nil {
+ i_draw_zhosts_box(data.ui)
+ }
+ if data.ui.mode == DELETE_MODE {
+ i_draw_delete_msg(data.ui, data.litems.curr)
+ }
+ if data.ui.mode == ERROR_MODE {
+ i_draw_error_msg(data.ui)
+ }
+ data.ui.s.Show()
+ i_events(&data)
+ }
+}