diff --git a/.gitignore b/.gitignore index 37183b78..9cc65068 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,5 @@ fpsconfig/gimx-fpsconfig core/gimx core/test/haptic/ff_lg_test loader/gimx-loader + +.vscode diff --git a/Makedefs b/Makedefs index 4db71f74..2540b84d 100644 --- a/Makedefs +++ b/Makedefs @@ -6,12 +6,18 @@ LD = $(CXX) -CFLAGS += -Wall -Wextra -O3 -CXXFLAGS += -Wall -Wextra -O3 +#CFLAGS += -Wall -Wextra -O3 +#CXXFLAGS += -Wall -Wextra -O3 #Comment the above two lines and uncomment the below three lines to compile with debug symbols. + #CFLAGS += -Wall -Wextra -O0 -g -fsanitize=address -fno-omit-frame-pointer #CXXFLAGS += -Wall -Wextra -O0 -g -fsanitize=address -fno-omit-frame-pointer +#Library address sanitiser is not available for mingw64. This applies to msys2. +#Uncomment below lines, and comment above lines, for plain debug symbols. +CFLAGS += -Wall -Wextra -O0 -g +CXXFLAGS += -Wall -Wextra -O0 -g + CPPFLAGS += -I../shared GIMXCONFIGEDITOR_LDFLAGS = -L../shared/gimxconfigeditor \ diff --git a/Makefile b/Makefile index 5a7d597d..32b13b91 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ -DIRS = shared utils core config launcher fpsconfig loader +DIRS = shared utils core config launcher fpsconfig loader fetchconfig + ifneq ($(OS),Windows_NT) DIRS+= po @@ -19,13 +20,14 @@ build-core: build-shared build-config: build-shared build-launcher: build-shared build-fpsconfig: build-shared +build-fetchconfig: build-shared clean: $(CLEANDIRS) -$(CLEANDIRS): +$(CLEANDIRS): $(MAKE) -C $(@:clean-%=%) clean ifeq ($(OS),Windows_NT) -DLLS = $(shell ntldd -R {core,config,fpsconfig,launcher}/*.exe | grep mingw | sed "s/.*=> //g" | cut -d' ' -f 1 | sed 's/\\/\\\\/g' | xargs cygpath -u | sort | uniq)\ +DLLS = $(shell ntldd -R {core,config,fpsconfig,launcher,fetchconfig}/*.exe | grep mingw | sed "s/.*=> //g" | cut -d' ' -f 1 | sed 's/\\/\\\\/g' | xargs cygpath -u | sort | uniq)\ $(shell ntldd -R shared/*/*.dll | grep mingw | sed "s/.*=> //g" | cut -d' ' -f 1 | sed 's/\\/\\\\/g' | xargs cygpath -u | sort | uniq) install: all mkdir -p setup @@ -36,6 +38,7 @@ install: all cp -u -f core/gimx setup/gimx.exe cp -u -f config/gimx-config setup/gimx-config.exe cp -u -f launcher/gimx-launcher setup/gimx-launcher.exe + cp -u -f fetchconfig/gimx-fetchconfig setup/gimx-fetchconfig.exe cp -u -f fpsconfig/gimx-fpsconfig setup/gimx-fpsconfig.exe cp -u -f loader/gimx-loader setup/gimx-loader.exe cp -u -f shared/gimxinput/src/windows/gamecontrollerdb.txt setup diff --git a/fetchconfig/Makefile b/fetchconfig/Makefile new file mode 100644 index 00000000..087c56ba --- /dev/null +++ b/fetchconfig/Makefile @@ -0,0 +1,68 @@ +include ../Makedefs + + +ifneq ($(OS),Windows_NT) +prefix=$(DESTDIR)/usr +bindir=$(prefix)/bin +endif + + +NAME=$(shell basename "$(shell pwd)") + + +ifneq ($(OS),Windows_NT) +LDLIBS += -lstdc++ -lm `pkg-config --libs ncursesw` +else +LDLIBS += -lws2_32 -lstdc++ -lpdcursesw -lintl +endif + +CPPFLAGS += -Iinclude -std=c++11 + +LDFLAGS += $(GIMXINPUT_LDFLAGS) $(GIMXUSB_LDFLAGS) $(GIMXPOLL_LDFLAGS) +LDLIBS += $(GIMXINPUT_LDLIBS) $(GIMXUSB_LDLIBS) $(GIMXPOLL_LDLIBS) +LDLIBS += $(GIMXUPDATER_LDLIBS) $(GIMXCONFIGUPDATER_LDLIBS) +LDFLAGS += $(GIMXUPDATER_LDFLAGS) $(GIMXCONFIGUPDATER_LDFLAGS) + + +OBJECTS := $(patsubst %.cpp,%.o,$(wildcard *.cpp)) + +OUT=gimx-$(NAME) + +ifneq ($(OS),Windows_NT) +BINS = $(OUT) +else +OBJECTS += $(NAME).rc.o +endif + + +all: $(OUT) + + +$(OUT): $(OBJECTS) + +ifeq ($(OS),Windows_NT) +$(NAME).rc.o: $(NAME).rc + WINDRES $^ -o $@ +endif + + +clean: + $(RM) $(OBJECTS) $(OUT) + +.PHONY: clean + + +ifneq ($(OS),Windows_NT) +install: all + mkdir -p $(prefix) + mkdir -p $(bindir) + for i in $(BINS); do cp $$i $(bindir)/; done + for i in $(BINS); do chmod ug+s $(bindir)/$$i; done + +uninstall: + -for i in $(BINS); do $(RM) $(bindir)/$$i; done + -rmdir $(bindir) + -rmdir $(prefix) + +really-clean: clean uninstall +endif diff --git a/fetchconfig/configDownload.cpp b/fetchconfig/configDownload.cpp new file mode 100644 index 00000000..d2a4308e --- /dev/null +++ b/fetchconfig/configDownload.cpp @@ -0,0 +1,333 @@ +/* + * configDownload.cpp + * + * Author: Zac + * Contact: codeohms@protonmail.com + * Created on: 10 Aug. 2018 + */ + +#include "include/configDownload.h" + + +int process_cb(GE_Event* event __attribute__((unused))) +{ + return 0; +} + + +int progress_callback_configupdater_terminal(void *clientp, configupdater::ConfigUpdaterStatus status, double progress, double total) +{ + return ((ConfigDownload *) clientp)->updateProgress(status, progress, total); +} + +int updateProgress_common(ttyProgressDialog* progressDialog, configupdater::ConfigUpdaterStatus status, double progress, double total) +{ + std::string message; + switch(status) + { + case configupdater::ConfigUpdaterStatusConnectionPending: + message = "Connecting"; + break; + case configupdater::ConfigUpdaterStatusDownloadInProgress: + message = "Progress: "; + message += Updater::getProgress(progress, total); + break; + default: + break; + } + + if (status >= 0) { + if(progressDialog->update(progress, message) == false) { + return 1; + } + } else { + return 1; + } + + return 0; +} + + +ConfigDownload::ConfigDownload(WINDOW* win, WinData* win1) : dlWinData(win1) +{ + setUpDirectories(win); + + //Progress dialog + int height = dlWinData->height; + int width = dlWinData->width; + dlWinData->height = 7; + dlWinData->width = 30; + dlWinData->startX = (width /2) - (dlWinData->width /2); + dlWinData->startY = (height /2) - (dlWinData->height /2); + dlScreen = newwin(dlWinData->height, dlWinData->width, dlWinData->startY, dlWinData->startX); + dlWinData->win = dlScreen; + progressDialog.reset(new ttyProgressDialog(dlWinData.get(), "Downloading")); +} + +void ConfigDownload::initDownload() +{ + progressDialog->dialog(); +} + +void ConfigDownload::cleanDownload() +{ + progressDialog->resetPBar(); + werase(dlScreen); + wrefresh(dlScreen); +} + +bool ConfigDownload::setUpDirectories(WINDOW* screen) +{ + /*Setup directory strings*/ + int res = getUserConfigDir(userDir); + if(res) + { + wprintw(screen, "Cannot access config directory. Press any key to continue"); + wrefresh(screen); + wgetch(screen); + return false; + } + gimxDir = userDir + GIMX_DIR; + gimxConfigDir = gimxDir + CONFIG_DIR; + return true; +} + +int ConfigDownload::getConfig(std::string& configName) +{ + configupdater::ConfigUpdaterStatus status = configupdater::ConfigUpdaterStatusOk; + status = configupdater().getconfig(gimxConfigDir, configName, progress_callback_configupdater_terminal, this); + return status; +} + +int ConfigDownload::grabConfigs(std::list& configs, WINDOW* screen) +{ + configupdater::ConfigUpdaterStatus status = configupdater::ConfigUpdaterStatusOk; + if(!configs.empty()) + { + initDownload(); + for(std::list::iterator it = configs.begin(); it != configs.end(); ++it) + { + status = configupdater().getconfig(gimxConfigDir, *it, progress_callback_configupdater_terminal, this); + if (status == configupdater::ConfigUpdaterStatusCancelled) + break; + } + cleanDownload(); + } + else + wprintw(screen, "No configs to download\n"); + + if(status == configupdater::ConfigUpdaterStatusOk) + wprintw(screen, "Completed\n"); + else if(status != configupdater::ConfigUpdaterStatusCancelled) + wprintw(screen, "Can't retrieve configs!\n"); + wprintw(screen, "Press any key to continue"); + + wrefresh(screen); + wgetch(screen); + + return status; +} + +int ConfigDownload::updateProgress(configupdater::ConfigUpdaterStatus status, double progress, double total) +{ + return updateProgress_common(progressDialog.get(), status, progress, total); +} + + +ManualConfigDownload::ManualConfigDownload() : ConfigDownload(stdscr, newWinData(stdscr)), + winData(newWinData(stdscr)) +{ + //Selection menu + winData->height -= 1; + screen = newwin(winData->height, winData->width, winData->startY, winData->startX); + winData->win = screen; + + //Help dialog + helpText = "Press:\n\nESC to exit\nENTER to select\nArrow keys to change selection\n"\ + "Page up and down keys to change page"; + SelectionMenu::keyBindings[104] = EasyCurses::NavContent::custom; //104 => 'h' +} + +bool ManualConfigDownload::help() +{ + BasicMenu helpMenu(helpText, winData.get(), "Help menu"); + /*Stylise the menu borders*/ + // borders => (bool, we, ns) + helpMenu.setDrawBorder(true, 0, 0); + flushinp(); + + mvwprintw(stdscr, winData->height, 0, "Press ESC to exit menu"); + wrefresh(stdscr); + + helpMenu.menuLoop(); + + wmove(stdscr, winData->height, 0); + wclrtoeol(stdscr); + mvwprintw(stdscr, winData->height, 0, "Press h for help"); + wrefresh(stdscr); + + return true; +} + +//Return codes => 0 ok, 1 cancelled, 2 something wrong with getting configs +int ManualConfigDownload::chooseConfigs() +{ + configupdater::ConfigUpdaterStatus status; + + /*Download config list*/ + initDownload(); + status = configupdater().getconfiglist(configList, progress_callback_configupdater_terminal, this); + cleanDownload(); + + if(status == configupdater::ConfigUpdaterStatusCancelled) + return status; + + /*Ensure the config list is not empty*/ + if(configList.empty()) + { + wprintw(screen, "Can't retrieve configs list!\nPress any key to continue"); + wgetch(screen); + wrefresh(screen); + return status; + } + + /*Add config names to options list*/ + std::vector chosen; + { + std::string options; + for(std::string configName : configList) + options += configName + "\n"; + + SelectionMenu selectionMenu(options, winData.get(), "Select the files to download"); + + /*Stylise the menu borders*/ + // borders => (bool, we, ns) + selectionMenu.setDrawBorder(true, 0, 0); + + const char* help = "Press h for help"; + selectionMenu.setCustomAction(std::bind(&ManualConfigDownload::help, this)); + mvwprintw(stdscr, winData->height, 0, help); + wrefresh(stdscr); + + selectionMenu.menuLoop(); + selectionMenu.getResult(chosen); + } + + werase(screen); + + std::string sel; + std::string file; + /*Check if any chosen configs exist already*/ + int c; + for(int cIndex : chosen) + { + sel = *(std::next(configList.begin(), cIndex)); + file = gimxConfigDir + sel; + if(fileExists(file)) + { + wprintw(screen, "Overwrite local file: %s?(y or n)\n", file.c_str()); + wrefresh(screen); + c = wgetch(screen); + + //No + if(not (c == 121 || c == 89) ) + continue; + } + selectedConfigs.push_back(sel); + } + werase(screen); + + status = static_cast(grabConfigs(selectedConfigs, screen)); + + wrefresh(screen); + return status; +} + + +AutoConfigDownload::AutoConfigDownload() : ConfigDownload(stdscr, newWinData(stdscr)) +{ + +} + +int AutoConfigDownload::chooseConfigs() +{ + std::list joysticks; + + GPOLL_INTERFACE poll_interace = + { + .fp_register = REGISTER_FUNCTION, + .fp_remove = REMOVE_FUNCTION, + }; + if(ginput_init(&poll_interace, GE_MKB_SOURCE_NONE, process_cb) < 0) + { + ginput_quit(); + return -4; + } + + for (int i = 0; ginput_joystick_name(i) != NULL; ++i) + { + joysticks.push_back(ginput_joystick_name(i)); + } + + ginput_quit(); + + // TODO MLA: have an online index with device -> config, and be able to merge multiple configs + + struct + { + std::string name; + std::string config; + } configs [] = +#ifndef WIN32 + { + { "Logitech Inc. WingMan Formula", "LogitechWingManFormula_G29.xml" }, + { "Logitech Inc. WingMan Formula GP", "LogitechWingManFormulaGP_G29.xml" }, + { "Logitech Inc. WingMan Formula Force", "LogitechWingManFormulaForce_G29.xml" }, + { "Logitech Inc. WingMan Formula Force GP", "LogitechWingManFormulaForceGP_G29.xml" }, + { "Logitech Logitech Driving Force", "LogitechDrivingForce_G29.xml" }, + { "Logitech Logitech Driving Force EX", "LogitechDrivingForceEx_G29.xml" }, + { "Logitech Logitech Driving Force Rx", "LogitechDrivingForceRx_G29.xml" }, + { "Logitech Logitech Formula Force EX", "LogitechFormulaForceEx_G29.xml" }, + { "PS3/USB Cordless Wheel", "LogitechDrivingForceWireless_DS4.xml" }, + // TODO MLA { "Logitech MOMO Force USB", "LogitechMomoForce_G29.xml" }, + { "Logitech Logitech Driving Force Pro", "LogitechDrivingForcePro_G29.xml" }, + { "G25 Racing Wheel", "LogitechG25_G29.xml" }, + { "Driving Force GT", "LogitechDrivingForceGT_G29.xml" }, + { "G27 Racing Wheel", "LogitechG27_G29.xml" }, + { "Logitech Logitech MOMO Racing ", "LogitechMomoRacing_G29.xml" }, + { "Logitech G920 Driving Force Racing Wheel", "LogitechG920_G29.xml" }, + }; +#else + { + { "Logitech WingMan Formula (Yellow) (USB)", "LogitechWingManFormula_G29.xml" }, + { "Logitech WingMan Formula GP", "LogitechWingManFormulaGP_G29.xml" }, + { "Logitech WingMan Formula Force USB", "LogitechWingManFormulaForce_G29.xml" }, + { "Logitech WingMan Formula Force GP USB", "LogitechWingManFormulaForceGP_G29.xml" }, + { "Logitech Driving Force USB", "LogitechDrivingForce_G29.xml" }, + { "Logitech MOMO Force USB", "LogitechMomoForce_G29.xml" }, + { "Logitech Driving Force Pro USB", "LogitechDrivingForcePro_G29.xml" }, + { "Logitech G25 Racing Wheel USB", "LogitechG25_G29.xml" }, + { "Logitech Driving Force GT USB", "LogitechDrivingForceGT_G29.xml" }, + { "Logitech G27 Racing Wheel USB", "LogitechG27_G29.xml" }, + { "Logitech MOMO Racing USB", "LogitechMomoRacing_G29.xml" }, + { "Logitech G920 Driving Force Racing Wheel USB", "LogitechG920_G29.xml" }, + }; +#endif + + std::list download; + + for (std::list::iterator it = joysticks.begin(); it != joysticks.end(); ++it) + { + for (unsigned int i = 0; i < sizeof(configs) / sizeof(*configs); ++i) + { + if(*it == configs[i].name) + { + download.push_back(configs[i].config); + } + } + } + + grabConfigs(download, dlScreen); + + return configupdater::ConfigUpdaterStatusOk; +} diff --git a/fetchconfig/easyCurses.cpp b/fetchconfig/easyCurses.cpp new file mode 100755 index 00000000..507b58d2 --- /dev/null +++ b/fetchconfig/easyCurses.cpp @@ -0,0 +1,778 @@ +/* + * ncursesIO.cpp + * + * Author: Zac + * Contact: codeohms@protonmail.com + * Created on: 10 Aug. 2018 + */ + +#include "include/easyCurses.h" + +namespace EasyCurses +{ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//Miscellaneous + + int roundUp(float n) + { + //First, check if float is integer/round number + //Isolate decimal point + int roundN = n; + //Subtract round number from float => 2.6 - 2 OR 3.0 - 3 + float decimal = n - roundN; + + //Check if decimal is .0 to see if n is an interger + if(decimal == .0f) + return roundN; + else + return roundN +1; + } + + int centreText(int windowWidth, int textLength) + { + //Math works best with even numbers + if((windowWidth % 2) != 0) + windowWidth -= 1; + return (windowWidth - textLength) / 2; + } + + std::string fillString(int textLength, char filler) + { + std::string temp; + for(int i = 0; i < textLength; ++i) + temp += filler; + return temp; + } + + void clrToEolFrom(WINDOW* win, int y, int x) + { + wmove(win, y, x); + wclrtoeol(win); + } + void eraseChunk(WINDOW* win, int y, int x, int amount) + { + mvwprintw(win, y, x, fillString(amount).c_str()); + wmove(win, y, x); + } + void eraseChunk(WINDOW* win, int startY, int endY, int startX, int endX) + { + for(int currentLine = startY; currentLine <= endY; ++currentLine) + { + eraseChunk(win, currentLine, startX, endX - startX); + } + } + + int maxLines(int height, int padding) { return height - (padding *2); } + int maxChars(int screenWidth, int padding) { return screenWidth - (padding *2); } + + namespace TextFormat + { + void overflow(std::string text, int maxLength, LineEnds& lineFormat, OverFlow& oFLayout, bool wrap) + { + //Each time this is run, information is recalculated. + lineFormat.clear(); + oFLayout.clear(); + + size_t numChars; + size_t start = 0; + size_t linePos = 0; + size_t currentLine = 0; + + LineEnds oFlow; + + while(linePos != text.length() && (linePos +1) != text.length()) + { //2nd case is when last char is new line + { + size_t endPoint; + + //Set end points + linePos = text.find("\n", linePos + (linePos == 0 ? 0 : 1)); + + if(linePos == std::string::npos) + linePos = text.length(); + + if(linePos > (maxLength + start)) + { + //Fill out overflow information + size_t virtStart = start + maxLength; + size_t virtEnd; + + auto commit = [&]() -> void { + if(wrap) + oFlow.push_back(std::make_pair(virtEnd, numChars)); + else + oFLayout.insert(std::make_pair( currentLine, + std::make_pair(virtEnd, numChars) )); + + }; + + + while(true) + { + virtEnd = virtStart + maxLength; + + if(virtEnd > linePos) + { + virtEnd = linePos; + numChars = linePos - virtStart; + + commit(); + break; + } + else + { + numChars = maxLength; + + commit(); + + if(virtEnd == linePos) + break; + } + + virtStart = virtEnd; + } + + numChars = maxLength +1; + endPoint = start + maxLength; + start = linePos +1; + } + else + { + numChars = linePos - start; + + start = linePos +1; + endPoint = linePos; + } + + lineFormat.push_back(std::make_pair(endPoint, numChars)); + if(wrap) + { + for(auto line : oFlow) + { + size_t ep = line.first; + int chars = line.second; + lineFormat.push_back(std::make_pair(ep, chars)); + } + oFlow.clear(); + } + } + + ++currentLine; + } + } + + void overflow(std::string text, int maxLength, LineEnds& lineFormat) + { + OverFlow placeHolder; + + overflow(text, maxLength, lineFormat, placeHolder, true); + } + } + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//Widgets + + ProgressBar::ProgressBar(WINDOW* win, int size, int startY, int startX, std::string prefix, char barChar, char point, std::string suffix) + { + init(win, size, startY, startX, prefix, barChar, point, suffix); + } + void ProgressBar::init(WINDOW* win, int size, int startY, int startX, std::string prefix, char barChar, char point, std::string suffix) + { + /*Setup values and generate progress bar*/ + this->prefix = prefix; + this->suffix = suffix; + this->barChar[0] = barChar; + this->point[0] = point; + this->barChar[1] = 0; + this->point[1] = 0; + setSize(size); + prevAmount = 0; + + window = win; + this->startY = startY; + this->startX = startX; + currentX = this->startX; + } + void ProgressBar::reset() + { + prevAmount = 0; + currentX = this->startX; + } + void ProgressBar::reset(WINDOW* win, int size, int startY, int startX, std::string prefix, char barChar, char point, std::string suffix) + { + init(win, size, startY, startX, prefix, barChar, point, suffix); + } + + void ProgressBar::first() + { + //-1 so zero initialised like progress + mvwprintw(window, startY, startX, "%s%s%s%s", prefix.c_str(), point, fillString(barSize -1).c_str(), suffix.c_str()); + currentX += prefix.length(); + } + void ProgressBar::update(double progress) + { + /*Only print difference between the last and future render*/ + //Progress needs to be a fraction to convert progress % into % of progress bar + int amount = (progress /100) * barSize; + int diff = amount - prevAmount; + + mvwprintw(window, startY, currentX, fillString(diff, barChar[0]).c_str()); + + currentX += diff; + prevAmount = amount; + + if(diff != 0 && amount < barSize) + mvwprintw(window, startY, currentX, "%s", point); + } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//Menus + + WinData* newWinData(WINDOW* window, int height, int width, int startY, int startX, int padY, int padX, \ + bool drawBorder, int bordersWE, int bordersNS) + { + WinData* data = new WinData; + + data->win = window; + + data->drawBorder = drawBorder; + data->bordersWE = bordersWE; + data->bordersNS = bordersNS; + + data->paddingY = padY; + data->paddingX = padX; + data->startY = startY; + data->startX = startX; + if(height == 0) + data->height = getmaxy(data->win); + else + data->height = height; + if(width == 0) + data->width = getmaxx(data->win); + else + data->width = width; + + return data; + } + + + void Menus::drawTitle() + { + /*Create title and message*/ + int padding = centreText(winData->width, title.length()); + mvwprintw(winData->win, 0, padding, title.c_str()); + wrefresh(winData->win); + } + + void Menus::drawFrame() + { + if(winData->drawBorder) + box(winData->win, winData->bordersWE, winData->bordersNS); + wrefresh(winData->win); + } + + + BasicMenu::BasicMenu(std::string lines, WinData* windowsData, std::string title) : Menus(windowsData, title) + { + text = lines; + TF::overflow(lines, _maxChars(), pageLayout); + numLines = pageLayout.size(); + + keypad(winData->win, true); + + this->cusAct = [](void) -> bool { + return false; + }; + } + + std::map BasicMenu::keyBindings = { + { KEY_NPAGE, NavContent::pageUp }, { KEY_PPAGE, NavContent::pageDown }, + { 27, NavContent::finish } + }; //27 => ESC + + int BasicMenu::getInput() + { + return wgetch(winData->win); + } + + NavContent BasicMenu::mapInput(int rawInput) + { + auto res = keyBindings.find(rawInput); + if(res != keyBindings.end()) + return res->second; + return NavContent::null; + } + + void BasicMenu::setCustomAction(F cusAct) + { + this->cusAct = cusAct; + } + + BasicMenu::F& BasicMenu::getCustomAction() + { + return cusAct; + } + + void BasicMenu::calculatePage(NavContent seek) + { + int startPage = page; + + switch(seek) + { + /* + * [pageUp] + * 1st case: turn over first page to last, 2nd case: page up, + * [pageDown] + * 3rd case: turn over last page to first, 4th case: page down + */ + case NavContent::pageUp: + if(page == 0) //1st + page = lastPage() -1; + else //2nd + --page; + break; + + case NavContent::pageDown: + if(page == lastPage() -1) //3rd + page = 0; + else //4th + ++page; + break; + + default: + break; + } + + if(startPage != page) + setUpdate(); + } + + void BasicMenu::inputHandling(NavContent& input) + { + input = mapInput(getInput()); + + if(input == NavContent::custom) + { + if(cusAct()) + update(); + } + } + + void BasicMenu::printStyle() + { + int x, y; + x = winData->paddingX; + y = winData->paddingY; + + for(int l = pageTop(); l <= pageBottom() && l < numLines; ++l) + { + auto info = pageLayout[l]; + int numChars = (info.second == _maxChars() +1? _maxChars() : info.second); //-1 so zero-initialised + size_t lineStart = info.first - numChars; + + wmove(winData->win, y, x); + waddstr(winData->win, text.substr(lineStart, numChars).c_str()); + ++y; + } + } + + void BasicMenu::drawContent() + { + int x, y; + + //Start postion of content in window + x = winData->paddingX; + y = winData->paddingY; + + eraseChunk(winData->win, y, _maxLines() +y, x, _maxChars() +x); + + printStyle(); + wrefresh(winData->win); + } + + void BasicMenu::drawPageNumber() + { + mvwprintw(winData->win, winData->height -1, 1, "pg %i / %i", page +1, lastPage()); + wrefresh(winData->win); + } + + bool BasicMenu::doUpdate() + { + bool old = changed; + changed = false; + return old; + } + + void BasicMenu::update() + { + drawContent(); + drawFrame(); + drawPageNumber(); + drawTitle(); + wrefresh(winData->win); + } + + void BasicMenu::menuLoop() + { + page = 0; + update(); + wrefresh(winData->win); + + NavContent input = NavContent::null; + do + { + inputHandling(input); + calculatePage(input); + if(doUpdate()) + update(); + } while(input != NavContent::finish); + } + + void BasicMenu::setDrawBorder(bool draw, int bordersWE, int bordersNS) + { + winData->drawBorder = draw; + winData->bordersWE = bordersWE; + winData->bordersNS = bordersNS; + } + + + SelectionMenu::SelectionMenu(std::string text, WinData* windowsData, std::string title) : BasicMenu(text, windowsData, title) + { + this->text = text; + + TF::overflow(text, _maxChars(), pageLayout, oFLayout); + oFLine = 0; + numLines = pageLayout.size(); + + for(int i = 0; i < numLines; ++i) + selected[i] = false; + + keyBindings[KEY_RIGHT] = NavContent::right; + keyBindings[KEY_LEFT] = NavContent::left; + keyBindings[KEY_UP] = NavContent::lineUp; + keyBindings[KEY_DOWN] = NavContent::lineDown; + keyBindings[10] = NavContent::select; //Enter key + + keypad(winData->win, true); + } + + void SelectionMenu::getResult(std::vector& chosen) + { + for(auto option : selected) + { + if(option.second == true) + chosen.push_back(option.first); + } + } + + void SelectionMenu::updateLineTrackers(int n) + { + highlight = n; + oFLine = 0; + } + + void SelectionMenu::drawCheckMark(int index, int y) + { + mvwprintw(winData->win, y, winData->width -1, (selected[index] ? checkMark : blankMark ).c_str()); + wrefresh(winData->win); + } + + void SelectionMenu::drawAllCheckMarks() + { + int y = winData->paddingY; + for(int cIndex = pageTop(); cIndex <= pageBottom() && cIndex < numLines; ++cIndex) + { + drawCheckMark(cIndex, y); + ++y; + } + } + + void SelectionMenu::calculatePage(NavContent seek) + { + //Deals with page up and down keys + BasicMenu::calculatePage(seek); + if(doUpdate()) + { + updateLineTrackers(pageTop()); + setUpdate(); + return; + } + + + int startPage = page; + + //Check if we need to turn to next page + //1st case: turn over last page to first, 2nd case: page up/next, 3rd case: turn over first page to last, 4th case: page down/back + switch(seek) + { + case NavContent::lineDown: + setUpdate(); + updateLineTrackers(highlight +1); + + if(highlight == numLines) //1st + { + page = 0; + updateLineTrackers(pageTop()); + break; + } + else if(highlight > pageBottom()) //2nd + { + ++page; + updateLineTrackers(pageTop()); + break; + } + break; + + case NavContent::lineUp: + setUpdate(); + updateLineTrackers(highlight -1); + + if(highlight == -1) //3rd + { + page = lastPage() -1; + updateLineTrackers(numLines -1); + } + else if(highlight < pageTop()) //4th + { + --page; + updateLineTrackers(pageBottom()); + } + break; + + default: + break; + } + + if(startPage != page) + setUpdate(); + } + + void SelectionMenu::navTrunc(NavContent input) + { + if(pageLayout[highlight].second != _maxChars() +1) + return; + + bool changed = false; + int numChars = 0; + size_t lineStart = 0; + + int first = 0; + int last = oFLayout.count(highlight); + + auto set = [&]() -> void { + auto temp = oFLayout.lower_bound(highlight); + for(int count = 1; count < oFLine; ++count) + { + ++temp; + } + numChars = (temp->second).second; + lineStart = ((temp->second).first) - numChars; + }; + + switch(input) + { + case NavContent::left: + if((oFLine -1) > first) + { + changed = true; + --oFLine; + set(); + } + else if((oFLine -1) < first) + break; + else // == first + { + changed = true; + oFLine = 0; + + auto info = pageLayout[highlight]; + numChars = (info.second == _maxChars() +1? _maxChars() : info.second); + lineStart = info.first - numChars; + } + break; + + case NavContent::right: + if(oFLine == first) + { + changed = true; + set(); + ++oFLine; + } + else if((oFLine +1) <= last) + { + changed = true; + ++oFLine; + set(); + } + break; + + default: + break; + } + + if(changed) + { + eraseChunk(winData->win, yCoord(), winData->paddingX, _maxChars()); + wattron(winData->win, A_REVERSE); + waddstr(winData->win, text.substr(lineStart, numChars).c_str()); + wattroff(winData->win, A_REVERSE); + } + + wrefresh(winData->win); + } + + void SelectionMenu::inputHandling(NavContent& input) + { + BasicMenu::inputHandling(input); + + + switch (input) + { + case NavContent::right: + navTrunc(input); + break; + + case NavContent::left: + navTrunc(input); + break; + + case NavContent::select: + if(selected[highlight] == true) + selected[highlight] = false; + else + selected[highlight] = true; + drawCheckMark(highlight, yCoord()); + break; + + case NavContent::finish: + //User done selecting + return; + + default: + break; + } + } + + void SelectionMenu::drawFrame() + { + BasicMenu::drawFrame(); + + drawAllCheckMarks(); + wrefresh(winData->win); + } + + void SelectionMenu::update() + { + BasicMenu::update(); + + drawAllCheckMarks(); + } + + void SelectionMenu::menuLoop(int startChoice) + { + updateLineTrackers(startChoice); + + BasicMenu::menuLoop(); + } + + void SelectionMenu::printStyle() + { + int x, y; + //Start postion of content in window + x = winData->paddingX; + y = winData->paddingY; + + for(int l = pageTop(); l <= pageBottom() && l < numLines; ++l) + { + auto info = pageLayout[l]; + int numChars = (info.second == _maxChars() +1? _maxChars() : info.second); + size_t lineStart = info.first - numChars; + + wmove(winData->win, y, x); + //Highlight the present choice + if(highlight == l) + { + wattron(winData->win, A_REVERSE); + waddstr(winData->win, text.substr(lineStart, numChars).c_str()); + wattroff(winData->win, A_REVERSE); + } + else + { + waddstr(winData->win, text.substr(lineStart, numChars).c_str()); + } + ++y; + } + } + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//Dialogs + + ttyProgressDialog::ttyProgressDialog(WinData* windowsData, std::string title, std::string mssg)\ + : Menus(windowsData, title), pBar(windowsData->win, 0, 0, 0) + { + //+2 to have space between eta data and message + progInfoPosY = winData->paddingY +2; + progInfoPosX = winData->paddingX; + + pBarPosY = progInfoPosY +1; + pBarPosX = progInfoPosX; + + message = mssg; + + progInfo = ""; + } + + void ttyProgressDialog::setDrawBorder(bool draw, int bordersWE, int bordersNS) + { + winData->drawBorder = draw; + winData->bordersWE = bordersWE; + winData->bordersNS = bordersNS; + } + + bool ttyProgressDialog::update(double progress, std::string mssg) + { + if(!mssg.empty()) + { + int diff = message.length() - mssg.length(); + if(diff > 0) + eraseChunk(winData->win, winData->paddingY, winData->paddingX +mssg.length(), diff); + + message = mssg; + mvwprintw(winData->win, winData->paddingY, winData->paddingX, message.c_str()); + } + + /*Update ETA*/ + //TODO add ETA + mvwprintw(winData->win, progInfoPosY, progInfoPosX, "%*.2f%%", 6, progress); + + /*Update progress bar*/ + pBar.update(progress); + + wrefresh(winData->win); + return true; + } + void ttyProgressDialog::dialog() + { + drawFrame(); + drawTitle(); + mvwprintw(winData->win, winData->paddingY, winData->paddingX, message.c_str()); + + /*Create ETA*/ + //This will be on the 4th line => index 3 + //TODO add ETA + mvwprintw(winData->win, progInfoPosY, progInfoPosX, progInfo.c_str()); + + /*Create space for progress bar*/ + //This will be on the 5th line => index 4 + pBar.setStartCoordinates(pBarPosY, pBarPosX); + pBar.setSize(winData->width - (winData->paddingX *2)); + pBar.first(); + + wrefresh(winData->win); + } + +} diff --git a/fetchconfig/fileOps.cpp b/fetchconfig/fileOps.cpp new file mode 100644 index 00000000..c7030335 --- /dev/null +++ b/fetchconfig/fileOps.cpp @@ -0,0 +1,35 @@ +/* + * fileOps.cpp + * + * Author: Zac + * Contact: codeohms@protonmail.com + * Created on: 17 Sep. 2018 + */ + + +#include "fileOps.h" + + +int getUserConfigDir(std::string& dir) +{ +#ifndef WIN32 + char* path = getenv("HOME"); + if(path == NULL) + return 1; + +#else + static char path[MAX_PATH]; + if(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, path)) + return 1; +#endif //WIN32 + + //TODO change forward slashes to back slashes for native windows path + dir = path; + return 0; +} + +bool fileExists(std::string fName) +{ + struct stat buffer; + return (stat (fName.c_str(), &buffer) == 0); +} diff --git a/fetchconfig/gimx-fetchconfig.cpp b/fetchconfig/gimx-fetchconfig.cpp new file mode 100644 index 00000000..de1ad7d4 --- /dev/null +++ b/fetchconfig/gimx-fetchconfig.cpp @@ -0,0 +1,120 @@ +/* + * gimxFileDownloader.cpp + * + * Author: Zac + * Contact: codeohms@protonmail.com + * Created on: 8 Aug. 2018 + */ +#include //for smart pointers +#include +#include + +#include +#include + +#include "parseArgs.h" +#include "easyCurses.h" +#include "configDownload.h" + +//Callback functionality for download progress +int progress_callback_configupdater_terminal(void *clientp, configupdater::ConfigUpdaterStatus status, double progress, double total); + + +void help() +{ + std::cout << "Usage: gimxFileDownloader