Add installer GUI and file system access feature

This commit introduces a new Tkinter-based GUI installer script (`installer/main.py`) that manages file extraction and shortcut creation. Additionally, a new file system access feature was added to the `ToolboxWindow` class, allowing users to open directories with platform-specific commands. Improvements also include platform-specific adjustments for Windows executables in the CMake configuration.
This commit is contained in:
2024-12-09 11:08:40 +01:00
parent 4fa4c6df6c
commit 543e2bae9c
10 changed files with 269 additions and 19 deletions

2
.gitignore vendored
View File

@@ -2,3 +2,5 @@
/cmake-build-debug/ /cmake-build-debug/
/cmake-build-debug-visual-studio/ /cmake-build-debug-visual-studio/
/cmake-build-release-visual-studio/ /cmake-build-release-visual-studio/
/cmake-build-minsizerel-visual-studio/
/installer/.idea/

View File

@@ -45,3 +45,7 @@ add_executable(Toolbox main.cpp ${SOURCES} ${RESOURCE_FILES} ${PLATFORM_SOURCES}
target_include_directories(${PROJECT_NAME} PRIVATE "out_embed") target_include_directories(${PROJECT_NAME} PRIVATE "out_embed")
target_link_libraries(Toolbox PRIVATE saucer::saucer sol2::sol2 lua tray nlohmann_json cppcodec) target_link_libraries(Toolbox PRIVATE saucer::saucer sol2::sol2 lua tray nlohmann_json cppcodec)
if(WIN32)
set_target_properties(Toolbox PROPERTIES WIN32_EXECUTABLE TRUE)
endif()

View File

@@ -45,6 +45,12 @@ private:
std::thread m_threadMonitor; std::thread m_threadMonitor;
std::atomic<bool> m_runningMonitor = true; std::atomic<bool> m_runningMonitor = true;
#ifdef _WIN32
std::filesystem::path userPath = std::getenv("USERPROFILE"); // Windows
#else
std::filesystem::path userPath = std::getenv("HOME"); // Unix/Linux/MacOS
#endif
void configureWindow(); void configureWindow();
void loadResources(); void loadResources();
void runLuaScript(); void runLuaScript();
@@ -60,4 +66,6 @@ private:
void run_lua(int id); void run_lua(int id);
void monitor_focus(); void monitor_focus();
void open_file_system();
}; };

20
installer/coder.py Normal file
View File

@@ -0,0 +1,20 @@
import base64
def encode_file_to_base64(file_path):
with open(file_path, 'rb') as file:
return base64.b64encode(file.read()).decode('utf-8')
# Base64-kodierte Strings generieren
exe_base64 = encode_file_to_base64("D:/Repos/toolbox/cmake-build-release-visual-studio/Toolbox.exe")
icon_base64 = encode_file_to_base64("D:/Repos/toolbox/cmake-build-release-visual-studio/toolbox.ico")
# Base64-Strings in Textdateien schreiben
with open("Toolbox_exe_base64.txt", 'w') as exe_file:
exe_file.write(exe_base64)
with open("Toolbox_icon_base64.txt", 'w') as icon_file:
icon_file.write(icon_base64)
print("Base64-Daten erfolgreich in Textdateien gespeichert.")

177
installer/main.py Normal file
View File

