diff options
author | Joe <rbo@gmx.us> | 2024-01-23 20:20:20 +0100 |
---|---|---|
committer | Joe <rbo@gmx.us> | 2024-01-23 20:20:20 +0100 |
commit | 730bbd15c64cf8324267176dc6ced128559ad4cd (patch) | |
tree | 8aac0527cf3616f04624ebf14e552d07396ae080 /src | |
parent | go (diff) | |
download | hardflip-730bbd15c64cf8324267176dc6ced128559ad4cd.tar.gz hardflip-730bbd15c64cf8324267176dc6ced128559ad4cd.tar.bz2 hardflip-730bbd15c64cf8324267176dc6ced128559ad4cd.tar.xz hardflip-730bbd15c64cf8324267176dc6ced128559ad4cd.tar.zst hardflip-730bbd15c64cf8324267176dc6ced128559ad4cd.zip |
better org
Diffstat (limited to 'src')
-rw-r--r-- | src/c_defs.go | 84 | ||||
-rw-r--r-- | src/c_exec.go | 164 | ||||
-rw-r--r-- | src/c_hardflip.go | 68 | ||||
-rw-r--r-- | src/c_init.go | 132 | ||||
-rw-r--r-- | src/c_ldirs.go | 135 | ||||
-rw-r--r-- | src/c_lhosts.go | 144 | ||||
-rw-r--r-- | src/c_litems.go | 221 | ||||
-rw-r--r-- | src/c_parse.go | 112 | ||||
-rw-r--r-- | src/c_utils.go | 107 | ||||
-rw-r--r-- | src/i_events.go | 349 | ||||
-rw-r--r-- | src/i_host.go | 131 | ||||
-rw-r--r-- | src/i_info.go | 343 | ||||
-rw-r--r-- | src/i_ui.go | 365 |
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) + } +} |