Большое обновление GUI интерфейс - Добавлен фраемворr Walles - Удалена консольная версия - Проработан интерфейс и дизайн - Добавлено кеширование для быстрой реакции. - Сделан .ps1 сборщик для удобной сборки проекта. - Обновлён Readme
192 lines
5.5 KiB
Go
192 lines
5.5 KiB
Go
//go:build windows
|
|
|
|
package tools
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
var (
|
|
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
|
|
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
|
procCreateMutex = kernel32.NewProc("CreateMutexW")
|
|
procCloseHandle = kernel32.NewProc("CloseHandle")
|
|
procCreateJobObject = kernel32.NewProc("CreateJobObjectW")
|
|
procAssignProcessToJobObject = kernel32.NewProc("AssignProcessToJobObject")
|
|
procSetInformationJobObject = kernel32.NewProc("SetInformationJobObject")
|
|
)
|
|
|
|
const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
|
|
const JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
|
|
|
|
var mutexHandle syscall.Handle
|
|
var jobHandle syscall.Handle
|
|
|
|
func init() {
|
|
enableVirtualTerminal()
|
|
createJobObject()
|
|
}
|
|
|
|
func createJobObject() {
|
|
handle, _, _ := procCreateJobObject.Call(0, 0)
|
|
if handle == 0 {
|
|
return
|
|
}
|
|
|
|
jobHandle = syscall.Handle(handle)
|
|
|
|
// Устанавливаем флаг автоматического завершения дочерних процессов
|
|
type JOBOBJECT_EXTENDED_LIMIT_INFORMATION struct {
|
|
BasicLimitInformation struct {
|
|
PerProcessUserTimeLimit uint64
|
|
PerJobUserTimeLimit uint64
|
|
LimitFlags uint32
|
|
MinimumWorkingSetSize uintptr
|
|
MaximumWorkingSetSize uintptr
|
|
ActiveProcessLimit uint32
|
|
Affinity uintptr
|
|
PriorityClass uint32
|
|
SchedulingClass uint32
|
|
}
|
|
IoInfo [48]byte
|
|
ProcessMemoryLimit uintptr
|
|
JobMemoryLimit uintptr
|
|
PeakProcessMemoryUsed uintptr
|
|
PeakJobMemoryUsed uintptr
|
|
}
|
|
|
|
var limitInfo JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
|
limitInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
|
|
|
|
procSetInformationJobObject.Call(
|
|
uintptr(jobHandle),
|
|
9, // JobObjectExtendedLimitInformation
|
|
uintptr(unsafe.Pointer(&limitInfo)),
|
|
unsafe.Sizeof(limitInfo),
|
|
)
|
|
|
|
// Добавляем текущий процесс в Job Object
|
|
currentProcess, _ := syscall.GetCurrentProcess()
|
|
procAssignProcessToJobObject.Call(uintptr(jobHandle), uintptr(currentProcess))
|
|
}
|
|
|
|
func enableVirtualTerminal() {
|
|
handle := os.Stdout.Fd()
|
|
var mode uint32
|
|
|
|
// Получаем текущий режим консоли
|
|
_, _, _ = procGetConsoleMode.Call(uintptr(handle), uintptr(unsafe.Pointer(&mode)))
|
|
|
|
// Добавляем флаг поддержки ANSI
|
|
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
|
|
|
// Устанавливаем новый режим
|
|
_, _, _ = procSetConsoleMode.Call(uintptr(handle), uintptr(mode))
|
|
}
|
|
|
|
func RunBatScript(script string) (string, error) {
|
|
// Создание временного файла
|
|
tmpFile, err := os.CreateTemp("", "script-*.bat")
|
|
if err != nil {
|
|
return "", fmt.Errorf("ошибка создания temp-файла: %w", err)
|
|
}
|
|
defer os.Remove(tmpFile.Name())
|
|
|
|
// Запись скрипта в файл
|
|
if _, err := tmpFile.WriteString(script); err != nil {
|
|
return "", fmt.Errorf("ошибка записи в temp-файл: %w", err)
|
|
}
|
|
tmpFile.Close()
|
|
|
|
// Выполняем файл через cmd
|
|
cmd := exec.Command("cmd", "/C", tmpFile.Name())
|
|
output, err := cmd.CombinedOutput()
|
|
|
|
return string(output), err
|
|
}
|
|
|
|
// Функция для логирования вывода процесса в консоль
|
|
func Logs_console(process *exec.Cmd, check bool) error {
|
|
|
|
// Скрываем окно процесса для GUI приложений
|
|
process.SysProcAttr = &syscall.SysProcAttr{
|
|
HideWindow: true,
|
|
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
|
|
}
|
|
|
|
if check {
|
|
// Настраиваем pipes для захвата вывода
|
|
stdout, err := process.StdoutPipe()
|
|
CheckError(err)
|
|
stderr, err := process.StderrPipe()
|
|
CheckError(err)
|
|
|
|
// Запускаем процесс
|
|
process.Start()
|
|
|
|
// Захватываем stdout и stderr для вывода логов
|
|
go func() {
|
|
scanner := bufio.NewScanner(stdout)
|
|
for scanner.Scan() {
|
|
fmt.Println(scanner.Text())
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
scanner := bufio.NewScanner(stderr)
|
|
for scanner.Scan() {
|
|
fmt.Println(scanner.Text())
|
|
}
|
|
}()
|
|
} else {
|
|
// Просто запускаем процесс без логирования
|
|
return process.Start()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CheckSingleInstance проверяет, не запущена ли программа уже через мьютекс
|
|
func CheckSingleInstance() bool {
|
|
mutexName, _ := syscall.UTF16PtrFromString("Global\\vServer_SingleInstance")
|
|
|
|
handle, _, err := procCreateMutex.Call(
|
|
0,
|
|
0,
|
|
uintptr(unsafe.Pointer(mutexName)),
|
|
)
|
|
|
|
if handle == 0 {
|
|
return false // не удалось создать мьютекс
|
|
}
|
|
|
|
mutexHandle = syscall.Handle(handle)
|
|
|
|
// Если GetLastError возвращает ERROR_ALREADY_EXISTS (183), значит мьютекс уже существует
|
|
if err.(syscall.Errno) == 183 {
|
|
return false // программа уже запущена
|
|
}
|
|
|
|
return true // успешно создали мьютекс, программа не запущена
|
|
}
|
|
|
|
// ReleaseMutex освобождает мьютекс при завершении программы
|
|
func ReleaseMutex() {
|
|
if mutexHandle != 0 {
|
|
procCloseHandle.Call(uintptr(mutexHandle))
|
|
mutexHandle = 0
|
|
}
|
|
|
|
// Закрываем Job Object - это автоматически убьёт все дочерние процессы
|
|
if jobHandle != 0 {
|
|
procCloseHandle.Call(uintptr(jobHandle))
|
|
jobHandle = 0
|
|
}
|
|
}
|