@@ -0,0 +1,177 @@
import base64
import os
from tkinter import Tk, Button, Label, Entry, filedialog, messagebox, StringVar, DoubleVar, Frame, Text
from tkinter.ttk import Progressbar, Style
# Base64-Strings für die Dateien (als Platzhalter)
exe_base64 = "..." # Base64-String der .exe
icon_base64 = "..." # Base64-String der .ico
def decode_base64_to_file(base64_str, output_path, progress_callback=None):
"""Decodes a Base64 string and writes it to a file."""
data = base64.b64decode(base64_str)
total_bytes = len(data)
chunk_size = 1024
with open(output_path, 'wb') as file:
for i in range(0, total_bytes, chunk_size):
file.write(data[i:i + chunk_size])
if progress_callback:
progress_callback(min((i + chunk_size) / total_bytes * 100, 100))
def update_progress(value):
"""Updates the progress bar."""
progress_var.set(value)
root.update_idletasks()
def select_directory():
"""Opens a directory selection dialog."""
directory = filedialog.askdirectory(
title="Select Output Directory",
initialdir=output_dir_var.get() or os.path.expandvars('%PROGRAMFILES(X86)%')
)
if directory:
os.makedirs(directory, exist_ok=True) # Create the folder if it doesn't exist
output_dir_var.set(directory)
def create_shortcut(target, shortcut_name, icon_path):
"""Creates shortcuts on Desktop and Startup folder."""
desktop = os.path.join(os.environ['USERPROFILE'], 'Desktop')
path = os.path.join(desktop, shortcut_name + ".lnk")
startup = os.path.join(os.getenv('APPDATA'), r'Microsoft\Windows\Start Menu\Programs\Startup')
startup_shortcut = os.path.join(startup, shortcut_name + ".lnk")
vbs_script = f'''
Set oWS = WScript.CreateObject("WScript.Shell")
sLinkFile = "{path}"
Set oLink = oWS.CreateShortcut(sLinkFile)
oLink.TargetPath = "{target}"
oLink.IconLocation = "{icon_path}"
oLink.Save
'''
startup_vbs_script = f'''
Set oWS = WScript.CreateObject("WScript.Shell")
sLinkFile = "{startup_shortcut}"
Set oLink = oWS.CreateShortcut(sLinkFile)
oLink.TargetPath = "{target}"
oLink.IconLocation = "{icon_path}"
oLink.Save
'''
for script, output in [(vbs_script, "create_shortcut.vbs"), (startup_vbs_script, "create_startup_shortcut.vbs")]:
with open(output, "w") as file:
file.write(script)
os.system(f"wscript {output}")
os.remove(output)
def extract_files():
"""Extracts the files to the selected directory and creates shortcuts."""
output_directory = output_dir_var.get()
if not output_directory:
messagebox.showerror("Error", "Please select a valid output directory.")
return
exe_path = os.path.join(output_directory, "application.exe")
icon_path = os.path.join(output_directory, "icon.ico")
try:
# Ensure the output directory exists
os.makedirs(output_directory, exist_ok=True)
# Decode and save files
decode_base64_to_file(exe_base64, exe_path, update_progress)
decode_base64_to_file(icon_base64, icon_path, update_progress)
# Create shortcuts
create_shortcut(exe_path, "DeinProgrammName", icon_path)
messagebox.showinfo("Success", "Installation abgeschlossen!")
except Exception as e:
messagebox.showerror("Error", f"Failed to extract files: {e}")
def show_readme():
"""Displays the final README page."""
readme_window = Frame(root, bg='#2e2e2e')
readme_window.pack(fill='both', expand=True)
Label(readme_window, text="Installation abgeschlossen!", bg="#2e2e2e", fg="white", font=("Helvetica", 16)).pack(pady=20)
readme_text = Text(readme_window, bg="#444", fg="white", wrap='word', height=10, width=50)
readme_text.insert('1.0', "Vielen Dank, dass Sie 'DeinProgrammName' installiert haben!\n\nWeitere Informationen finden Sie auf unserer Website.")
readme_text.configure(state='disabled')
readme_text.pack(pady=10)
Button(readme_window, text="Beenden", command=root.quit).pack(pady=10)
def next_page():
"""Handles the navigation between pages."""
global current_page
if current_page == 1:
setup_page_2()
elif current_page == 2:
extract_files()
setup_page_3()
def setup_page_1():
"""Sets up the welcome page."""
global current_page
current_page = 1
welcome_page = Frame(root, bg='#2e2e2e')
welcome_page.pack(fill='both', expand=True)
Label(welcome_page, text="Willkommen beim Installer!", bg="#2e2e2e", fg="white", font=("Helvetica", 16)).pack(pady=20)
Label(welcome_page, text="Klicken Sie auf 'Weiter', um fortzufahren.", bg="#2e2e2e", fg="white").pack(pady=10)
Button(welcome_page, text="Weiter", command=lambda: (welcome_page.pack_forget(), next_page())).pack()
def setup_page_2():
"""Sets up the installation folder selection page."""
global current_page
current_page = 2
folder_page = Frame(root, bg='#2e2e2e')
folder_page.pack(fill='both', expand=True)
Label(folder_page, text="Installationsordner wählen:", bg="#2e2e2e", fg="white").pack(pady=5)
Entry(folder_page, textvariable=output_dir_var, width=50).pack(pady=5)
Button(folder_page, text="Durchsuchen...", command=select_directory).pack(pady=5)
Button(folder_page, text="Weiter", command=lambda: (folder_page.pack_forget(), next_page())).pack()
def setup_page_3():
"""Sets up the installation progress page."""
global current_page
current_page = 3
progress_page = Frame(root, bg='#2e2e2e')
progress_page.pack(fill='both', expand=True)
Label(progress_page, text="Installation läuft...", bg="#2e2e2e", fg="white").pack(pady=20)
progress_bar = Progressbar(progress_page, orient='horizontal', length=400, variable=progress_var,
style='Horizontal.TProgressbar')
progress_bar.pack(pady=10)
Button(progress_page, text="Weiter", command=lambda: (progress_page.pack_forget(), show_readme())).pack()
# Tkinter Setup
root = Tk()
root.title("Installer")
root.geometry("500x350")
# Darkmode Style
style = Style(root)
style.theme_use('clam')
style.configure('TLabel', background='#2e2e2e', foreground='white', font=('Helvetica', 12))
style.configure('TButton', background='#444', foreground='white', font=('Helvetica', 10))
style.configure('Horizontal.TProgressbar', troughcolor='#444', background='#5a9')
root.configure(background='#2e2e2e')
output_dir_var = StringVar(value=os.path.join(os.environ['PROGRAMFILES(X86)'], "DeinProgrammName"))
progress_var = DoubleVar()
current_page = 0
setup_page_1()
root.mainloop()

