Refactor and split tui into smaller files
This commit is contained in:
parent
b339dab29f
commit
123d1ca18a
12 changed files with 603 additions and 508 deletions
57
internal/gen_vbios_dumper.go
Normal file
57
internal/gen_vbios_dumper.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
|
||||
"github.com/HikariKnight/quickpassthrough/internal/configs"
|
||||
)
|
||||
|
||||
func generateVBIOSDumper(m model) {
|
||||
// Get the vbios path
|
||||
m.vbios_path = getIOMMU("-g", "-i", m.gpu_group, "--rom")[0]
|
||||
|
||||
// Get the config directories
|
||||
config := configs.GetConfigPaths()
|
||||
|
||||
// Get the program directory
|
||||
exe, _ := os.Executable()
|
||||
scriptdir := filepath.Dir(exe)
|
||||
|
||||
// If we are using go run use the working directory instead
|
||||
if strings.Contains(scriptdir, "/tmp/go-build") {
|
||||
scriptdir, _ = os.Getwd()
|
||||
}
|
||||
|
||||
vbios_script_template := fmt.Sprint(
|
||||
"#!/bin/bash\n",
|
||||
"# THIS FILE IS AUTO GENERATED!\n",
|
||||
"# IF YOU HAVE CHANGED GPU, PLEASE RE-RUN QUICKPASSTHROUGH!\n",
|
||||
"echo 1 | sudo tee %s\n",
|
||||
"sudo bash -c \"cat %s\" > %s/%s/vfio_card.rom\n",
|
||||
"echo 0 | sudo tee %s\n",
|
||||
)
|
||||
|
||||
vbios_script := fmt.Sprintf(
|
||||
vbios_script_template,
|
||||
m.vbios_path,
|
||||
m.vbios_path,
|
||||
scriptdir,
|
||||
config.QUICKEMU,
|
||||
m.vbios_path,
|
||||
)
|
||||
|
||||
scriptfile, err := os.Create("utils/dump_vbios.sh")
|
||||
errorcheck.ErrorCheck(err, "Cannot create file \"utils/dump_vbios.sh\"")
|
||||
defer scriptfile.Close()
|
||||
|
||||
// Make the script executable
|
||||
scriptfile.Chmod(0775)
|
||||
errorcheck.ErrorCheck(err, "Could not change permissions of \"utils/dump_vbios.sh\"")
|
||||
|
||||
// Write the script
|
||||
scriptfile.WriteString(vbios_script)
|
||||
}
|
|
@ -14,7 +14,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
|
||||
"github.com/HikariKnight/quickpassthrough/internal/untar"
|
||||
"github.com/HikariKnight/quickpassthrough/pkg/untar"
|
||||
"github.com/cavaliergopher/grab/v3"
|
||||
)
|
||||
|
||||
|
@ -92,7 +92,7 @@ type Response struct {
|
|||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
func GetLsIOMMU() {
|
||||
func CheckLsIOMMU() {
|
||||
// Check the API for releases
|
||||
resp, err := http.Get("https://api.github.com/repos/hikariknight/ls-iommu/releases/latest")
|
||||
errorcheck.ErrorCheck(err)
|
||||
|
|
91
internal/params/params.go
Normal file
91
internal/params/params.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
package params
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/akamensky/argparse"
|
||||
)
|
||||
|
||||
/*
|
||||
The whole purpose of this module is to make a struct
|
||||
to just carry all our parsed arguments around between functions
|
||||
|
||||
Create a Params struct with all the argparse arguments
|
||||
pArg := params.NewParams()
|
||||
*/
|
||||
|
||||
type Params struct {
|
||||
Flag map[string]bool
|
||||
FlagCounter map[string]int
|
||||
IntList map[string][]int
|
||||
StringList map[string][]string
|
||||
String map[string]string
|
||||
}
|
||||
|
||||
func (p *Params) addFlag(name string, flag bool) {
|
||||
p.Flag[name] = flag
|
||||
}
|
||||
|
||||
func (p *Params) addFlagCounter(name string, flag int) {
|
||||
p.FlagCounter[name] = flag
|
||||
}
|
||||
|
||||
func (p *Params) addIntList(name string, flag []int) {
|
||||
p.IntList[name] = flag
|
||||
}
|
||||
|
||||
func (p *Params) addStringList(name string, flag []string) {
|
||||
p.StringList[name] = flag
|
||||
}
|
||||
|
||||
func (p *Params) addString(name string, flag string) {
|
||||
p.String[name] = flag
|
||||
}
|
||||
|
||||
func NewParams() *Params {
|
||||
// Setup the parser for arguments
|
||||
parser := argparse.NewParser("quickpassthrough", "A utility to help you configure your host for GPU Passthrough")
|
||||
|
||||
// Configure arguments
|
||||
gui := parser.Flag("g", "gui", &argparse.Options{
|
||||
Required: false,
|
||||
Help: "Launch GUI (placeholder for now)",
|
||||
})
|
||||
|
||||
// Parse arguments
|
||||
err := parser.Parse(os.Args)
|
||||
if err != nil {
|
||||
// In case of error print error and print usage
|
||||
// This can also be done by passing -h or --help flags
|
||||
fmt.Print(parser.Usage(err))
|
||||
os.Exit(4)
|
||||
}
|
||||
|
||||
// Make our struct
|
||||
pArg := &Params{
|
||||
Flag: make(map[string]bool),
|
||||
FlagCounter: make(map[string]int),
|
||||
IntList: make(map[string][]int),
|
||||
StringList: make(map[string][]string),
|
||||
String: make(map[string]string),
|
||||
}
|
||||
|
||||
// Add all parsed arguments to a struct for portability since we will use them all over the program
|
||||
pArg.addFlag("gui", *gui)
|
||||
/*pArg.addFlag("gpu", *gpu)
|
||||
pArg.addFlag("usb", *usb)
|
||||
pArg.addFlag("nic", *nic)
|
||||
pArg.addFlag("sata", *sata)
|
||||
pArg.addFlagCounter("related", *related)
|
||||
pArg.addStringList("ignore", *ignore)
|
||||
pArg.addIntList("iommu_group", *iommu_group)
|
||||
pArg.addFlag("kernelmodules", *kernelmodules)
|
||||
pArg.addFlag("legacyoutput", *legacyoutput)
|
||||
pArg.addFlag("id", *id)
|
||||
pArg.addFlag("pciaddr", *pciaddr)
|
||||
pArg.addFlag("rom", *rom)
|
||||
pArg.addString("format", *format)*/
|
||||
|
||||
return pArg
|
||||
}
|
|
@ -1,499 +0,0 @@
|
|||
package tuimode
|
||||
|
||||
// A simple example demonstrating the use of multiple text input components
|
||||
// from the Bubbles component library.
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
|
||||
"github.com/HikariKnight/quickpassthrough/internal/configs"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
var (
|
||||
titleStyle = lipgloss.NewStyle().
|
||||
Background(lipgloss.Color("#5F5FD7")).
|
||||
Foreground(lipgloss.Color("#FFFFFF")).
|
||||
PaddingLeft(2).PaddingRight(2)
|
||||
helpStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color(241))
|
||||
listStyle = lipgloss.NewStyle().
|
||||
PaddingLeft(2)
|
||||
choiceStyle = lipgloss.NewStyle().PaddingLeft(4)
|
||||
selectedChoiceStyle = lipgloss.NewStyle().
|
||||
PaddingLeft(2).
|
||||
Foreground(lipgloss.Color("170"))
|
||||
dialogStyle = lipgloss.NewStyle().
|
||||
PaddingLeft(2).
|
||||
Width(78)
|
||||
)
|
||||
|
||||
// Make a status type
|
||||
type status int
|
||||
|
||||
// List item struct
|
||||
type item struct {
|
||||
title, desc string
|
||||
}
|
||||
|
||||
// Functions needed for item struct
|
||||
func (i item) Title() string { return i.title }
|
||||
func (i item) Description() string { return i.desc }
|
||||
func (i item) FilterValue() string { return i.title }
|
||||
|
||||
// Choice delegate (for our dialog boxes)
|
||||
type choiceDelegate struct{}
|
||||
|
||||
func (d choiceDelegate) Height() int { return 1 }
|
||||
func (d choiceDelegate) Spacing() int { return 0 }
|
||||
func (d choiceDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
|
||||
func (d choiceDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
|
||||
i, ok := listItem.(item)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
str := i.title
|
||||
|
||||
fn := choiceStyle.Render
|
||||
if index == m.Index() {
|
||||
fn = func(s ...string) string {
|
||||
return selectedChoiceStyle.Render("| " + strings.Join(s, " "))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprint(w, fn(str))
|
||||
}
|
||||
|
||||
// Main Model
|
||||
type model struct {
|
||||
fetched []bool
|
||||
lists []list.Model
|
||||
gpu_group string
|
||||
vbios_path string
|
||||
loaded bool
|
||||
focused status
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
// Consts used to navigate the main model
|
||||
const (
|
||||
INTRO status = iota
|
||||
GPUS
|
||||
GPU_GROUP
|
||||
VBIOS
|
||||
VIDEO
|
||||
USB
|
||||
USB_GROUP
|
||||
DONE
|
||||
)
|
||||
|
||||
func (m *model) initLists(width, height int) {
|
||||
defaultList := list.New([]list.Item{}, list.NewDefaultDelegate(), 0, 10)
|
||||
choiceList := list.New([]list.Item{}, choiceDelegate{}, 0, 7)
|
||||
|
||||
// Disable features we wont need
|
||||
defaultList.SetShowTitle(false)
|
||||
defaultList.SetFilteringEnabled(false)
|
||||
defaultList.SetSize(width, height)
|
||||
choiceList.SetShowTitle(false)
|
||||
choiceList.SetFilteringEnabled(false)
|
||||
|
||||
// Add height and width to our model so we can use it later
|
||||
m.width = width
|
||||
m.height = height
|
||||
|
||||
m.lists = []list.Model{
|
||||
choiceList,
|
||||
defaultList,
|
||||
defaultList,
|
||||
choiceList,
|
||||
choiceList,
|
||||
defaultList,
|
||||
defaultList,
|
||||
choiceList,
|
||||
}
|
||||
m.fetched = []bool{
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
}
|
||||
m.focused = INTRO
|
||||
|
||||
// Init INTRO choices
|
||||
items := []list.Item{
|
||||
item{title: "CONTINUE"},
|
||||
}
|
||||
m.lists[INTRO].SetHeight(5)
|
||||
m.lists[INTRO].SetItems(items)
|
||||
|
||||
// Init GPU list
|
||||
//m.lists[GPUS].Title = "Select a GPU to check the IOMMU groups of"
|
||||
items = StringList2ListItem(GetIOMMU("-g", "-F", "name,device_id,optional_revision"))
|
||||
m.lists[GPUS].SetItems(items)
|
||||
m.fetched[GPUS] = true
|
||||
|
||||
m.lists[GPU_GROUP].Title = ""
|
||||
m.lists[GPU_GROUP].SetItems(items)
|
||||
|
||||
// Init USB Controller list
|
||||
items = StringList2ListItem(GetIOMMU("-u", "-F", "name,device_id,optional_revision"))
|
||||
m.lists[USB].SetItems(items)
|
||||
m.fetched[USB] = true
|
||||
|
||||
m.lists[USB_GROUP].Title = ""
|
||||
m.lists[USB_GROUP].SetItems(items)
|
||||
|
||||
// Init VBIOS choices
|
||||
items = []list.Item{
|
||||
item{title: "OK"},
|
||||
}
|
||||
m.lists[VBIOS].SetItems(items)
|
||||
|
||||
// Init VIDEO disable choises
|
||||
items = []list.Item{
|
||||
item{title: "YES"},
|
||||
item{title: "NO"},
|
||||
}
|
||||
m.lists[VIDEO].SetItems(items)
|
||||
|
||||
// Init VIDEO disable choises
|
||||
items = []list.Item{
|
||||
item{title: "FINISH"},
|
||||
}
|
||||
m.lists[DONE].SetItems(items)
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This function processes the enter event
|
||||
func (m *model) processSelection() {
|
||||
switch m.focused {
|
||||
case GPUS:
|
||||
configs.InitConfigs()
|
||||
|
||||
// Gets the selected item
|
||||
selectedItem := m.lists[m.focused].SelectedItem()
|
||||
|
||||
// Gets the IOMMU group of the selected item
|
||||
iommu_group_regex := regexp.MustCompile(`(\d{1,3})`)
|
||||
iommu_group := iommu_group_regex.FindString(selectedItem.(item).desc)
|
||||
|
||||
// Add the gpu group to our model
|
||||
m.gpu_group = iommu_group
|
||||
|
||||
items := StringList2ListItem(GetIOMMU("-gr", "-i", m.gpu_group, "-F", "name,device_id,optional_revision"))
|
||||
m.lists[GPU_GROUP].SetItems(items)
|
||||
|
||||
// Adjust height to correct for a bigger title
|
||||
m.lists[GPU_GROUP].SetSize(m.width, m.height-1)
|
||||
|
||||
// Change focus to next index
|
||||
m.focused++
|
||||
|
||||
case GPU_GROUP:
|
||||
// Generate the VBIOS dumper script once the user has selected a GPU
|
||||
GenerateVBIOSDumper(*m)
|
||||
m.focused++
|
||||
|
||||
case USB:
|
||||
// Gets the selected item
|
||||
selectedItem := m.lists[m.focused].SelectedItem()
|
||||
|
||||
// Gets the IOMMU group of the selected item
|
||||
iommu_group_regex := regexp.MustCompile(`(\d{1,3})`)
|
||||
iommu_group := iommu_group_regex.FindString(selectedItem.(item).desc)
|
||||
|
||||
items := StringList2ListItem(GetIOMMU("-ur", "-i", iommu_group, "-F", "name,device_id,optional_revision"))
|
||||
|
||||
m.lists[USB_GROUP].SetItems(items)
|
||||
|
||||
// Adjust height to correct for a bigger title
|
||||
m.lists[USB_GROUP].SetSize(m.width, m.height-1)
|
||||
|
||||
// Change focus to next index
|
||||
m.focused++
|
||||
|
||||
case USB_GROUP:
|
||||
m.focused++
|
||||
|
||||
case VBIOS:
|
||||
m.focused++
|
||||
|
||||
case VIDEO:
|
||||
m.focused++
|
||||
|
||||
case INTRO:
|
||||
m.focused++
|
||||
|
||||
case DONE:
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "ctrl+c", "q":
|
||||
return m, tea.Quit
|
||||
|
||||
case "enter":
|
||||
if m.loaded {
|
||||
m.processSelection()
|
||||
}
|
||||
case "ctrl+z", "backspace":
|
||||
if m.focused > 0 {
|
||||
m.focused--
|
||||
return m, nil
|
||||
} else {
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
case tea.WindowSizeMsg:
|
||||
if !m.loaded {
|
||||
// Get the terminal frame size
|
||||
//h, v := docStyle.GetFrameSize()
|
||||
|
||||
// Initialize the static lists and make sure the content
|
||||
// does not extend past the screen
|
||||
m.initLists(msg.Width-2, msg.Height-2)
|
||||
|
||||
// Set model loaded to true
|
||||
m.loaded = true
|
||||
}
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
m.lists[m.focused], cmd = m.lists[m.focused].Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m model) View() string {
|
||||
if m.loaded {
|
||||
title := ""
|
||||
switch m.focused {
|
||||
case INTRO:
|
||||
title = dialogStyle.Render(
|
||||
fmt.Sprint(
|
||||
titleStyle.Render("Welcome to QuickPassthrough!"),
|
||||
"\n\n",
|
||||
"This script is meant to make it easier to setup GPU passthrough for Qemu systems.\n",
|
||||
"However due to the complexity of GPU passthrough, this script assumes you know how to do (or have done) the following.\n\n",
|
||||
"* You have already enabled IOMMU, VT-d, SVM and/or AMD-v\n inside your UEFI/BIOS advanced settings.\n",
|
||||
"* Know how to edit your bootloader\n",
|
||||
"* Have a bootloader timeout of at least 3 seconds to access the menu\n",
|
||||
"* Enable & Configure kernel modules\n",
|
||||
"* Have a backup/snapshot of your system in case the script causes your\n system to be unbootable\n\n",
|
||||
"By continuing you accept that I am not liable if your system\n",
|
||||
"becomes unbootable, as you will be asked to verify the files generated",
|
||||
),
|
||||
)
|
||||
case GPUS:
|
||||
title = titleStyle.Render(
|
||||
"Select a GPU to check the IOMMU groups of",
|
||||
)
|
||||
|
||||
case GPU_GROUP:
|
||||
title = titleStyle.Render(
|
||||
fmt.Sprint(
|
||||
"Press ENTER/RETURN to set up all these devices for passthrough.\n",
|
||||
"This list should only contain items related to your GPU.",
|
||||
),
|
||||
)
|
||||
|
||||
case USB:
|
||||
title = titleStyle.Render(
|
||||
"[OPTIONAL]: Select a USB Controller to check the IOMMU groups of",
|
||||
)
|
||||
|
||||
case USB_GROUP:
|
||||
title = titleStyle.Render(
|
||||
fmt.Sprint(
|
||||
"Press ENTER/RETURN to set up all these devices for passthrough.\n",
|
||||
"This list should only contain the USB controller you want to use.",
|
||||
),
|
||||
)
|
||||
|
||||
case VBIOS:
|
||||
// Get the program directory
|
||||
exe, _ := os.Executable()
|
||||
scriptdir := filepath.Dir(exe)
|
||||
|
||||
// If we are using go run use the working directory instead
|
||||
if strings.Contains(scriptdir, "/tmp/go-build") {
|
||||
scriptdir, _ = os.Getwd()
|
||||
}
|
||||
|
||||
text := dialogStyle.Render(
|
||||
fmt.Sprint(
|
||||
"Based on your GPU selection, a vbios extraction script has been generated for your convenience.\n",
|
||||
"Passing a VBIOS rom to the card used for passthrough is required for some cards, but not all.\n",
|
||||
"Some cards also requires you to patch your VBIOS romfile, check online if this is neccessary for your card!\n",
|
||||
"The VBIOS will be read from:\n",
|
||||
"%s\n\n",
|
||||
"The script to extract the vbios has to be run as sudo and without a displaymanager running for proper dumping!\n",
|
||||
"\n",
|
||||
"You can run the script with:\n",
|
||||
"%s/utils/dump_vbios.sh",
|
||||
),
|
||||
)
|
||||
|
||||
title = fmt.Sprintf(text, m.vbios_path, scriptdir)
|
||||
|
||||
case VIDEO:
|
||||
title = dialogStyle.Render(
|
||||
fmt.Sprint(
|
||||
"Disabling video output in Linux for the card you want to use in a VM\n",
|
||||
"will make it easier to successfully do the passthrough without issues.\n",
|
||||
"\n",
|
||||
"Do you want to force disable video output in linux on this card?",
|
||||
),
|
||||
)
|
||||
|
||||
case DONE:
|
||||
title = dialogStyle.Render(
|
||||
fmt.Sprint(
|
||||
"The configuration files have been generated and are\n",
|
||||
"located inside the \"config\" folder\n",
|
||||
"\n",
|
||||
"* The \"cmdline\" file contains kernel arguments that your bootloader needs\n",
|
||||
"* The \"quickemu\" folder contains files that might be\n useable for quickemu in the future\n",
|
||||
"* The files inside the \"etc\" folder must be copied to your system.\n NOTE: Verify that these files are correctly formated/edited!\n",
|
||||
"\n",
|
||||
"A script file named \"install.sh\" has been generated, run it to copy the files to your system and make a backup of your old files.",
|
||||
),
|
||||
)
|
||||
}
|
||||
//return listStyle.SetString(fmt.Sprintf("%s\n\n", title)).Render(m.lists[m.focused].View())
|
||||
return lipgloss.JoinVertical(lipgloss.Left, fmt.Sprintf("%s\n%s\n", title, listStyle.Render(m.lists[m.focused].View())))
|
||||
} else {
|
||||
return "Loading..."
|
||||
}
|
||||
}
|
||||
|
||||
func NewModel() *model {
|
||||
// Create a blank model and return it
|
||||
return &model{}
|
||||
}
|
||||
|
||||
// This is where we build everything
|
||||
func App() {
|
||||
// Make a blank model to keep our state in
|
||||
m := NewModel()
|
||||
|
||||
// Start the program with the model
|
||||
p := tea.NewProgram(m, tea.WithAltScreen())
|
||||
_, err := p.Run()
|
||||
errorcheck.ErrorCheck(err, "Failed to initialize UI")
|
||||
}
|
||||
|
||||
func GetIOMMU(args ...string) []string {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
// Configure the ls-iommu command
|
||||
cmd := exec.Command("utils/ls-iommu", args...)
|
||||
cmd.Stderr = &stderr
|
||||
cmd.Stdout = &stdout
|
||||
|
||||
// Execute the command
|
||||
err := cmd.Run()
|
||||
|
||||
// If ls-iommu returns an error then IOMMU is disabled
|
||||
errorcheck.ErrorCheck(err, "IOMMU disabled in either UEFI/BIOS or in bootloader!")
|
||||
|
||||
// Read the output
|
||||
var items []string
|
||||
output, _ := io.ReadAll(&stdout)
|
||||
|
||||
// Parse the output line by line
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(output)))
|
||||
for scanner.Scan() {
|
||||
// Write the objects into the list
|
||||
items = append(items, scanner.Text())
|
||||
}
|
||||
|
||||
// Return our list of items
|
||||
return items
|
||||
}
|
||||
|
||||
func StringList2ListItem(stringList []string) []list.Item {
|
||||
// Make the []list.Item struct
|
||||
items := []list.Item{}
|
||||
|
||||
// Parse the output line by line
|
||||
for _, v := range stringList {
|
||||
// Get the current line and split by :
|
||||
objects := strings.Split(v, ": ")
|
||||
// Write the objects into the list
|
||||
items = append(items, item{title: objects[1], desc: objects[0]})
|
||||
}
|
||||
|
||||
// Return our list of items
|
||||
return items
|
||||
}
|
||||
|
||||
func GenerateVBIOSDumper(m model) {
|
||||
// Get the vbios path
|
||||
m.vbios_path = GetIOMMU("-g", "-i", m.gpu_group, "--rom")[0]
|
||||
|
||||
// Get the config directories
|
||||
config := configs.GetConfigPaths()
|
||||
|
||||
// Get the program directory
|
||||
exe, _ := os.Executable()
|
||||
scriptdir := filepath.Dir(exe)
|
||||
|
||||
// If we are using go run use the working directory instead
|
||||
if strings.Contains(scriptdir, "/tmp/go-build") {
|
||||
scriptdir, _ = os.Getwd()
|
||||
}
|
||||
|
||||
vbios_script_template := fmt.Sprint(
|
||||
"#!/bin/bash\n",
|
||||
"# THIS FILE IS AUTO GENERATED!\n",
|
||||
"# IF YOU HAVE CHANGED GPU, PLEASE RE-RUN QUICKPASSTHROUGH!\n",
|
||||
"echo 1 | sudo tee %s\n",
|
||||
"sudo bash -c \"cat %s\" > %s/%s/vfio_card.rom\n",
|
||||
"echo 0 | sudo tee %s\n",
|
||||
)
|
||||
|
||||
vbios_script := fmt.Sprintf(
|
||||
vbios_script_template,
|
||||
m.vbios_path,
|
||||
m.vbios_path,
|
||||
scriptdir,
|
||||
config.QUICKEMU,
|
||||
m.vbios_path,
|
||||
)
|
||||
|
||||
scriptfile, err := os.Create("utils/dump_vbios.sh")
|
||||
errorcheck.ErrorCheck(err, "Cannot create file \"utils/dump_vbios.sh\"")
|
||||
defer scriptfile.Close()
|
||||
|
||||
// Make the script executable
|
||||
scriptfile.Chmod(0775)
|
||||
errorcheck.ErrorCheck(err, "Could not change permissions of \"utils/dump_vbios.sh\"")
|
||||
|
||||
// Write the script
|
||||
scriptfile.WriteString(vbios_script)
|
||||
}
|
77
internal/ui_main.go
Normal file
77
internal/ui_main.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
package internal
|
||||
|
||||
// A simple example demonstrating the use of multiple text input components
|
||||
// from the Bubbles component library.
|
||||
|
||||
import (
|
||||
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
// Setup keybindings
|
||||
switch msg.String() {
|
||||
case "ctrl+c", "q":
|
||||
// Exit when user presses Q or CTRL+C
|
||||
return m, tea.Quit
|
||||
|
||||
case "enter":
|
||||
if m.loaded {
|
||||
// Process the selected item
|
||||
m.processSelection()
|
||||
}
|
||||
case "ctrl+z", "backspace":
|
||||
// Go backwards in the model
|
||||
if m.focused > 0 {
|
||||
m.focused--
|
||||
return m, nil
|
||||
} else {
|
||||
// If we are at the beginning, just exit
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
case tea.WindowSizeMsg:
|
||||
if !m.loaded {
|
||||
// Initialize the static lists and make sure the content
|
||||
// does not extend past the screen
|
||||
m.initLists(msg.Width, msg.Height)
|
||||
|
||||
// Set model loaded to true
|
||||
m.loaded = true
|
||||
} else {
|
||||
// Else we are loaded and will update the sizing on the fly
|
||||
m.height = msg.Height
|
||||
m.width = msg.Width
|
||||
|
||||
// TODO: Find a better way to resize widgets when word wrapping happens
|
||||
// BUG: currently breaks the UI rendering if word wrapping happens in some cases...
|
||||
views := len(m.lists)
|
||||
if msg.Width > 83 {
|
||||
for i := 0; i < views; i++ {
|
||||
m.lists[i].SetSize(m.width-m.offsetx[i], m.height-m.offsety[i])
|
||||
// Update the styles with the correct width
|
||||
dialogStyle = dialogStyle.Width(m.width)
|
||||
listStyle = listStyle.Width(m.width)
|
||||
titleStyle = titleStyle.Width(m.width - 2)
|
||||
choiceStyle = choiceStyle.Width(m.width)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m.lists[m.focused], cmd = m.lists[m.focused].Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
// This is where we build everything
|
||||
func Tui() {
|
||||
// Make a blank model to keep our state in
|
||||
m := NewModel()
|
||||
|
||||
// Start the program with the model
|
||||
p := tea.NewProgram(m, tea.WithAltScreen())
|
||||
_, err := p.Run()
|
||||
errorcheck.ErrorCheck(err, "Failed to initialize UI")
|
||||
}
|
72
internal/ui_main_functions.go
Normal file
72
internal/ui_main_functions.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/HikariKnight/quickpassthrough/internal/configs"
|
||||
)
|
||||
|
||||
// This function processes the enter event
|
||||
func (m *model) processSelection() {
|
||||
switch m.focused {
|
||||
case GPUS:
|
||||
configs.InitConfigs()
|
||||
|
||||
// Gets the selected item
|
||||
selectedItem := m.lists[m.focused].SelectedItem()
|
||||
|
||||
// Gets the IOMMU group of the selected item
|
||||
iommu_group_regex := regexp.MustCompile(`(\d{1,3})`)
|
||||
iommu_group := iommu_group_regex.FindString(selectedItem.(item).desc)
|
||||
|
||||
// Add the gpu group to our model (this is so we can grab the vbios details later)
|
||||
m.gpu_group = iommu_group
|
||||
|
||||
// Get all the gpu devices and related devices (same device id or in the same group)
|
||||
items := iommuList2ListItem(getIOMMU("-grr", "-i", m.gpu_group, "-F", "vendor:,prod_name,optional_revision:,device_id"))
|
||||
|
||||
// Add the devices to the list
|
||||
m.lists[GPU_GROUP].SetItems(items)
|
||||
|
||||
// Change focus to next index
|
||||
m.focused++
|
||||
|
||||
case GPU_GROUP:
|
||||
// Generate the VBIOS dumper script once the user has selected a GPU
|
||||
generateVBIOSDumper(*m)
|
||||
m.focused++
|
||||
|
||||
case USB:
|
||||
// Gets the selected item
|
||||
selectedItem := m.lists[m.focused].SelectedItem()
|
||||
|
||||
// Gets the IOMMU group of the selected item
|
||||
iommu_group_regex := regexp.MustCompile(`(\d{1,3})`)
|
||||
iommu_group := iommu_group_regex.FindString(selectedItem.(item).desc)
|
||||
|
||||
// Get the USB controllers in the selected iommu group
|
||||
items := iommuList2ListItem(getIOMMU("-ur", "-i", iommu_group, "-F", "vendor:,prod_name,optional_revision:,device_id"))
|
||||
|
||||
// Add the items to the list
|
||||
m.lists[USB_GROUP].SetItems(items)
|
||||
|
||||
// Change focus to next index
|
||||
m.focused++
|
||||
|
||||
case USB_GROUP:
|
||||
m.focused++
|
||||
|
||||
case VBIOS:
|
||||
m.focused++
|
||||
|
||||
case VIDEO:
|
||||
m.focused++
|
||||
|
||||
case INTRO:
|
||||
m.focused++
|
||||
|
||||
case DONE:
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
117
internal/ui_main_view.go
Normal file
117
internal/ui_main_view.go
Normal file
|
@ -0,0 +1,117 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
func (m model) View() string {
|
||||
if m.loaded {
|
||||
title := ""
|
||||
switch m.focused {
|
||||
case INTRO:
|
||||
title = dialogStyle.Render(
|
||||
fmt.Sprint(
|
||||
titleStyle.MarginLeft(0).Render("Welcome to QuickPassthrough!"),
|
||||
"\n\n",
|
||||
"This script is meant to make it easier to setup GPU passthrough for\n",
|
||||
"Qemu based systems.\n",
|
||||
"However due to the complexity of GPU passthrough\n",
|
||||
"This script assumes you know how to do (or have done) the following.\n\n",
|
||||
"* You have already enabled IOMMU, VT-d, SVM and/or AMD-v\n inside your UEFI/BIOS advanced settings.\n",
|
||||
"* Know how to edit your bootloader\n",
|
||||
"* Have a bootloader timeout of at least 3 seconds to access the menu\n",
|
||||
"* Enable & Configure kernel modules\n",
|
||||
"* Have a backup/snapshot of your system in case the script causes your\n system to be unbootable\n\n",
|
||||
"By continuing you accept that I am not liable if your system\n",
|
||||
"becomes unbootable, as you will be asked to verify the files generated",
|
||||
),
|
||||
)
|
||||
case GPUS:
|
||||
title = titleStyle.MarginLeft(2).Render(
|
||||
"Select a GPU to check the IOMMU groups of",
|
||||
)
|
||||
|
||||
case GPU_GROUP:
|
||||
title = titleStyle.Render(
|
||||
fmt.Sprint(
|
||||
"Press ENTER/RETURN to set up all these devices for passthrough.\n",
|
||||
"This list should only contain items related to your GPU.",
|
||||
),
|
||||
)
|
||||
|
||||
case USB:
|
||||
title = titleStyle.Render(
|
||||
"[OPTIONAL]: Select a USB Controller to check the IOMMU groups of",
|
||||
)
|
||||
|
||||
case USB_GROUP:
|
||||
title = titleStyle.Render(
|
||||
fmt.Sprint(
|
||||
"Press ENTER/RETURN to set up all these devices for passthrough.\n",
|
||||
"This list should only contain the USB controller you want to use.",
|
||||
),
|
||||
)
|
||||
|
||||
case VBIOS:
|
||||
// Get the program directory
|
||||
exe, _ := os.Executable()
|
||||
scriptdir := filepath.Dir(exe)
|
||||
|
||||
// If we are using go run use the working directory instead
|
||||
if strings.Contains(scriptdir, "/tmp/go-build") {
|
||||
scriptdir, _ = os.Getwd()
|
||||
}
|
||||
|
||||
text := dialogStyle.Render(
|
||||
fmt.Sprint(
|
||||
"Based on your GPU selection, a vbios extraction script has been generated for your convenience.\n",
|
||||
"Passing a VBIOS rom to the card used for passthrough is required for some cards, but not all.\n",
|
||||
"Some cards also requires you to patch your VBIOS romfile, check online if this is neccessary for your card!\n",
|
||||
"The VBIOS will be read from:\n",
|
||||
"%s\n\n",
|
||||
"The script to extract the vbios has to be run as sudo and without a displaymanager running for proper dumping!\n",
|
||||
"\n",
|
||||
"You can run the script with:\n",
|
||||
"%s/utils/dump_vbios.sh",
|
||||
),
|
||||
)
|
||||
|
||||
title = fmt.Sprintf(text, m.vbios_path, scriptdir)
|
||||
|
||||
case VIDEO:
|
||||
title = dialogStyle.Render(
|
||||
fmt.Sprint(
|
||||
"Disabling video output in Linux for the card you want to use in a VM\n",
|
||||
"will make it easier to successfully do the passthrough without issues.\n",
|
||||
"\n",
|
||||
"Do you want to force disable video output in linux on this card?",
|
||||
),
|
||||
)
|
||||
|
||||
case DONE:
|
||||
title = dialogStyle.Render(
|
||||
fmt.Sprint(
|
||||
"The configuration files have been generated and are\n",
|
||||
"located inside the \"config\" folder\n",
|
||||
"\n",
|
||||
"* The \"cmdline\" file contains kernel arguments that your bootloader needs\n",
|
||||
"* The \"quickemu\" folder contains files that might be\n useable for quickemu in the future\n",
|
||||
"* The files inside the \"etc\" folder must be copied to your system.\n",
|
||||
" NOTE: Verify that these files are correctly formated/edited!\n",
|
||||
"\n",
|
||||
"A script file named \"install.sh\" has been generated,\n",
|
||||
"run it to copy the files to your system and make a backup of your old files.",
|
||||
),
|
||||
)
|
||||
}
|
||||
//return listStyle.SetString(fmt.Sprintf("%s\n\n", title)).Render(m.lists[m.focused].View())
|
||||
return lipgloss.JoinVertical(lipgloss.Left, fmt.Sprintf("%s\n%s\n", title, listStyle.Render(m.lists[m.focused].View())))
|
||||
} else {
|
||||
return "Loading..."
|
||||
}
|
||||
}
|
154
internal/ui_model.go
Normal file
154
internal/ui_model.go
Normal file
|
@ -0,0 +1,154 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// Make a status type
|
||||
type status int
|
||||
|
||||
// List item struct
|
||||
type item struct {
|
||||
title, desc string
|
||||
}
|
||||
|
||||
// Functions needed for item struct
|
||||
func (i item) Title() string { return i.title }
|
||||
func (i item) Description() string { return i.desc }
|
||||
func (i item) FilterValue() string { return i.title }
|
||||
|
||||
// Main Model
|
||||
type model struct {
|
||||
fetched []bool
|
||||
lists []list.Model
|
||||
gpu_group string
|
||||
vbios_path string
|
||||
loaded bool
|
||||
focused status
|
||||
offsetx []int
|
||||
offsety []int
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
// Consts used to navigate the main model
|
||||
const (
|
||||
INTRO status = iota
|
||||
GPUS
|
||||
GPU_GROUP
|
||||
VBIOS
|
||||
VIDEO
|
||||
USB
|
||||
USB_GROUP
|
||||
DONE
|
||||
)
|
||||
|
||||
func NewModel() *model {
|
||||
// Create a blank model and return it
|
||||
return &model{}
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *model) initLists(width, height int) {
|
||||
defaultList := list.New([]list.Item{}, list.NewDefaultDelegate(), 0, 10)
|
||||
choiceList := list.New([]list.Item{}, choiceDelegate{}, 0, 7)
|
||||
|
||||
// Disable features we wont need
|
||||
defaultList.SetShowTitle(false)
|
||||
defaultList.SetFilteringEnabled(false)
|
||||
defaultList.SetSize(m.width, m.height)
|
||||
choiceList.SetShowTitle(false)
|
||||
choiceList.SetFilteringEnabled(false)
|
||||
|
||||
// Add height and width to our model so we can use it later
|
||||
m.width = width
|
||||
m.height = height
|
||||
|
||||
m.lists = []list.Model{
|
||||
choiceList,
|
||||
defaultList,
|
||||
defaultList,
|
||||
choiceList,
|
||||
choiceList,
|
||||
defaultList,
|
||||
defaultList,
|
||||
choiceList,
|
||||
}
|
||||
|
||||
// Configure offsets for sizing
|
||||
m.offsetx = []int{
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
}
|
||||
m.offsety = []int{
|
||||
18, 2, 3, 13, 5, 2, 3, 12,
|
||||
}
|
||||
|
||||
// Update the styles with the correct width
|
||||
dialogStyle = dialogStyle.Width(m.width)
|
||||
listStyle = listStyle.Width(m.width)
|
||||
titleStyle = titleStyle.Width(m.width - 2)
|
||||
choiceStyle = choiceStyle.Width(m.width)
|
||||
|
||||
// Make m.fetched and set all values to FALSE
|
||||
m.fetched = []bool{}
|
||||
for range m.lists {
|
||||
m.fetched = append(m.fetched, false)
|
||||
}
|
||||
|
||||
// Set INTRO to the focused view
|
||||
m.focused = INTRO
|
||||
|
||||
// Init INTRO choices
|
||||
items := []list.Item{
|
||||
item{title: "CONTINUE"},
|
||||
}
|
||||
//m.lists[INTRO].SetHeight(5)
|
||||
m.lists[INTRO].SetItems(items)
|
||||
m.lists[INTRO].SetSize(m.width-m.offsetx[INTRO], m.height-m.offsety[INTRO])
|
||||
|
||||
// Init GPU list
|
||||
items = iommuList2ListItem(getIOMMU("-g", "-F", "vendor:,prod_name,optional_revision:,device_id"))
|
||||
m.lists[GPUS].SetItems(items)
|
||||
m.lists[GPUS].SetSize(m.width-m.offsetx[GPUS], m.height-m.offsety[GPUS])
|
||||
m.fetched[GPUS] = true
|
||||
|
||||
// Setup the initial GPU_GROUP list
|
||||
// The content in this list is generated from the selected choice from the GPU view
|
||||
m.lists[GPU_GROUP].SetSize(m.width-m.offsetx[GPU_GROUP], m.height-m.offsety[GPU_GROUP])
|
||||
|
||||
// Init USB Controller list
|
||||
items = iommuList2ListItem(getIOMMU("-u", "-F", "vendor:,prod_name,optional_revision:,device_id"))
|
||||
m.lists[USB].SetItems(items)
|
||||
m.lists[USB].SetSize(m.width-m.offsetx[USB], m.height-m.offsety[USB])
|
||||
m.fetched[USB] = true
|
||||
|
||||
// Setup the initial USB_GROUP list
|
||||
// The content in this list is generated from the selected choice from the USB view
|
||||
m.lists[USB_GROUP].SetSize(m.width-m.offsetx[USB_GROUP], m.height-m.offsety[USB_GROUP])
|
||||
|
||||
// Init VBIOS choices
|
||||
items = []list.Item{
|
||||
item{title: "OK"},
|
||||
}
|
||||
m.lists[VBIOS].SetItems(items)
|
||||
m.lists[VBIOS].SetSize(m.width-m.offsetx[VBIOS], m.height-m.offsety[VBIOS])
|
||||
|
||||
// Init VIDEO disable choises
|
||||
items = []list.Item{
|
||||
item{title: "YES"},
|
||||
item{title: "NO"},
|
||||
}
|
||||
m.lists[VIDEO].SetItems(items)
|
||||
m.lists[VIDEO].SetSize(m.width-m.offsetx[VIDEO], m.height-m.offsety[VIDEO])
|
||||
|
||||
// Init DONE choises
|
||||
items = []list.Item{
|
||||
item{title: "FINISH"},
|
||||
}
|
||||
m.lists[DONE].SetItems(items)
|
||||
m.lists[DONE].SetSize(m.width-m.offsetx[DONE], m.height-m.offsety[DONE])
|
||||
}
|
55
internal/ui_style.go
Normal file
55
internal/ui_style.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
var (
|
||||
titleStyle = lipgloss.NewStyle().
|
||||
Background(lipgloss.Color("#5F5FD7")).
|
||||
Foreground(lipgloss.Color("#FFFFFF")).
|
||||
PaddingLeft(2).PaddingRight(2)
|
||||
helpStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color(241))
|
||||
listStyle = lipgloss.NewStyle().
|
||||
PaddingLeft(2).
|
||||
PaddingRight(2)
|
||||
choiceStyle = lipgloss.NewStyle().
|
||||
PaddingLeft(4).
|
||||
PaddingRight(4)
|
||||
selectedChoiceStyle = lipgloss.NewStyle().
|
||||
PaddingLeft(2).
|
||||
Foreground(lipgloss.Color("170"))
|
||||
dialogStyle = lipgloss.NewStyle().
|
||||
PaddingLeft(2)
|
||||
)
|
||||
|
||||
// Choice delegate (for our dialog boxes)
|
||||
type choiceDelegate struct{}
|
||||
|
||||
func (d choiceDelegate) Height() int { return 1 }
|
||||
func (d choiceDelegate) Spacing() int { return 0 }
|
||||
func (d choiceDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
|
||||
func (d choiceDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
|
||||
i, ok := listItem.(item)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
str := i.title
|
||||
|
||||
fn := choiceStyle.Render
|
||||
if index == m.Index() {
|
||||
fn = func(s ...string) string {
|
||||
return selectedChoiceStyle.Render("| " + strings.Join(s, " "))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprint(w, fn(str))
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
package untar
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
|
||||
)
|
||||
|
||||
// Source: https://medium.com/@skdomino/taring-untaring-files-in-go-6b07cf56bc07
|
||||
|
||||
// Untar takes a destination path and a reader; a tar reader loops over the tarfile
|
||||
// creating the file structure at 'dst' along the way, and writing any files
|
||||
func Untar(dst string, fileName string) error {
|
||||
r, err := os.Open(fileName)
|
||||
errorcheck.ErrorCheck(err)
|
||||
defer r.Close()
|
||||
|
||||
gzr, err := gzip.NewReader(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer gzr.Close()
|
||||
|
||||
tr := tar.NewReader(gzr)
|
||||
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
|
||||
switch {
|
||||
|
||||
// if no more files are found return
|
||||
case err == io.EOF:
|
||||
return nil
|
||||
|
||||
// return any other error
|
||||
case err != nil:
|
||||
return err
|
||||
|
||||
// if the header is nil, just skip it (not sure how this happens)
|
||||
case header == nil:
|
||||
continue
|
||||
}
|
||||
|
||||
// the target location where the dir/file should be created
|
||||
target := filepath.Join(dst, header.Name)
|
||||
|
||||
// the following switch could also be done using fi.Mode(), not sure if there
|
||||
// a benefit of using one vs. the other.
|
||||
// fi := header.FileInfo()
|
||||
|
||||
// check the file type
|
||||
switch header.Typeflag {
|
||||
|
||||
// if its a dir and it doesn't exist create it
|
||||
case tar.TypeDir:
|
||||
if _, err := os.Stat(target); err != nil {
|
||||
if err := os.MkdirAll(target, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// if it's a file create it
|
||||
case tar.TypeReg:
|
||||
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// copy over contents
|
||||
if _, err := io.Copy(f, tr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// manually close here after each file operation; defering would cause each file close to wait until all operations have completed.
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
}
|
61
internal/utiil_ls-iommu.go
Normal file
61
internal/utiil_ls-iommu.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/HikariKnight/ls-iommu/pkg/errorcheck"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
)
|
||||
|
||||
func getIOMMU(args ...string) []string {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
// Configure the ls-iommu command
|
||||
cmd := exec.Command("utils/ls-iommu", args...)
|
||||
cmd.Stderr = &stderr
|
||||
cmd.Stdout = &stdout
|
||||
|
||||
// Execute the command
|
||||
err := cmd.Run()
|
||||
|
||||
// If ls-iommu returns an error then IOMMU is disabled
|
||||
errorcheck.ErrorCheck(err, "IOMMU disabled in either UEFI/BIOS or in bootloader!")
|
||||
|
||||
// Read the output
|
||||
var items []string
|
||||
output, _ := io.ReadAll(&stdout)
|
||||
|
||||
// Parse the output line by line
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(output)))
|
||||
for scanner.Scan() {
|
||||
// Write the objects into the list
|
||||
items = append(items, scanner.Text())
|
||||
}
|
||||
|
||||
// Return our list of items
|
||||
return items
|
||||
}
|
||||
|
||||
func iommuList2ListItem(stringList []string) []list.Item {
|
||||
// Make the []list.Item struct
|
||||
items := []list.Item{}
|
||||
|
||||
deviceID := regexp.MustCompile(`\[[a-f0-9]{4}:[a-f0-9]{4}\]\s+`)
|
||||
// Parse the output line by line
|
||||
for _, v := range stringList {
|
||||
// Get the current line and split by :
|
||||
objects := strings.Split(v, ": ")
|
||||
|
||||
// Write the objects into the list
|
||||
items = append(items, item{title: deviceID.ReplaceAllString(objects[2], ""), desc: fmt.Sprintf("%s: %s: DeviceID: %s", objects[0], objects[1], objects[3])})
|
||||
}
|
||||
|
||||
// Return our list of items
|
||||
return items
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue