Compare commits
11 Commits
8fa73f9938
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e51644fa1 | |||
| 9b544ce55d | |||
| aceec99522 | |||
| fe5a09edfb | |||
| ffa88142e8 | |||
| 1fff6cf5be | |||
| a143b44f59 | |||
| c68adc95fb | |||
| a8eae150d4 | |||
| 6415199b8c | |||
| b510b2830f |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
.svn
|
||||
hexagon
|
||||
hexagon.exe
|
||||
hexagon-*
|
||||
Hexagon.app
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
## 1.3.0 (????年??月??日)
|
||||
* macOS対応
|
||||
|
||||
## 1.2.0 (2026年05月03日)
|
||||
* Debianでコンパイル出来る様に
|
||||
* マウス対応の追加
|
||||
* ファイルなしでも開ける様に
|
||||
* `:o`コマンドの追加
|
||||
|
||||
## 1.1.0 (2025年12月28日)
|
||||
* PgUp・PgDownで使ってページを動ける様に
|
||||
|
||||
18
Makefile
18
Makefile
@@ -35,11 +35,17 @@ PREFIX = /usr
|
||||
PREFIX = /boot/home/config/non-packaged
|
||||
.endif
|
||||
|
||||
CFLAGS = -I/usr/include -I/usr/local/include -I/usr/pkg/include\
|
||||
-I/usr/pkg/include/ncurses -I/boot/system/develop/headers\
|
||||
-L/usr/lib -L/usr/local/lib -L/usr/pkg/lib -L/boot/system/develop/lib
|
||||
CFLAGS = -I/usr/include -I/usr/local/include -L/usr/lib -L/usr/local/lib
|
||||
|
||||
.if ${UNAME_S} == "NetBSD"
|
||||
.if ${OS} == "netbsd"
|
||||
CFLAGS += -I/usr/pkg/include -I/usr/pkg/include/ncurses -L/usr/pkg/lib
|
||||
.elif ${OS} == "macos"
|
||||
CFLAGS += -I/opt/homebrew/opt/ncurses/include -L/opt/homebrew/opt/ncurses/lib
|
||||
.elif ${OS} == "haiku"
|
||||
CFLAGS += -I/boot/system/develop/headers -L/boot/system/develop/lib
|
||||
.endif
|
||||
|
||||
.if ${OS} == "netbsd"
|
||||
LDFLAGS = -lncurses
|
||||
.else
|
||||
LDFLAGS = -lncursesw
|
||||
@@ -52,6 +58,10 @@ all:
|
||||
${CC} -O3 ${CFLAGS} -o ${NAME} ${FILES} -std=c++17 -static ${LDFLAGS}
|
||||
strip ${NAME}
|
||||
|
||||
mac:
|
||||
${CC} -O3 ${CFLAGS} -o ${NAME} ${FILES} -std=c++17 ${LDFLAGS}
|
||||
strip ${NAME}
|
||||
|
||||
debian:
|
||||
${CC} -O3 ${CFLAGS} -o ${NAME} ${FILES} -std=c++17 -static ${LDFLAGS} -ltinfo
|
||||
strip ${NAME}
|
||||
|
||||
14
README.md
14
README.md
@@ -8,12 +8,24 @@ make
|
||||
doas make install
|
||||
```
|
||||
|
||||
### Linux/macOS
|
||||
### Linux
|
||||
```sh
|
||||
bmake
|
||||
sudo bmake install
|
||||
```
|
||||
|
||||
#### Debianの場合
|
||||
```sh
|
||||
bmake debian
|
||||
sudo bmake install
|
||||
```
|
||||
|
||||
### macOS
|
||||
```sh
|
||||
bmake mac
|
||||
sudo bmake install
|
||||
```
|
||||
|
||||
### Haiku
|
||||
```sh
|
||||
bmake
|
||||
|
||||
BIN
macos/Hexagon.icns
Executable file
BIN
macos/Hexagon.icns
Executable file
Binary file not shown.
22
macos/Info.plist
Normal file
22
macos/Info.plist
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>Hexagon</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>moe.technicalsuwako.hexagon</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>hexagon</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>11.0</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>Hexagon.icns</string>
|
||||
</dict>
|
||||
</plist>
|
||||
12
macos/hexagon.sh
Normal file
12
macos/hexagon.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ADIR="$(dirname "$DIR")"
|
||||
|
||||
BIN="$ADIR/MacOS/hexagon_real"
|
||||
|
||||
osascript <<EOF
|
||||
tell application "Terminal"
|
||||
activate
|
||||
do script "\"$BIN\"; exit"
|
||||
end tell
|
||||
EOF
|
||||
10
main.cc
10
main.cc
@@ -39,13 +39,13 @@ OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#include "src/hexeditor.hh"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc != 2) {
|
||||
std::cerr << "usage: hexagon <filename>\n";
|
||||
return 1;
|
||||
}
|
||||
std:: string filename;
|
||||
|
||||
if (argc > 1) filename = argv[1];
|
||||
else filename = "";
|
||||
|
||||
try {
|
||||
HexEditor editor(argv[1]);
|
||||
HexEditor editor(filename);
|
||||
editor.run();
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "エラー: " << e.what() << "\n";
|
||||
|
||||
32
make-macos.sh
Executable file
32
make-macos.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/bin/sh
|
||||
|
||||
c++ -O3 -I/usr/include -I/usr/local/include -L/usr/lib -L/usr/local/lib -I/opt/homebrew/opt/ncurses/include -L/opt/homebrew/opt/ncurses/lib -o hexagon main.cc src/*.cc -std=c++17 -lncursesw
|
||||
strip hexagon
|
||||
|
||||
mkdir -p Hexagon.app/Contents/MacOS
|
||||
mkdir -p Hexagon.app/Contents/Resources
|
||||
mkdir -p Hexagon.app/Contents/Frameworks
|
||||
cp macos/Hexagon.icns Hexagon.app/Contents/Resources/
|
||||
cp macos/Info.plist Hexagon.app/Contents/
|
||||
mv hexagon Hexagon.app/Contents/MacOS/hexagon_real
|
||||
cp macos/hexagon.sh Hexagon.app/Contents/MacOS/hexagon
|
||||
chmod +x Hexagon.app/Contents/MacOS/hexagon
|
||||
chmod +x Hexagon.app/Contents/MacOS/hexagon_real
|
||||
|
||||
cd Hexagon.app/Contents/MacOS
|
||||
install_name_tool -change \
|
||||
/opt/homebrew/opt/ncurses/lib/libncursesw.6.dylib \
|
||||
@executable_path/../Frameworks/libncursesw.6.dylib \
|
||||
hexagon_real
|
||||
|
||||
cp /opt/homebrew/opt/ncurses/lib/libncursesw.6.dylib \
|
||||
../Frameworks/
|
||||
install_name_tool -id @rpath/libncursesw.6.dylib \
|
||||
../Frameworks/libncursesw.6.dylib
|
||||
|
||||
cd ../../..
|
||||
codesign --force --deep --sign - Hexagon.app/Contents/MacOS/hexagon_real
|
||||
codesign --force --deep --sign - Hexagon.app/Contents/Frameworks/libncursesw.6.dylib
|
||||
|
||||
tar zcfv hexagon-macos-aarch64.tar.gz Hexagon.app
|
||||
rm -rf Hexagon.app
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
g++ -O3 -o hexagon.exe -I/mingw64/include -L/mingw64/lib main.cc src/*.cc\
|
||||
g++ -O3 -DNDEBUG -o hexagon.exe -I/mingw64/include -L/mingw64/lib main.cc src/*.cc\
|
||||
-std=c++17 -static -lpdcurses -lgdi32 -lcomdlg32 -lwinmm -luser32 -lwinpthread\
|
||||
-lstdc++ -mwindows
|
||||
-lstdc++ -mconsole -s
|
||||
|
||||
209
src/hexeditor.cc
209
src/hexeditor.cc
@@ -80,7 +80,7 @@ const std::vector<HexEditor::FileSignature> HexEditor::signatures = {
|
||||
HexEditor::HexEditor(const std::string &filename)
|
||||
: curPos(0), dpOffset(0), bpr(16),
|
||||
statusMode(Status_Normal), modified(false),
|
||||
running(true),
|
||||
running(true), hasFile(!filename.empty()),
|
||||
lastSearchDir(Direction_Forward),
|
||||
headerLen(0), headerType("") {
|
||||
// SIGINT (CTRL + C)を無効に
|
||||
@@ -94,6 +94,40 @@ HexEditor::HexEditor(const std::string &filename)
|
||||
|
||||
// ncurses
|
||||
initscr();
|
||||
#ifdef _WIN32
|
||||
HWND console = GetConsoleWindow();
|
||||
if (console) {
|
||||
LONG style = GetWindowLong(console, GWL_STYLE);
|
||||
style |= WS_MAXIMIZEBOX | WS_THICKFRAME;
|
||||
SetWindowLong(console, GWL_STYLE, style);
|
||||
|
||||
const int MIN_WIDTH = 100;
|
||||
const int MIN_HEIGHT = 30;
|
||||
|
||||
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
||||
GetConsoleScreenBufferInfo(hOut, &csbi);
|
||||
|
||||
RECT rect;
|
||||
GetWindowRect(console, &rect);
|
||||
|
||||
int cw = csbi.srWindow.Right - csbi.srWindow.Left + 1;
|
||||
int ch = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
|
||||
|
||||
MINMAXINFO mmi = {};
|
||||
mmi.ptMinTrackSize.x = MIN_WIDTH * 8;
|
||||
mmi.ptMinTrackSize.y = MIN_HEIGHT * 16;
|
||||
|
||||
COORD minBuf = { MIN_WIDTH, MIN_HEIGHT };
|
||||
SetConsoleScreenBufferSize(hOut, minBuf);
|
||||
|
||||
SMALL_RECT rectWin = { 0, 0, MIN_WIDTH - 1, MIN_HEIGHT - 1 };
|
||||
SetConsoleWindowInfo(hOut, TRUE, &rectWin);
|
||||
}
|
||||
|
||||
resize_term(40, 120);
|
||||
#endif
|
||||
getmaxyx(stdscr, rows, cols);
|
||||
clearok(stdscr, TRUE);
|
||||
cbreak();
|
||||
noecho();
|
||||
@@ -102,7 +136,13 @@ HexEditor::HexEditor(const std::string &filename)
|
||||
start_color();
|
||||
|
||||
// マウス対応
|
||||
#ifdef _WIN32
|
||||
mousemask(ALL_MOUSE_EVENTS, nullptr);
|
||||
mouse_set(ALL_MOUSE_EVENTS);
|
||||
request_mouse_pos();
|
||||
#else
|
||||
mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, nullptr);
|
||||
#endif
|
||||
isMouse = true;
|
||||
|
||||
init_pair(1, COLOR_BLACK, COLOR_WHITE); // カーソル
|
||||
@@ -117,39 +157,46 @@ HexEditor::HexEditor(const std::string &filename)
|
||||
refresh();
|
||||
|
||||
// ファイルをバッファーに読み込む
|
||||
std::ifstream file(filename, std::ios::binary);
|
||||
if (!file) {
|
||||
endwin();
|
||||
throw std::runtime_error("ファイルを開くに失敗");
|
||||
}
|
||||
if (hasFile) {
|
||||
std::ifstream file(filename, std::ios::binary);
|
||||
if (!file) {
|
||||
endwin();
|
||||
throw std::runtime_error("ファイルを開くに失敗");
|
||||
}
|
||||
|
||||
buf.assign((std::istreambuf_iterator<char>(file)), {});
|
||||
file.close();
|
||||
buf.assign((std::istreambuf_iterator<char>(file)), {});
|
||||
file.close();
|
||||
|
||||
if (buf.empty()) {
|
||||
endwin();
|
||||
throw std::runtime_error("ファイルが空です");
|
||||
}
|
||||
if (buf.empty()) {
|
||||
endwin();
|
||||
throw std::runtime_error("ファイルが空です");
|
||||
}
|
||||
|
||||
fname = filename;
|
||||
fname = filename;
|
||||
hasFile = true;
|
||||
|
||||
// ファイルヘッダー
|
||||
for (const auto &sig : signatures) {
|
||||
if (buf.size() >= sig.signature.size()) {
|
||||
bool match = true;
|
||||
for (size_t i = 0; i < sig.signature.size(); ++i) {
|
||||
if (buf[i] != sig.signature[i]) {
|
||||
match = false;
|
||||
// ファイルヘッダー
|
||||
for (const auto &sig : signatures) {
|
||||
if (buf.size() >= sig.signature.size()) {
|
||||
bool match = true;
|
||||
for (size_t i = 0; i < sig.signature.size(); ++i) {
|
||||
if (buf[i] != sig.signature[i]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
headerLen = sig.signature.size();
|
||||
headerType = sig.type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
headerLen = sig.signature.size();
|
||||
headerType = sig.type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fname = "[No File]";
|
||||
buf.clear();
|
||||
hasFile = false;
|
||||
}
|
||||
|
||||
// ウィンドウを作成する
|
||||
@@ -510,9 +557,13 @@ void HexEditor::handleCommand() {
|
||||
} else if (statusText == "noh") {
|
||||
lastSearch = "";
|
||||
isCommand = true;
|
||||
} else if (statusText.rfind("o ", 0) == 0 || statusText.rfind("open ", 0) == 0) {
|
||||
std::string path = statusText.substr(statusText.find(' ') + 1);
|
||||
handleOpen(path);
|
||||
isCommand = true;
|
||||
} else {
|
||||
statusMode = Status_Error;
|
||||
statusText.clear();
|
||||
statusText = "不正なコマンド。";
|
||||
}
|
||||
|
||||
if (isCommand) {
|
||||
@@ -532,6 +583,67 @@ void HexEditor::handleQuit(bool force) {
|
||||
}
|
||||
}
|
||||
|
||||
void HexEditor::handleOpen(const std::string &path) {
|
||||
if (path.empty()) {
|
||||
statusMode = Status_Error;
|
||||
statusText = "利用方法: :o <filename>";
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if (!file) {
|
||||
statusMode = Status_Error;
|
||||
statusText = "ファイルを開くに失敗: " + path;
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> newbuf((std::istreambuf_iterator<char>(file)), {});
|
||||
file.close();
|
||||
|
||||
if (newbuf.empty()) {
|
||||
statusMode = Status_Error;
|
||||
statusText = "ファイルは空です。";
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
buf = std::move(newbuf);
|
||||
fname = path;
|
||||
hasFile = true;
|
||||
curPos = 0;
|
||||
dpOffset = 0;
|
||||
modified = false;
|
||||
undoStack.clear();
|
||||
redoStack.clear();
|
||||
lastSearch.clear();
|
||||
lastHexSearch.clear();
|
||||
headerLen = 0;
|
||||
headerType = "";
|
||||
|
||||
for (const auto &sig : signatures) {
|
||||
if (buf.size() >= sig.signature.size()) {
|
||||
bool match = true;
|
||||
for (size_t i = 0; i < sig.signature.size(); ++i) {
|
||||
if (buf[i] != sig.signature[i]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
headerLen = sig.signature.size();
|
||||
headerType = sig.type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
statusMode = Status_Normal;
|
||||
statusText = path + " を開きました。";
|
||||
render();
|
||||
}
|
||||
|
||||
void HexEditor::handleSave() {
|
||||
std::ofstream file(fname, std::ios::binary);
|
||||
if (file) {
|
||||
@@ -764,17 +876,24 @@ void HexEditor::handleReplace() {
|
||||
|
||||
void HexEditor::handleMouse() {
|
||||
#ifdef _WIN32
|
||||
if (getmouse() == 0) return;
|
||||
request_mouse_pos();
|
||||
|
||||
int mx = Mouse_status.x;
|
||||
int my = Mouse_status.y;
|
||||
|
||||
if (!(Mouse_status.button[0] & BUTTON1_PRESSED)
|
||||
|| (Mouse_status.changes & 0x01)) return;
|
||||
bool click = false;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if ((Mouse_status.button[i] & BUTTON1_PRESSED)
|
||||
|| (Mouse_status.button[i] & BUTTON1_CLICKED)) {
|
||||
click = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!click) return;
|
||||
#else
|
||||
MEVENT event;
|
||||
if (getmouse(&event) != OK) return;
|
||||
if (!(event.bstate & BUTTON1_PRESSED)) return;
|
||||
if (!(event.bstate & (BUTTON1_PRESSED | BUTTON1_CLICKED))) return;
|
||||
|
||||
int mx = event.x;
|
||||
int my = event.y;
|
||||
@@ -849,6 +968,34 @@ void HexEditor::input() {
|
||||
if (statusMode != Status_Normal && statusMode != Status_Error) continue;
|
||||
|
||||
ch = getch();
|
||||
|
||||
if (ch == KEY_RESIZE) {
|
||||
getmaxyx(stdscr, rows, cols);
|
||||
delwin(hexPanel);
|
||||
delwin(asciiPanel);
|
||||
|
||||
bpr = std::min<size_t>(16, (cols / 2 - 10) / 4);
|
||||
if (bpr < 4) bpr = 4;
|
||||
|
||||
hexPanel = newwin(rows - 3, cols / 2, 1, 0);
|
||||
asciiPanel = newwin(rows - 3, cols / 2, 1, cols / 2);
|
||||
|
||||
scrollok(hexPanel, TRUE);
|
||||
scrollok(asciiPanel, TRUE);
|
||||
wattrset(hexPanel, COLOR_PAIR(8));
|
||||
wattrset(asciiPanel, COLOR_PAIR(8));
|
||||
|
||||
render();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!hasFile && ch != ':' && ch != 'Z') {
|
||||
statusMode = Status_Error;
|
||||
statusText = "ファイルを読み込んでいません。「:o <file>」を御利用下さい。";
|
||||
render();
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((ch == 'j' || ch == KEY_DOWN) && curPos + bpr < buf.size()) {
|
||||
curPos += bpr; // 下
|
||||
if (statusMode == Status_Error) statusMode = Status_Normal;
|
||||
|
||||
@@ -79,6 +79,7 @@ class HexEditor {
|
||||
bool running;
|
||||
std::string lastSearch;
|
||||
bool isMouse = false;
|
||||
bool hasFile = false;
|
||||
|
||||
WINDOW *hexPanel;
|
||||
WINDOW *asciiPanel;
|
||||
@@ -102,6 +103,7 @@ class HexEditor {
|
||||
void topbar();
|
||||
void statusbar();
|
||||
void handleCommand();
|
||||
void handleOpen(const std::string &path);
|
||||
void handleSave();
|
||||
void handleQuit(bool force);
|
||||
void handleSearch();
|
||||
|
||||
Reference in New Issue
Block a user