0
jenkinsfile Normal file
View File

View File

@@ -3,11 +3,8 @@
#include "tray.h" #include "tray.h"
#include "resource.h" #include "resource.h"
ToolboxWindow* g_toolboxWindow = nullptr;
void on_toggle(struct tray_menu *item) { ToolboxWindow* g_toolboxWindow = nullptr;
item->checked = !item->checked;
}
void on_quit(struct tray_menu *item) { void on_quit(struct tray_menu *item) {
tray_exit(); tray_exit();
@@ -20,8 +17,7 @@ void on_click(struct tray_menu *item) {
g_toolboxWindow->show(); g_toolboxWindow->show();
} }
int start(){
int main(int argc, char** argv) {
// Neue Anwendung erstellen // Neue Anwendung erstellen
auto app = saucer::application::acquire({ auto app = saucer::application::acquire({
@@ -33,13 +29,12 @@ int main(int argc, char** argv) {
toolboxWindow.initialize(); toolboxWindow.initialize();
g_toolboxWindow = &toolboxWindow; g_toolboxWindow = &toolboxWindow;
toolboxWindow.show(); //toolboxWindow.show();
HICON hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON1)); HICON hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON1));
struct tray_menu tray_menu_items[] = { struct tray_menu tray_menu_items[] = {
{"<EFBFBD>ffnen", 0, 0, NULL, on_click, 0}, {"<EFBFBD>ffnen", 0, 0, NULL, on_click, 0},
{"Option umschalten", 0, 0, NULL, on_toggle, 0},
{"-", 0, 0, NULL, nullptr, 0}, {"-", 0, 0, NULL, nullptr, 0},
{"Beenden", 0, 0, NULL, on_quit, 0}, {"Beenden", 0, 0, NULL, on_quit, 0},
{nullptr, 0, 0, NULL, nullptr, 0} {nullptr, 0, 0, NULL, nullptr, 0}
@@ -65,3 +60,14 @@ int main(int argc, char** argv) {
return 0; return 0;
} }
#ifdef _DEBUG
int main(int argc, char** argv) {
return start(); // Konsole bleibt aktiv im Debug-Modus
}
#else
#include <Windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
return start(); // Keine Konsole im Release-Modus
}
#endif

File diff suppressed because one or more lines are too long

View File

@@ -147,7 +147,7 @@
<body> <body>
<div class="header"> <div class="header">
<h1>Toolbox</h1> <h1>Toolbox</h1>
<button id="options-button" class="options-button">Optionen</button> <button onclick="openFileSystem()" id="options-button" class="options-button">Ordner</button>
</div> </div>
<div class="main" id="toolbox"> <div class="main" id="toolbox">
<!-- Tool Cards werden hier dynamisch geladen --> <!-- Tool Cards werden hier dynamisch geladen -->
@@ -162,7 +162,7 @@
if (toolCard) { if (toolCard) {
const toolId = toolCard.getAttribute('data-tool-id'); const toolId = toolCard.getAttribute('data-tool-id');
if (!button.disabled) { if (sessionStorage.getItem(`toolButtonDisabled_${toolId}`) !== 'true') {
console.log(`Disabling button for tool ID: ${toolId}`); console.log(`Disabling button for tool ID: ${toolId}`);
saucer.exposed.run_lua(parseInt(toolId, 10)); saucer.exposed.run_lua(parseInt(toolId, 10));
@@ -183,6 +183,10 @@
saucer.exposed.debug_print(); saucer.exposed.debug_print();
} }
function openFileSystem() {
saucer.exposed.open_file_system();
}
function enableButtonByToolId(toolId) { function enableButtonByToolId(toolId) {
saucer.exposed.debug_print(); saucer.exposed.debug_print();
// Finden Sie den Button mit dem entsprechenden data-tool-id // Finden Sie den Button mit dem entsprechenden data-tool-id

View File

@@ -14,7 +14,11 @@ void ToolboxWindow::initialize() {
configureWindow(); configureWindow();
loadResources(); loadResources();
ExposeToJs(); ExposeToJs();
m_webview.set_always_on_top(true);
m_webview.set_decorations(false);
#ifdef _DEBUG
m_webview.set_dev_tools(true); m_webview.set_dev_tools(true);
#endif
//m_webview.set_decorations(false); //m_webview.set_decorations(false);
//m_webview.set_always_on_top(true); //m_webview.set_always_on_top(true);
@@ -122,6 +126,10 @@ void ToolboxWindow::ExposeToJs() {
m_webview.expose("debug_print", [&]() { m_webview.expose("debug_print", [&]() {
debug_print(); debug_print();
}, saucer::launch::async); }, saucer::launch::async);
m_webview.expose("open_file_system", [&]() {
open_file_system();
},saucer::launch::async);
} }
void ToolboxWindow::OptionWindow() { void ToolboxWindow::OptionWindow() {
@@ -193,13 +201,6 @@ void encodeImageToBase64(const std::filesystem::path& imagePath, ToolboxWindow::
void ToolboxWindow::fillToolboxMenu() { void ToolboxWindow::fillToolboxMenu() {
// Bestimme das Benutzerverzeichnis abhängig vom Betriebssystem // Bestimme das Benutzerverzeichnis abhängig vom Betriebssystem
std::filesystem::path userPath;
#ifdef _WIN32
userPath = std::getenv("USERPROFILE"); // Windows
#else
userPath = std::getenv("HOME"); // Unix/Linux/MacOS
#endif
std::filesystem::path toolboxDir = userPath / ".Toolbox"; std::filesystem::path toolboxDir = userPath / ".Toolbox";
@@ -279,3 +280,31 @@ void ToolboxWindow::monitor_focus() {
} }
} }
} }
void ToolboxWindow::open_file_system() {
std::filesystem::path toolboxDir = userPath / ".Toolbox";
if (std::filesystem::exists(toolboxDir) && std::filesystem::is_directory(toolboxDir)) {
std::string path = toolboxDir.string();
std::string command;
#ifdef _WIN32
command = "explorer \"" + path + "\"";
#elif __APPLE__
command = "open \"" + path + "\"";
#elif __linux__
command = "xdg-open \"" + path + "\"";
#else
std::cerr << "Fehler: Plattform nicht unterstützt." << std::endl;
return;
#endif
int result = std::system(command.c_str());
if (result != 0) {
std::cerr << "Fehler beim Öffnen des Verzeichnisses: " << result << std::endl;
}
} else {
std::cerr << "Fehler: Das Verzeichnis .Toolbox existiert nicht." << std::endl;
}
}