From cd3092186eb698a9ed175dacb6884f0404e7c062 Mon Sep 17 00:00:00 2001 From: Amlal El Mahrouss Date: Thu, 4 Dec 2025 15:13:22 +0100 Subject: chore! Breaking API changes for CompilerKit and DebuggerKit. Signed-off-by: Amlal El Mahrouss --- examples/asm/exit_nekernel_abi.asm | 10 - examples/asm/return_5_rax.asm | 4 - examples/cxx/example.cc | 16 - examples/example_01_asm/exit_nekernel_abi.asm | 10 + examples/example_01_asm/return_5_rax.asm | 4 + examples/example_02_cxx/example.cc | 16 + examples/example_03_compilerkit/.keep | 0 examples/example_04_debuggerkit/.keep | 0 src/CompilerKit/AE.h | 2 +- src/CompilerKit/CodeGenerator.h | 14 +- src/CompilerKit/Detail/32x0.h | 94 ++ src/CompilerKit/Detail/64x0.h | 100 ++ src/CompilerKit/Detail/AMD64.h | 50 + src/CompilerKit/Detail/Aarch64.h | 41 + src/CompilerKit/Detail/Config.h | 64 + src/CompilerKit/Detail/Power64.h | 1557 ++++++++++++++++++ src/CompilerKit/Detail/PreConfig.h | 120 ++ src/CompilerKit/ErrorID.h | 2 +- src/CompilerKit/ErrorOr.h | 2 +- src/CompilerKit/PEF.h | 2 +- src/CompilerKit/Ref.h | 2 +- src/CompilerKit/StringKit.h | 2 +- src/CompilerKit/Utilities/Assembler.h | 92 ++ src/CompilerKit/Utilities/Compiler.h | 119 ++ src/CompilerKit/Utilities/DLL.h | 63 + src/CompilerKit/XCOFF.h | 2 +- src/CompilerKit/detail/Config.h | 65 - src/CompilerKit/detail/PreConfig.h | 120 -- src/CompilerKit/impl/32x0.h | 94 -- src/CompilerKit/impl/64x0.h | 100 -- src/CompilerKit/impl/Aarch64.h | 41 - src/CompilerKit/impl/PowerPC.h | 1557 ------------------ src/CompilerKit/impl/X64.h | 50 - src/CompilerKit/src/Backend/Assembler32x0.cc | 4 +- src/CompilerKit/src/Backend/Assembler64x0.cc | 4 +- src/CompilerKit/src/Backend/AssemblerAMD64.cc | 4 +- src/CompilerKit/src/Backend/AssemblerARM64.cc | 6 +- src/CompilerKit/src/Backend/AssemblerPowerPC.cc | 6 +- src/CompilerKit/src/Frontend/CCompiler64x0.cc | 42 +- src/CompilerKit/src/Frontend/CCompilerARM64.cc | 40 +- src/CompilerKit/src/Frontend/CCompilerPower64.cc | 50 +- .../src/Frontend/CPlusPlusCompilerAMD64.cc | 20 +- src/CompilerKit/src/Linker/DynamicLinker64PEF.cc | 7 +- src/CompilerKit/utils/AsmUtils.h | 92 -- src/CompilerKit/utils/CompilerUtils.h | 116 -- src/CompilerKit/utils/DylibHelpers.h | 63 - src/DebuggerKit/DebuggerContract.h | 2 +- src/DebuggerKit/Detail/Config.h | 44 + src/DebuggerKit/NeKernelContract.h | 2 +- src/DebuggerKit/detail/Config.h | 44 - src/DebuggerKit/src/NeKernelContract.cc | 6 +- src/DebuggerKit/src/NeKernelContractCLI.cc | 4 +- src/DebuggerKit/src/POSIXMachContractCLI.cc | 4 +- src/ThirdParty/Dialogs.h | 1738 -------------------- src/ThirdParty/Dialogs/Dialogs.h | 1738 ++++++++++++++++++++ src/Tools/asm.cc | 2 +- src/Tools/cppdrv.cc | 2 +- src/Tools/dbg.cc | 2 +- src/Tools/kdbg.cc | 2 +- src/Tools/ld64.cc | 2 +- src/Tools/pef-amd64-cxxdrv.cc | 6 +- src/Tools/pef-arm64-cdrv.cc | 6 +- tests/samples/sample.asm | 4 - tests/samples/sample.cc | 3 - tests/samples/sample.cc.pp | 3 - tests/test_01_codegen/codegen_test.cc | 2 +- tests/test_02_linker/linker_test.cc | 6 +- tests/test_samples/sample.asm | 4 + tests/test_samples/sample.cc | 3 + 69 files changed, 4211 insertions(+), 4287 deletions(-) delete mode 100644 examples/asm/exit_nekernel_abi.asm delete mode 100644 examples/asm/return_5_rax.asm delete mode 100644 examples/cxx/example.cc create mode 100644 examples/example_01_asm/exit_nekernel_abi.asm create mode 100644 examples/example_01_asm/return_5_rax.asm create mode 100644 examples/example_02_cxx/example.cc create mode 100644 examples/example_03_compilerkit/.keep create mode 100644 examples/example_04_debuggerkit/.keep create mode 100644 src/CompilerKit/Detail/32x0.h create mode 100644 src/CompilerKit/Detail/64x0.h create mode 100644 src/CompilerKit/Detail/AMD64.h create mode 100644 src/CompilerKit/Detail/Aarch64.h create mode 100644 src/CompilerKit/Detail/Config.h create mode 100644 src/CompilerKit/Detail/Power64.h create mode 100644 src/CompilerKit/Detail/PreConfig.h create mode 100644 src/CompilerKit/Utilities/Assembler.h create mode 100644 src/CompilerKit/Utilities/Compiler.h create mode 100644 src/CompilerKit/Utilities/DLL.h delete mode 100644 src/CompilerKit/detail/Config.h delete mode 100644 src/CompilerKit/detail/PreConfig.h delete mode 100644 src/CompilerKit/impl/32x0.h delete mode 100644 src/CompilerKit/impl/64x0.h delete mode 100644 src/CompilerKit/impl/Aarch64.h delete mode 100644 src/CompilerKit/impl/PowerPC.h delete mode 100644 src/CompilerKit/impl/X64.h delete mode 100644 src/CompilerKit/utils/AsmUtils.h delete mode 100644 src/CompilerKit/utils/CompilerUtils.h delete mode 100644 src/CompilerKit/utils/DylibHelpers.h create mode 100644 src/DebuggerKit/Detail/Config.h delete mode 100644 src/DebuggerKit/detail/Config.h delete mode 100644 src/ThirdParty/Dialogs.h create mode 100644 src/ThirdParty/Dialogs/Dialogs.h delete mode 100644 tests/samples/sample.asm delete mode 100644 tests/samples/sample.cc delete mode 100644 tests/samples/sample.cc.pp create mode 100644 tests/test_samples/sample.asm create mode 100644 tests/test_samples/sample.cc diff --git a/examples/asm/exit_nekernel_abi.asm b/examples/asm/exit_nekernel_abi.asm deleted file mode 100644 index 6f69a00..0000000 --- a/examples/asm/exit_nekernel_abi.asm +++ /dev/null @@ -1,10 +0,0 @@ -%bits 64 -%org 0x40000000 - -public_segment .code64 __ImageStart - xor rax, rax - mov rcx, 1 ;; syscall id - mov rdx, 0 ;; arg1 - syscall ;; exit - mov rax, rbx - ret \ No newline at end of file diff --git a/examples/asm/return_5_rax.asm b/examples/asm/return_5_rax.asm deleted file mode 100644 index 82c0320..0000000 --- a/examples/asm/return_5_rax.asm +++ /dev/null @@ -1,4 +0,0 @@ -public_segment .code64 __ImageStart - ;; rax is the return value register. - mov rax, 5 - ret \ No newline at end of file diff --git a/examples/cxx/example.cc b/examples/cxx/example.cc deleted file mode 100644 index e74acbb..0000000 --- a/examples/cxx/example.cc +++ /dev/null @@ -1,16 +0,0 @@ -#define main __ImageStart -#warning test macro warning #1 - -int bar() { - auto yyy = 17800; - return yyy; -} - -int foo() { - int arg1 = bar(); - return arg1; -} - -int main() { - return foo(); -} diff --git a/examples/example_01_asm/exit_nekernel_abi.asm b/examples/example_01_asm/exit_nekernel_abi.asm new file mode 100644 index 0000000..6f69a00 --- /dev/null +++ b/examples/example_01_asm/exit_nekernel_abi.asm @@ -0,0 +1,10 @@ +%bits 64 +%org 0x40000000 + +public_segment .code64 __ImageStart + xor rax, rax + mov rcx, 1 ;; syscall id + mov rdx, 0 ;; arg1 + syscall ;; exit + mov rax, rbx + ret \ No newline at end of file diff --git a/examples/example_01_asm/return_5_rax.asm b/examples/example_01_asm/return_5_rax.asm new file mode 100644 index 0000000..82c0320 --- /dev/null +++ b/examples/example_01_asm/return_5_rax.asm @@ -0,0 +1,4 @@ +public_segment .code64 __ImageStart + ;; rax is the return value register. + mov rax, 5 + ret \ No newline at end of file diff --git a/examples/example_02_cxx/example.cc b/examples/example_02_cxx/example.cc new file mode 100644 index 0000000..e74acbb --- /dev/null +++ b/examples/example_02_cxx/example.cc @@ -0,0 +1,16 @@ +#define main __ImageStart +#warning test macro warning #1 + +int bar() { + auto yyy = 17800; + return yyy; +} + +int foo() { + int arg1 = bar(); + return arg1; +} + +int main() { + return foo(); +} diff --git a/examples/example_03_compilerkit/.keep b/examples/example_03_compilerkit/.keep new file mode 100644 index 0000000..e69de29 diff --git a/examples/example_04_debuggerkit/.keep b/examples/example_04_debuggerkit/.keep new file mode 100644 index 0000000..e69de29 diff --git a/src/CompilerKit/AE.h b/src/CompilerKit/AE.h index 4692d04..cf6e71b 100644 --- a/src/CompilerKit/AE.h +++ b/src/CompilerKit/AE.h @@ -10,7 +10,7 @@ #ifndef _NECTI_AE_H_ #define _NECTI_AE_H_ -#include +#include #include #define kAEIdentVersion (0x0122) diff --git a/src/CompilerKit/CodeGenerator.h b/src/CompilerKit/CodeGenerator.h index 836d588..3a18803 100644 --- a/src/CompilerKit/CodeGenerator.h +++ b/src/CompilerKit/CodeGenerator.h @@ -6,7 +6,7 @@ #pragma once -#include +#include #include #include #include @@ -84,7 +84,9 @@ union NumberCast64 final { ~NumberCast64() { raw = 0; } - Char number[8]; + static constexpr auto kLimit = 8; + + Char number[kLimit]; UInt64 raw; }; @@ -94,7 +96,9 @@ union NumberCast32 final { ~NumberCast32() { raw = 0; } - Char number[4]; + static constexpr auto kLimit = 4; + + Char number[kLimit]; UInt32 raw; }; @@ -104,7 +108,9 @@ union NumberCast16 final { ~NumberCast16() { raw = 0; } - Char number[2]; + static constexpr auto kLimit = 2; + + Char number[kLimit]; UInt16 raw; }; diff --git a/src/CompilerKit/Detail/32x0.h b/src/CompilerKit/Detail/32x0.h new file mode 100644 index 0000000..fc3e0e9 --- /dev/null +++ b/src/CompilerKit/Detail/32x0.h @@ -0,0 +1,94 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +#include +#include + +// @brief Open32x0 support. +// @file Detail/32x0.h + +#define CK_ASM_OPCODE(__NAME, __OPCODE, __FUNCT3, __FUNCT7) \ + {.fName = __NAME, .fOpcode = __OPCODE, .fFunct3 = __FUNCT3, .fFunct7 = __FUNCT7}, + +#define kAsmImmediate 0x01 +#define kAsmSyscall 0x02 +#define kAsmJump 0x03 +#define kAsmNoArgs 0x04 + +#define kAsmByte 0 +#define kAsmHWord 1 +#define kAsmWord 2 + +struct CpuCode32x0 { + const char fName[32]; + uint8_t fOpcode; + uint8_t fSize; + uint8_t fFunct3; + uint8_t fFunct7; +}; + +#define kAsmDWordStr ".dword" /* 64 bit */ +#define kAsmWordStr ".word" /* 32-bit */ +#define kAsmHWordStr ".half" /* 16-bit */ +#define kAsmByteStr ".byte" /* 8-bit */ + +inline std::vector kOpcodes32x0 = { + CK_ASM_OPCODE("nop", 0b0100011, 0b000, kAsmNoArgs) // nothing to do. (1C) + CK_ASM_OPCODE("jmp", 0b1110011, 0b001, kAsmJump) // jump to branch (2C) + CK_ASM_OPCODE("mov", 0b0100011, 0b101, kAsmImmediate) // move registers (3C) + CK_ASM_OPCODE("psh", 0b0111011, 0b000, kAsmImmediate) // push to sp (2C) + CK_ASM_OPCODE("pop", 0b0111011, 0b001, kAsmImmediate) // pop from sp. (1C) + CK_ASM_OPCODE("lea", 0b0111011, 0b010, + kAsmImmediate) // setup stack and call, store address to CR (1C). + CK_ASM_OPCODE("ret", 0b0111011, 0b110, + kAsmImmediate) // return from procedure (2C). + CK_ASM_OPCODE("uc", 0b0111111, 0b000, kAsmSyscall) // user call (1C) + CK_ASM_OPCODE("kc", 0b0111111, 0b001, kAsmSyscall) // kernel call (1C) + CK_ASM_OPCODE("int", 0b0111111, 0b010, kAsmSyscall) // raise interrupt (1C) +}; + +// \brief 64x0 register prefix +// example: r32, r0 +// r32 -> sp +// r0 -> hw zero + +#define kAsmRegisterPrefix "r" +#define kAsmRegisterLimit 16 +#define kAsmPcRegister 17 +#define kAsmCrRegister 18 +#define kAsmSpRegister 5 + +/* return address register */ +#define kAsmRetRegister 19 + +///////////////////////////////////////////////////////////////////////////// + +// SYSTEM CALL ADDRESSING + +// | OPCODE | FUNCT3 | FUNCT7 | OFF | + +// IMMEDIATE ADDRESSING + +// | OPCODE | FUNCT3 | FUNCT7 | REG | OFF | +// | OPCODE | FUNCT3 | FUNCT7 | REG | OFF | REG | +// | OPCODE | FUNCT3 | FUNCT7 | REG | REG | OFF | + +// REG TO REG ADDRESSING + +// | OPCODE | FUNCT3 | FUNCT7 | REG | REG2 | + +//////////////////////////////// + +// LOAD/CALL INTERRUPTS + +// SET A HANDLER IN ADDRESS: TODO: find one +// DISABLE INTERRUPTS +// PROCESS INTERRUPT +// ENABLE INTERRUPTS + +//////////////////////////////// diff --git a/src/CompilerKit/Detail/64x0.h b/src/CompilerKit/Detail/64x0.h new file mode 100644 index 0000000..ba539f6 --- /dev/null +++ b/src/CompilerKit/Detail/64x0.h @@ -0,0 +1,100 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +#include +#include + +// @brief Open64x0 support. +// @file Detail/64x0.h + +#define CK_ASM_OPCODE(__NAME, __OPCODE, __FUNCT3, __FUNCT7) \ + {.fName = __NAME, .fOpcode = __OPCODE, .fFunct3 = __FUNCT3, .fFunct7 = __FUNCT7}, + +#define kAsmImmediate 0x01 +#define kAsmRegToReg 0x02 +#define kAsmSyscall 0x03 +#define kAsmJump 0x04 +#define kAsmNoArgs 0x00 + +typedef char e64k_character_t; +typedef uint8_t e64k_num_t; + +struct CpuOpcode64x0 { + const e64k_character_t fName[32]; + e64k_num_t fOpcode; + e64k_num_t fFunct3; + e64k_num_t fFunct7; +}; + +inline std::vector kOpcodes64x0 = { + CK_ASM_OPCODE("nop", 0b0000000, 0b0000000, kAsmNoArgs) // no-operation. + CK_ASM_OPCODE("np", 0b0000000, 0b0000000, kAsmNoArgs) // no-operation. + CK_ASM_OPCODE("jlr", 0b1110011, 0b0000111, + kAsmJump) // jump to linked return register + CK_ASM_OPCODE("jrl", 0b1110011, 0b0001111, + kAsmJump) // jump from return register. + CK_ASM_OPCODE("mv", 0b0100011, 0b101, kAsmRegToReg) CK_ASM_OPCODE( + "bg", 0b1100111, 0b111, kAsmRegToReg) CK_ASM_OPCODE("bl", 0b1100111, 0b011, kAsmRegToReg) + CK_ASM_OPCODE("beq", 0b1100111, 0b000, kAsmRegToReg) + CK_ASM_OPCODE("bne", 0b1100111, 0b001, kAsmRegToReg) + CK_ASM_OPCODE("bge", 0b1100111, 0b101, kAsmRegToReg) + CK_ASM_OPCODE("ble", 0b1100111, 0b100, kAsmRegToReg) + CK_ASM_OPCODE("stw", 0b0001111, 0b100, kAsmImmediate) + CK_ASM_OPCODE("ldw", 0b0001111, 0b100, kAsmImmediate) + CK_ASM_OPCODE("lda", 0b0001111, 0b101, kAsmImmediate) + CK_ASM_OPCODE("sta", 0b0001111, 0b001, kAsmImmediate) + // add/sub without carry flag + CK_ASM_OPCODE("add", 0b0101011, 0b100, kAsmImmediate) + CK_ASM_OPCODE("sub", 0b0101011, 0b101, kAsmImmediate) + // add/sub with carry flag + CK_ASM_OPCODE("addc", 0b0101011, 0b110, kAsmImmediate) CK_ASM_OPCODE( + "subc", 0b0101011, 0b111, kAsmImmediate) CK_ASM_OPCODE("sc", 0b1110011, 0b00, kAsmSyscall)}; + +// \brief 64x0 register prefix +// example: r32, r0 +// r32 -> sp +// r0 -> hw zero + +#define kAsmFloatZeroRegister 0 +#define kAsmZeroRegister 0 + +#define kAsmRegisterPrefix "r" +#define kAsmRegisterLimit 30 +#define kAsmPcRegister 17 +#define kAsmCrRegister 18 +#define kAsmSpRegister 5 + +/* return address register */ +#define kAsmRetRegister 19 + +///////////////////////////////////////////////////////////////////////////// + +// SYSTEM CALL/JUMP ADDRESSING + +// | OPCODE | FUNCT3 | FUNCT7 | OFF | + +// IMMEDIATE ADDRESSING + +// | OPCODE | FUNCT3 | FUNCT7 | REG | OFF | +// | OPCODE | FUNCT3 | FUNCT7 | REG | OFF | REG | +// | OPCODE | FUNCT3 | FUNCT7 | REG | REG | OFF | + +// REG TO REG ADDRESSING + +// | OPCODE | FUNCT3 | FUNCT7 | REG | REG2 | + +//////////////////////////////// + +// LOAD/CALL INTERRUPTS + +// SET A HANDLER IN ADDRESS: +// DISABLE INTERRUPTS +// PROCESS INTERRUPT +// ENABLE INTERRUPTS + +//////////////////////////////// diff --git a/src/CompilerKit/Detail/AMD64.h b/src/CompilerKit/Detail/AMD64.h new file mode 100644 index 0000000..a123c02 --- /dev/null +++ b/src/CompilerKit/Detail/AMD64.h @@ -0,0 +1,50 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +#include +#include + +// @brief AMD64 support. +// @file Detail/AMD64.h + +#define CK_ASM_OPCODE(__NAME, __OPCODE) {.fName = __NAME, .fOpcode = __OPCODE}, + +typedef char i64_character_t; +typedef uint8_t i64_byte_t; +typedef uint16_t i64_hword_t; +typedef uint32_t i64_word_t; + +#define kAsmRegisterPrefix "r" + +struct CpuOpcodeAMD64 { + std::string fName; + i64_byte_t fPrefixBytes[4]; + i64_hword_t fOpcode; + i64_hword_t fModReg; + i64_word_t fDisplacment; + i64_word_t fImmediate; +}; + +/// these two are edge cases +#define kAsmIntOpcode 0xCC +#define kasmIntOpcodeAlt 0xCD + +#define kAsmJumpOpcode 0x0F80 +#define kJumpLimit 30 +#define kJumpLimitStandard 0xE3 +#define kJumpLimitStandardLimit 0xEB + +inline std::vector kOpcodesAMD64 = { + CK_ASM_OPCODE("int", 0xCD) CK_ASM_OPCODE("into", 0xCE) CK_ASM_OPCODE("intd", 0xF1) + CK_ASM_OPCODE("int3", 0xC3) CK_ASM_OPCODE("iret", 0xCF) CK_ASM_OPCODE("retf", 0xCB) + CK_ASM_OPCODE("retn", 0xC3) CK_ASM_OPCODE("ret", 0xC3) CK_ASM_OPCODE("sti", 0xfb) + CK_ASM_OPCODE("cli", 0xfa) CK_ASM_OPCODE("hlt", 0xf4) CK_ASM_OPCODE("nop", 0x90) + CK_ASM_OPCODE("mov", 0x48) CK_ASM_OPCODE("call", 0xFF) + CK_ASM_OPCODE("syscall", 0x0F) CK_ASM_OPCODE("xor", 0x48)}; + +#define kAsmRegisterLimit 16 diff --git a/src/CompilerKit/Detail/Aarch64.h b/src/CompilerKit/Detail/Aarch64.h new file mode 100644 index 0000000..1ce1719 --- /dev/null +++ b/src/CompilerKit/Detail/Aarch64.h @@ -0,0 +1,41 @@ +/* ======================================== + +Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +#include +#include + +/// @brief ARM64 encoding support. +/// @file Detail/Aarch64.h + +struct CpuOpcodeArm64; + +/// @brief ARM64 opcode header. +struct PACKED CpuOpcodeArm64_Data final { + uint32_t fOpcode : 10; // Bits 31–22: Opcode for operation + uint32_t fRm : 5; // Bits 21–16: Source register Rm + uint32_t fShamt : 6; // Bits 15–10: Shift amount + uint32_t fRn : 5; // Bits 9–5: Source register Rn + uint32_t fRd : 5; // Bits 4–0: Destination register Rd +}; + +typedef struct { + uint32_t opcode : 6; // Bits 31–26: Branch opcode + int32_t offset : 26; // Bits 25–0: Signed offset (branch target) +} PACKED CpuOpcodeArm64_Branch; + +typedef struct { + uint32_t size : 2; // Bits 31–30: Size of the data + uint32_t opcode : 7; // Bits 29–23: Opcode for load/store + uint32_t offset : 12; // Bits 22–10: Offset + uint32_t rn : 5; // Bits 9–5: Base address register Rn + uint32_t rt : 5; // Bits 4–0: Target/source register Rt +} PACKED CpuOpcodeArm64_LoadStore; + +#define kAsmRegisterLimit (30) +#define kAsmRegisterPrefix "x" +#define kOpcodeARM64Count (1000) diff --git a/src/CompilerKit/Detail/Config.h b/src/CompilerKit/Detail/Config.h new file mode 100644 index 0000000..6137e2a --- /dev/null +++ b/src/CompilerKit/Detail/Config.h @@ -0,0 +1,64 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +/// =========================================================== /// +/// @file detail/Config.h +/// @author Amlal El Mahrouss +/// @brief Basic defines and types for CompilerKit. +/// =========================================================== /// + +#include + +namespace CompilerKit { +inline constexpr int kBaseYear = 1900; +using STLString = std::string; + +inline STLString current_date() noexcept { + auto time_data = time(nullptr); + auto time_struct = gmtime(&time_data); + + STLString fmt = std::to_string(kBaseYear + time_struct->tm_year); + + fmt += "-"; + fmt += std::to_string(time_struct->tm_mon + 1); + fmt += "-"; + fmt += std::to_string(time_struct->tm_mday); + + return fmt; +} + +inline bool to_str(Char* str, Int32 limit, Int32 base) noexcept { + if (limit == 0) return false; + + Int32 copy_limit = limit; + Int32 cnt = 0; + Int32 ret = base; + + while (limit != 1) { + ret = ret % 10; + str[cnt] = ret; + + ++cnt; + --limit; + --ret; + } + + str[copy_limit] = '\0'; + return true; +} + +inline bool install_signal(Int32 signal, void (*handler)(int)) noexcept { + if (handler == nullptr) return false; + + if (::signal(signal, handler) == SIG_ERR) { + return false; + } + + return true; +} +} // namespace CompilerKit diff --git a/src/CompilerKit/Detail/Power64.h b/src/CompilerKit/Detail/Power64.h new file mode 100644 index 0000000..7c7f67c --- /dev/null +++ b/src/CompilerKit/Detail/Power64.h @@ -0,0 +1,1557 @@ +/* ======================================== + + Some modifications are copyrighted under: + Amlal El Mahrouss + + Original author: + Apple Inc + +======================================== */ + +#pragma once + +#include + +/// @note Based of: +/// https://opensource.apple.com/source/cctools/cctools-750/as/ppc-opcode.h.auto.html + +#define kOpcodePPCCount (1073U) + +/* + * These defines are use in the cpus field of the instructions. If the field + * is zero it can execute on all cpus. The defines are or'ed together. This + * information is used to set the cpusubtype in the resulting object file. + */ +#define CPU601 0x1 +#define IMPL64 0x2 +#define OPTIONAL 0x4 +#define VMX 0x8 +#define CPU970 0x10 /* added to OPTIONAL insts that the 970 has */ +#define CPUMAHROUSS 0x12 /* optional mahrouss insts. */ + +enum OpcodeType { + NONE, /* no operand */ + JBSR, /* jbsr pseudo op */ + PCREL, /* PC relative (branch offset) */ + BADDR, /* Branch address (sign extended absolute address) */ + D, /* 16 bit displacement */ + DS, /* 14 bit displacement (double word) */ + SI, /* signed 16 bit immediate */ + UI, /* unsigned 16 bit immediate */ + HI, /* high 16 bit immediate (with truncation) */ + GREG, /* general register */ + G0REG, /* general register r1-r31 or 0 */ + FREG, /* float register */ + VREG, /* vector register */ + SGREG, /* segment register */ + SPREG, /* special register (or 10 bit number, 5 bit halves reversed) */ + BCND, /* branch condition opcode */ + CRF, /* condition register field */ + CRFONLY, /* condition register field only no expression allowed */ + sh, /* 6 bit number (0 - 63) (sh field, split and reversed) */ + mb, /* 6 bit number (0 - 63) (mb field, mb5 || mb0:4 reversed) */ + NUM, /* number */ + SNUM, /* signed number */ + NUM0, /* number (where 1< +#include +#include +#include +#include +#include + +#define kDistVersion "v0.0.7-compilerkit" +#define kDistVersionBCD 0x0002 + +#define ToString(X) Stringify(X) +#define Stringify(X) #X + +#define kDistRelease ToString(kDistReleaseBranch) + +#ifndef kDistRelease + +#define kDistVersion "v0.0.7-compilerkit" +#define kDistVersionBCD 0x0002 + +#define ToString(X) Stringify(X) +#define Stringify(X) #X + +#define kDistRelease ToString(kDistReleaseBranch) + +#endif // !kDistRelease + +#define MUST_PASS(E) assert(E) + +#ifndef __FORCE_STRLEN +#define __FORCE_STRLEN 1 + +#define string_length(len) strlen(len) +#endif + +#ifndef __FORCE_MEMCPY +#define __FORCE_MEMCPY 1 + +#define rt_copy_memory(dst, src, len) memcpy(dst, src, len) +#endif + +#define ATTRIBUTE(X) __attribute__((X)) +#define PACKED ATTRIBUTE(packed) + +#define kObjectFileExt ".obj" +#define kBinaryFileExt ".bin" + +#define kAsmFileExts {".64x", ".32x", ".masm", ".s", ".S", ".asm", ".x64"} + +#define kAsmFileExtsMax (7U) + +#define NECTI_MODULE(name) extern "C" int name(int argc, char** argv) + +#ifdef MSVC +#pragma scalar_storage_order big - endian +#endif // ifdef MSVC + +#define NECTI_COPY_DELETE(KLASS) \ + KLASS& operator=(const KLASS&) = delete; \ + KLASS(const KLASS&) = delete; + +#define NECTI_COPY_DEFAULT(KLASS) \ + KLASS& operator=(const KLASS&) = default; \ + KLASS(const KLASS&) = default; + +#define NECTI_MOVE_DELETE(KLASS) \ + KLASS& operator=(KLASS&&) = delete; \ + KLASS(KLASS&&) = delete; + +#define NECTI_MOVE_DEFAULT(KLASS) \ + KLASS& operator=(KLASS&&) = default; \ + KLASS(KLASS&&) = default; + +#define CK_IMPORT_C extern "C" +#define CK_IMPORT extern diff --git a/src/CompilerKit/ErrorID.h b/src/CompilerKit/ErrorID.h index 5781842..3e3b0d7 100644 --- a/src/CompilerKit/ErrorID.h +++ b/src/CompilerKit/ErrorID.h @@ -9,7 +9,7 @@ #pragma once -#include +#include /// =========================================================== /// /// @file ErrorID.h diff --git a/src/CompilerKit/ErrorOr.h b/src/CompilerKit/ErrorOr.h index 3a8f160..100624e 100644 --- a/src/CompilerKit/ErrorOr.h +++ b/src/CompilerKit/ErrorOr.h @@ -15,7 +15,7 @@ /// @brief ErrorOr for CompilerKit. /// =========================================================== /// -#include +#include #include #include diff --git a/src/CompilerKit/PEF.h b/src/CompilerKit/PEF.h index 68a4f9a..2838f52 100644 --- a/src/CompilerKit/PEF.h +++ b/src/CompilerKit/PEF.h @@ -6,7 +6,7 @@ #pragma once -#include +#include #include // @file PEF.h diff --git a/src/CompilerKit/Ref.h b/src/CompilerKit/Ref.h index f66e8c1..863a100 100644 --- a/src/CompilerKit/Ref.h +++ b/src/CompilerKit/Ref.h @@ -10,7 +10,7 @@ #pragma once -#include +#include namespace CompilerKit { /// @author Amlal El Mahrouss diff --git a/src/CompilerKit/StringKit.h b/src/CompilerKit/StringKit.h index 251ab4e..d3eb3ad 100644 --- a/src/CompilerKit/StringKit.h +++ b/src/CompilerKit/StringKit.h @@ -10,7 +10,7 @@ #ifndef __NECTI_STRINGKIT__ #define __NECTI_STRINGKIT__ -#include +#include #include #include diff --git a/src/CompilerKit/Utilities/Assembler.h b/src/CompilerKit/Utilities/Assembler.h new file mode 100644 index 0000000..3da48cc --- /dev/null +++ b/src/CompilerKit/Utilities/Assembler.h @@ -0,0 +1,92 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +#include +#include +#include + +using namespace CompilerKit; + +/// @brief Get Number from lineBuffer. +/// @param lineBuffer the lineBuffer to fetch from. +/// @param numberKey where to seek that number. +/// @return A numbercast of 32-bit width. +static NumberCast32 GetNumber32(STLString lineBuffer, STLString numberKey) { + auto pos = lineBuffer.find(numberKey) + numberKey.size(); + + while (lineBuffer[pos] == ' ') { + ++pos; + } + + switch (lineBuffer[pos + 1]) { + case 'x': { + if (auto res = strtol(lineBuffer.substr(pos).c_str(), nullptr, 16); !res) { + if (errno != 0) { + Detail::print_error("invalid hex number: " + lineBuffer, "CompilerKit"); + throw std::runtime_error("invalid_hex"); + } + } + + NumberCast32 numOffset(strtol(lineBuffer.substr(pos).c_str(), nullptr, 16)); + + if (kVerbose) { + kStdOut << "asm: found a base 16 number here: " << lineBuffer.substr(pos) << "\n"; + } + + return numOffset; + } + case 'b': { + if (auto res = strtol(lineBuffer.substr(pos).c_str(), nullptr, 2); !res) { + if (errno != 0) { + Detail::print_error("invalid binary number:" + lineBuffer, "CompilerKit"); + throw std::runtime_error("invalid_bin"); + } + } + + NumberCast32 numOffset(strtol(lineBuffer.substr(pos).c_str(), nullptr, 2)); + + if (kVerbose) { + kStdOut << "asm: found a base 2 number here:" << lineBuffer.substr(pos) << "\n"; + } + + return numOffset; + } + case 'o': { + if (auto res = strtol(lineBuffer.substr(pos).c_str(), nullptr, 7); !res) { + if (errno != 0) { + Detail::print_error("invalid octal number: " + lineBuffer, "CompilerKit"); + throw std::runtime_error("invalid_octal"); + } + } + + NumberCast32 numOffset(strtol(lineBuffer.substr(pos).c_str(), nullptr, 7)); + + if (kVerbose) { + kStdOut << "asm: found a base 8 number here:" << lineBuffer.substr(pos) << "\n"; + } + + return numOffset; + } + default: { + if (auto res = strtol(lineBuffer.substr(pos).c_str(), nullptr, 10); !res) { + if (errno != 0) { + Detail::print_error("invalid hex number: " + lineBuffer, "CompilerKit"); + throw std::runtime_error("invalid_hex"); + } + } + + NumberCast32 numOffset(strtol(lineBuffer.substr(pos).c_str(), nullptr, 10)); + + if (kVerbose) { + kStdOut << "asm: found a base 10 number here:" << lineBuffer.substr(pos) << "\n"; + } + + return numOffset; + } + } +} diff --git a/src/CompilerKit/Utilities/Compiler.h b/src/CompilerKit/Utilities/Compiler.h new file mode 100644 index 0000000..165ab53 --- /dev/null +++ b/src/CompilerKit/Utilities/Compiler.h @@ -0,0 +1,119 @@ +/* ======================================== + + Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#define kZero64Section ".zero64" +#define kCode64Section ".code64" +#define kData64Section ".data64" + +#define kZero128Section ".zero128" +#define kCode128Section ".code128" +#define kData128Section ".data128" + +#define kBlank "\e[0;30m" +#define kRed "\e[0;31m" +#define kWhite "\e[0;97m" +#define kYellow "\e[0;33m" + +#define kStdOut (std::cout << kRed << "drv: " << kWhite) +#define kStdErr (std::cerr << kYellow << "drv: " << kWhite) + +#define kPrintF kStdOut +#define kPrintErr kStdErr + +inline static UInt32 kErrorLimit = 10; +inline static UInt32 kAcceptableErrors = 0; +inline static bool kVerbose = false; +inline static bool kOutputAsBinary = false; + +namespace Detail { +/// @brief Linker specific blob metadata structure +struct DynamicLinkerBlob final { + std::vector mBlob{}; // PEF code/bss/data blob. + UIntPtr mOffset{0UL}; // the offset of the PEF container header... +}; + +inline void print_error(std::string reason, std::string file) noexcept { + if (reason[0] == '\n') reason.erase(0, 1); + + kStdErr << reason << kBlank << std::endl; + + if (kAcceptableErrors > kErrorLimit) std::exit(NECTI_EXEC_ERROR); + + ++kAcceptableErrors; +} + +inline void print_warning(std::string reason, std::string file) noexcept { + if (reason[0] == '\n') reason.erase(0, 1); + + kStdOut << kYellow << reason << kBlank << std::endl; +} + +/// @internal +/// @brief Handler for SIGSEGV signal. +inline void drvi_crash_handler(std::int32_t id) { + CompilerKit::STLString verbose_header = "LIBCOMPILER CRASH REPORT - "; + verbose_header += kDistVersion; + verbose_header += " - "; + verbose_header += CompilerKit::current_date(); + + for (auto& ch : verbose_header) { + std::cout << '='; + } + + std::cout << std::endl; + + std::cout << verbose_header << std::endl; + + for (auto& ch : verbose_header) { + std::cout << '='; + } + + std::cout << std::endl; + + kStdOut << "DATE: " << CompilerKit::current_date() << std::endl; + kStdOut << "VERSION: " << kDistVersion << std::endl; + kStdOut << "ERRNO: " << errno << std::endl; + kStdOut << "ERRNO(STRING): " << strerror(errno) << std::endl; + + switch (id) { + case SIGSEGV: { + kStdOut << "SIGNAL: Segmentation Fault." << kBlank << std::endl; + break; + } + case SIGABRT: { + kStdOut << "SIGNAL: Aborted." << kBlank << std::endl; + break; + } + } + + std::cout << kWhite; + + for (auto& ch : verbose_header) { + std::cout << '='; + } + + std::cout << std::endl; + + std::cout << verbose_header << std::endl; + + for (auto& ch : verbose_header) { + std::cout << '='; + } + + std::cout << std::endl; + + std::exit(NECTI_EXEC_ERROR); +} +} // namespace Detail diff --git a/src/CompilerKit/Utilities/DLL.h b/src/CompilerKit/Utilities/DLL.h new file mode 100644 index 0000000..58e0cc7 --- /dev/null +++ b/src/CompilerKit/Utilities/DLL.h @@ -0,0 +1,63 @@ +/* ======================================== + + Copyright (C) 2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license + +======================================== */ + +#pragma once + +#include +#include +#include + +struct CompilerKitDylibTraits; + +typedef Int32 (*CompilerKitEntrypoint)(Int32 argc, Char const* argv[]); +typedef VoidPtr CompilerKitDylib; + +struct CompilerKitDylibTraits final { + CompilerKitDylib fDylib{nullptr}; + CompilerKitEntrypoint fEntrypoint{nullptr}; + std::mutex fMutex; + + CompilerKitDylibTraits& operator()(const Char* path, const Char* fEntrypoint) { + std::lock_guard lock(this->fMutex); + + if (!path || !fEntrypoint) return *this; + + if (this->fDylib) { + dlclose(this->fDylib); + this->fDylib = nullptr; + } + + this->fDylib = dlopen(path, RTLD_LAZY); + + if (!this->fDylib) { + return *this; + } + + this->fEntrypoint = (CompilerKitEntrypoint) dlsym(this->fDylib, fEntrypoint); + + if (!this->fEntrypoint) { + dlclose(this->fDylib); + this->fDylib = nullptr; + + return *this; + } + + return *this; + } + + NECTI_COPY_DELETE(CompilerKitDylibTraits); + + CompilerKitDylibTraits() = default; + + ~CompilerKitDylibTraits() { + if (this->fDylib) { + dlclose(this->fDylib); + this->fDylib = nullptr; + } + + this->fEntrypoint = nullptr; + } +}; diff --git a/src/CompilerKit/XCOFF.h b/src/CompilerKit/XCOFF.h index 64828b9..777f501 100644 --- a/src/CompilerKit/XCOFF.h +++ b/src/CompilerKit/XCOFF.h @@ -14,7 +14,7 @@ #ifndef _NECTI_XCOFF_H_ #define _NECTI_XCOFF_H_ -#include +#include #define kXCOFF64Magic 0x01F7 diff --git a/src/CompilerKit/detail/Config.h b/src/CompilerKit/detail/Config.h deleted file mode 100644 index 1b2d2ae..0000000 --- a/src/CompilerKit/detail/Config.h +++ /dev/null @@ -1,65 +0,0 @@ -/* ======================================== - - Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license - -======================================== */ - -#pragma once - -/// =========================================================== /// -/// @file detail/Config.h -/// @author Amlal El Mahrouss -/// @brief Basic defines and types for CompilerKit. -/// =========================================================== /// - -#include - -namespace CompilerKit { -inline constexpr int kBaseYear = 1900; - -typedef std::string STLString; - -inline STLString current_date() noexcept { - auto time_data = time(nullptr); - auto time_struct = gmtime(&time_data); - - STLString fmt = std::to_string(kBaseYear + time_struct->tm_year); - - fmt += "-"; - fmt += std::to_string(time_struct->tm_mon + 1); - fmt += "-"; - fmt += std::to_string(time_struct->tm_mday); - - return fmt; -} - -inline bool to_str(Char* str, Int32 limit, Int32 base) noexcept { - if (limit == 0) return false; - - Int32 copy_limit = limit; - Int32 cnt = 0; - Int32 ret = base; - - while (limit != 1) { - ret = ret % 10; - str[cnt] = ret; - - ++cnt; - --limit; - --ret; - } - - str[copy_limit] = '\0'; - return true; -} - -inline bool install_signal(Int32 signal, void (*handler)(int)) noexcept { - if (handler == nullptr) return false; - - if (::signal(signal, handler) == SIG_ERR) { - return false; - } - - return true; -} -} // namespace CompilerKit diff --git a/src/CompilerKit/detail/PreConfig.h b/src/CompilerKit/detail/PreConfig.h deleted file mode 100644 index 6742c53..0000000 --- a/src/CompilerKit/detail/PreConfig.h +++ /dev/null @@ -1,120 +0,0 @@ -/* ======================================== - - Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license - -======================================== */ - -#pragma once - -#ifndef Yes -#define Yes true -#endif // ifndef Yes - -#ifndef No -#define No false -#endif // ifndef No - -#ifndef YES -#define YES true -#endif // ifndef YES - -#ifndef NO -#define NO false -#endif // ifndef NO - -#define SizeType size_t - -#define VoidPtr void* -#define voidPtr VoidPtr - -#define UIntPtr uintptr_t - -#define Int64 int64_t -#define UInt64 uint64_t - -#define Int32 int -#define UInt32 unsigned - -#define Int16 int16_t -#define UInt16 uint16_t - -#define Int8 int8_t -#define UInt8 uint8_t - -#define Char char - -#include -#include -#include -#include -#include -#include - -#define kDistVersion "v0.0.7-compilerkit" -#define kDistVersionBCD 0x0002 - -#define ToString(X) Stringify(X) -#define Stringify(X) #X - -#define kDistRelease ToString(kDistReleaseBranch) - -#ifndef kDistRelease - -#define kDistVersion "v0.0.7-compilerkit" -#define kDistVersionBCD 0x0002 - -#define ToString(X) Stringify(X) -#define Stringify(X) #X - -#define kDistRelease ToString(kDistReleaseBranch) - -#endif // !kDistRelease - -#define MUST_PASS(E) assert(E) - -#ifndef __FORCE_STRLEN -#define __FORCE_STRLEN 1 - -#define string_length(len) strlen(len) -#endif - -#ifndef __FORCE_MEMCPY -#define __FORCE_MEMCPY 1 - -#define rt_copy_memory(dst, src, len) memcpy(dst, src, len) -#endif - -#define ATTRIBUTE(X) __attribute__((X)) -#define PACKED ATTRIBUTE(packed) - -#define kObjectFileExt ".obj" -#define kBinaryFileExt ".bin" - -#define kAsmFileExts {".64x", ".32x", ".masm", ".s", ".S", ".asm", ".x64"} - -#define kAsmFileExtsMax (7U) - -#define NECTI_MODULE(name) extern "C" int name(int argc, char** argv) - -#ifdef MSVC -#pragma scalar_storage_order big - endian -#endif // ifdef MSVC - -#define NECTI_COPY_DELETE(KLASS) \ - KLASS& operator=(const KLASS&) = delete; \ - KLASS(const KLASS&) = delete; - -#define NECTI_COPY_DEFAULT(KLASS) \ - KLASS& operator=(const KLASS&) = default; \ - KLASS(const KLASS&) = default; - -#define NECTI_MOVE_DELETE(KLASS) \ - KLASS& operator=(KLASS&&) = delete; \ - KLASS(KLASS&&) = delete; - -#define NECTI_MOVE_DEFAULT(KLASS) \ - KLASS& operator=(KLASS&&) = default; \ - KLASS(KLASS&&) = default; - -#define CK_IMPORT_C extern "C" -#define CK_IMPORT extern diff --git a/src/CompilerKit/impl/32x0.h b/src/CompilerKit/impl/32x0.h deleted file mode 100644 index 625e09e..0000000 --- a/src/CompilerKit/impl/32x0.h +++ /dev/null @@ -1,94 +0,0 @@ -/* ======================================== - - Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license - -======================================== */ - -#pragma once - -#include -#include - -// @brief Open32x0 support. -// @file impl/32x0.h - -#define CK_ASM_OPCODE(__NAME, __OPCODE, __FUNCT3, __FUNCT7) \ - {.fName = __NAME, .fOpcode = __OPCODE, .fFunct3 = __FUNCT3, .fFunct7 = __FUNCT7}, - -#define kAsmImmediate 0x01 -#define kAsmSyscall 0x02 -#define kAsmJump 0x03 -#define kAsmNoArgs 0x04 - -#define kAsmByte 0 -#define kAsmHWord 1 -#define kAsmWord 2 - -struct CpuCode32x0 { - const char fName[32]; - uint8_t fOpcode; - uint8_t fSize; - uint8_t fFunct3; - uint8_t fFunct7; -}; - -#define kAsmDWordStr ".dword" /* 64 bit */ -#define kAsmWordStr ".word" /* 32-bit */ -#define kAsmHWordStr ".half" /* 16-bit */ -#define kAsmByteStr ".byte" /* 8-bit */ - -inline std::vector kOpcodes32x0 = { - CK_ASM_OPCODE("nop", 0b0100011, 0b000, kAsmNoArgs) // nothing to do. (1C) - CK_ASM_OPCODE("jmp", 0b1110011, 0b001, kAsmJump) // jump to branch (2C) - CK_ASM_OPCODE("mov", 0b0100011, 0b101, kAsmImmediate) // move registers (3C) - CK_ASM_OPCODE("psh", 0b0111011, 0b000, kAsmImmediate) // push to sp (2C) - CK_ASM_OPCODE("pop", 0b0111011, 0b001, kAsmImmediate) // pop from sp. (1C) - CK_ASM_OPCODE("lea", 0b0111011, 0b010, - kAsmImmediate) // setup stack and call, store address to CR (1C). - CK_ASM_OPCODE("ret", 0b0111011, 0b110, - kAsmImmediate) // return from procedure (2C). - CK_ASM_OPCODE("uc", 0b0111111, 0b000, kAsmSyscall) // user call (1C) - CK_ASM_OPCODE("kc", 0b0111111, 0b001, kAsmSyscall) // kernel call (1C) - CK_ASM_OPCODE("int", 0b0111111, 0b010, kAsmSyscall) // raise interrupt (1C) -}; - -// \brief 64x0 register prefix -// example: r32, r0 -// r32 -> sp -// r0 -> hw zero - -#define kAsmRegisterPrefix "r" -#define kAsmRegisterLimit 16 -#define kAsmPcRegister 17 -#define kAsmCrRegister 18 -#define kAsmSpRegister 5 - -/* return address register */ -#define kAsmRetRegister 19 - -///////////////////////////////////////////////////////////////////////////// - -// SYSTEM CALL ADDRESSING - -// | OPCODE | FUNCT3 | FUNCT7 | OFF | - -// IMMEDIATE ADDRESSING - -// | OPCODE | FUNCT3 | FUNCT7 | REG | OFF | -// | OPCODE | FUNCT3 | FUNCT7 | REG | OFF | REG | -// | OPCODE | FUNCT3 | FUNCT7 | REG | REG | OFF | - -// REG TO REG ADDRESSING - -// | OPCODE | FUNCT3 | FUNCT7 | REG | REG2 | - -//////////////////////////////// - -// LOAD/CALL INTERRUPTS - -// SET A HANDLER IN ADDRESS: TODO: find one -// DISABLE INTERRUPTS -// PROCESS INTERRUPT -// ENABLE INTERRUPTS - -//////////////////////////////// diff --git a/src/CompilerKit/impl/64x0.h b/src/CompilerKit/impl/64x0.h deleted file mode 100644 index ea16bca..0000000 --- a/src/CompilerKit/impl/64x0.h +++ /dev/null @@ -1,100 +0,0 @@ -/* ======================================== - - Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license - -======================================== */ - -#pragma once - -#include -#include - -// @brief Open64x0 support. -// @file impl/64x0.h - -#define CK_ASM_OPCODE(__NAME, __OPCODE, __FUNCT3, __FUNCT7) \ - {.fName = __NAME, .fOpcode = __OPCODE, .fFunct3 = __FUNCT3, .fFunct7 = __FUNCT7}, - -#define kAsmImmediate 0x01 -#define kAsmRegToReg 0x02 -#define kAsmSyscall 0x03 -#define kAsmJump 0x04 -#define kAsmNoArgs 0x00 - -typedef char e64k_character_t; -typedef uint8_t e64k_num_t; - -struct CpuOpcode64x0 { - const e64k_character_t fName[32]; - e64k_num_t fOpcode; - e64k_num_t fFunct3; - e64k_num_t fFunct7; -}; - -inline std::vector kOpcodes64x0 = { - CK_ASM_OPCODE("nop", 0b0000000, 0b0000000, kAsmNoArgs) // no-operation. - CK_ASM_OPCODE("np", 0b0000000, 0b0000000, kAsmNoArgs) // no-operation. - CK_ASM_OPCODE("jlr", 0b1110011, 0b0000111, - kAsmJump) // jump to linked return register - CK_ASM_OPCODE("jrl", 0b1110011, 0b0001111, - kAsmJump) // jump from return register. - CK_ASM_OPCODE("mv", 0b0100011, 0b101, kAsmRegToReg) CK_ASM_OPCODE( - "bg", 0b1100111, 0b111, kAsmRegToReg) CK_ASM_OPCODE("bl", 0b1100111, 0b011, kAsmRegToReg) - CK_ASM_OPCODE("beq", 0b1100111, 0b000, kAsmRegToReg) - CK_ASM_OPCODE("bne", 0b1100111, 0b001, kAsmRegToReg) - CK_ASM_OPCODE("bge", 0b1100111, 0b101, kAsmRegToReg) - CK_ASM_OPCODE("ble", 0b1100111, 0b100, kAsmRegToReg) - CK_ASM_OPCODE("stw", 0b0001111, 0b100, kAsmImmediate) - CK_ASM_OPCODE("ldw", 0b0001111, 0b100, kAsmImmediate) - CK_ASM_OPCODE("lda", 0b0001111, 0b101, kAsmImmediate) - CK_ASM_OPCODE("sta", 0b0001111, 0b001, kAsmImmediate) - // add/sub without carry flag - CK_ASM_OPCODE("add", 0b0101011, 0b100, kAsmImmediate) - CK_ASM_OPCODE("sub", 0b0101011, 0b101, kAsmImmediate) - // add/sub with carry flag - CK_ASM_OPCODE("addc", 0b0101011, 0b110, kAsmImmediate) CK_ASM_OPCODE( - "subc", 0b0101011, 0b111, kAsmImmediate) CK_ASM_OPCODE("sc", 0b1110011, 0b00, kAsmSyscall)}; - -// \brief 64x0 register prefix -// example: r32, r0 -// r32 -> sp -// r0 -> hw zero - -#define kAsmFloatZeroRegister 0 -#define kAsmZeroRegister 0 - -#define kAsmRegisterPrefix "r" -#define kAsmRegisterLimit 30 -#define kAsmPcRegister 17 -#define kAsmCrRegister 18 -#define kAsmSpRegister 5 - -/* return address register */ -#define kAsmRetRegister 19 - -///////////////////////////////////////////////////////////////////////////// - -// SYSTEM CALL/JUMP ADDRESSING - -// | OPCODE | FUNCT3 | FUNCT7 | OFF | - -// IMMEDIATE ADDRESSING - -// | OPCODE | FUNCT3 | FUNCT7 | REG | OFF | -// | OPCODE | FUNCT3 | FUNCT7 | REG | OFF | REG | -// | OPCODE | FUNCT3 | FUNCT7 | REG | REG | OFF | - -// REG TO REG ADDRESSING - -// | OPCODE | FUNCT3 | FUNCT7 | REG | REG2 | - -//////////////////////////////// - -// LOAD/CALL INTERRUPTS - -// SET A HANDLER IN ADDRESS: -// DISABLE INTERRUPTS -// PROCESS INTERRUPT -// ENABLE INTERRUPTS - -//////////////////////////////// diff --git a/src/CompilerKit/impl/Aarch64.h b/src/CompilerKit/impl/Aarch64.h deleted file mode 100644 index d2eb197..0000000 --- a/src/CompilerKit/impl/Aarch64.h +++ /dev/null @@ -1,41 +0,0 @@ -/* ======================================== - -Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license - -======================================== */ - -#pragma once - -#include -#include - -/// @brief ARM64 encoding support. -/// @file impl/Aarch64.h - -struct CpuOpcodeArm64; - -/// @brief ARM64 opcode header. -struct PACKED CpuOpcodeArm64_Data final { - uint32_t fOpcode : 10; // Bits 31–22: Opcode for operation - uint32_t fRm : 5; // Bits 21–16: Source register Rm - uint32_t fShamt : 6; // Bits 15–10: Shift amount - uint32_t fRn : 5; // Bits 9–5: Source register Rn - uint32_t fRd : 5; // Bits 4–0: Destination register Rd -}; - -typedef struct { - uint32_t opcode : 6; // Bits 31–26: Branch opcode - int32_t offset : 26; // Bits 25–0: Signed offset (branch target) -} PACKED CpuOpcodeArm64_Branch; - -typedef struct { - uint32_t size : 2; // Bits 31–30: Size of the data - uint32_t opcode : 7; // Bits 29–23: Opcode for load/store - uint32_t offset : 12; // Bits 22–10: Offset - uint32_t rn : 5; // Bits 9–5: Base address register Rn - uint32_t rt : 5; // Bits 4–0: Target/source register Rt -} PACKED CpuOpcodeArm64_LoadStore; - -#define kAsmRegisterLimit (30) -#define kAsmRegisterPrefix "x" -#define kOpcodeARM64Count (1000) diff --git a/src/CompilerKit/impl/PowerPC.h b/src/CompilerKit/impl/PowerPC.h deleted file mode 100644 index 7c7f67c..0000000 --- a/src/CompilerKit/impl/PowerPC.h +++ /dev/null @@ -1,1557 +0,0 @@ -/* ======================================== - - Some modifications are copyrighted under: - Amlal El Mahrouss - - Original author: - Apple Inc - -======================================== */ - -#pragma once - -#include - -/// @note Based of: -/// https://opensource.apple.com/source/cctools/cctools-750/as/ppc-opcode.h.auto.html - -#define kOpcodePPCCount (1073U) - -/* - * These defines are use in the cpus field of the instructions. If the field - * is zero it can execute on all cpus. The defines are or'ed together. This - * information is used to set the cpusubtype in the resulting object file. - */ -#define CPU601 0x1 -#define IMPL64 0x2 -#define OPTIONAL 0x4 -#define VMX 0x8 -#define CPU970 0x10 /* added to OPTIONAL insts that the 970 has */ -#define CPUMAHROUSS 0x12 /* optional mahrouss insts. */ - -enum OpcodeType { - NONE, /* no operand */ - JBSR, /* jbsr pseudo op */ - PCREL, /* PC relative (branch offset) */ - BADDR, /* Branch address (sign extended absolute address) */ - D, /* 16 bit displacement */ - DS, /* 14 bit displacement (double word) */ - SI, /* signed 16 bit immediate */ - UI, /* unsigned 16 bit immediate */ - HI, /* high 16 bit immediate (with truncation) */ - GREG, /* general register */ - G0REG, /* general register r1-r31 or 0 */ - FREG, /* float register */ - VREG, /* vector register */ - SGREG, /* segment register */ - SPREG, /* special register (or 10 bit number, 5 bit halves reversed) */ - BCND, /* branch condition opcode */ - CRF, /* condition register field */ - CRFONLY, /* condition register field only no expression allowed */ - sh, /* 6 bit number (0 - 63) (sh field, split and reversed) */ - mb, /* 6 bit number (0 - 63) (mb field, mb5 || mb0:4 reversed) */ - NUM, /* number */ - SNUM, /* signed number */ - NUM0, /* number (where 1< -#include - -// @brief AMD64 support. -// @file impl/X64.h - -#define CK_ASM_OPCODE(__NAME, __OPCODE) {.fName = __NAME, .fOpcode = __OPCODE}, - -typedef char i64_character_t; -typedef uint8_t i64_byte_t; -typedef uint16_t i64_hword_t; -typedef uint32_t i64_word_t; - -#define kAsmRegisterPrefix "r" - -struct CpuOpcodeAMD64 { - std::string fName; - i64_byte_t fPrefixBytes[4]; - i64_hword_t fOpcode; - i64_hword_t fModReg; - i64_word_t fDisplacment; - i64_word_t fImmediate; -}; - -/// these two are edge cases -#define kAsmIntOpcode 0xCC -#define kasmIntOpcodeAlt 0xCD - -#define kAsmJumpOpcode 0x0F80 -#define kJumpLimit 30 -#define kJumpLimitStandard 0xE3 -#define kJumpLimitStandardLimit 0xEB - -inline std::vector kOpcodesAMD64 = { - CK_ASM_OPCODE("int", 0xCD) CK_ASM_OPCODE("into", 0xCE) CK_ASM_OPCODE("intd", 0xF1) - CK_ASM_OPCODE("int3", 0xC3) CK_ASM_OPCODE("iret", 0xCF) CK_ASM_OPCODE("retf", 0xCB) - CK_ASM_OPCODE("retn", 0xC3) CK_ASM_OPCODE("ret", 0xC3) CK_ASM_OPCODE("sti", 0xfb) - CK_ASM_OPCODE("cli", 0xfa) CK_ASM_OPCODE("hlt", 0xf4) CK_ASM_OPCODE("nop", 0x90) - CK_ASM_OPCODE("mov", 0x48) CK_ASM_OPCODE("call", 0xFF) - CK_ASM_OPCODE("syscall", 0x0F) CK_ASM_OPCODE("xor", 0x48)}; - -#define kAsmRegisterLimit 16 diff --git a/src/CompilerKit/src/Backend/Assembler32x0.cc b/src/CompilerKit/src/Backend/Assembler32x0.cc index 51e4265..fd44ffa 100644 --- a/src/CompilerKit/src/Backend/Assembler32x0.cc +++ b/src/CompilerKit/src/Backend/Assembler32x0.cc @@ -24,8 +24,8 @@ #include #include #include -#include -#include +#include +#include ///////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/CompilerKit/src/Backend/Assembler64x0.cc b/src/CompilerKit/src/Backend/Assembler64x0.cc index 75f0a81..f84ee46 100644 --- a/src/CompilerKit/src/Backend/Assembler64x0.cc +++ b/src/CompilerKit/src/Backend/Assembler64x0.cc @@ -24,8 +24,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include diff --git a/src/CompilerKit/src/Backend/AssemblerAMD64.cc b/src/CompilerKit/src/Backend/AssemblerAMD64.cc index 823a1c7..ba1dd77 100644 --- a/src/CompilerKit/src/Backend/AssemblerAMD64.cc +++ b/src/CompilerKit/src/Backend/AssemblerAMD64.cc @@ -31,7 +31,7 @@ #include #include #include -#include +#include #include #include #include @@ -75,7 +75,7 @@ static const std::string kUndefinedSymbol = ":UndefinedSymbol:"; // \brief forward decl. static bool asm_read_attributes(std::string line); -#include +#include ///////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/CompilerKit/src/Backend/AssemblerARM64.cc b/src/CompilerKit/src/Backend/AssemblerARM64.cc index 78cba1c..abc7d13 100644 --- a/src/CompilerKit/src/Backend/AssemblerARM64.cc +++ b/src/CompilerKit/src/Backend/AssemblerARM64.cc @@ -21,11 +21,11 @@ #include #include -#include +#include #include #include -#include -#include +#include +#include #include #include #include diff --git a/src/CompilerKit/src/Backend/AssemblerPowerPC.cc b/src/CompilerKit/src/Backend/AssemblerPowerPC.cc index 0ad59d2..c7d57e3 100644 --- a/src/CompilerKit/src/Backend/AssemblerPowerPC.cc +++ b/src/CompilerKit/src/Backend/AssemblerPowerPC.cc @@ -21,11 +21,11 @@ #include #include -#include +#include #include #include -#include -#include +#include +#include #include #include #include diff --git a/src/CompilerKit/src/Frontend/CCompiler64x0.cc b/src/CompilerKit/src/Frontend/CCompiler64x0.cc index 8cceb37..e0aed61 100644 --- a/src/CompilerKit/src/Frontend/CCompiler64x0.cc +++ b/src/CompilerKit/src/Frontend/CCompiler64x0.cc @@ -12,8 +12,8 @@ #include #include -#include -#include +#include +#include #include #include #include @@ -39,11 +39,7 @@ ///////////////////// -#define kExitOK (0) - -#define kBlank "\e[0;30m" -#define kRed "\e[0;31m" -#define kWhite "\e[0;97m" +#define kFrontendExt ".c" ///////////////////////////////////// @@ -1179,25 +1175,10 @@ class AssemblyCCInterface final CK_ASSEMBLY_INTERFACE { kState.fOutputAssembly->flush(); kState.fOutputAssembly.reset(); - return kExitOK; + return EXIT_SUCCESS; } }; -///////////////////////////////////////////////////////////////////////////////////////// - -#include - -#define kPrintF printf -#define kSplashCxx() kPrintF(kWhite "NeCTI C Driver, %s, (c) Amlal El Mahrouss\n", kDistVersion) - -static void cc_print_help() { - kSplashCxx(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -#define kExt ".c" - NECTI_MODULE(CompilerCLang64x0) { ::signal(SIGSEGV, Detail::drvi_crash_handler); @@ -1222,8 +1203,7 @@ NECTI_MODULE(CompilerCLang64x0) { if (argv[index][0] == '-') { if (strcmp(argv[index], "--v") == 0 || strcmp(argv[index], "--version") == 0) { - kSplashCxx(); - return kExitOK; + return EXIT_SUCCESS; } if (strcmp(argv[index], "--verbose") == 0) { @@ -1233,15 +1213,13 @@ NECTI_MODULE(CompilerCLang64x0) { } if (strcmp(argv[index], "--h") == 0 || strcmp(argv[index], "--help") == 0) { - cc_print_help(); - - return kExitOK; + return EXIT_SUCCESS; } if (strcmp(argv[index], "--dialect") == 0) { if (kCompilerFrontend) std::cout << kCompilerFrontend->Language() << "\n"; - return kExitOK; + return EXIT_SUCCESS; } if (strcmp(argv[index], "--fmax-exceptions") == 0) { @@ -1270,7 +1248,7 @@ NECTI_MODULE(CompilerCLang64x0) { std::string srcFile = argv[index]; - if (strstr(argv[index], kExt) == nullptr) { + if (strstr(argv[index], kFrontendExt) == nullptr) { if (kState.fVerbose) { Detail::print_error(srcFile + " is not a valid C source.\n", "cc"); } @@ -1278,10 +1256,10 @@ NECTI_MODULE(CompilerCLang64x0) { return 1; } - if (kFactory.Compile(srcFile, kMachine) != kExitOK) return 1; + if (kFactory.Compile(srcFile, kMachine) != EXIT_SUCCESS) return 1; } - return kExitOK; + return EXIT_SUCCESS; } // Last rev 8-1-24 diff --git a/src/CompilerKit/src/Frontend/CCompilerARM64.cc b/src/CompilerKit/src/Frontend/CCompilerARM64.cc index ffee4a8..811f4ea 100644 --- a/src/CompilerKit/src/Frontend/CCompilerARM64.cc +++ b/src/CompilerKit/src/Frontend/CCompilerARM64.cc @@ -12,8 +12,8 @@ #include #include -#include -#include +#include +#include #include #include @@ -40,11 +40,7 @@ ///////////////////// -#define kExitOK (0) - -#define kBlank "\e[0;30m" -#define kRed "\e[0;31m" -#define kWhite "\e[0;97m" +#define kCExtension ".c" ///////////////////////////////////// @@ -1178,25 +1174,10 @@ class AssemblyCCInterface final CK_ASSEMBLY_INTERFACE { kState.fOutputAssembly->flush(); kState.fOutputAssembly.reset(); - return kExitOK; + return EXIT_SUCCESS; } }; -///////////////////////////////////////////////////////////////////////////////////////// - -#include - -#define kPrintF printf -#define kSplashCxx() kPrintF(kWhite "NeCTI C Driver, %s, (c) Amlal El Mahrouss\n", kDistVersion) - -static void cc_print_help() { - kSplashCxx(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -#define kCExtension ".c" - NECTI_MODULE(CompilerCLangARM64) { ::signal(SIGSEGV, Detail::drvi_crash_handler); @@ -1221,8 +1202,7 @@ NECTI_MODULE(CompilerCLangARM64) { if (argv[index][0] == '-') { if (strcmp(argv[index], "--v") == 0 || strcmp(argv[index], "--version") == 0) { - kSplashCxx(); - return kExitOK; + return EXIT_SUCCESS; } if (strcmp(argv[index], "--verbose") == 0) { @@ -1232,15 +1212,13 @@ NECTI_MODULE(CompilerCLangARM64) { } if (strcmp(argv[index], "--h") == 0 || strcmp(argv[index], "--help") == 0) { - cc_print_help(); - - return kExitOK; + return EXIT_SUCCESS; } if (strcmp(argv[index], "--dialect") == 0) { if (kCompilerFrontend) std::cout << kCompilerFrontend->Language() << "\n"; - return kExitOK; + return EXIT_SUCCESS; } if (strcmp(argv[index], "--fmax-exceptions") == 0) { @@ -1277,10 +1255,10 @@ NECTI_MODULE(CompilerCLangARM64) { return 1; } - if (kFactory.Compile(srcFile, kMachine) != kExitOK) return 1; + if (kFactory.Compile(srcFile, kMachine) != EXIT_SUCCESS) return 1; } - return kExitOK; + return EXIT_SUCCESS; } // Last rev 8-1-24 diff --git a/src/CompilerKit/src/Frontend/CCompilerPower64.cc b/src/CompilerKit/src/Frontend/CCompilerPower64.cc index cee3cd2..03c4d66 100644 --- a/src/CompilerKit/src/Frontend/CCompilerPower64.cc +++ b/src/CompilerKit/src/Frontend/CCompilerPower64.cc @@ -9,8 +9,8 @@ #include #include -#include -#include +#include +#include #include #include #include @@ -20,22 +20,12 @@ #include #include -#define kExitOK 0 +#define kSourceExt ".c" /// @author Amlal El Mahrouss (amlal@nekernel.org) /// @file cc.cc /// @brief POWER64 C Compiler. -///////////////////// - -/// ANSI ESCAPE CODES - -///////////////////// - -#define kBlank "\e[0;30m" -#define kRed "\e[0;31m" -#define kWhite "\e[0;97m" - ///////////////////////////////////// /// INTERNAL STRUCTURES OF THE C COMPILER @@ -1196,25 +1186,10 @@ class AssemblyMountpointCLang final CK_ASSEMBLY_INTERFACE { kState.fOutputAssembly->flush(); kState.fOutputAssembly.reset(); - return kExitOK; + return EXIT_SUCCESS; } }; -///////////////////////////////////////////////////////////////////////////////////////// - -#include - -#define kPrintF printf -#define kSplashCxx() kPrintF(kWhite "cc, %s, (c) Amlal El Mahrouss\n", kDistVersion) - -static void cc_print_help() { - kSplashCxx(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -#define kExt ".c" - NECTI_MODULE(CompilerCLangPowerPC) { ::signal(SIGSEGV, Detail::drvi_crash_handler); @@ -1239,8 +1214,8 @@ NECTI_MODULE(CompilerCLangPowerPC) { if (argv[index][0] == '-') { if (strcmp(argv[index], "-v") == 0 || strcmp(argv[index], "-version") == 0) { - kSplashCxx(); - return kExitOK; + // AMLALE Compiler Version. + return EXIT_SUCCESS; } if (strcmp(argv[index], "-verbose") == 0) { @@ -1250,15 +1225,14 @@ NECTI_MODULE(CompilerCLangPowerPC) { } if (strcmp(argv[index], "-h") == 0 || strcmp(argv[index], "-help") == 0) { - cc_print_help(); - - return kExitOK; + // AMLALE Compiler Help. + return EXIT_SUCCESS; } if (strcmp(argv[index], "-dialect") == 0) { if (kCompilerFrontend) std::cout << kCompilerFrontend->Language() << "\n"; - return kExitOK; + return EXIT_SUCCESS; } if (strcmp(argv[index], "-fmax-exceptions") == 0) { @@ -1287,7 +1261,7 @@ NECTI_MODULE(CompilerCLangPowerPC) { std::string srcFile = argv[index]; - if (strstr(argv[index], kExt) == nullptr) { + if (strstr(argv[index], kSourceExt) == nullptr) { if (kState.fVerbose) { Detail::print_error(srcFile + " is not a valid C source.\n", "cc"); } @@ -1295,10 +1269,10 @@ NECTI_MODULE(CompilerCLangPowerPC) { return 1; } - if (kFactory.Compile(srcFile, kMachine) != kExitOK) return 1; + if (kFactory.Compile(srcFile, kMachine) != EXIT_SUCCESS) return 1; } - return kExitOK; + return EXIT_SUCCESS; } // Last rev 8-1-24 diff --git a/src/CompilerKit/src/Frontend/CPlusPlusCompilerAMD64.cc b/src/CompilerKit/src/Frontend/CPlusPlusCompilerAMD64.cc index c3f6197..87de974 100644 --- a/src/CompilerKit/src/Frontend/CPlusPlusCompilerAMD64.cc +++ b/src/CompilerKit/src/Frontend/CPlusPlusCompilerAMD64.cc @@ -21,21 +21,11 @@ /////////////////////// -#define kPrintF printf -#define kPrintErr std::cerr - -#define kExitOK (EXIT_SUCCESS) -#define kExitNO (EXIT_FAILURE) - -#define kBlank "\e[0;30m" -#define kRed "\e[0;31m" -#define kWhite "\e[0;97m" - #include #include #include -#include -#include +#include +#include #include #include #include @@ -738,7 +728,7 @@ class AssemblyCPlusPlusInterfaceAMD64 final CK_ASSEMBLY_INTERFACE { UInt32 Arch() noexcept override { return CompilerKit::AssemblyFactory::kArchAMD64; } Int32 CompileToFormat(CompilerKit::STLString src, Int32 arch) override { - if (kFrontend == nullptr) return kExitNO; + if (kFrontend == nullptr) return EXIT_FAILURE; CompilerKit::STLString dest = src; dest += ".pp.masm"; @@ -755,7 +745,7 @@ class AssemblyCPlusPlusInterfaceAMD64 final CK_ASSEMBLY_INTERFACE { out_fp << kFrontend->Compile(line_source, src).fUserValue; } - return kExitOK; + return EXIT_SUCCESS; } }; @@ -887,7 +877,7 @@ NECTI_MODULE(CompilerCPlusPlusAMD64) { for (CompilerKit::STLString ext : exts) { if (argv_i.ends_with(ext)) { - if (kAssembler.Compile(argv_i, kMachine) != kExitOK) { + if (kAssembler.Compile(argv_i, kMachine) != EXIT_SUCCESS) { return NECTI_INVALID_DATA; } diff --git a/src/CompilerKit/src/Linker/DynamicLinker64PEF.cc b/src/CompilerKit/src/Linker/DynamicLinker64PEF.cc index b5e21f1..58a06d2 100644 --- a/src/CompilerKit/src/Linker/DynamicLinker64PEF.cc +++ b/src/CompilerKit/src/Linker/DynamicLinker64PEF.cc @@ -15,11 +15,11 @@ #include #include -#include +#include #include #include #include -#include +#include #include #define kLinkerVersionStr "NeCTI 64-Bit Linker (Preferred Executable Format)" @@ -31,8 +31,7 @@ #define kLinkerId (0x5046FF) #define kLinkerAbiContainer "__PEFContainer:ABI:" -#define kPrintF printf -#define kLinkerSplash() kConsoleOut << kLinkerVersionStr << std::endl +#define kLinkerSplash() kStdOut << kLinkerVersionStr << std::endl /// @brief PEF stack size symbol. #define kLinkerStackSizeSymbol "__PEFSizeOfReserveStack" diff --git a/src/CompilerKit/utils/AsmUtils.h b/src/CompilerKit/utils/AsmUtils.h deleted file mode 100644 index 2cde706..0000000 --- a/src/CompilerKit/utils/AsmUtils.h +++ /dev/null @@ -1,92 +0,0 @@ -/* ======================================== - - Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license - -======================================== */ - -#pragma once - -#include -#include -#include - -using namespace CompilerKit; - -/// @brief Get Number from lineBuffer. -/// @param lineBuffer the lineBuffer to fetch from. -/// @param numberKey where to seek that number. -/// @return -static NumberCast32 GetNumber32(STLString lineBuffer, STLString numberKey) { - auto pos = lineBuffer.find(numberKey) + numberKey.size(); - - while (lineBuffer[pos] == ' ') { - ++pos; - } - - switch (lineBuffer[pos + 1]) { - case 'x': { - if (auto res = strtol(lineBuffer.substr(pos).c_str(), nullptr, 16); !res) { - if (errno != 0) { - Detail::print_error("invalid hex number: " + lineBuffer, "CompilerKit"); - throw std::runtime_error("invalid_hex"); - } - } - - NumberCast32 numOffset(strtol(lineBuffer.substr(pos).c_str(), nullptr, 16)); - - if (kVerbose) { - kStdOut << "asm: found a base 16 number here: " << lineBuffer.substr(pos) << "\n"; - } - - return numOffset; - } - case 'b': { - if (auto res = strtol(lineBuffer.substr(pos).c_str(), nullptr, 2); !res) { - if (errno != 0) { - Detail::print_error("invalid binary number:" + lineBuffer, "CompilerKit"); - throw std::runtime_error("invalid_bin"); - } - } - - NumberCast32 numOffset(strtol(lineBuffer.substr(pos).c_str(), nullptr, 2)); - - if (kVerbose) { - kStdOut << "asm: found a base 2 number here:" << lineBuffer.substr(pos) << "\n"; - } - - return numOffset; - } - case 'o': { - if (auto res = strtol(lineBuffer.substr(pos).c_str(), nullptr, 7); !res) { - if (errno != 0) { - Detail::print_error("invalid octal number: " + lineBuffer, "CompilerKit"); - throw std::runtime_error("invalid_octal"); - } - } - - NumberCast32 numOffset(strtol(lineBuffer.substr(pos).c_str(), nullptr, 7)); - - if (kVerbose) { - kStdOut << "asm: found a base 8 number here:" << lineBuffer.substr(pos) << "\n"; - } - - return numOffset; - } - default: { - if (auto res = strtol(lineBuffer.substr(pos).c_str(), nullptr, 10); !res) { - if (errno != 0) { - Detail::print_error("invalid hex number: " + lineBuffer, "CompilerKit"); - throw std::runtime_error("invalid_hex"); - } - } - - NumberCast32 numOffset(strtol(lineBuffer.substr(pos).c_str(), nullptr, 10)); - - if (kVerbose) { - kStdOut << "asm: found a base 10 number here:" << lineBuffer.substr(pos) << "\n"; - } - - return numOffset; - } - } -} diff --git a/src/CompilerKit/utils/CompilerUtils.h b/src/CompilerKit/utils/CompilerUtils.h deleted file mode 100644 index ebde2b9..0000000 --- a/src/CompilerKit/utils/CompilerUtils.h +++ /dev/null @@ -1,116 +0,0 @@ -/* ======================================== - - Copyright (C) 2024-2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license - -======================================== */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -#define kZero64Section ".zero64" -#define kCode64Section ".code64" -#define kData64Section ".data64" - -#define kZero128Section ".zero128" -#define kCode128Section ".code128" -#define kData128Section ".data128" - -#define kBlank "\e[0;30m" -#define kRed "\e[0;31m" -#define kWhite "\e[0;97m" -#define kYellow "\e[0;33m" - -#define kStdOut (std::cout << kRed << "drv: " << kWhite) -#define kStdErr (std::cout << kYellow << "drv: " << kWhite) - -inline static UInt32 kErrorLimit = 10; -inline static UInt32 kAcceptableErrors = 0; -inline static bool kVerbose = false; -inline static bool kOutputAsBinary = false; - -namespace Detail { -/// @brief Linker specific blob metadata structure -struct DynamicLinkerBlob final { - std::vector mBlob{}; // PEF code/bss/data blob. - UIntPtr mOffset{0UL}; // the offset of the PEF container header... -}; - -inline void print_error(std::string reason, std::string file) noexcept { - if (reason[0] == '\n') reason.erase(0, 1); - - kStdErr << reason << kBlank << std::endl; - - if (kAcceptableErrors > kErrorLimit) std::exit(NECTI_EXEC_ERROR); - - ++kAcceptableErrors; -} - -inline void print_warning(std::string reason, std::string file) noexcept { - if (reason[0] == '\n') reason.erase(0, 1); - - kStdOut << kYellow << reason << kBlank << std::endl; -} - -/// @internal -/// @brief Handler for SIGSEGV signal. -inline void drvi_crash_handler(std::int32_t id) { - CompilerKit::STLString verbose_header = "LIBCOMPILER CRASH REPORT - "; - verbose_header += kDistVersion; - verbose_header += " - "; - verbose_header += CompilerKit::current_date(); - - for (auto& ch : verbose_header) { - std::cout << '='; - } - - std::cout << std::endl; - - std::cout << verbose_header << std::endl; - - for (auto& ch : verbose_header) { - std::cout << '='; - } - - std::cout << std::endl; - - kStdOut << "DATE: " << CompilerKit::current_date() << std::endl; - kStdOut << "VERSION: " << kDistVersion << std::endl; - kStdOut << "ERRNO: " << errno << std::endl; - kStdOut << "ERRNO(STRING): " << strerror(errno) << std::endl; - - switch (id) { - case SIGSEGV: { - kStdOut << "SIGNAL: Segmentation Fault." << kBlank << std::endl; - break; - } - case SIGABRT: { - kStdOut << "SIGNAL: Aborted." << kBlank << std::endl; - break; - } - } - - std::cout << kWhite; - - for (auto& ch : verbose_header) { - std::cout << '='; - } - - std::cout << std::endl; - - std::cout << verbose_header << std::endl; - - for (auto& ch : verbose_header) { - std::cout << '='; - } - - std::cout << std::endl; - - std::exit(NECTI_EXEC_ERROR); -} -} // namespace Detail diff --git a/src/CompilerKit/utils/DylibHelpers.h b/src/CompilerKit/utils/DylibHelpers.h deleted file mode 100644 index 6b03823..0000000 --- a/src/CompilerKit/utils/DylibHelpers.h +++ /dev/null @@ -1,63 +0,0 @@ -/* ======================================== - - Copyright (C) 2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license - -======================================== */ - -#pragma once - -#include -#include -#include - -struct CompilerKitDylibTraits; - -typedef Int32 (*CompilerKitEntrypoint)(Int32 argc, Char const* argv[]); -typedef VoidPtr CompilerKitDylib; - -struct CompilerKitDylibTraits final { - CompilerKitDylib fDylib{nullptr}; - CompilerKitEntrypoint fEntrypoint{nullptr}; - std::mutex fMutex; - - CompilerKitDylibTraits& operator()(const Char* path, const Char* fEntrypoint) { - std::lock_guard lock(this->fMutex); - - if (!path || !fEntrypoint) return *this; - - if (this->fDylib) { - dlclose(this->fDylib); - this->fDylib = nullptr; - } - - this->fDylib = dlopen(path, RTLD_LAZY); - - if (!this->fDylib) { - return *this; - } - - this->fEntrypoint = (CompilerKitEntrypoint) dlsym(this->fDylib, fEntrypoint); - - if (!this->fEntrypoint) { - dlclose(this->fDylib); - this->fDylib = nullptr; - - return *this; - } - - return *this; - } - - NECTI_COPY_DELETE(CompilerKitDylibTraits); - - CompilerKitDylibTraits() = default; - - ~CompilerKitDylibTraits() { - if (this->fDylib) { - dlclose(this->fDylib); - this->fDylib = nullptr; - } - - this->fEntrypoint = nullptr; - } -}; diff --git a/src/DebuggerKit/DebuggerContract.h b/src/DebuggerKit/DebuggerContract.h index e727b3b..04b9b32 100644 --- a/src/DebuggerKit/DebuggerContract.h +++ b/src/DebuggerKit/DebuggerContract.h @@ -6,7 +6,7 @@ #pragma once -#include +#include #include #define DK_DEBUGGER_CONTRACT : public ::DebuggerKit::IDebuggerContract diff --git a/src/DebuggerKit/Detail/Config.h b/src/DebuggerKit/Detail/Config.h new file mode 100644 index 0000000..7124543 --- /dev/null +++ b/src/DebuggerKit/Detail/Config.h @@ -0,0 +1,44 @@ +/* ======================================== + + Copyright (C) 2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. + +======================================== */ + +#pragma once + +/// =========================================================== /// +/// @author Amlal El Mahrouss +/// =========================================================== /// + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#ifdef __APPLE__ +#include +#include +#include +#endif + +#ifndef kDistRelease + +#define kDistVersion "v0.0.7-debuggerkit" +#define kDistVersionBCD 0x0001 + +#define ToString(X) Stringify(X) +#define Stringify(X) #X + +#define kDistRelease ToString(kDistReleaseBranch) + +#endif // !kDistRelease \ No newline at end of file diff --git a/src/DebuggerKit/NeKernelContract.h b/src/DebuggerKit/NeKernelContract.h index bbcc061..1448fa7 100644 --- a/src/DebuggerKit/NeKernelContract.h +++ b/src/DebuggerKit/NeKernelContract.h @@ -12,7 +12,7 @@ #ifdef DK_NEKERNEL_DEBUGGER -#include +#include #include namespace DebuggerKit::NeKernel { diff --git a/src/DebuggerKit/detail/Config.h b/src/DebuggerKit/detail/Config.h deleted file mode 100644 index 5d6d26b..0000000 --- a/src/DebuggerKit/detail/Config.h +++ /dev/null @@ -1,44 +0,0 @@ -/* ======================================== - - Copyright (C) 2025 Amlal El Mahrouss, Licensed under the Apache 2.0 license. - -======================================== */ - -#pragma once - -/// =========================================================== /// -/// @author Amlal El Mahrouss -/// =========================================================== /// - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -#ifdef __APPLE__ -#include -#include -#include -#endif - -#ifndef kDistRelease - -#define kDistVersion "v0.0.7-debuggerkit" -#define kDistVersionBCD 0x0001 - -#define ToString(X) Stringify(X) -#define Stringify(X) #X - -#define kDistRelease ToString(kDistReleaseBranch) - -#endif // !kDistRelease \ No newline at end of file diff --git a/src/DebuggerKit/src/NeKernelContract.cc b/src/DebuggerKit/src/NeKernelContract.cc index 383056c..b62ee6e 100644 --- a/src/DebuggerKit/src/NeKernelContract.cc +++ b/src/DebuggerKit/src/NeKernelContract.cc @@ -9,10 +9,10 @@ /// @author Amlal El Mahrouss /// @brief Kernel Debugger Protocol -#include -#include +#include +#include #include -#include +#include using namespace DebuggerKit::NeKernel; using namespace DebuggerKit::NeKernel::Detail; diff --git a/src/DebuggerKit/src/NeKernelContractCLI.cc b/src/DebuggerKit/src/NeKernelContractCLI.cc index 467676c..3079568 100644 --- a/src/DebuggerKit/src/NeKernelContractCLI.cc +++ b/src/DebuggerKit/src/NeKernelContractCLI.cc @@ -6,9 +6,9 @@ #ifdef DK_NEKERNEL_DEBUGGER -#include +#include #include -#include +#include #include #include diff --git a/src/DebuggerKit/src/POSIXMachContractCLI.cc b/src/DebuggerKit/src/POSIXMachContractCLI.cc index 1530a03..9093ec2 100644 --- a/src/DebuggerKit/src/POSIXMachContractCLI.cc +++ b/src/DebuggerKit/src/POSIXMachContractCLI.cc @@ -6,9 +6,9 @@ #ifdef DK_MACH_DEBUGGER -#include +#include #include -#include +#include #ifdef __APPLE__ #include diff --git a/src/ThirdParty/Dialogs.h b/src/ThirdParty/Dialogs.h deleted file mode 100644 index f632026..0000000 --- a/src/ThirdParty/Dialogs.h +++ /dev/null @@ -1,1738 +0,0 @@ -// -// Portable File Dialogs -// -// Copyright © 2018–2022 Sam Hocevar -// -// This library is free software. It comes without any warranty, to -// the extent permitted by applicable law. You can redistribute it -// and/or modify it under the terms of the Do What the Fuck You Want -// to Public License, Version 2, as published by the WTFPL Task Force. -// See http://www.wtfpl.net/ for more details. -// - -#pragma once - -#if _WIN32 -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN 1 -#endif -#include -#include -#include -#include // IFileDialog -#include -#include // GetUserProfileDirectory() -#include -#include // std::async - -#elif __EMSCRIPTEN__ -#include - -#else -#ifndef _POSIX_C_SOURCE -#define _POSIX_C_SOURCE 2 // for popen() -#endif -#ifdef __APPLE__ -#ifndef _DARWIN_C_SOURCE -#define _DARWIN_C_SOURCE -#endif -#endif -#include // fcntl() -#include // getpwnam() -#include // stat() -#include // waitpid() -#include // read(), pipe(), dup2(), getuid() -#include // ::kill, std::signal -#include // popen() -#include // std::getenv() -#endif - -#include // std::chrono -#include // std::ostream -#include // std::map -#include // std::shared_ptr -#include // std::regex -#include // std::set -#include // std::string -#include // std::mutex, std::this_thread - -// Versions of mingw64 g++ up to 9.3.0 do not have a complete IFileDialog -#ifndef PFD_HAS_IFILEDIALOG -#define PFD_HAS_IFILEDIALOG 1 -#if (defined __MINGW64__ || defined __MINGW32__) && defined __GXX_ABI_VERSION -#if __GXX_ABI_VERSION <= 1013 -#undef PFD_HAS_IFILEDIALOG -#define PFD_HAS_IFILEDIALOG 0 -#endif -#endif -#endif - -namespace pfd { - -enum class button { - cancel = -1, - ok, - yes, - no, - abort, - retry, - ignore, -}; - -enum class choice { - ok = 0, - ok_cancel, - yes_no, - yes_no_cancel, - retry_cancel, - abort_retry_ignore, -}; - -enum class icon { - info = 0, - warning, - error, - question, -}; - -// Additional option flags for various dialog constructors -enum class opt : uint8_t { - none = 0, - // For file open, allow multiselect. - multiselect = 0x1, - // For file save, force overwrite and disable the confirmation dialog. - force_overwrite = 0x2, - // For folder select, force path to be the provided argument instead - // of the last opened directory, which is the Microsoft-recommended, - // user-friendly behaviour. - force_path = 0x4, -}; - -inline opt operator|(opt a, opt b) { - return opt(uint8_t(a) | uint8_t(b)); -} -inline bool operator&(opt a, opt b) { - return bool(uint8_t(a) & uint8_t(b)); -} - -// The settings class, only exposing to the user a way to set verbose mode -// and to force a rescan of installed desktop helpers (zenity, kdialog…). -class settings { - public: - static bool available(); - - static void verbose(bool value); - static void rescan(); - - protected: - explicit settings(bool resync = false); - - bool check_program(std::string const& program); - - inline bool is_osascript() const; - inline bool is_zenity() const; - inline bool is_kdialog() const; - - enum class flag { - is_scanned = 0, - is_verbose, - - has_zenity, - has_matedialog, - has_qarma, - has_kdialog, - is_vista, - - max_flag, - }; - - // Static array of flags for internal state - bool const& flags(flag in_flag) const; - - // Non-const getter for the static array of flags - bool& flags(flag in_flag); -}; - -// Internal classes, not to be used by client applications -namespace internal { - - // Process wait timeout, in milliseconds - static int const default_wait_timeout = 20; - - class executor { - friend class dialog; - - public: - // High level function to get the result of a command - std::string result(int* exit_code = nullptr); - - // High level function to abort - bool kill(); - -#if _WIN32 - void start_func(std::function const& fun); - static BOOL CALLBACK enum_windows_callback(HWND hwnd, LPARAM lParam); -#elif __EMSCRIPTEN__ - void start(int exit_code); -#else - void start_process(std::vector const& command); -#endif - - ~executor(); - - protected: - bool ready(int timeout = default_wait_timeout); - void stop(); - - private: - bool m_running = false; - std::string m_stdout; - int m_exit_code = -1; -#if _WIN32 - std::future m_future; - std::set m_windows; - std::condition_variable m_cond; - std::mutex m_mutex; - DWORD m_tid; -#elif __EMSCRIPTEN__ || __NX__ - // FIXME: do something -#else - pid_t m_pid = 0; - int m_fd = -1; -#endif - }; - - class platform { - protected: -#if _WIN32 - // Helper class around LoadLibraryA() and GetProcAddress() with some safety - class dll { - public: - dll(std::string const& name); - ~dll(); - - template - class proc { - public: - proc(dll const& lib, std::string const& sym) - : m_proc(reinterpret_cast((void*) ::GetProcAddress(lib.handle, sym.c_str()))) {} - - explicit operator bool() const { return m_proc != nullptr; } - operator T*() const { return m_proc; } - - private: - T* m_proc; - }; - - private: - HMODULE handle; - }; - - // Helper class around CoInitialize() and CoUnInitialize() - class ole32_dll : public dll { - public: - ole32_dll(); - ~ole32_dll(); - bool is_initialized(); - - private: - HRESULT m_state; - }; - - // Helper class around CreateActCtx() and ActivateActCtx() - class new_style_context { - public: - new_style_context(); - ~new_style_context(); - - private: - HANDLE create(); - ULONG_PTR m_cookie = 0; - }; -#endif - }; - - class dialog : protected settings, protected platform { - public: - bool ready(int timeout = default_wait_timeout) const; - bool kill() const; - - protected: - explicit dialog(); - - std::vector desktop_helper() const; - static std::string buttons_to_name(choice _choice); - static std::string get_icon_name(icon _icon); - - std::string powershell_quote(std::string const& str) const; - std::string osascript_quote(std::string const& str) const; - std::string shell_quote(std::string const& str) const; - - // Keep handle to executing command - std::shared_ptr m_async; - }; - - class file_dialog : public dialog { - protected: - enum type { - open, - save, - folder, - }; - - file_dialog(type in_type, std::string const& title, std::string const& default_path = "", - std::vector const& filters = {}, opt options = opt::none); - - protected: - std::string string_result(); - std::vector vector_result(); - -#if _WIN32 - static int CALLBACK bffcallback(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData); -#if PFD_HAS_IFILEDIALOG - std::string select_folder_vista(IFileDialog* ifd, bool force_path); -#endif - - std::wstring m_wtitle; - std::wstring m_wdefault_path; - - std::vector m_vector_result; -#endif - }; - -} // namespace internal - -// -// The path class provides some platform-specific path constants -// - -class path : protected internal::platform { - public: - static std::string home(); - static std::string separator(); -}; - -// -// The notify widget -// - -class notify : public internal::dialog { - public: - notify(std::string const& title, std::string const& message, icon _icon = icon::info); -}; - -// -// The message widget -// - -class message : public internal::dialog { - public: - message(std::string const& title, std::string const& text, choice _choice = choice::ok_cancel, - icon _icon = icon::info); - - button result(); - - private: - // Some extra logic to map the exit code to button number - std::map m_mappings; -}; - -// -// The open_file, save_file, and open_folder widgets -// - -class open_file : public internal::file_dialog { - public: - open_file(std::string const& title, std::string const& default_path = "", - std::vector const& filters = {"All Files", "*"}, opt options = opt::none); - -#if defined(__has_cpp_attribute) -#if __has_cpp_attribute(deprecated) - // Backwards compatibility - [[deprecated("Use pfd::opt::multiselect instead of allow_multiselect")]] -#endif -#endif - open_file(std::string const& title, std::string const& default_path, - std::vector const& filters, bool allow_multiselect); - - std::vector result(); -}; - -class save_file : public internal::file_dialog { - public: - save_file(std::string const& title, std::string const& default_path = "", - std::vector const& filters = {"All Files", "*"}, opt options = opt::none); - -#if defined(__has_cpp_attribute) -#if __has_cpp_attribute(deprecated) - // Backwards compatibility - [[deprecated("Use pfd::opt::force_overwrite instead of confirm_overwrite")]] -#endif -#endif - save_file(std::string const& title, std::string const& default_path, - std::vector const& filters, bool confirm_overwrite); - - std::string result(); -}; - -class select_folder : public internal::file_dialog { - public: - select_folder(std::string const& title, std::string const& default_path = "", - opt options = opt::none); - - std::string result(); -}; - -// -// Below this are all the method implementations. You may choose to define the -// macro PFD_SKIP_IMPLEMENTATION everywhere before including this header except -// in one place. This may reduce compilation times. -// - -#if !defined PFD_SKIP_IMPLEMENTATION - -// internal free functions implementations - -namespace internal { - -#if _WIN32 - static inline std::wstring str2wstr(std::string const& str) { - int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int) str.size(), nullptr, 0); - std::wstring ret(len, '\0'); - MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int) str.size(), (LPWSTR) ret.data(), - (int) ret.size()); - return ret; - } - - static inline std::string wstr2str(std::wstring const& str) { - int len = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int) str.size(), nullptr, 0, nullptr, - nullptr); - std::string ret(len, '\0'); - WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int) str.size(), (LPSTR) ret.data(), - (int) ret.size(), nullptr, nullptr); - return ret; - } - - static inline bool is_vista() { - OSVERSIONINFOEXW osvi; - memset(&osvi, 0, sizeof(osvi)); - DWORDLONG const mask = VerSetConditionMask( - VerSetConditionMask(VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL), - VER_MINORVERSION, VER_GREATER_EQUAL), - VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); - osvi.dwOSVersionInfoSize = sizeof(osvi); - osvi.dwMajorVersion = HIBYTE(_WIN32_WINNT_VISTA); - osvi.dwMinorVersion = LOBYTE(_WIN32_WINNT_VISTA); - osvi.wServicePackMajor = 0; - - return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, - mask) != FALSE; - } -#endif - - // This is necessary until C++20 which will have std::string::ends_with() etc. - - static inline bool ends_with(std::string const& str, std::string const& suffix) { - return suffix.size() <= str.size() && - str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; - } - - static inline bool starts_with(std::string const& str, std::string const& prefix) { - return prefix.size() <= str.size() && str.compare(0, prefix.size(), prefix) == 0; - } - - // This is necessary until C++17 which will have std::filesystem::is_directory - - static inline bool is_directory(std::string const& path) { -#if _WIN32 - auto attr = GetFileAttributesA(path.c_str()); - return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY); -#elif __EMSCRIPTEN__ - // TODO - return false; -#else - struct stat s; - return stat(path.c_str(), &s) == 0 && S_ISDIR(s.st_mode); -#endif - } - - // This is necessary because getenv is not thread-safe - - static inline std::string getenv(std::string const& str) { -#if _MSC_VER - char* buf = nullptr; - size_t size = 0; - if (_dupenv_s(&buf, &size, str.c_str()) == 0 && buf) { - std::string ret(buf); - free(buf); - return ret; - } - return ""; -#else - auto buf = std::getenv(str.c_str()); - return buf ? buf : ""; -#endif - } - -} // namespace internal - -// settings implementation - -inline settings::settings(bool resync) { - flags(flag::is_scanned) &= !resync; - - if (flags(flag::is_scanned)) return; - - auto pfd_verbose = internal::getenv("PFD_VERBOSE"); - auto match_no = std::regex("(|0|no|false)", std::regex_constants::icase); - if (!std::regex_match(pfd_verbose, match_no)) flags(flag::is_verbose) = true; - -#if _WIN32 - flags(flag::is_vista) = internal::is_vista(); -#elif !__APPLE__ - flags(flag::has_zenity) = check_program("zenity"); - flags(flag::has_matedialog) = check_program("matedialog"); - flags(flag::has_qarma) = check_program("qarma"); - flags(flag::has_kdialog) = check_program("kdialog"); - - // If multiple helpers are available, try to default to the best one - if (flags(flag::has_zenity) && flags(flag::has_kdialog)) { - auto desktop_name = internal::getenv("XDG_SESSION_DESKTOP"); - if (desktop_name == std::string("gnome")) - flags(flag::has_kdialog) = false; - else if (desktop_name == std::string("KDE")) - flags(flag::has_zenity) = false; - } -#endif - - flags(flag::is_scanned) = true; -} - -inline bool settings::available() { -#if _WIN32 - return true; -#elif __APPLE__ - return true; -#elif __EMSCRIPTEN__ - // FIXME: Return true after implementation is complete. - return false; -#else - settings tmp; - return tmp.flags(flag::has_zenity) || tmp.flags(flag::has_matedialog) || - tmp.flags(flag::has_qarma) || tmp.flags(flag::has_kdialog); -#endif -} - -inline void settings::verbose(bool value) { - settings().flags(flag::is_verbose) = value; -} - -inline void settings::rescan() { - settings(/* resync = */ true); -} - -// Check whether a program is present using “which”. -inline bool settings::check_program(std::string const& program) { -#if _WIN32 - (void) program; - return false; -#elif __EMSCRIPTEN__ - (void) program; - return false; -#else - int exit_code = -1; - internal::executor async; - async.start_process({"/bin/sh", "-c", "which " + program}); - async.result(&exit_code); - return exit_code == 0; -#endif -} - -inline bool settings::is_osascript() const { -#if __APPLE__ - return true; -#else - return false; -#endif -} - -inline bool settings::is_zenity() const { - return flags(flag::has_zenity) || flags(flag::has_matedialog) || flags(flag::has_qarma); -} - -inline bool settings::is_kdialog() const { - return flags(flag::has_kdialog); -} - -inline bool const& settings::flags(flag in_flag) const { - static bool flags[size_t(flag::max_flag)]; - return flags[size_t(in_flag)]; -} - -inline bool& settings::flags(flag in_flag) { - return const_cast(static_cast(this)->flags(in_flag)); -} - -// path implementation -inline std::string path::home() { -#if _WIN32 - // First try the USERPROFILE environment variable - auto user_profile = internal::getenv("USERPROFILE"); - if (user_profile.size() > 0) return user_profile; - // Otherwise, try GetUserProfileDirectory() - HANDLE token = nullptr; - DWORD len = MAX_PATH; - char buf[MAX_PATH] = {'\0'}; - if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { - dll userenv("userenv.dll"); - dll::proc get_user_profile_directory( - userenv, "GetUserProfileDirectoryA"); - get_user_profile_directory(token, buf, &len); - CloseHandle(token); - if (*buf) return buf; - } -#elif __EMSCRIPTEN__ - return "/"; -#else - // First try the HOME environment variable - auto home = internal::getenv("HOME"); - if (home.size() > 0) return home; - // Otherwise, try getpwuid_r() - size_t len = 4096; -#if defined(_SC_GETPW_R_SIZE_MAX) - auto size_max = sysconf(_SC_GETPW_R_SIZE_MAX); - if (size_max != -1) len = size_t(size_max); -#endif - std::vector buf(len); - struct passwd pwd, *result; - if (getpwuid_r(getuid(), &pwd, buf.data(), buf.size(), &result) == 0) return result->pw_dir; -#endif - return "/"; -} - -inline std::string path::separator() { -#if _WIN32 - return "\\"; -#else - return "/"; -#endif -} - -// executor implementation - -inline std::string internal::executor::result(int* exit_code /* = nullptr */) { - stop(); - if (exit_code) *exit_code = m_exit_code; - return m_stdout; -} - -inline bool internal::executor::kill() { -#if _WIN32 - if (m_future.valid()) { - // Close all windows that weren’t open when we started the future - auto previous_windows = m_windows; - EnumWindows(&enum_windows_callback, (LPARAM) this); - for (auto hwnd : m_windows) - if (previous_windows.find(hwnd) == previous_windows.end()) { - SendMessage(hwnd, WM_CLOSE, 0, 0); - // Also send IDNO in case of a Yes/No or Abort/Retry/Ignore messagebox - SendMessage(hwnd, WM_COMMAND, IDNO, 0); - } - } -#elif __EMSCRIPTEN__ || __NX__ - // FIXME: do something - return false; // cannot kill -#else - ::kill(m_pid, SIGKILL); -#endif - stop(); - return true; -} - -#if _WIN32 -inline BOOL CALLBACK internal::executor::enum_windows_callback(HWND hwnd, LPARAM lParam) { - auto that = (executor*) lParam; - - DWORD pid; - auto tid = GetWindowThreadProcessId(hwnd, &pid); - if (tid == that->m_tid) that->m_windows.insert(hwnd); - return TRUE; -} -#endif - -#if _WIN32 -inline void internal::executor::start_func(std::function const& fun) { - stop(); - - auto trampoline = [fun, this]() { - // Save our thread id so that the caller can cancel us - m_tid = GetCurrentThreadId(); - EnumWindows(&enum_windows_callback, (LPARAM) this); - m_cond.notify_all(); - return fun(&m_exit_code); - }; - - std::unique_lock lock(m_mutex); - m_future = std::async(std::launch::async, trampoline); - m_cond.wait(lock); - m_running = true; -} - -#elif __EMSCRIPTEN__ -inline void internal::executor::start(int exit_code) { - m_exit_code = exit_code; -} - -#else -inline void internal::executor::start_process(std::vector const& command) { - stop(); - m_stdout.clear(); - m_exit_code = -1; - - int in[2], out[2]; - if (pipe(in) != 0 || pipe(out) != 0) return; - - m_pid = fork(); - if (m_pid < 0) return; - - close(in[m_pid ? 0 : 1]); - close(out[m_pid ? 1 : 0]); - - if (m_pid == 0) { - dup2(in[0], STDIN_FILENO); - dup2(out[1], STDOUT_FILENO); - - // Ignore stderr so that it doesn’t pollute the console (e.g. GTK+ errors from zenity) - int fd = open("/src/null", O_WRONLY); - dup2(fd, STDERR_FILENO); - close(fd); - - std::vector args; - std::transform(command.cbegin(), command.cend(), std::back_inserter(args), - [](std::string const& s) { return const_cast(s.c_str()); }); - args.push_back(nullptr); // null-terminate argv[] - - execvp(args[0], args.data()); - exit(1); - } - - close(in[1]); - m_fd = out[0]; - auto flags = fcntl(m_fd, F_GETFL); - fcntl(m_fd, F_SETFL, flags | O_NONBLOCK); - - m_running = true; -} -#endif - -inline internal::executor::~executor() { - stop(); -} - -inline bool internal::executor::ready(int timeout /* = default_wait_timeout */) { - if (!m_running) return true; - -#if _WIN32 - if (m_future.valid()) { - auto status = m_future.wait_for(std::chrono::milliseconds(timeout)); - if (status != std::future_status::ready) { - // On Windows, we need to run the message pump. If the async - // thread uses a Windows API dialog, it may be attached to the - // main thread and waiting for messages that only we can dispatch. - MSG msg; - while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - return false; - } - - m_stdout = m_future.get(); - } -#elif __EMSCRIPTEN__ || __NX__ - // FIXME: do something - (void) timeout; -#else - char buf[BUFSIZ]; - ssize_t received = read(m_fd, buf, BUFSIZ); // Flawfinder: ignore - if (received > 0) { - m_stdout += std::string(buf, received); - return false; - } - - // Reap child process if it is dead. It is possible that the system has already reaped it - // (this happens when the calling application handles or ignores SIG_CHLD) and results in - // waitpid() failing with ECHILD. Otherwise we assume the child is running and we sleep for - // a little while. - int status; - pid_t child = waitpid(m_pid, &status, WNOHANG); - if (child != m_pid && (child >= 0 || errno != ECHILD)) { - // FIXME: this happens almost always at first iteration - std::this_thread::sleep_for(std::chrono::milliseconds(timeout)); - return false; - } - - close(m_fd); - m_exit_code = WEXITSTATUS(status); -#endif - - m_running = false; - return true; -} - -inline void internal::executor::stop() { - // Loop until the user closes the dialog - while (!ready()); -} - -// dll implementation - -#if _WIN32 -inline internal::platform::dll::dll(std::string const& name) - : handle(::LoadLibraryA(name.c_str())) {} - -inline internal::platform::dll::~dll() { - if (handle) ::FreeLibrary(handle); -} -#endif // _WIN32 - -// ole32_dll implementation - -#if _WIN32 -inline internal::platform::ole32_dll::ole32_dll() : dll("ole32.dll") { - // Use COINIT_MULTITHREADED because COINIT_APARTMENTTHREADED causes crashes. - // See https://github.com/samhocevar/portable-file-dialogs/issues/51 - auto coinit = proc(*this, "CoInitializeEx"); - m_state = coinit(nullptr, COINIT_MULTITHREADED); -} - -inline internal::platform::ole32_dll::~ole32_dll() { - if (is_initialized()) proc(*this, "CoUninitialize")(); -} - -inline bool internal::platform::ole32_dll::is_initialized() { - return m_state == S_OK || m_state == S_FALSE; -} -#endif - -// new_style_context implementation - -#if _WIN32 -inline internal::platform::new_style_context::new_style_context() { - // Only create one activation context for the whole app lifetime. - static HANDLE hctx = create(); - - if (hctx != INVALID_HANDLE_VALUE) ActivateActCtx(hctx, &m_cookie); -} - -inline internal::platform::new_style_context::~new_style_context() { - DeactivateActCtx(0, m_cookie); -} - -inline HANDLE internal::platform::new_style_context::create() { - // This “hack” seems to be necessary for this code to work on windows XP. - // Without it, dialogs do not show and close immediately. GetError() - // returns 0 so I don’t know what causes this. I was not able to reproduce - // this behavior on Windows 7 and 10 but just in case, let it be here for - // those versions too. - // This hack is not required if other dialogs are used (they load comdlg32 - // automatically), only if message boxes are used. - dll comdlg32("comdlg32.dll"); - - // Using approach as shown here: https://stackoverflow.com/a/10444161 - UINT len = ::GetSystemDirectoryA(nullptr, 0); - std::string sys_dir(len, '\0'); - ::GetSystemDirectoryA(&sys_dir[0], len); - - ACTCTXA act_ctx = { - // Do not set flag ACTCTX_FLAG_SET_PROCESS_DEFAULT, since it causes a - // crash with error “default context is already set”. - sizeof(act_ctx), - ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID, - "shell32.dll", - 0, - 0, - sys_dir.c_str(), - (LPCSTR) 124, - nullptr, - 0, - }; - - return ::CreateActCtxA(&act_ctx); -} -#endif // _WIN32 - -// dialog implementation - -inline bool internal::dialog::ready(int timeout /* = default_wait_timeout */) const { - return m_async->ready(timeout); -} - -inline bool internal::dialog::kill() const { - return m_async->kill(); -} - -inline internal::dialog::dialog() : m_async(std::make_shared()) {} - -inline std::vector internal::dialog::desktop_helper() const { -#if __APPLE__ - return {"osascript"}; -#else - return {flags(flag::has_zenity) ? "zenity" - : flags(flag::has_matedialog) ? "matedialog" - : flags(flag::has_qarma) ? "qarma" - : flags(flag::has_kdialog) ? "kdialog" - : "echo"}; -#endif -} - -inline std::string internal::dialog::buttons_to_name(choice _choice) { - switch (_choice) { - case choice::ok_cancel: - return "okcancel"; - case choice::yes_no: - return "yesno"; - case choice::yes_no_cancel: - return "yesnocancel"; - case choice::retry_cancel: - return "retrycancel"; - case choice::abort_retry_ignore: - return "abortretryignore"; - /* case choice::ok: */ default: - return "ok"; - } -} - -inline std::string internal::dialog::get_icon_name(icon _icon) { - switch (_icon) { - case icon::warning: - return "warning"; - case icon::error: - return "error"; - case icon::question: - return "question"; - // Zenity wants "information" but WinForms wants "info" - /* case icon::info: */ default: -#if _WIN32 - return "info"; -#else - return "information"; -#endif - } -} - -// This is only used for debugging purposes -inline std::ostream& operator<<(std::ostream& s, std::vector const& v) { - int not_first = 0; - for (auto& e : v) s << (not_first++ ? " " : "") << e; - return s; -} - -// Properly quote a string for Powershell: replace ' or " with '' or "" -// FIXME: we should probably get rid of newlines! -// FIXME: the \" sequence seems unsafe, too! -// XXX: this is no longer used but I would like to keep it around just in case -inline std::string internal::dialog::powershell_quote(std::string const& str) const { - return "'" + std::regex_replace(str, std::regex("['\"]"), "$&$&") + "'"; -} - -// Properly quote a string for osascript: replace \ or " with \\ or \" -// XXX: this also used to replace ' with \' when popen was used, but it would be -// smarter to do shell_quote(osascript_quote(...)) if this is needed again. -inline std::string internal::dialog::osascript_quote(std::string const& str) const { - return "\"" + std::regex_replace(str, std::regex("[\\\\\"]"), "\\$&") + "\""; -} - -// Properly quote a string for the shell: just replace ' with '\'' -// XXX: this is no longer used but I would like to keep it around just in case -inline std::string internal::dialog::shell_quote(std::string const& str) const { - return "'" + std::regex_replace(str, std::regex("'"), "'\\''") + "'"; -} - -// file_dialog implementation - -inline internal::file_dialog::file_dialog(type in_type, std::string const& title, - std::string const& default_path /* = "" */, - std::vector const& filters /* = {} */, - opt options /* = opt::none */) { -#if _WIN32 - std::string filter_list; - std::regex whitespace(" *"); - for (size_t i = 0; i + 1 < filters.size(); i += 2) { - filter_list += filters[i] + '\0'; - filter_list += std::regex_replace(filters[i + 1], whitespace, ";") + '\0'; - } - filter_list += '\0'; - - m_async->start_func( - [this, in_type, title, default_path, filter_list, options](int* exit_code) -> std::string { - (void) exit_code; - m_wtitle = internal::str2wstr(title); - m_wdefault_path = internal::str2wstr(default_path); - auto wfilter_list = internal::str2wstr(filter_list); - - // Initialise COM. This is required for the new folder selection window, - // (see https://github.com/samhocevar/portable-file-dialogs/pull/21) - // and to avoid random crashes with GetOpenFileNameW() (see - // https://github.com/samhocevar/portable-file-dialogs/issues/51) - ole32_dll ole32; - - // Folder selection uses a different method - if (in_type == type::folder) { -#if PFD_HAS_IFILEDIALOG - if (flags(flag::is_vista)) { - // On Vista and higher we should be able to use IFileDialog for folder selection - IFileDialog* ifd; - HRESULT hr = dll::proc( - ole32, "CoCreateInstance")(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&ifd)); - - // In case CoCreateInstance fails (which it should not), try legacy approach - if (SUCCEEDED(hr)) return select_folder_vista(ifd, options & opt::force_path); - } -#endif - - BROWSEINFOW bi; - memset(&bi, 0, sizeof(bi)); - - bi.lpfn = &bffcallback; - bi.lParam = (LPARAM) this; - - if (flags(flag::is_vista)) { - if (ole32.is_initialized()) bi.ulFlags |= BIF_NEWDIALOGSTYLE; - bi.ulFlags |= BIF_EDITBOX; - bi.ulFlags |= BIF_STATUSTEXT; - } - - auto* list = SHBrowseForFolderW(&bi); - std::string ret; - if (list) { - auto buffer = new wchar_t[MAX_PATH]; - SHGetPathFromIDListW(list, buffer); - dll::proc(ole32, "CoTaskMemFree")(list); - ret = internal::wstr2str(buffer); - delete[] buffer; - } - return ret; - } - - OPENFILENAMEW ofn; - memset(&ofn, 0, sizeof(ofn)); - ofn.lStructSize = sizeof(OPENFILENAMEW); - ofn.hwndOwner = GetActiveWindow(); - - ofn.lpstrFilter = wfilter_list.c_str(); - - auto woutput = std::wstring(MAX_PATH * 256, L'\0'); - ofn.lpstrFile = (LPWSTR) woutput.data(); - ofn.nMaxFile = (DWORD) woutput.size(); - if (!m_wdefault_path.empty()) { - // If a directory was provided, use it as the initial directory. If - // a valid path was provided, use it as the initial file. Otherwise, - // let the Windows API decide. - auto path_attr = GetFileAttributesW(m_wdefault_path.c_str()); - if (path_attr != INVALID_FILE_ATTRIBUTES && (path_attr & FILE_ATTRIBUTE_DIRECTORY)) - ofn.lpstrInitialDir = m_wdefault_path.c_str(); - else if (m_wdefault_path.size() <= woutput.size()) - // second argument is size of buffer, not length of string - StringCchCopyW(ofn.lpstrFile, MAX_PATH * 256 + 1, m_wdefault_path.c_str()); - else { - ofn.lpstrFileTitle = (LPWSTR) m_wdefault_path.data(); - ofn.nMaxFileTitle = (DWORD) m_wdefault_path.size(); - } - } - ofn.lpstrTitle = m_wtitle.c_str(); - ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER; - - dll comdlg32("comdlg32.dll"); - - // Apply new visual style (required for windows XP) - new_style_context ctx; - - if (in_type == type::save) { - if (!(options & opt::force_overwrite)) ofn.Flags |= OFN_OVERWRITEPROMPT; - - dll::proc get_save_file_name(comdlg32, "GetSaveFileNameW"); - if (get_save_file_name(&ofn) == 0) return ""; - return internal::wstr2str(woutput.c_str()); - } else { - if (options & opt::multiselect) ofn.Flags |= OFN_ALLOWMULTISELECT; - ofn.Flags |= OFN_PATHMUSTEXIST; - - dll::proc get_open_file_name(comdlg32, "GetOpenFileNameW"); - if (get_open_file_name(&ofn) == 0) return ""; - } - - std::string prefix; - for (wchar_t const* p = woutput.c_str(); *p;) { - auto filename = internal::wstr2str(p); - p += wcslen(p); - // In multiselect mode, we advance p one wchar further and - // check for another filename. If there is one and the - // prefix is empty, it means we just read the prefix. - if ((options & opt::multiselect) && *++p && prefix.empty()) { - prefix = filename + "/"; - continue; - } - - m_vector_result.push_back(prefix + filename); - } - - return ""; - }); -#elif __EMSCRIPTEN__ - // FIXME: do something - (void) in_type; - (void) title; - (void) default_path; - (void) filters; - (void) options; -#else - auto command = desktop_helper(); - - if (is_osascript()) { - std::string script = "set ret to choose"; - switch (in_type) { - case type::save: - script += " file name"; - break; - case type::open: - default: - script += " file"; - if (options & opt::multiselect) script += " with multiple selections allowed"; - break; - case type::folder: - script += " folder"; - break; - } - - if (default_path.size()) { - if (in_type == type::folder || is_directory(default_path)) - script += " default location "; - else - script += " default name "; - script += osascript_quote(default_path); - } - - script += " with prompt " + osascript_quote(title); - - if (in_type == type::open) { - // Concatenate all user-provided filter patterns - std::string patterns; - for (size_t i = 0; i < filters.size() / 2; ++i) patterns += " " + filters[2 * i + 1]; - - // Split the pattern list to check whether "*" is in there; if it - // is, we have to disable filters because there is no mechanism in - // OS X for the user to override the filter. - std::regex sep("\\s+"); - std::string filter_list; - bool has_filter = true; - std::sregex_token_iterator iter(patterns.begin(), patterns.end(), sep, -1); - std::sregex_token_iterator end; - for (; iter != end; ++iter) { - auto pat = iter->str(); - if (pat == "*" || pat == "*.*") - has_filter = false; - else if (internal::starts_with(pat, "*.")) - filter_list += "," + osascript_quote(pat.substr(2, pat.size() - 2)); - } - - if (has_filter && filter_list.size() > 0) { - // There is a weird AppleScript bug where file extensions of length != 3 are - // ignored, e.g. type{"txt"} works, but type{"json"} does not. Fortunately if - // the whole list starts with a 3-character extension, everything works again. - // We use "///" for such an extension because we are sure it cannot appear in - // an actual filename. - script += " of type {\"///\"" + filter_list + "}"; - } - } - - if (in_type == type::open && (options & opt::multiselect)) { - script += "\nset s to \"\""; - script += "\nrepeat with i in ret"; - script += "\n set s to s & (POSIX path of i) & \"\\n\""; - script += "\nend repeat"; - script += "\ncopy s to stdout"; - } else { - script += "\nPOSIX path of ret"; - } - - command.push_back("-e"); - command.push_back(script); - } else if (is_zenity()) { - command.push_back("--file-selection"); - - // If the default path is a directory, make sure it ends with "/" otherwise zenity will - // open the file dialog in the parent directory. - auto filename_arg = "--filename=" + default_path; - if (in_type != type::folder && !ends_with(default_path, "/") && - internal::is_directory(default_path)) - filename_arg += "/"; - command.push_back(filename_arg); - - command.push_back("--title"); - command.push_back(title); - command.push_back("--separator=\n"); - - for (size_t i = 0; i < filters.size() / 2; ++i) { - command.push_back("--file-filter"); - command.push_back(filters[2 * i] + "|" + filters[2 * i + 1]); - } - - if (in_type == type::save) command.push_back("--save"); - if (in_type == type::folder) command.push_back("--directory"); - if (!(options & opt::force_overwrite)) command.push_back("--confirm-overwrite"); - if (options & opt::multiselect) command.push_back("--multiple"); - } else if (is_kdialog()) { - switch (in_type) { - case type::save: - command.push_back("--getsavefilename"); - break; - case type::open: - command.push_back("--getopenfilename"); - break; - case type::folder: - command.push_back("--getexistingdirectory"); - break; - } - if (options & opt::multiselect) { - command.push_back("--multiple"); - command.push_back("--separate-output"); - } - - command.push_back(default_path); - - std::string filter; - for (size_t i = 0; i < filters.size() / 2; ++i) - filter += (i == 0 ? "" : " | ") + filters[2 * i] + "(" + filters[2 * i + 1] + ")"; - command.push_back(filter); - - command.push_back("--title"); - command.push_back(title); - } - - if (flags(flag::is_verbose)) std::cerr << "pfd: " << command << std::endl; - - m_async->start_process(command); -#endif -} - -inline std::string internal::file_dialog::string_result() { -#if _WIN32 - return m_async->result(); -#else - auto ret = m_async->result(); - // Strip potential trailing newline (zenity). Also strip trailing slash - // added by osascript for consistency with other backends. - while (!ret.empty() && (ret.back() == '\n' || ret.back() == '/')) ret.pop_back(); - return ret; -#endif -} - -inline std::vector internal::file_dialog::vector_result() { -#if _WIN32 - m_async->result(); - return m_vector_result; -#else - std::vector ret; - auto result = m_async->result(); - for (;;) { - // Split result along newline characters - auto i = result.find('\n'); - if (i == 0 || i == std::string::npos) break; - ret.push_back(result.substr(0, i)); - result = result.substr(i + 1, result.size()); - } - return ret; -#endif -} - -#if _WIN32 -// Use a static function to pass as BFFCALLBACK for legacy folder select -inline int CALLBACK internal::file_dialog::bffcallback(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData) { - auto inst = (file_dialog*) pData; - switch (uMsg) { - case BFFM_INITIALIZED: - SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, (LPARAM) inst->m_wdefault_path.c_str()); - break; - } - return 0; -} - -#if PFD_HAS_IFILEDIALOG -inline std::string internal::file_dialog::select_folder_vista(IFileDialog* ifd, bool force_path) { - std::string result; - - IShellItem* folder; - - // Load library at runtime so app doesn't link it at load time (which will fail on windows XP) - dll shell32("shell32.dll"); - dll::proc create_item( - shell32, "SHCreateItemFromParsingName"); - - if (!create_item) return ""; - - auto hr = create_item(m_wdefault_path.c_str(), nullptr, IID_PPV_ARGS(&folder)); - - // Set default folder if found. This only sets the default folder. If - // Windows has any info about the most recently selected folder, it - // will display it instead. Generally, calling SetFolder() to set the - // current directory “is not a good or expected user experience and - // should therefore be avoided”: - // https://docs.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setfolder - if (SUCCEEDED(hr)) { - if (force_path) - ifd->SetFolder(folder); - else - ifd->SetDefaultFolder(folder); - folder->Release(); - } - - // Set the dialog title and option to select folders - ifd->SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM); - ifd->SetTitle(m_wtitle.c_str()); - - hr = ifd->Show(GetActiveWindow()); - if (SUCCEEDED(hr)) { - IShellItem* item; - hr = ifd->GetResult(&item); - if (SUCCEEDED(hr)) { - wchar_t* wname = nullptr; - // This is unlikely to fail because we use FOS_FORCEFILESYSTEM, but try - // to output a debug message just in case. - if (SUCCEEDED(item->GetDisplayName(SIGDN_FILESYSPATH, &wname))) { - result = internal::wstr2str(std::wstring(wname)); - dll::proc(ole32_dll(), "CoTaskMemFree")(wname); - } else { - if (SUCCEEDED(item->GetDisplayName(SIGDN_NORMALDISPLAY, &wname))) { - auto name = internal::wstr2str(std::wstring(wname)); - dll::proc(ole32_dll(), "CoTaskMemFree")(wname); - std::cerr << "pfd: failed to get path for " << name << std::endl; - } else - std::cerr << "pfd: item of unknown type selected" << std::endl; - } - - item->Release(); - } - } - - ifd->Release(); - - return result; -} -#endif -#endif - -// notify implementation - -inline notify::notify(std::string const& title, std::string const& message, - icon _icon /* = icon::info */) { - if (_icon == icon::question) // Not supported by notifications - _icon = icon::info; - -#if _WIN32 - // Use a static shared pointer for notify_icon so that we can delete - // it whenever we need to display a new one, and we can also wait - // until the program has finished running. - struct notify_icon_data : public NOTIFYICONDATAW { - ~notify_icon_data() { Shell_NotifyIconW(NIM_DELETE, this); } - }; - - static std::shared_ptr nid; - - // Release the previous notification icon, if any, and allocate a new - // one. Note that std::make_shared() does value initialization, so there - // is no need to memset the structure. - nid = nullptr; - nid = std::make_shared(); - - // For XP support - nid->cbSize = NOTIFYICONDATAW_V2_SIZE; - nid->hWnd = nullptr; - nid->uID = 0; - - // Flag Description: - // - NIF_ICON The hIcon member is valid. - // - NIF_MESSAGE The uCallbackMessage member is valid. - // - NIF_TIP The szTip member is valid. - // - NIF_STATE The dwState and dwStateMask members are valid. - // - NIF_INFO Use a balloon ToolTip instead of a standard ToolTip. The szInfo, uTimeout, - // szInfoTitle, and dwInfoFlags members are valid. - // - NIF_GUID Reserved. - nid->uFlags = NIF_MESSAGE | NIF_ICON | NIF_INFO; - - // Flag Description - // - NIIF_ERROR An error icon. - // - NIIF_INFO An information icon. - // - NIIF_NONE No icon. - // - NIIF_WARNING A warning icon. - // - NIIF_ICON_MASK Version 6.0. Reserved. - // - NIIF_NOSOUND Version 6.0. Do not play the associated sound. Applies only to balloon - // ToolTips - switch (_icon) { - case icon::warning: - nid->dwInfoFlags = NIIF_WARNING; - break; - case icon::error: - nid->dwInfoFlags = NIIF_ERROR; - break; - /* case icon::info: */ default: - nid->dwInfoFlags = NIIF_INFO; - break; - } - - ENUMRESNAMEPROC icon_enum_callback = [](HMODULE, LPCTSTR, LPTSTR lpName, - LONG_PTR lParam) -> BOOL { - ((NOTIFYICONDATAW*) lParam)->hIcon = ::LoadIcon(GetModuleHandle(nullptr), lpName); - return false; - }; - - nid->hIcon = ::LoadIcon(nullptr, IDI_APPLICATION); - ::EnumResourceNames(nullptr, RT_GROUP_ICON, icon_enum_callback, (LONG_PTR) nid.get()); - - nid->uTimeout = 5000; - - StringCchCopyW(nid->szInfoTitle, ARRAYSIZE(nid->szInfoTitle), internal::str2wstr(title).c_str()); - StringCchCopyW(nid->szInfo, ARRAYSIZE(nid->szInfo), internal::str2wstr(message).c_str()); - - // Display the new icon - Shell_NotifyIconW(NIM_ADD, nid.get()); -#elif __EMSCRIPTEN__ - // FIXME: do something - (void) title; - (void) message; -#else - auto command = desktop_helper(); - - if (is_osascript()) { - command.push_back("-e"); - command.push_back("display notification " + osascript_quote(message) + " with title " + - osascript_quote(title)); - } else if (is_zenity()) { - command.push_back("--notification"); - command.push_back("--window-icon"); - command.push_back(get_icon_name(_icon)); - command.push_back("--text"); - command.push_back(title + "\n" + message); - } else if (is_kdialog()) { - command.push_back("--icon"); - command.push_back(get_icon_name(_icon)); - command.push_back("--title"); - command.push_back(title); - command.push_back("--passivepopup"); - command.push_back(message); - command.push_back("5"); - } - - if (flags(flag::is_verbose)) std::cerr << "pfd: " << command << std::endl; - - m_async->start_process(command); -#endif -} - -// message implementation - -inline message::message(std::string const& title, std::string const& text, - choice _choice /* = choice::ok_cancel */, icon _icon /* = icon::info */) { -#if _WIN32 - // Use MB_SYSTEMMODAL rather than MB_TOPMOST to ensure the message window is brought - // to front. See https://github.com/samhocevar/portable-file-dialogs/issues/52 - UINT style = MB_SYSTEMMODAL; - switch (_icon) { - case icon::warning: - style |= MB_ICONWARNING; - break; - case icon::error: - style |= MB_ICONERROR; - break; - case icon::question: - style |= MB_ICONQUESTION; - break; - /* case icon::info: */ default: - style |= MB_ICONINFORMATION; - break; - } - - switch (_choice) { - case choice::ok_cancel: - style |= MB_OKCANCEL; - break; - case choice::yes_no: - style |= MB_YESNO; - break; - case choice::yes_no_cancel: - style |= MB_YESNOCANCEL; - break; - case choice::retry_cancel: - style |= MB_RETRYCANCEL; - break; - case choice::abort_retry_ignore: - style |= MB_ABORTRETRYIGNORE; - break; - /* case choice::ok: */ default: - style |= MB_OK; - break; - } - - m_mappings[IDCANCEL] = button::cancel; - m_mappings[IDOK] = button::ok; - m_mappings[IDYES] = button::yes; - m_mappings[IDNO] = button::no; - m_mappings[IDABORT] = button::abort; - m_mappings[IDRETRY] = button::retry; - m_mappings[IDIGNORE] = button::ignore; - - m_async->start_func([text, title, style](int* exit_code) -> std::string { - auto wtext = internal::str2wstr(text); - auto wtitle = internal::str2wstr(title); - // Apply new visual style (required for all Windows versions) - new_style_context ctx; - *exit_code = MessageBoxW(GetActiveWindow(), wtext.c_str(), wtitle.c_str(), style); - return ""; - }); - -#elif __EMSCRIPTEN__ - std::string full_message; - switch (_icon) { - case icon::warning: - full_message = "⚠️"; - break; - case icon::error: - full_message = "⛔"; - break; - case icon::question: - full_message = "❓"; - break; - /* case icon::info: */ default: - full_message = "ℹ"; - break; - } - - full_message += ' ' + title + "\n\n" + text; - - // This does not really start an async task; it just passes the - // EM_ASM_INT return value to a fake start() function. - m_async->start(EM_ASM_INT( - { - if ($1) return window.confirm(UTF8ToString($0)) ? 0 : -1; - alert(UTF8ToString($0)); - return 0; - }, - full_message.c_str(), _choice == choice::ok_cancel)); -#else - auto command = desktop_helper(); - - if (is_osascript()) { - std::string script = - "display dialog " + osascript_quote(text) + " with title " + osascript_quote(title); - auto if_cancel = button::cancel; - switch (_choice) { - case choice::ok_cancel: - script += - "buttons {\"OK\", \"Cancel\"}" - " default button \"OK\"" - " cancel button \"Cancel\""; - break; - case choice::yes_no: - script += - "buttons {\"Yes\", \"No\"}" - " default button \"Yes\"" - " cancel button \"No\""; - if_cancel = button::no; - break; - case choice::yes_no_cancel: - script += - "buttons {\"Yes\", \"No\", \"Cancel\"}" - " default button \"Yes\"" - " cancel button \"Cancel\""; - break; - case choice::retry_cancel: - script += - "buttons {\"Retry\", \"Cancel\"}" - " default button \"Retry\"" - " cancel button \"Cancel\""; - break; - case choice::abort_retry_ignore: - script += - "buttons {\"Abort\", \"Retry\", \"Ignore\"}" - " default button \"Abort\"" - " cancel button \"Retry\""; - if_cancel = button::retry; - break; - case choice::ok: - default: - script += - "buttons {\"OK\"}" - " default button \"OK\"" - " cancel button \"OK\""; - if_cancel = button::ok; - break; - } - m_mappings[1] = if_cancel; - m_mappings[256] = if_cancel; // XXX: I think this was never correct - script += " with icon "; - switch (_icon) { -#define PFD_OSX_ICON(n) \ - "alias ((path to library folder from system domain) as text " \ - "& \"CoreServices:CoreTypes.bundle:Contents:Resources:" n ".icns\")" - case icon::info: - default: - script += PFD_OSX_ICON("ToolBarInfo"); - break; - case icon::warning: - script += "caution"; - break; - case icon::error: - script += "stop"; - break; - case icon::question: - script += PFD_OSX_ICON("GenericQuestionMarkIcon"); - break; -#undef PFD_OSX_ICON - } - - command.push_back("-e"); - command.push_back(script); - } else if (is_zenity()) { - switch (_choice) { - case choice::ok_cancel: - command.insert(command.end(), {"--question", "--cancel-label=Cancel", "--ok-label=OK"}); - break; - case choice::yes_no: - // Do not use standard --question because it causes “No” to return -1, - // which is inconsistent with the “Yes/No/Cancel” mode below. - command.insert(command.end(), - {"--question", "--switch", "--extra-button=No", "--extra-button=Yes"}); - break; - case choice::yes_no_cancel: - command.insert(command.end(), {"--question", "--switch", "--extra-button=Cancel", - "--extra-button=No", "--extra-button=Yes"}); - break; - case choice::retry_cancel: - command.insert(command.end(), - {"--question", "--switch", "--extra-button=Cancel", "--extra-button=Retry"}); - break; - case choice::abort_retry_ignore: - command.insert(command.end(), {"--question", "--switch", "--extra-button=Ignore", - "--extra-button=Abort", "--extra-button=Retry"}); - break; - case choice::ok: - default: - switch (_icon) { - case icon::error: - command.push_back("--error"); - break; - case icon::warning: - command.push_back("--warning"); - break; - default: - command.push_back("--info"); - break; - } - } - - command.insert(command.end(), - {"--title", title, "--width=300", "--height=0", // sensible defaults - "--no-markup", // do not interpret text as Pango markup - "--text", text, "--icon-name=dialog-" + get_icon_name(_icon)}); - } else if (is_kdialog()) { - if (_choice == choice::ok) { - switch (_icon) { - case icon::error: - command.push_back("--error"); - break; - case icon::warning: - command.push_back("--sorry"); - break; - default: - command.push_back("--msgbox"); - break; - } - } else { - std::string flag = "--"; - if (_icon == icon::warning || _icon == icon::error) flag += "warning"; - flag += "yesno"; - if (_choice == choice::yes_no_cancel) flag += "cancel"; - command.push_back(flag); - if (_choice == choice::yes_no || _choice == choice::yes_no_cancel) { - m_mappings[0] = button::yes; - m_mappings[256] = button::no; - } - } - - command.push_back(text); - command.push_back("--title"); - command.push_back(title); - - // Must be after the above part - if (_choice == choice::ok_cancel) - command.insert(command.end(), {"--yes-label", "OK", "--no-label", "Cancel"}); - } - - if (flags(flag::is_verbose)) std::cerr << "pfd: " << command << std::endl; - - m_async->start_process(command); -#endif -} - -inline button message::result() { - int exit_code; - auto ret = m_async->result(&exit_code); - // osascript will say "button returned:Cancel\n" - // and others will just say "Cancel\n" - if (internal::ends_with(ret, "Cancel\n")) return button::cancel; - if (internal::ends_with(ret, "OK\n")) return button::ok; - if (internal::ends_with(ret, "Yes\n")) return button::yes; - if (internal::ends_with(ret, "No\n")) return button::no; - if (internal::ends_with(ret, "Abort\n")) return button::abort; - if (internal::ends_with(ret, "Retry\n")) return button::retry; - if (internal::ends_with(ret, "Ignore\n")) return button::ignore; - if (m_mappings.count(exit_code) != 0) return m_mappings[exit_code]; - return exit_code == 0 ? button::ok : button::cancel; -} - -// open_file implementation - -inline open_file::open_file(std::string const& title, std::string const& default_path /* = "" */, - std::vector const& filters /* = { "All Files", "*" } */, - opt options /* = opt::none */) - : file_dialog(type::open, title, default_path, filters, options) {} - -inline open_file::open_file(std::string const& title, std::string const& default_path, - std::vector const& filters, bool allow_multiselect) - : open_file(title, default_path, filters, (allow_multiselect ? opt::multiselect : opt::none)) {} - -inline std::vector open_file::result() { - return vector_result(); -} - -// save_file implementation - -inline save_file::save_file(std::string const& title, std::string const& default_path /* = "" */, - std::vector const& filters /* = { "All Files", "*" } */, - opt options /* = opt::none */) - : file_dialog(type::save, title, default_path, filters, options) {} - -inline save_file::save_file(std::string const& title, std::string const& default_path, - std::vector const& filters, bool confirm_overwrite) - : save_file(title, default_path, filters, - (confirm_overwrite ? opt::none : opt::force_overwrite)) {} - -inline std::string save_file::result() { - return string_result(); -} - -// select_folder implementation - -inline select_folder::select_folder(std::string const& title, - std::string const& default_path /* = "" */, - opt options /* = opt::none */) - : file_dialog(type::folder, title, default_path, {}, options) {} - -inline std::string select_folder::result() { - return string_result(); -} - -#endif // PFD_SKIP_IMPLEMENTATION - -} // namespace pfd diff --git a/src/ThirdParty/Dialogs/Dialogs.h b/src/ThirdParty/Dialogs/Dialogs.h new file mode 100644 index 0000000..f632026 --- /dev/null +++ b/src/ThirdParty/Dialogs/Dialogs.h @@ -0,0 +1,1738 @@ +// +// Portable File Dialogs +// +// Copyright © 2018–2022 Sam Hocevar +// +// This library is free software. It comes without any warranty, to +// the extent permitted by applicable law. You can redistribute it +// and/or modify it under the terms of the Do What the Fuck You Want +// to Public License, Version 2, as published by the WTFPL Task Force. +// See http://www.wtfpl.net/ for more details. +// + +#pragma once + +#if _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#include +#include +#include // IFileDialog +#include +#include // GetUserProfileDirectory() +#include +#include // std::async + +#elif __EMSCRIPTEN__ +#include + +#else +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 2 // for popen() +#endif +#ifdef __APPLE__ +#ifndef _DARWIN_C_SOURCE +#define _DARWIN_C_SOURCE +#endif +#endif +#include // fcntl() +#include // getpwnam() +#include // stat() +#include // waitpid() +#include // read(), pipe(), dup2(), getuid() +#include // ::kill, std::signal +#include // popen() +#include // std::getenv() +#endif + +#include // std::chrono +#include // std::ostream +#include // std::map +#include // std::shared_ptr +#include // std::regex +#include // std::set +#include // std::string +#include // std::mutex, std::this_thread + +// Versions of mingw64 g++ up to 9.3.0 do not have a complete IFileDialog +#ifndef PFD_HAS_IFILEDIALOG +#define PFD_HAS_IFILEDIALOG 1 +#if (defined __MINGW64__ || defined __MINGW32__) && defined __GXX_ABI_VERSION +#if __GXX_ABI_VERSION <= 1013 +#undef PFD_HAS_IFILEDIALOG +#define PFD_HAS_IFILEDIALOG 0 +#endif +#endif +#endif + +namespace pfd { + +enum class button { + cancel = -1, + ok, + yes, + no, + abort, + retry, + ignore, +}; + +enum class choice { + ok = 0, + ok_cancel, + yes_no, + yes_no_cancel, + retry_cancel, + abort_retry_ignore, +}; + +enum class icon { + info = 0, + warning, + error, + question, +}; + +// Additional option flags for various dialog constructors +enum class opt : uint8_t { + none = 0, + // For file open, allow multiselect. + multiselect = 0x1, + // For file save, force overwrite and disable the confirmation dialog. + force_overwrite = 0x2, + // For folder select, force path to be the provided argument instead + // of the last opened directory, which is the Microsoft-recommended, + // user-friendly behaviour. + force_path = 0x4, +}; + +inline opt operator|(opt a, opt b) { + return opt(uint8_t(a) | uint8_t(b)); +} +inline bool operator&(opt a, opt b) { + return bool(uint8_t(a) & uint8_t(b)); +} + +// The settings class, only exposing to the user a way to set verbose mode +// and to force a rescan of installed desktop helpers (zenity, kdialog…). +class settings { + public: + static bool available(); + + static void verbose(bool value); + static void rescan(); + + protected: + explicit settings(bool resync = false); + + bool check_program(std::string const& program); + + inline bool is_osascript() const; + inline bool is_zenity() const; + inline bool is_kdialog() const; + + enum class flag { + is_scanned = 0, + is_verbose, + + has_zenity, + has_matedialog, + has_qarma, + has_kdialog, + is_vista, + + max_flag, + }; + + // Static array of flags for internal state + bool const& flags(flag in_flag) const; + + // Non-const getter for the static array of flags + bool& flags(flag in_flag); +}; + +// Internal classes, not to be used by client applications +namespace internal { + + // Process wait timeout, in milliseconds + static int const default_wait_timeout = 20; + + class executor { + friend class dialog; + + public: + // High level function to get the result of a command + std::string result(int* exit_code = nullptr); + + // High level function to abort + bool kill(); + +#if _WIN32 + void start_func(std::function const& fun); + static BOOL CALLBACK enum_windows_callback(HWND hwnd, LPARAM lParam); +#elif __EMSCRIPTEN__ + void start(int exit_code); +#else + void start_process(std::vector const& command); +#endif + + ~executor(); + + protected: + bool ready(int timeout = default_wait_timeout); + void stop(); + + private: + bool m_running = false; + std::string m_stdout; + int m_exit_code = -1; +#if _WIN32 + std::future m_future; + std::set m_windows; + std::condition_variable m_cond; + std::mutex m_mutex; + DWORD m_tid; +#elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something +#else + pid_t m_pid = 0; + int m_fd = -1; +#endif + }; + + class platform { + protected: +#if _WIN32 + // Helper class around LoadLibraryA() and GetProcAddress() with some safety + class dll { + public: + dll(std::string const& name); + ~dll(); + + template + class proc { + public: + proc(dll const& lib, std::string const& sym) + : m_proc(reinterpret_cast((void*) ::GetProcAddress(lib.handle, sym.c_str()))) {} + + explicit operator bool() const { return m_proc != nullptr; } + operator T*() const { return m_proc; } + + private: + T* m_proc; + }; + + private: + HMODULE handle; + }; + + // Helper class around CoInitialize() and CoUnInitialize() + class ole32_dll : public dll { + public: + ole32_dll(); + ~ole32_dll(); + bool is_initialized(); + + private: + HRESULT m_state; + }; + + // Helper class around CreateActCtx() and ActivateActCtx() + class new_style_context { + public: + new_style_context(); + ~new_style_context(); + + private: + HANDLE create(); + ULONG_PTR m_cookie = 0; + }; +#endif + }; + + class dialog : protected settings, protected platform { + public: + bool ready(int timeout = default_wait_timeout) const; + bool kill() const; + + protected: + explicit dialog(); + + std::vector desktop_helper() const; + static std::string buttons_to_name(choice _choice); + static std::string get_icon_name(icon _icon); + + std::string powershell_quote(std::string const& str) const; + std::string osascript_quote(std::string const& str) const; + std::string shell_quote(std::string const& str) const; + + // Keep handle to executing command + std::shared_ptr m_async; + }; + + class file_dialog : public dialog { + protected: + enum type { + open, + save, + folder, + }; + + file_dialog(type in_type, std::string const& title, std::string const& default_path = "", + std::vector const& filters = {}, opt options = opt::none); + + protected: + std::string string_result(); + std::vector vector_result(); + +#if _WIN32 + static int CALLBACK bffcallback(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData); +#if PFD_HAS_IFILEDIALOG + std::string select_folder_vista(IFileDialog* ifd, bool force_path); +#endif + + std::wstring m_wtitle; + std::wstring m_wdefault_path; + + std::vector m_vector_result; +#endif + }; + +} // namespace internal + +// +// The path class provides some platform-specific path constants +// + +class path : protected internal::platform { + public: + static std::string home(); + static std::string separator(); +}; + +// +// The notify widget +// + +class notify : public internal::dialog { + public: + notify(std::string const& title, std::string const& message, icon _icon = icon::info); +}; + +// +// The message widget +// + +class message : public internal::dialog { + public: + message(std::string const& title, std::string const& text, choice _choice = choice::ok_cancel, + icon _icon = icon::info); + + button result(); + + private: + // Some extra logic to map the exit code to button number + std::map m_mappings; +}; + +// +// The open_file, save_file, and open_folder widgets +// + +class open_file : public internal::file_dialog { + public: + open_file(std::string const& title, std::string const& default_path = "", + std::vector const& filters = {"All Files", "*"}, opt options = opt::none); + +#if defined(__has_cpp_attribute) +#if __has_cpp_attribute(deprecated) + // Backwards compatibility + [[deprecated("Use pfd::opt::multiselect instead of allow_multiselect")]] +#endif +#endif + open_file(std::string const& title, std::string const& default_path, + std::vector const& filters, bool allow_multiselect); + + std::vector result(); +}; + +class save_file : public internal::file_dialog { + public: + save_file(std::string const& title, std::string const& default_path = "", + std::vector const& filters = {"All Files", "*"}, opt options = opt::none); + +#if defined(__has_cpp_attribute) +#if __has_cpp_attribute(deprecated) + // Backwards compatibility + [[deprecated("Use pfd::opt::force_overwrite instead of confirm_overwrite")]] +#endif +#endif + save_file(std::string const& title, std::string const& default_path, + std::vector const& filters, bool confirm_overwrite); + + std::string result(); +}; + +class select_folder : public internal::file_dialog { + public: + select_folder(std::string const& title, std::string const& default_path = "", + opt options = opt::none); + + std::string result(); +}; + +// +// Below this are all the method implementations. You may choose to define the +// macro PFD_SKIP_IMPLEMENTATION everywhere before including this header except +// in one place. This may reduce compilation times. +// + +#if !defined PFD_SKIP_IMPLEMENTATION + +// internal free functions implementations + +namespace internal { + +#if _WIN32 + static inline std::wstring str2wstr(std::string const& str) { + int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int) str.size(), nullptr, 0); + std::wstring ret(len, '\0'); + MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int) str.size(), (LPWSTR) ret.data(), + (int) ret.size()); + return ret; + } + + static inline std::string wstr2str(std::wstring const& str) { + int len = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int) str.size(), nullptr, 0, nullptr, + nullptr); + std::string ret(len, '\0'); + WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int) str.size(), (LPSTR) ret.data(), + (int) ret.size(), nullptr, nullptr); + return ret; + } + + static inline bool is_vista() { + OSVERSIONINFOEXW osvi; + memset(&osvi, 0, sizeof(osvi)); + DWORDLONG const mask = VerSetConditionMask( + VerSetConditionMask(VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL), + VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + osvi.dwOSVersionInfoSize = sizeof(osvi); + osvi.dwMajorVersion = HIBYTE(_WIN32_WINNT_VISTA); + osvi.dwMinorVersion = LOBYTE(_WIN32_WINNT_VISTA); + osvi.wServicePackMajor = 0; + + return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, + mask) != FALSE; + } +#endif + + // This is necessary until C++20 which will have std::string::ends_with() etc. + + static inline bool ends_with(std::string const& str, std::string const& suffix) { + return suffix.size() <= str.size() && + str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; + } + + static inline bool starts_with(std::string const& str, std::string const& prefix) { + return prefix.size() <= str.size() && str.compare(0, prefix.size(), prefix) == 0; + } + + // This is necessary until C++17 which will have std::filesystem::is_directory + + static inline bool is_directory(std::string const& path) { +#if _WIN32 + auto attr = GetFileAttributesA(path.c_str()); + return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY); +#elif __EMSCRIPTEN__ + // TODO + return false; +#else + struct stat s; + return stat(path.c_str(), &s) == 0 && S_ISDIR(s.st_mode); +#endif + } + + // This is necessary because getenv is not thread-safe + + static inline std::string getenv(std::string const& str) { +#if _MSC_VER + char* buf = nullptr; + size_t size = 0; + if (_dupenv_s(&buf, &size, str.c_str()) == 0 && buf) { + std::string ret(buf); + free(buf); + return ret; + } + return ""; +#else + auto buf = std::getenv(str.c_str()); + return buf ? buf : ""; +#endif + } + +} // namespace internal + +// settings implementation + +inline settings::settings(bool resync) { + flags(flag::is_scanned) &= !resync; + + if (flags(flag::is_scanned)) return; + + auto pfd_verbose = internal::getenv("PFD_VERBOSE"); + auto match_no = std::regex("(|0|no|false)", std::regex_constants::icase); + if (!std::regex_match(pfd_verbose, match_no)) flags(flag::is_verbose) = true; + +#if _WIN32 + flags(flag::is_vista) = internal::is_vista(); +#elif !__APPLE__ + flags(flag::has_zenity) = check_program("zenity"); + flags(flag::has_matedialog) = check_program("matedialog"); + flags(flag::has_qarma) = check_program("qarma"); + flags(flag::has_kdialog) = check_program("kdialog"); + + // If multiple helpers are available, try to default to the best one + if (flags(flag::has_zenity) && flags(flag::has_kdialog)) { + auto desktop_name = internal::getenv("XDG_SESSION_DESKTOP"); + if (desktop_name == std::string("gnome")) + flags(flag::has_kdialog) = false; + else if (desktop_name == std::string("KDE")) + flags(flag::has_zenity) = false; + } +#endif + + flags(flag::is_scanned) = true; +} + +inline bool settings::available() { +#if _WIN32 + return true; +#elif __APPLE__ + return true; +#elif __EMSCRIPTEN__ + // FIXME: Return true after implementation is complete. + return false; +#else + settings tmp; + return tmp.flags(flag::has_zenity) || tmp.flags(flag::has_matedialog) || + tmp.flags(flag::has_qarma) || tmp.flags(flag::has_kdialog); +#endif +} + +inline void settings::verbose(bool value) { + settings().flags(flag::is_verbose) = value; +} + +inline void settings::rescan() { + settings(/* resync = */ true); +} + +// Check whether a program is present using “which”. +inline bool settings::check_program(std::string const& program) { +#if _WIN32 + (void) program; + return false; +#elif __EMSCRIPTEN__ + (void) program; + return false; +#else + int exit_code = -1; + internal::executor async; + async.start_process({"/bin/sh", "-c", "which " + program}); + async.result(&exit_code); + return exit_code == 0; +#endif +} + +inline bool settings::is_osascript() const { +#if __APPLE__ + return true; +#else + return false; +#endif +} + +inline bool settings::is_zenity() const { + return flags(flag::has_zenity) || flags(flag::has_matedialog) || flags(flag::has_qarma); +} + +inline bool settings::is_kdialog() const { + return flags(flag::has_kdialog); +} + +inline bool const& settings::flags(flag in_flag) const { + static bool flags[size_t(flag::max_flag)]; + return flags[size_t(in_flag)]; +} + +inline bool& settings::flags(flag in_flag) { + return const_cast(static_cast(this)->flags(in_flag)); +} + +// path implementation +inline std::string path::home() { +#if _WIN32 + // First try the USERPROFILE environment variable + auto user_profile = internal::getenv("USERPROFILE"); + if (user_profile.size() > 0) return user_profile; + // Otherwise, try GetUserProfileDirectory() + HANDLE token = nullptr; + DWORD len = MAX_PATH; + char buf[MAX_PATH] = {'\0'}; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { + dll userenv("userenv.dll"); + dll::proc get_user_profile_directory( + userenv, "GetUserProfileDirectoryA"); + get_user_profile_directory(token, buf, &len); + CloseHandle(token); + if (*buf) return buf; + } +#elif __EMSCRIPTEN__ + return "/"; +#else + // First try the HOME environment variable + auto home = internal::getenv("HOME"); + if (home.size() > 0) return home; + // Otherwise, try getpwuid_r() + size_t len = 4096; +#if defined(_SC_GETPW_R_SIZE_MAX) + auto size_max = sysconf(_SC_GETPW_R_SIZE_MAX); + if (size_max != -1) len = size_t(size_max); +#endif + std::vector buf(len); + struct passwd pwd, *result; + if (getpwuid_r(getuid(), &pwd, buf.data(), buf.size(), &result) == 0) return result->pw_dir; +#endif + return "/"; +} + +inline std::string path::separator() { +#if _WIN32 + return "\\"; +#else + return "/"; +#endif +} + +// executor implementation + +inline std::string internal::executor::result(int* exit_code /* = nullptr */) { + stop(); + if (exit_code) *exit_code = m_exit_code; + return m_stdout; +} + +inline bool internal::executor::kill() { +#if _WIN32 + if (m_future.valid()) { + // Close all windows that weren’t open when we started the future + auto previous_windows = m_windows; + EnumWindows(&enum_windows_callback, (LPARAM) this); + for (auto hwnd : m_windows) + if (previous_windows.find(hwnd) == previous_windows.end()) { + SendMessage(hwnd, WM_CLOSE, 0, 0); + // Also send IDNO in case of a Yes/No or Abort/Retry/Ignore messagebox + SendMessage(hwnd, WM_COMMAND, IDNO, 0); + } + } +#elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something + return false; // cannot kill +#else + ::kill(m_pid, SIGKILL); +#endif + stop(); + return true; +} + +#if _WIN32 +inline BOOL CALLBACK internal::executor::enum_windows_callback(HWND hwnd, LPARAM lParam) { + auto that = (executor*) lParam; + + DWORD pid; + auto tid = GetWindowThreadProcessId(hwnd, &pid); + if (tid == that->m_tid) that->m_windows.insert(hwnd); + return TRUE; +} +#endif + +#if _WIN32 +inline void internal::executor::start_func(std::function const& fun) { + stop(); + + auto trampoline = [fun, this]() { + // Save our thread id so that the caller can cancel us + m_tid = GetCurrentThreadId(); + EnumWindows(&enum_windows_callback, (LPARAM) this); + m_cond.notify_all(); + return fun(&m_exit_code); + }; + + std::unique_lock lock(m_mutex); + m_future = std::async(std::launch::async, trampoline); + m_cond.wait(lock); + m_running = true; +} + +#elif __EMSCRIPTEN__ +inline void internal::executor::start(int exit_code) { + m_exit_code = exit_code; +} + +#else +inline void internal::executor::start_process(std::vector const& command) { + stop(); + m_stdout.clear(); + m_exit_code = -1; + + int in[2], out[2]; + if (pipe(in) != 0 || pipe(out) != 0) return; + + m_pid = fork(); + if (m_pid < 0) return; + + close(in[m_pid ? 0 : 1]); + close(out[m_pid ? 1 : 0]); + + if (m_pid == 0) { + dup2(in[0], STDIN_FILENO); + dup2(out[1], STDOUT_FILENO); + + // Ignore stderr so that it doesn’t pollute the console (e.g. GTK+ errors from zenity) + int fd = open("/src/null", O_WRONLY); + dup2(fd, STDERR_FILENO); + close(fd); + + std::vector args; + std::transform(command.cbegin(), command.cend(), std::back_inserter(args), + [](std::string const& s) { return const_cast(s.c_str()); }); + args.push_back(nullptr); // null-terminate argv[] + + execvp(args[0], args.data()); + exit(1); + } + + close(in[1]); + m_fd = out[0]; + auto flags = fcntl(m_fd, F_GETFL); + fcntl(m_fd, F_SETFL, flags | O_NONBLOCK); + + m_running = true; +} +#endif + +inline internal::executor::~executor() { + stop(); +} + +inline bool internal::executor::ready(int timeout /* = default_wait_timeout */) { + if (!m_running) return true; + +#if _WIN32 + if (m_future.valid()) { + auto status = m_future.wait_for(std::chrono::milliseconds(timeout)); + if (status != std::future_status::ready) { + // On Windows, we need to run the message pump. If the async + // thread uses a Windows API dialog, it may be attached to the + // main thread and waiting for messages that only we can dispatch. + MSG msg; + while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return false; + } + + m_stdout = m_future.get(); + } +#elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something + (void) timeout; +#else + char buf[BUFSIZ]; + ssize_t received = read(m_fd, buf, BUFSIZ); // Flawfinder: ignore + if (received > 0) { + m_stdout += std::string(buf, received); + return false; + } + + // Reap child process if it is dead. It is possible that the system has already reaped it + // (this happens when the calling application handles or ignores SIG_CHLD) and results in + // waitpid() failing with ECHILD. Otherwise we assume the child is running and we sleep for + // a little while. + int status; + pid_t child = waitpid(m_pid, &status, WNOHANG); + if (child != m_pid && (child >= 0 || errno != ECHILD)) { + // FIXME: this happens almost always at first iteration + std::this_thread::sleep_for(std::chrono::milliseconds(timeout)); + return false; + } + + close(m_fd); + m_exit_code = WEXITSTATUS(status); +#endif + + m_running = false; + return true; +} + +inline void internal::executor::stop() { + // Loop until the user closes the dialog + while (!ready()); +} + +// dll implementation + +#if _WIN32 +inline internal::platform::dll::dll(std::string const& name) + : handle(::LoadLibraryA(name.c_str())) {} + +inline internal::platform::dll::~dll() { + if (handle) ::FreeLibrary(handle); +} +#endif // _WIN32 + +// ole32_dll implementation + +#if _WIN32 +inline internal::platform::ole32_dll::ole32_dll() : dll("ole32.dll") { + // Use COINIT_MULTITHREADED because COINIT_APARTMENTTHREADED causes crashes. + // See https://github.com/samhocevar/portable-file-dialogs/issues/51 + auto coinit = proc(*this, "CoInitializeEx"); + m_state = coinit(nullptr, COINIT_MULTITHREADED); +} + +inline internal::platform::ole32_dll::~ole32_dll() { + if (is_initialized()) proc(*this, "CoUninitialize")(); +} + +inline bool internal::platform::ole32_dll::is_initialized() { + return m_state == S_OK || m_state == S_FALSE; +} +#endif + +// new_style_context implementation + +#if _WIN32 +inline internal::platform::new_style_context::new_style_context() { + // Only create one activation context for the whole app lifetime. + static HANDLE hctx = create(); + + if (hctx != INVALID_HANDLE_VALUE) ActivateActCtx(hctx, &m_cookie); +} + +inline internal::platform::new_style_context::~new_style_context() { + DeactivateActCtx(0, m_cookie); +} + +inline HANDLE internal::platform::new_style_context::create() { + // This “hack” seems to be necessary for this code to work on windows XP. + // Without it, dialogs do not show and close immediately. GetError() + // returns 0 so I don’t know what causes this. I was not able to reproduce + // this behavior on Windows 7 and 10 but just in case, let it be here for + // those versions too. + // This hack is not required if other dialogs are used (they load comdlg32 + // automatically), only if message boxes are used. + dll comdlg32("comdlg32.dll"); + + // Using approach as shown here: https://stackoverflow.com/a/10444161 + UINT len = ::GetSystemDirectoryA(nullptr, 0); + std::string sys_dir(len, '\0'); + ::GetSystemDirectoryA(&sys_dir[0], len); + + ACTCTXA act_ctx = { + // Do not set flag ACTCTX_FLAG_SET_PROCESS_DEFAULT, since it causes a + // crash with error “default context is already set”. + sizeof(act_ctx), + ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID, + "shell32.dll", + 0, + 0, + sys_dir.c_str(), + (LPCSTR) 124, + nullptr, + 0, + }; + + return ::CreateActCtxA(&act_ctx); +} +#endif // _WIN32 + +// dialog implementation + +inline bool internal::dialog::ready(int timeout /* = default_wait_timeout */) const { + return m_async->ready(timeout); +} + +inline bool internal::dialog::kill() const { + return m_async->kill(); +} + +inline internal::dialog::dialog() : m_async(std::make_shared()) {} + +inline std::vector internal::dialog::desktop_helper() const { +#if __APPLE__ + return {"osascript"}; +#else + return {flags(flag::has_zenity) ? "zenity" + : flags(flag::has_matedialog) ? "matedialog" + : flags(flag::has_qarma) ? "qarma" + : flags(flag::has_kdialog) ? "kdialog" + : "echo"}; +#endif +} + +inline std::string internal::dialog::buttons_to_name(choice _choice) { + switch (_choice) { + case choice::ok_cancel: + return "okcancel"; + case choice::yes_no: + return "yesno"; + case choice::yes_no_cancel: + return "yesnocancel"; + case choice::retry_cancel: + return "retrycancel"; + case choice::abort_retry_ignore: + return "abortretryignore"; + /* case choice::ok: */ default: + return "ok"; + } +} + +inline std::string internal::dialog::get_icon_name(icon _icon) { + switch (_icon) { + case icon::warning: + return "warning"; + case icon::error: + return "error"; + case icon::question: + return "question"; + // Zenity wants "information" but WinForms wants "info" + /* case icon::info: */ default: +#if _WIN32 + return "info"; +#else + return "information"; +#endif + } +} + +// This is only used for debugging purposes +inline std::ostream& operator<<(std::ostream& s, std::vector const& v) { + int not_first = 0; + for (auto& e : v) s << (not_first++ ? " " : "") << e; + return s; +} + +// Properly quote a string for Powershell: replace ' or " with '' or "" +// FIXME: we should probably get rid of newlines! +// FIXME: the \" sequence seems unsafe, too! +// XXX: this is no longer used but I would like to keep it around just in case +inline std::string internal::dialog::powershell_quote(std::string const& str) const { + return "'" + std::regex_replace(str, std::regex("['\"]"), "$&$&") + "'"; +} + +// Properly quote a string for osascript: replace \ or " with \\ or \" +// XXX: this also used to replace ' with \' when popen was used, but it would be +// smarter to do shell_quote(osascript_quote(...)) if this is needed again. +inline std::string internal::dialog::osascript_quote(std::string const& str) const { + return "\"" + std::regex_replace(str, std::regex("[\\\\\"]"), "\\$&") + "\""; +} + +// Properly quote a string for the shell: just replace ' with '\'' +// XXX: this is no longer used but I would like to keep it around just in case +inline std::string internal::dialog::shell_quote(std::string const& str) const { + return "'" + std::regex_replace(str, std::regex("'"), "'\\''") + "'"; +} + +// file_dialog implementation + +inline internal::file_dialog::file_dialog(type in_type, std::string const& title, + std::string const& default_path /* = "" */, + std::vector const& filters /* = {} */, + opt options /* = opt::none */) { +#if _WIN32 + std::string filter_list; + std::regex whitespace(" *"); + for (size_t i = 0; i + 1 < filters.size(); i += 2) { + filter_list += filters[i] + '\0'; + filter_list += std::regex_replace(filters[i + 1], whitespace, ";") + '\0'; + } + filter_list += '\0'; + + m_async->start_func( + [this, in_type, title, default_path, filter_list, options](int* exit_code) -> std::string { + (void) exit_code; + m_wtitle = internal::str2wstr(title); + m_wdefault_path = internal::str2wstr(default_path); + auto wfilter_list = internal::str2wstr(filter_list); + + // Initialise COM. This is required for the new folder selection window, + // (see https://github.com/samhocevar/portable-file-dialogs/pull/21) + // and to avoid random crashes with GetOpenFileNameW() (see + // https://github.com/samhocevar/portable-file-dialogs/issues/51) + ole32_dll ole32; + + // Folder selection uses a different method + if (in_type == type::folder) { +#if PFD_HAS_IFILEDIALOG + if (flags(flag::is_vista)) { + // On Vista and higher we should be able to use IFileDialog for folder selection + IFileDialog* ifd; + HRESULT hr = dll::proc( + ole32, "CoCreateInstance")(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&ifd)); + + // In case CoCreateInstance fails (which it should not), try legacy approach + if (SUCCEEDED(hr)) return select_folder_vista(ifd, options & opt::force_path); + } +#endif + + BROWSEINFOW bi; + memset(&bi, 0, sizeof(bi)); + + bi.lpfn = &bffcallback; + bi.lParam = (LPARAM) this; + + if (flags(flag::is_vista)) { + if (ole32.is_initialized()) bi.ulFlags |= BIF_NEWDIALOGSTYLE; + bi.ulFlags |= BIF_EDITBOX; + bi.ulFlags |= BIF_STATUSTEXT; + } + + auto* list = SHBrowseForFolderW(&bi); + std::string ret; + if (list) { + auto buffer = new wchar_t[MAX_PATH]; + SHGetPathFromIDListW(list, buffer); + dll::proc(ole32, "CoTaskMemFree")(list); + ret = internal::wstr2str(buffer); + delete[] buffer; + } + return ret; + } + + OPENFILENAMEW ofn; + memset(&ofn, 0, sizeof(ofn)); + ofn.lStructSize = sizeof(OPENFILENAMEW); + ofn.hwndOwner = GetActiveWindow(); + + ofn.lpstrFilter = wfilter_list.c_str(); + + auto woutput = std::wstring(MAX_PATH * 256, L'\0'); + ofn.lpstrFile = (LPWSTR) woutput.data(); + ofn.nMaxFile = (DWORD) woutput.size(); + if (!m_wdefault_path.empty()) { + // If a directory was provided, use it as the initial directory. If + // a valid path was provided, use it as the initial file. Otherwise, + // let the Windows API decide. + auto path_attr = GetFileAttributesW(m_wdefault_path.c_str()); + if (path_attr != INVALID_FILE_ATTRIBUTES && (path_attr & FILE_ATTRIBUTE_DIRECTORY)) + ofn.lpstrInitialDir = m_wdefault_path.c_str(); + else if (m_wdefault_path.size() <= woutput.size()) + // second argument is size of buffer, not length of string + StringCchCopyW(ofn.lpstrFile, MAX_PATH * 256 + 1, m_wdefault_path.c_str()); + else { + ofn.lpstrFileTitle = (LPWSTR) m_wdefault_path.data(); + ofn.nMaxFileTitle = (DWORD) m_wdefault_path.size(); + } + } + ofn.lpstrTitle = m_wtitle.c_str(); + ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER; + + dll comdlg32("comdlg32.dll"); + + // Apply new visual style (required for windows XP) + new_style_context ctx; + + if (in_type == type::save) { + if (!(options & opt::force_overwrite)) ofn.Flags |= OFN_OVERWRITEPROMPT; + + dll::proc get_save_file_name(comdlg32, "GetSaveFileNameW"); + if (get_save_file_name(&ofn) == 0) return ""; + return internal::wstr2str(woutput.c_str()); + } else { + if (options & opt::multiselect) ofn.Flags |= OFN_ALLOWMULTISELECT; + ofn.Flags |= OFN_PATHMUSTEXIST; + + dll::proc get_open_file_name(comdlg32, "GetOpenFileNameW"); + if (get_open_file_name(&ofn) == 0) return ""; + } + + std::string prefix; + for (wchar_t const* p = woutput.c_str(); *p;) { + auto filename = internal::wstr2str(p); + p += wcslen(p); + // In multiselect mode, we advance p one wchar further and + // check for another filename. If there is one and the + // prefix is empty, it means we just read the prefix. + if ((options & opt::multiselect) && *++p && prefix.empty()) { + prefix = filename + "/"; + continue; + } + + m_vector_result.push_back(prefix + filename); + } + + return ""; + }); +#elif __EMSCRIPTEN__ + // FIXME: do something + (void) in_type; + (void) title; + (void) default_path; + (void) filters; + (void) options; +#else + auto command = desktop_helper(); + + if (is_osascript()) { + std::string script = "set ret to choose"; + switch (in_type) { + case type::save: + script += " file name"; + break; + case type::open: + default: + script += " file"; + if (options & opt::multiselect) script += " with multiple selections allowed"; + break; + case type::folder: + script += " folder"; + break; + } + + if (default_path.size()) { + if (in_type == type::folder || is_directory(default_path)) + script += " default location "; + else + script += " default name "; + script += osascript_quote(default_path); + } + + script += " with prompt " + osascript_quote(title); + + if (in_type == type::open) { + // Concatenate all user-provided filter patterns + std::string patterns; + for (size_t i = 0; i < filters.size() / 2; ++i) patterns += " " + filters[2 * i + 1]; + + // Split the pattern list to check whether "*" is in there; if it + // is, we have to disable filters because there is no mechanism in + // OS X for the user to override the filter. + std::regex sep("\\s+"); + std::string filter_list; + bool has_filter = true; + std::sregex_token_iterator iter(patterns.begin(), patterns.end(), sep, -1); + std::sregex_token_iterator end; + for (; iter != end; ++iter) { + auto pat = iter->str(); + if (pat == "*" || pat == "*.*") + has_filter = false; + else if (internal::starts_with(pat, "*.")) + filter_list += "," + osascript_quote(pat.substr(2, pat.size() - 2)); + } + + if (has_filter && filter_list.size() > 0) { + // There is a weird AppleScript bug where file extensions of length != 3 are + // ignored, e.g. type{"txt"} works, but type{"json"} does not. Fortunately if + // the whole list starts with a 3-character extension, everything works again. + // We use "///" for such an extension because we are sure it cannot appear in + // an actual filename. + script += " of type {\"///\"" + filter_list + "}"; + } + } + + if (in_type == type::open && (options & opt::multiselect)) { + script += "\nset s to \"\""; + script += "\nrepeat with i in ret"; + script += "\n set s to s & (POSIX path of i) & \"\\n\""; + script += "\nend repeat"; + script += "\ncopy s to stdout"; + } else { + script += "\nPOSIX path of ret"; + } + + command.push_back("-e"); + command.push_back(script); + } else if (is_zenity()) { + command.push_back("--file-selection"); + + // If the default path is a directory, make sure it ends with "/" otherwise zenity will + // open the file dialog in the parent directory. + auto filename_arg = "--filename=" + default_path; + if (in_type != type::folder && !ends_with(default_path, "/") && + internal::is_directory(default_path)) + filename_arg += "/"; + command.push_back(filename_arg); + + command.push_back("--title"); + command.push_back(title); + command.push_back("--separator=\n"); + + for (size_t i = 0; i < filters.size() / 2; ++i) { + command.push_back("--file-filter"); + command.push_back(filters[2 * i] + "|" + filters[2 * i + 1]); + } + + if (in_type == type::save) command.push_back("--save"); + if (in_type == type::folder) command.push_back("--directory"); + if (!(options & opt::force_overwrite)) command.push_back("--confirm-overwrite"); + if (options & opt::multiselect) command.push_back("--multiple"); + } else if (is_kdialog()) { + switch (in_type) { + case type::save: + command.push_back("--getsavefilename"); + break; + case type::open: + command.push_back("--getopenfilename"); + break; + case type::folder: + command.push_back("--getexistingdirectory"); + break; + } + if (options & opt::multiselect) { + command.push_back("--multiple"); + command.push_back("--separate-output"); + } + + command.push_back(default_path); + + std::string filter; + for (size_t i = 0; i < filters.size() / 2; ++i) + filter += (i == 0 ? "" : " | ") + filters[2 * i] + "(" + filters[2 * i + 1] + ")"; + command.push_back(filter); + + command.push_back("--title"); + command.push_back(title); + } + + if (flags(flag::is_verbose)) std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); +#endif +} + +inline std::string internal::file_dialog::string_result() { +#if _WIN32 + return m_async->result(); +#else + auto ret = m_async->result(); + // Strip potential trailing newline (zenity). Also strip trailing slash + // added by osascript for consistency with other backends. + while (!ret.empty() && (ret.back() == '\n' || ret.back() == '/')) ret.pop_back(); + return ret; +#endif +} + +inline std::vector internal::file_dialog::vector_result() { +#if _WIN32 + m_async->result(); + return m_vector_result; +#else + std::vector ret; + auto result = m_async->result(); + for (;;) { + // Split result along newline characters + auto i = result.find('\n'); + if (i == 0 || i == std::string::npos) break; + ret.push_back(result.substr(0, i)); + result = result.substr(i + 1, result.size()); + } + return ret; +#endif +} + +#if _WIN32 +// Use a static function to pass as BFFCALLBACK for legacy folder select +inline int CALLBACK internal::file_dialog::bffcallback(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData) { + auto inst = (file_dialog*) pData; + switch (uMsg) { + case BFFM_INITIALIZED: + SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, (LPARAM) inst->m_wdefault_path.c_str()); + break; + } + return 0; +} + +#if PFD_HAS_IFILEDIALOG +inline std::string internal::file_dialog::select_folder_vista(IFileDialog* ifd, bool force_path) { + std::string result; + + IShellItem* folder; + + // Load library at runtime so app doesn't link it at load time (which will fail on windows XP) + dll shell32("shell32.dll"); + dll::proc create_item( + shell32, "SHCreateItemFromParsingName"); + + if (!create_item) return ""; + + auto hr = create_item(m_wdefault_path.c_str(), nullptr, IID_PPV_ARGS(&folder)); + + // Set default folder if found. This only sets the default folder. If + // Windows has any info about the most recently selected folder, it + // will display it instead. Generally, calling SetFolder() to set the + // current directory “is not a good or expected user experience and + // should therefore be avoided”: + // https://docs.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setfolder + if (SUCCEEDED(hr)) { + if (force_path) + ifd->SetFolder(folder); + else + ifd->SetDefaultFolder(folder); + folder->Release(); + } + + // Set the dialog title and option to select folders + ifd->SetOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM); + ifd->SetTitle(m_wtitle.c_str()); + + hr = ifd->Show(GetActiveWindow()); + if (SUCCEEDED(hr)) { + IShellItem* item; + hr = ifd->GetResult(&item); + if (SUCCEEDED(hr)) { + wchar_t* wname = nullptr; + // This is unlikely to fail because we use FOS_FORCEFILESYSTEM, but try + // to output a debug message just in case. + if (SUCCEEDED(item->GetDisplayName(SIGDN_FILESYSPATH, &wname))) { + result = internal::wstr2str(std::wstring(wname)); + dll::proc(ole32_dll(), "CoTaskMemFree")(wname); + } else { + if (SUCCEEDED(item->GetDisplayName(SIGDN_NORMALDISPLAY, &wname))) { + auto name = internal::wstr2str(std::wstring(wname)); + dll::proc(ole32_dll(), "CoTaskMemFree")(wname); + std::cerr << "pfd: failed to get path for " << name << std::endl; + } else + std::cerr << "pfd: item of unknown type selected" << std::endl; + } + + item->Release(); + } + } + + ifd->Release(); + + return result; +} +#endif +#endif + +// notify implementation + +inline notify::notify(std::string const& title, std::string const& message, + icon _icon /* = icon::info */) { + if (_icon == icon::question) // Not supported by notifications + _icon = icon::info; + +#if _WIN32 + // Use a static shared pointer for notify_icon so that we can delete + // it whenever we need to display a new one, and we can also wait + // until the program has finished running. + struct notify_icon_data : public NOTIFYICONDATAW { + ~notify_icon_data() { Shell_NotifyIconW(NIM_DELETE, this); } + }; + + static std::shared_ptr nid; + + // Release the previous notification icon, if any, and allocate a new + // one. Note that std::make_shared() does value initialization, so there + // is no need to memset the structure. + nid = nullptr; + nid = std::make_shared(); + + // For XP support + nid->cbSize = NOTIFYICONDATAW_V2_SIZE; + nid->hWnd = nullptr; + nid->uID = 0; + + // Flag Description: + // - NIF_ICON The hIcon member is valid. + // - NIF_MESSAGE The uCallbackMessage member is valid. + // - NIF_TIP The szTip member is valid. + // - NIF_STATE The dwState and dwStateMask members are valid. + // - NIF_INFO Use a balloon ToolTip instead of a standard ToolTip. The szInfo, uTimeout, + // szInfoTitle, and dwInfoFlags members are valid. + // - NIF_GUID Reserved. + nid->uFlags = NIF_MESSAGE | NIF_ICON | NIF_INFO; + + // Flag Description + // - NIIF_ERROR An error icon. + // - NIIF_INFO An information icon. + // - NIIF_NONE No icon. + // - NIIF_WARNING A warning icon. + // - NIIF_ICON_MASK Version 6.0. Reserved. + // - NIIF_NOSOUND Version 6.0. Do not play the associated sound. Applies only to balloon + // ToolTips + switch (_icon) { + case icon::warning: + nid->dwInfoFlags = NIIF_WARNING; + break; + case icon::error: + nid->dwInfoFlags = NIIF_ERROR; + break; + /* case icon::info: */ default: + nid->dwInfoFlags = NIIF_INFO; + break; + } + + ENUMRESNAMEPROC icon_enum_callback = [](HMODULE, LPCTSTR, LPTSTR lpName, + LONG_PTR lParam) -> BOOL { + ((NOTIFYICONDATAW*) lParam)->hIcon = ::LoadIcon(GetModuleHandle(nullptr), lpName); + return false; + }; + + nid->hIcon = ::LoadIcon(nullptr, IDI_APPLICATION); + ::EnumResourceNames(nullptr, RT_GROUP_ICON, icon_enum_callback, (LONG_PTR) nid.get()); + + nid->uTimeout = 5000; + + StringCchCopyW(nid->szInfoTitle, ARRAYSIZE(nid->szInfoTitle), internal::str2wstr(title).c_str()); + StringCchCopyW(nid->szInfo, ARRAYSIZE(nid->szInfo), internal::str2wstr(message).c_str()); + + // Display the new icon + Shell_NotifyIconW(NIM_ADD, nid.get()); +#elif __EMSCRIPTEN__ + // FIXME: do something + (void) title; + (void) message; +#else + auto command = desktop_helper(); + + if (is_osascript()) { + command.push_back("-e"); + command.push_back("display notification " + osascript_quote(message) + " with title " + + osascript_quote(title)); + } else if (is_zenity()) { + command.push_back("--notification"); + command.push_back("--window-icon"); + command.push_back(get_icon_name(_icon)); + command.push_back("--text"); + command.push_back(title + "\n" + message); + } else if (is_kdialog()) { + command.push_back("--icon"); + command.push_back(get_icon_name(_icon)); + command.push_back("--title"); + command.push_back(title); + command.push_back("--passivepopup"); + command.push_back(message); + command.push_back("5"); + } + + if (flags(flag::is_verbose)) std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); +#endif +} + +// message implementation + +inline message::message(std::string const& title, std::string const& text, + choice _choice /* = choice::ok_cancel */, icon _icon /* = icon::info */) { +#if _WIN32 + // Use MB_SYSTEMMODAL rather than MB_TOPMOST to ensure the message window is brought + // to front. See https://github.com/samhocevar/portable-file-dialogs/issues/52 + UINT style = MB_SYSTEMMODAL; + switch (_icon) { + case icon::warning: + style |= MB_ICONWARNING; + break; + case icon::error: + style |= MB_ICONERROR; + break; + case icon::question: + style |= MB_ICONQUESTION; + break; + /* case icon::info: */ default: + style |= MB_ICONINFORMATION; + break; + } + + switch (_choice) { + case choice::ok_cancel: + style |= MB_OKCANCEL; + break; + case choice::yes_no: + style |= MB_YESNO; + break; + case choice::yes_no_cancel: + style |= MB_YESNOCANCEL; + break; + case choice::retry_cancel: + style |= MB_RETRYCANCEL; + break; + case choice::abort_retry_ignore: + style |= MB_ABORTRETRYIGNORE; + break; + /* case choice::ok: */ default: + style |= MB_OK; + break; + } + + m_mappings[IDCANCEL] = button::cancel; + m_mappings[IDOK] = button::ok; + m_mappings[IDYES] = button::yes; + m_mappings[IDNO] = button::no; + m_mappings[IDABORT] = button::abort; + m_mappings[IDRETRY] = button::retry; + m_mappings[IDIGNORE] = button::ignore; + + m_async->start_func([text, title, style](int* exit_code) -> std::string { + auto wtext = internal::str2wstr(text); + auto wtitle = internal::str2wstr(title); + // Apply new visual style (required for all Windows versions) + new_style_context ctx; + *exit_code = MessageBoxW(GetActiveWindow(), wtext.c_str(), wtitle.c_str(), style); + return ""; + }); + +#elif __EMSCRIPTEN__ + std::string full_message; + switch (_icon) { + case icon::warning: + full_message = "⚠️"; + break; + case icon::error: + full_message = "⛔"; + break; + case icon::question: + full_message = "❓"; + break; + /* case icon::info: */ default: + full_message = "ℹ"; + break; + } + + full_message += ' ' + title + "\n\n" + text; + + // This does not really start an async task; it just passes the + // EM_ASM_INT return value to a fake start() function. + m_async->start(EM_ASM_INT( + { + if ($1) return window.confirm(UTF8ToString($0)) ? 0 : -1; + alert(UTF8ToString($0)); + return 0; + }, + full_message.c_str(), _choice == choice::ok_cancel)); +#else + auto command = desktop_helper(); + + if (is_osascript()) { + std::string script = + "display dialog " + osascript_quote(text) + " with title " + osascript_quote(title); + auto if_cancel = button::cancel; + switch (_choice) { + case choice::ok_cancel: + script += + "buttons {\"OK\", \"Cancel\"}" + " default button \"OK\"" + " cancel button \"Cancel\""; + break; + case choice::yes_no: + script += + "buttons {\"Yes\", \"No\"}" + " default button \"Yes\"" + " cancel button \"No\""; + if_cancel = button::no; + break; + case choice::yes_no_cancel: + script += + "buttons {\"Yes\", \"No\", \"Cancel\"}" + " default button \"Yes\"" + " cancel button \"Cancel\""; + break; + case choice::retry_cancel: + script += + "buttons {\"Retry\", \"Cancel\"}" + " default button \"Retry\"" + " cancel button \"Cancel\""; + break; + case choice::abort_retry_ignore: + script += + "buttons {\"Abort\", \"Retry\", \"Ignore\"}" + " default button \"Abort\"" + " cancel button \"Retry\""; + if_cancel = button::retry; + break; + case choice::ok: + default: + script += + "buttons {\"OK\"}" + " default button \"OK\"" + " cancel button \"OK\""; + if_cancel = button::ok; + break; + } + m_mappings[1] = if_cancel; + m_mappings[256] = if_cancel; // XXX: I think this was never correct + script += " with icon "; + switch (_icon) { +#define PFD_OSX_ICON(n) \ + "alias ((path to library folder from system domain) as text " \ + "& \"CoreServices:CoreTypes.bundle:Contents:Resources:" n ".icns\")" + case icon::info: + default: + script += PFD_OSX_ICON("ToolBarInfo"); + break; + case icon::warning: + script += "caution"; + break; + case icon::error: + script += "stop"; + break; + case icon::question: + script += PFD_OSX_ICON("GenericQuestionMarkIcon"); + break; +#undef PFD_OSX_ICON + } + + command.push_back("-e"); + command.push_back(script); + } else if (is_zenity()) { + switch (_choice) { + case choice::ok_cancel: + command.insert(command.end(), {"--question", "--cancel-label=Cancel", "--ok-label=OK"}); + break; + case choice::yes_no: + // Do not use standard --question because it causes “No” to return -1, + // which is inconsistent with the “Yes/No/Cancel” mode below. + command.insert(command.end(), + {"--question", "--switch", "--extra-button=No", "--extra-button=Yes"}); + break; + case choice::yes_no_cancel: + command.insert(command.end(), {"--question", "--switch", "--extra-button=Cancel", + "--extra-button=No", "--extra-button=Yes"}); + break; + case choice::retry_cancel: + command.insert(command.end(), + {"--question", "--switch", "--extra-button=Cancel", "--extra-button=Retry"}); + break; + case choice::abort_retry_ignore: + command.insert(command.end(), {"--question", "--switch", "--extra-button=Ignore", + "--extra-button=Abort", "--extra-button=Retry"}); + break; + case choice::ok: + default: + switch (_icon) { + case icon::error: + command.push_back("--error"); + break; + case icon::warning: + command.push_back("--warning"); + break; + default: + command.push_back("--info"); + break; + } + } + + command.insert(command.end(), + {"--title", title, "--width=300", "--height=0", // sensible defaults + "--no-markup", // do not interpret text as Pango markup + "--text", text, "--icon-name=dialog-" + get_icon_name(_icon)}); + } else if (is_kdialog()) { + if (_choice == choice::ok) { + switch (_icon) { + case icon::error: + command.push_back("--error"); + break; + case icon::warning: + command.push_back("--sorry"); + break; + default: + command.push_back("--msgbox"); + break; + } + } else { + std::string flag = "--"; + if (_icon == icon::warning || _icon == icon::error) flag += "warning"; + flag += "yesno"; + if (_choice == choice::yes_no_cancel) flag += "cancel"; + command.push_back(flag); + if (_choice == choice::yes_no || _choice == choice::yes_no_cancel) { + m_mappings[0] = button::yes; + m_mappings[256] = button::no; + } + } + + command.push_back(text); + command.push_back("--title"); + command.push_back(title); + + // Must be after the above part + if (_choice == choice::ok_cancel) + command.insert(command.end(), {"--yes-label", "OK", "--no-label", "Cancel"}); + } + + if (flags(flag::is_verbose)) std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); +#endif +} + +inline button message::result() { + int exit_code; + auto ret = m_async->result(&exit_code); + // osascript will say "button returned:Cancel\n" + // and others will just say "Cancel\n" + if (internal::ends_with(ret, "Cancel\n")) return button::cancel; + if (internal::ends_with(ret, "OK\n")) return button::ok; + if (internal::ends_with(ret, "Yes\n")) return button::yes; + if (internal::ends_with(ret, "No\n")) return button::no; + if (internal::ends_with(ret, "Abort\n")) return button::abort; + if (internal::ends_with(ret, "Retry\n")) return button::retry; + if (internal::ends_with(ret, "Ignore\n")) return button::ignore; + if (m_mappings.count(exit_code) != 0) return m_mappings[exit_code]; + return exit_code == 0 ? button::ok : button::cancel; +} + +// open_file implementation + +inline open_file::open_file(std::string const& title, std::string const& default_path /* = "" */, + std::vector const& filters /* = { "All Files", "*" } */, + opt options /* = opt::none */) + : file_dialog(type::open, title, default_path, filters, options) {} + +inline open_file::open_file(std::string const& title, std::string const& default_path, + std::vector const& filters, bool allow_multiselect) + : open_file(title, default_path, filters, (allow_multiselect ? opt::multiselect : opt::none)) {} + +inline std::vector open_file::result() { + return vector_result(); +} + +// save_file implementation + +inline save_file::save_file(std::string const& title, std::string const& default_path /* = "" */, + std::vector const& filters /* = { "All Files", "*" } */, + opt options /* = opt::none */) + : file_dialog(type::save, title, default_path, filters, options) {} + +inline save_file::save_file(std::string const& title, std::string const& default_path, + std::vector const& filters, bool confirm_overwrite) + : save_file(title, default_path, filters, + (confirm_overwrite ? opt::none : opt::force_overwrite)) {} + +inline std::string save_file::result() { + return string_result(); +} + +// select_folder implementation + +inline select_folder::select_folder(std::string const& title, + std::string const& default_path /* = "" */, + opt options /* = opt::none */) + : file_dialog(type::folder, title, default_path, {}, options) {} + +inline std::string select_folder::result() { + return string_result(); +} + +#endif // PFD_SKIP_IMPLEMENTATION + +} // namespace pfd diff --git a/src/Tools/asm.cc b/src/Tools/asm.cc index e07bc32..90b104c 100644 --- a/src/Tools/asm.cc +++ b/src/Tools/asm.cc @@ -7,7 +7,7 @@ /// @file asm.cc /// @brief Assembler frontend. -#include +#include #include #include diff --git a/src/Tools/cppdrv.cc b/src/Tools/cppdrv.cc index 566c0c5..05ffcec 100644 --- a/src/Tools/cppdrv.cc +++ b/src/Tools/cppdrv.cc @@ -7,7 +7,7 @@ /// @file cxxdrv.cc /// @brief NeCTI frontend preprocessor. -#include +#include #include #include #include diff --git a/src/Tools/dbg.cc b/src/Tools/dbg.cc index 25deacb..b8c4932 100644 --- a/src/Tools/dbg.cc +++ b/src/Tools/dbg.cc @@ -4,7 +4,7 @@ ======================================== */ -#include +#include /// @file dbg.cc /// @brief NeCTI debugger. diff --git a/src/Tools/kdbg.cc b/src/Tools/kdbg.cc index 1811f56..ca82002 100644 --- a/src/Tools/kdbg.cc +++ b/src/Tools/kdbg.cc @@ -4,7 +4,7 @@ ======================================== */ -#include +#include /// @file kdbg.cc /// @brief NeKernel debugger. diff --git a/src/Tools/ld64.cc b/src/Tools/ld64.cc index 6955870..a2ce544 100644 --- a/src/Tools/ld64.cc +++ b/src/Tools/ld64.cc @@ -4,7 +4,7 @@ ======================================== */ -#include +#include /// @file ld64.cc /// @brief NeCTI linker for AE objects. diff --git a/src/Tools/pef-amd64-cxxdrv.cc b/src/Tools/pef-amd64-cxxdrv.cc index 7060d86..9c29a88 100644 --- a/src/Tools/pef-amd64-cxxdrv.cc +++ b/src/Tools/pef-amd64-cxxdrv.cc @@ -7,10 +7,10 @@ /// @file cxxdrv.cc /// @brief NeCTI C++ frontend compiler. -#include +#include #include -#include -#include +#include +#include #ifdef __APPLE__ static auto kPath = "/usr/local/lib/libCompilerKit.dylib"; diff --git a/src/Tools/pef-arm64-cdrv.cc b/src/Tools/pef-arm64-cdrv.cc index b7557f3..97812b7 100644 --- a/src/Tools/pef-arm64-cdrv.cc +++ b/src/Tools/pef-arm64-cdrv.cc @@ -7,10 +7,10 @@ /// @file cxxdrv.cc /// @brief NeCTI C++ frontend compiler. -#include +#include #include -#include -#include +#include +#include #ifdef __APPLE__ static auto kPath = "/usr/local/lib/libCompilerKit.dylib"; diff --git a/tests/samples/sample.asm b/tests/samples/sample.asm deleted file mode 100644 index 82c0320..0000000 --- a/tests/samples/sample.asm +++ /dev/null @@ -1,4 +0,0 @@ -public_segment .code64 __ImageStart - ;; rax is the return value register. - mov rax, 5 - ret \ No newline at end of file diff --git a/tests/samples/sample.cc b/tests/samples/sample.cc deleted file mode 100644 index 4cce7f6..0000000 --- a/tests/samples/sample.cc +++ /dev/null @@ -1,3 +0,0 @@ -int main() { - return 0; -} diff --git a/tests/samples/sample.cc.pp b/tests/samples/sample.cc.pp deleted file mode 100644 index cb3f748..0000000 --- a/tests/samples/sample.cc.pp +++ /dev/null @@ -1,3 +0,0 @@ -int main() { - return 0; -} diff --git a/tests/test_01_codegen/codegen_test.cc b/tests/test_01_codegen/codegen_test.cc index 24d4bf6..d94379a 100644 --- a/tests/test_01_codegen/codegen_test.cc +++ b/tests/test_01_codegen/codegen_test.cc @@ -10,6 +10,6 @@ #include TEST(CodegenTest, BasicCodegenTest) { - auto expr = std::system("asm -asm-x64 sample/sample.asm"); + auto expr = std::system("asm -asm-x64 test_samples/sample.asm"); EXPECT_TRUE(expr == 0) << "ASM Driver did not compile the easy ASM unit."; } diff --git a/tests/test_02_linker/linker_test.cc b/tests/test_02_linker/linker_test.cc index e164717..d126060 100644 --- a/tests/test_02_linker/linker_test.cc +++ b/tests/test_02_linker/linker_test.cc @@ -11,19 +11,19 @@ /// c++ TEST(LinkerTest, BasicLinkTest) { - auto expr = std::system("pef-amd64-cxxdrv sample/sample.cc"); + auto expr = std::system("pef-amd64-cxxdrv test_samples/sample.cc"); EXPECT_TRUE(expr == 0) << "C++ Driver did not compile the easy C++ unit."; } /// assemble TEST(LinkerTest, BasicLinkTest2) { - auto expr = std::system("asm -asm:x64 sample/sample.cc.pp.masm"); + auto expr = std::system("asm -asm:x64 test_samples/sample.cc.pp.masm"); EXPECT_TRUE(expr == 0) << "Assembler did not assemble the easy asm unit."; } /// link TEST(LinkerTest, BasicLinkTest3) { auto expr = - std::system("ld64 -amd64 sample/sample.cc.pp.obj -start __NECTI_main -output main.exec"); + std::system("ld64 -amd64 test_samples/sample.cc.pp.obj -start __NECTI_main -output main.exec"); EXPECT_TRUE(expr == 0) << "Linker did not link the easy object."; } diff --git a/tests/test_samples/sample.asm b/tests/test_samples/sample.asm new file mode 100644 index 0000000..82c0320 --- /dev/null +++ b/tests/test_samples/sample.asm @@ -0,0 +1,4 @@ +public_segment .code64 __ImageStart + ;; rax is the return value register. + mov rax, 5 + ret \ No newline at end of file diff --git a/tests/test_samples/sample.cc b/tests/test_samples/sample.cc new file mode 100644 index 0000000..4cce7f6 --- /dev/null +++ b/tests/test_samples/sample.cc @@ -0,0 +1,3 @@ +int main() { + return 0; +} -- cgit v1.2.3