From 329793172214a4f467d495de106613fbea59361a Mon Sep 17 00:00:00 2001 From: proconsule <34319995+proconsule@users.noreply.github.com> Date: Sat, 5 Feb 2022 14:05:46 +0100 Subject: [PATCH] Merged preliminary 0.6.1 code (need cleanup) --- Makefile | 10 +- README.md | 4 +- include/appwindows.h | 11 + include/config.h | 1 + include/gui.h | 32 +- include/libmpv.h | 4 + include/localfiles.h | 2 +- include/platforms.h | 27 + include/utils.h | 1 + .../{ => opengl3}/imgui_impl_opengl3.cpp | 0 libs/imgui/{ => opengl3}/imgui_impl_opengl3.h | 0 romfs/ftp.png | Bin 17038 -> 7629 bytes romfs/http.png | Bin 17275 -> 9615 bytes romfs/network.png | Bin 7816 -> 27511 bytes romfs/nfs.png | Bin 0 -> 8144 bytes romfs/sftp.png | Bin 17927 -> 7876 bytes romfs/smb.png | Bin 18416 -> 9015 bytes romfs/upnp.png | Bin 0 -> 4742 bytes source/UI/apppopups.cpp | 2 +- source/UI/enigmaui.cpp | 2 + source/UI/mainMenu.cpp | 37 +- source/UI/networkBrowser.cpp | 174 +++++- source/UI/playerWindows.cpp | 7 + source/UI/upnpBrowser.cpp | 156 +++++ source/config.cpp | 11 +- source/curldownloader/curldownloader.cpp | 99 ++++ source/curldownloader/curldownloader.h | 33 ++ source/gui.cpp | 95 +++- source/libmpv.cpp | 20 + source/localfs/localFs.cpp | 7 + source/localfs/usb/usbfs.cpp | 5 +- source/localfs/usb/usbfs.h | 5 +- source/main.cpp | 123 +++- source/remotefs/Enigma2/Enigma2.cpp | 62 +- source/remotefs/Enigma2/Enigma2.h | 13 +- source/remotefs/HTTPDir/HTTPDir.cpp | 6 +- source/remotefs/HTTPDir/HTTPDir.h | 5 + source/remotefs/UPNP/NX-UPNP.cpp | 537 ++++++++++++++++++ source/remotefs/UPNP/NX-UPNP.h | 104 ++++ source/remotefs/UPNP/entities.c | 388 +++++++++++++ source/remotefs/UPNP/entities.h | 20 + source/remotefs/ftplib/FTPDir.cpp | 6 +- source/remotefs/ftplib/FTPDir.h | 5 + source/remotefs/ftplib/ftplib.h | 5 + source/remotefs/nfsDir/nfsDir.cpp | 182 ++++++ source/remotefs/nfsDir/nfsDir.h | 42 ++ source/remotefs/smb2/sambaDir.h | 5 + source/remotefs/sshDir/sshDir.cpp | 6 +- source/remotefs/sshDir/sshDir.h | 6 +- source/utils.cpp | 33 ++ 50 files changed, 2163 insertions(+), 130 deletions(-) create mode 100644 include/platforms.h rename libs/imgui/{ => opengl3}/imgui_impl_opengl3.cpp (100%) rename libs/imgui/{ => opengl3}/imgui_impl_opengl3.h (100%) create mode 100644 romfs/nfs.png create mode 100644 romfs/upnp.png create mode 100644 source/UI/upnpBrowser.cpp create mode 100644 source/curldownloader/curldownloader.cpp create mode 100644 source/curldownloader/curldownloader.h create mode 100644 source/remotefs/UPNP/NX-UPNP.cpp create mode 100644 source/remotefs/UPNP/NX-UPNP.h create mode 100644 source/remotefs/UPNP/entities.c create mode 100644 source/remotefs/UPNP/entities.h create mode 100644 source/remotefs/nfsDir/nfsDir.cpp create mode 100644 source/remotefs/nfsDir/nfsDir.h diff --git a/Makefile b/Makefile index 8119869..7280a68 100644 --- a/Makefile +++ b/Makefile @@ -39,14 +39,14 @@ include $(DEVKITPRO)/libnx/switch_rules #--------------------------------------------------------------------------------- TARGET := $(notdir $(CURDIR)) BUILD := build -SOURCES := libs/imgui libs/imgui/misc/freetype source source/touchcontrols source/playlist source/shadermania source/eqpreset source/database source/UI source/remotefs/smb2 source/remotefs/sshDir source/remotefs/Enigma2 source/localfs source/localfs/usb source/remotefs/ftplib source/remotefs/HTTPDir +SOURCES := libs/imgui libs/imgui/opengl3 libs/imgui/misc/freetype source source/curldownloader source/touchcontrols source/playlist source/shadermania source/eqpreset source/database source/UI source/remotefs/UPNP source/remotefs/nfsDir source/remotefs/smb2 source/remotefs/sshDir source/remotefs/Enigma2 source/localfs source/localfs/usb source/remotefs/ftplib source/remotefs/HTTPDir DATA := data -INCLUDES := libs/simpleini libs/imgui include source/touchcontrols source/playlist source/shadermania source/eqpreset source/database source/remotefs/smb2 source/remotefs/sshDir source/remotefs/Enigma2 source/localfs source/localfs/usb source/remotefs/ftplib source/remotefs/HTTPDir +INCLUDES := libs/simpleini libs/imgui libs/imgui/opengl3 include source/curldownloader source/touchcontrols source/playlist source/shadermania source/eqpreset source/database source/remotefs/UPNP source/remotefs/nfsDir source/remotefs/smb2 source/remotefs/sshDir source/remotefs/Enigma2 source/localfs source/localfs/usb source/remotefs/ftplib source/remotefs/HTTPDir ROMFS := romfs VERSION_MAJOR := 0 VERSION_MINOR := 6 -VERSION_MICRO := 0 +VERSION_MICRO := 1 APP_TITLE := NXMP APP_AUTHOR := proconsule @@ -60,7 +60,7 @@ ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE CFLAGS := -g -Wall -Wno-sign-compare -O2 -ffunction-sections \ $(ARCH) $(DEFINES) -CFLAGS += `sdl2-config --cflags` `freetype-config --cflags` -fpermissive +CFLAGS += `sdl2-config --cflags` `freetype-config --cflags` -fpermissive -DIMGUI_USE_WCHAR32 -I${PORTLIBS}/include/upnp/ CFLAGS += $(INCLUDE) -D__SWITCH__ $(BUILD_TYPE) @@ -72,7 +72,7 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fexceptions -DIMGUI_IMPL_OPENGL_LOADER_GLAD \ ASFLAGS := -g $(ARCH) LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) -LIBS := `curl-config --libs` `sdl2-config --libs` `freetype-config --libs` -lmpv -lssh2 -lswscale -lswresample -lavformat -lavfilter -lavcodec -lavutil -llzma -lopus -lvpx -lass -lharfbuzz -lfreetype -lfribidi -lpng -lbz2 -lusbhsfs -lntfs-3g -llwext4 -lglad -lEGL -lglapi -ldrm_nouveau -ltinyxml2 -lturbojpeg -llua -lmbedcrypto -lmbedx509 -lmbedtls -lmbedcrypto -lmbedx509 -lmbedtls -lsqlite3 -lsmb2 -lnx -lz +LIBS := `curl-config --libs` `sdl2-config --libs` `freetype-config --libs` -lmpv -lssh2 -lswscale -lswresample -lavformat -lavfilter -lavcodec -lavutil -llzma -lopus -lvpx -lass -lharfbuzz -lfreetype -lfribidi -lpng -lbz2 -lusbhsfs -lntfs-3g -llwext4 -lglad -lEGL -lglapi -ldrm_nouveau -ltinyxml2 -lturbojpeg -llua -lmbedcrypto -lmbedx509 -lmbedtls -lmbedcrypto -lmbedx509 -lmbedtls -lsqlite3 -lsmb2 -lupnp -lixml -lnfs -lnx -lz diff --git a/README.md b/README.md index 36bb4b3..3dba09c 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ source=TESTFtp|ftp://user:pass@192.168.1.10:21/path/ source=TestHTTP|http://192.168.1.10/path/ source=TestSFTp|sftp://192.168.1.10/path/ (without path it default to home) source=TestSamba|smb://192.168.1.10/share/ (share is a share name not a path) +source=TestNFS|nfs://192.168.1.10/export/ ``` Enigma2 have syntax @@ -69,9 +70,10 @@ Thanks to ----- - Cpasjuste for pPlay https://github.com/Cpasjuste/pplay some code was taken here (mpv part) - DarkMatterCore for libusbhsfs https://github.com/DarkMatterCore/libusbhsfs (this gives NXMP USB support) +- Chocola @ GBATemp forum for usage guide and beta testing +- sashka69 @ GBATemp forum for beta testing - bodyXY @ GBATemp forum for banner and icons - Godorowski @ GBATemp forum for player UI design and icons -- Chocola @ GBATemp forum for usage guide and beta testing - student @ reboot.ms forum for beta testing - docgold @ GBATemp forum for Enigma2 samples and support on decoders - tataniko @ GBATemp forum for suggestions and bug hunting diff --git a/include/appwindows.h b/include/appwindows.h index 7c520bb..e040982 100644 --- a/include/appwindows.h +++ b/include/appwindows.h @@ -2,6 +2,7 @@ #define NXMP_WINDOWS_H #include "imgui.h" +#include "platforms.h" namespace Windows { @@ -30,14 +31,24 @@ namespace Windows { }; void FileBrowserWindow(bool *focus, bool *first_item); +#ifdef NXMP_USBSUPPORT void USBBrowserWindow(bool *focus, bool *first_item); +#endif +#ifdef NXMP_ENIGMASUPPORT void EnigmaWindow(bool *focus, bool *first_item); +#endif void MainMenuWindow(bool *focus, bool *first_item); +#ifdef NXMP_NETWORKSUPPORT void FtpWindow(bool *focus, bool *first_item); void HttpWindow(bool *focus, bool *first_item); void SSHWindow(bool *focus, bool *first_item); void SambaWindow(bool *focus, bool *first_item); + void NFSWindow(bool *focus, bool *first_item); void NetworkWindow(bool *focus, bool *first_item); +#endif +#ifdef NXMP_UPNPSUPPORT + void UPNPBrowserWindow(bool *focus, bool *first_item); +#endif void InfoMenuWindow(bool *focus, bool *first_item); void VideoWindow(bool *focus, bool *first_item); void SettingsMenuWindow(bool *focus, bool *first_item); diff --git a/include/config.h b/include/config.h index c0c531f..3b21376 100644 --- a/include/config.h +++ b/include/config.h @@ -4,6 +4,7 @@ #include #include +#include "platforms.h" #include "SimpleIni.h" diff --git a/include/gui.h b/include/gui.h index 8e34a3c..d182258 100644 --- a/include/gui.h +++ b/include/gui.h @@ -4,6 +4,7 @@ #include #include #include +#include "platforms.h" #ifdef __SWITCH__ #include #endif @@ -22,12 +23,22 @@ #include "remotefs.h" #include "localFs.h" #include "utils.h" +#ifdef NXMP_USBSUPPORT #include "usbfs.h" +#endif +#ifdef NXMP_ENIGMASUPPORT #include "Enigma2.h" +#endif +#ifdef NXMP_NETWORKSUPPORT #include "HTTPDir.h" #include "FTPDir.h" #include "sshDir.h" #include "sambaDir.h" +#include "nfsDir.h" +#endif +#ifdef NXMP_UPNPSUPPORT +#include "NX-UPNP.h" +#endif #include "imgui_impl_sdl.h" @@ -44,6 +55,8 @@ enum MENU_STATES { MENU_STATE_HTTPBROWSER, MENU_STATE_SSHBROWSER, MENU_STATE_SAMBABROWSER, + MENU_STATE_NFSBROWSER, + MENU_STATE_UPNPBROWSER, MENU_STATE_ENIGMABROWSER, MENU_STATE_PLAYLISTBROWSER, MENU_STATE_INFO, @@ -137,17 +150,30 @@ typedef struct { extern SDL_Window *window; +#ifdef _WIN32 +extern bool fullscreen; +#endif extern MenuItem item; extern libMpv *libmpv; extern localFs *localdir; +#ifdef NXMP_NETWORKSUPPORT extern HTTPDir *httpdir; extern FTPDir *ftpdir; extern sshDir *sshdir; extern sambaDir *sambadir; -#ifdef __SWITCH__ +extern nfsDir *nfsdir; +#endif +#ifdef NXMP_UPNPSUPPORT +extern NXUPnP *nxupnp; +#endif +#ifdef NXMP_USBSUPPORT extern USBMounter *usbmounter; #endif +#ifdef NXMP_ENIGMASUPPORT +extern Enigma2 *enigma2; +#endif + extern uint32_t wakeup_on_mpv_render_update; extern uint32_t wakeup_on_mpv_events; @@ -159,8 +185,6 @@ extern bool renderloopdone; extern std::string nxmpTitle; -extern Enigma2 *enigma2; - extern Config *configini; extern EQPreset *eqpreset; extern SQLiteDB *sqlitedb; @@ -184,6 +208,8 @@ extern Tex FTPTexture; extern Tex HTTPTexture; extern Tex SFTPTexture; extern Tex SMBTexture; +extern Tex NFSTexture; +extern Tex UPNPTexture; extern Tex FFMPEGTexture; extern Tex MPVTexture; diff --git a/include/libmpv.h b/include/libmpv.h index 401f213..72bd8d5 100644 --- a/include/libmpv.h +++ b/include/libmpv.h @@ -84,6 +84,9 @@ class libMpv{ void setLoop(bool val); bool getLoop(); + void setAudioNormalize(bool val); + bool getAudioNormalize(); + void getfileInfo(); void setShader(std::string _filename); @@ -108,6 +111,7 @@ class libMpv{ fileInfo * fileinfo = nullptr; bool loop = false; + bool audionorm = false; int volume = 100; int tmpvolume = 100; diff --git a/include/localfiles.h b/include/localfiles.h index 1603972..6242d74 100644 --- a/include/localfiles.h +++ b/include/localfiles.h @@ -30,7 +30,7 @@ namespace FS { struct FileEntry{ std::string name; std::string path; - size_t size = 0; + int64_t size = 0; FileEntryType type = FileEntryType::Unknown; bool checked = false; }; diff --git a/include/platforms.h b/include/platforms.h new file mode 100644 index 0000000..ced2ba4 --- /dev/null +++ b/include/platforms.h @@ -0,0 +1,27 @@ +#ifndef NXMP_PLATFORMS_H +#define NXMP_PLATFORMS_H + +#ifdef _WIN32 + +#define NXMP_WIN32 +#define NXMP_NETWORKSUPPORT +#define NXMP_UPNPSUPPORT +#define NXMP_ENIGMASUPPORT + + +#endif + +#ifdef __SWITCH__ + + + +#define NXMP_SWITCH +#define NXMP_USBSUPPORT +#define NXMP_NETWORKSUPPORT +#define NXMP_UPNPSUPPORT +#define NXMP_ENIGMASUPPORT + +#endif + + +#endif \ No newline at end of file diff --git a/include/utils.h b/include/utils.h index e7e61cb..92d1585 100644 --- a/include/utils.h +++ b/include/utils.h @@ -43,6 +43,7 @@ namespace Utility{ urlschema parseUrl(std::string url); bool compare(const remotefs_entry &a, const remotefs_entry &b); bool TxtLoadFromFile(std::string filename, GLuint* out_texture, int* out_width, int* out_height); + bool TxtLoadFromMemory(unsigned char* data,size_t image_size, GLuint* out_texture, int* out_width, int* out_height); std::string toLower(const std::string &str); std::string toUpper(const std::string &str); bool endsWith(const std::string &value, const std::string &ending, bool sensitive); diff --git a/libs/imgui/imgui_impl_opengl3.cpp b/libs/imgui/opengl3/imgui_impl_opengl3.cpp similarity index 100% rename from libs/imgui/imgui_impl_opengl3.cpp rename to libs/imgui/opengl3/imgui_impl_opengl3.cpp diff --git a/libs/imgui/imgui_impl_opengl3.h b/libs/imgui/opengl3/imgui_impl_opengl3.h similarity index 100% rename from libs/imgui/imgui_impl_opengl3.h rename to libs/imgui/opengl3/imgui_impl_opengl3.h diff --git a/romfs/ftp.png b/romfs/ftp.png index 006d2673be2b0d87372119e490769344fa03a6be..17c09f1fc7b8aa64225e15c652a459cdce99fd8c 100644 GIT binary patch literal 7629 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 literal 17275 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 diff --git a/romfs/network.png b/romfs/network.png index 05e2f60305f700a05abc1f9489d2e99dc4876a15..b4ca31131ff5a128634de11412da2b9776890c20 100644 GIT binary patch delta 24608 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

G!n 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 xtb4E=gk zSaechcOYzvqd z-amfiei!rY2acoX=&zGG+@JdU{Kxg_ab%%yK9Kn32LCYQo(<=ApLf>%;?a0=?XjKv z|K79gy{_H0kO)J%Hg%aWAIorICrbX@mUw0UM}E)eRe2TL?2uq57CWyYRh)RcCCD_= zWXqDtamc{Of8dtO9Gx4^pL4}$SKaW|ix2ve?Xs_a`r@4*Zh7y6@$TV^9J}?{8&a@5 zg-e~Vm~hK)U*=C2{N%Owu*67&-OC*-{Bln7AR0!m?_5SgWZ!v|7QJ-W3wm@Z9;} zIvJ1{-NZ>3yieq&fFo>kPnL77i|#s4sgR%Olh5R)`bM%sh~OuO0)|@jF~k^C%(288 zn@K)}6jMq$Y0b6lbI38LoO8)Fx8h4Ev80kqDYdlfs;4AlO*Pk2Yi$jiEtnP?cQ>wR zt-J1ff9SENo_p!Fw*h=c7;&VLM;Udr>877y#+hcGW!Bl2ETCjoSaGG5S6Own4c4~b zVaJ_z-euR_9$CAy`ttgVtjV1yo3k!-mJsbTQFBJdTo5vzH3IzVM4H0P{w z$FXo1uJ7r ze~Hs)vs0Kg@3F1kH1h0wr1D|R+GFmmE%u<+uw{B_SM$01R#I7v9V{^WTo`c zFgkm$W96LY=otX*W>^Aq5?!A?a7Rr{UVtVO&^Zg{sDmhhf4@DN-(QgW0wrnJN@Zk4 ziMM!@*n9A+R}wl(TeSyM*(E-mZP*jue`$56aW)4+mjj)J8+MIpJHk?yZ?3qJL)U|U~0DstveZXQ^{!0wh>(kq%ZF@oS_Jw2mD zVJ`dDFTWg7J$?!X7?YOve2m|=F?UZ$yz6dC*1Se#vSemtbk%_oOYHNtN9ZR zn%GJSexk{6kE8i|;s-~~c0J{Dr(5ZRA=kI@dMsjItHct`CPH|6j}z=Czjmtl$3BTt z@NF?GHLTRkp5EYdKYx=0D4r|^f1~1&UA{Tyx^wBCY8*M9Qpd+_cK1Njx_;s>iPFqv zwG%7++&g87gPFl>m=Uu|^=(TqLV)l4#tTIf6;r$!;KU<{U2wP?PMQ-s1)Ee}#2LWAzIJ z+IU)-k199cmc6VzmmA0CrlAno-mpEWVLH6lT|5AgWEjQx-r!UV2qTCIn9 z`l@4tfY`K#PU4{Eyi$<`*=3xO3K!16%mc$`hz*x~JGh5LT;8l*e=t2#Bm_+Y*|0iJI+bi`iq zd3NP?)h#1k;qTFo26G556n5=U7rwAZ z*)hY~VyX)^56uvR_-PPJL0x$uxH=)2O4(2A-;lhibk>7Lr;SS)(91b19pP7Gs+K7e z`Z7={)`#j;j`=iq4Y}KkLic$Wa#l?Bo^PHFJsyRkJ=8edf1g`>U-9v|!T~w`-C3(O zFkM5SOLZos*$T!Md4r7g*bZ+$I9k5I+$C0oT!TDkbboNGN{I=gg>bIQY-4K>EqFs( zZM9Has=-#u5Ct|Wh$rNpW{$>hf3AP?{}^f%5}?qwx(^^Df@#zJTh#{v`ottxWtC)_ zc*La2izFr$f0cUpekz&AiDps7TGcK$ZIZU49-5nJ2}L6g3FxHF+GTn-C%u+d#n5um zS7e+)Qm>W)9yC?qR^J?=ox(5hfth40Ndb&H zXgrj{>){=&jQI<)ArLRKB;w?dg#8Aca`I=xe$QqWIRAk)2UDtaj*LvpB+P9vyh zP+o}6$oWu}Z-u&wk``?xT!#4H;cowZfsmbIFE

a<=VZwG0jh0(y6x5EE#o0&Qnj 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 TA$g5M00000NkvXXu0mjfTTJ+W diff --git a/romfs/nfs.png b/romfs/nfs.png new file mode 100644 index 0000000000000000000000000000000000000000..5e9db0c1412e9b9ae8f00dc86517fd0b3432754a GIT binary patch literal 8144 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 bi-eW0M<~~y9@0;)5Ql*JW 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 KMJt630{;)^0IpB~ diff --git a/romfs/smb.png b/romfs/smb.png index 5b084259ae79c53a1272ab1f718f69fd8b0c0985..c0c6c0cdfd649f22fc33ef425e323184484d7b23 100644 GIT binary patch literal 9015 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 T9}4{UeW9&x@T3~@Eb4y%9|{1Z literal 0 HcmV?d00001 diff --git a/source/UI/apppopups.cpp b/source/UI/apppopups.cpp index 1c6e0d8..2883af2 100644 --- a/source/UI/apppopups.cpp +++ b/source/UI/apppopups.cpp @@ -13,7 +13,7 @@ namespace Popups{ ImGui::Text("Start Playlist?"); ImVec2 button_size(ImGui::GetFontSize() * 7.0f, 0.0f); if (ImGui::Button("Yes", button_size)) { - if(item.state == MENU_STATE_FILEBROWSER || item.state == MENU_STATE_USB ||item.state == MENU_STATE_FTPBROWSER || item.state == MENU_STATE_HTTPBROWSER || item.state == MENU_STATE_SSHBROWSER || item.state == MENU_STATE_SAMBABROWSER){ + if(item.state == MENU_STATE_FILEBROWSER || item.state == MENU_STATE_USB ||item.state == MENU_STATE_FTPBROWSER || item.state == MENU_STATE_HTTPBROWSER || item.state == MENU_STATE_SSHBROWSER || item.state == MENU_STATE_SAMBABROWSER || item.state == MENU_STATE_NFSBROWSER){ item.popupstate = POPUP_STATE_NONE; Playlist::playlist_struct nextfile = playlist->getPlaylist()[0]; libmpv->loadFile(nextfile.fulluri); diff --git a/source/UI/enigmaui.cpp b/source/UI/enigmaui.cpp index 7a35c02..fe9ab3d 100644 --- a/source/UI/enigmaui.cpp +++ b/source/UI/enigmaui.cpp @@ -8,6 +8,7 @@ namespace Windows { +#ifdef NXMP_ENIGMASUPPORT void EnigmaWindow(bool *focus, bool *first_item) { Windows::SetupWindow(); std::vector topmenu = {"Local Files","Network","Enigma2"}; @@ -80,4 +81,5 @@ namespace Windows { } Windows::ExitWindow(); } +#endif } \ No newline at end of file diff --git a/source/UI/mainMenu.cpp b/source/UI/mainMenu.cpp index f5b8f6c..69b6472 100644 --- a/source/UI/mainMenu.cpp +++ b/source/UI/mainMenu.cpp @@ -16,33 +16,36 @@ namespace Windows { for (unsigned int n = 0; n < topmenu.size(); n++){ std::string itemid = "##" + std::to_string(n); if(topmenu[n] == "Local Files"){ - ImGui::Image((void*)(intptr_t)SdCardTexture.id, ImVec2(40,40)); + ImGui::Image((void*)(intptr_t)SdCardTexture.id, ImVec2(50,50)); } else if(topmenu[n] == "USB"){ - ImGui::Image((void*)(intptr_t)UsbTexture.id, ImVec2(40,40)); + ImGui::Image((void*)(intptr_t)UsbTexture.id, ImVec2(50,50)); } else if(topmenu[n] == "Network"){ - ImGui::Image((void*)(intptr_t)NetworkTexture.id, ImVec2(40,40)); + ImGui::Image((void*)(intptr_t)NetworkTexture.id, ImVec2(50,50)); + } + else if(topmenu[n] == "UPNP"){ + ImGui::Image((void*)(intptr_t)UPNPTexture.id, ImVec2(50,50)); } else if(topmenu[n] == "Enigma2"){ - ImGui::Image((void*)(intptr_t)Enigma2Texture.id, ImVec2(40,40)); + ImGui::Image((void*)(intptr_t)Enigma2Texture.id, ImVec2(50,50)); } else if(topmenu[n] == "Playlist"){ - ImGui::Image((void*)(intptr_t)PlaylistTexture.id, ImVec2(40,40)); + ImGui::Image((void*)(intptr_t)PlaylistTexture.id, ImVec2(50,50)); } else if(topmenu[n] == "Info"){ - ImGui::Image((void*)(intptr_t)InfoTexture.id, ImVec2(40,40)); + ImGui::Image((void*)(intptr_t)InfoTexture.id, ImVec2(50,50)); } else if(topmenu[n] == "Settings"){ - ImGui::Image((void*)(intptr_t)SettingsTexture.id, ImVec2(40,40)); + ImGui::Image((void*)(intptr_t)SettingsTexture.id, ImVec2(50,50)); } else if(topmenu[n] == "Exit"){ - ImGui::Image((void*)(intptr_t)ExitTexture.id, ImVec2(40,40)); + ImGui::Image((void*)(intptr_t)ExitTexture.id, ImVec2(50,50)); } ImGui::SameLine(); - ImGui::SetCursorPos({ImGui::GetCursorPos().x, ImGui::GetCursorPos().y + (40 - ImGui::GetFont()->FontSize) / 2}); + ImGui::SetCursorPos({ImGui::GetCursorPos().x, ImGui::GetCursorPos().y + (50 - ImGui::GetFont()->FontSize) / 2}); if (ImGui::Selectable(itemid.c_str(), selected == n)){ if(topmenu[n] == "Local Files"){ @@ -52,7 +55,7 @@ namespace Windows { item.first_item = true; } if(topmenu[n] == "USB"){ -#ifdef __SWITCH__ +#ifdef NXMP_USBSUPPORT usbInit(); item.state = MENU_STATE_USB; if(usbmounter == nullptr){ @@ -62,13 +65,26 @@ namespace Windows { #endif } if(topmenu[n] == "Network"){ +#ifdef NXMP_NETWORKSUPPORT item.networksources.clear(); item.networksources = configini->getNetworks(); item.state = MENU_STATE_NETWORKBROWSER; item.first_item = true; +#endif + } + if(topmenu[n] == "UPNP"){ +#ifdef NXMP_UPNPSUPPORT + printf("upnp selected\n"); + fflush(stdout); + nxupnp = new NXUPnP(); + nxupnp->Discovery(); + item.state = MENU_STATE_UPNPBROWSER; + item.first_item = true; +#endif } if(topmenu[n] == "Enigma2"){ +#ifdef NXMP_ENIGMASUPPORT enigma2 = new Enigma2(configini->getEnigma()); item.first_item = true; if(configini->getEnigma() == ""){ @@ -77,6 +93,7 @@ namespace Windows { enigma2->getServices(); item.state = MENU_STATE_ENIGMABROWSER; } +#endif } if(topmenu[n] == "Settings"){ configini->setLongSeek(configini->getLongSeek(false)); diff --git a/source/UI/networkBrowser.cpp b/source/UI/networkBrowser.cpp index a203a70..ab83eb0 100644 --- a/source/UI/networkBrowser.cpp +++ b/source/UI/networkBrowser.cpp @@ -8,7 +8,7 @@ namespace Windows { - +#ifdef NXMP_NETWORKSUPPORT void FtpWindow(bool *focus, bool *first_item){ Windows::SetupWindow(); if (ImGui::Begin("Ftp Browser", nullptr, ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_MenuBar)) { @@ -107,7 +107,8 @@ namespace Windows { } } - ImGui::TextColored(textcolor,"%s",thislist[n].name.c_str()); + std::string nametext = Utility::truncateLen(thislist[n].name,87); + ImGui::TextColored(textcolor,"%s",nametext.c_str()); if(thislist[n].type == FS::FileEntryType::File){ ImGui::SameLine(total_w-150); ImGui::TextColored(textcolor,"%s",Utility::humanSize(thislist[n].size).c_str()); @@ -245,7 +246,9 @@ namespace Windows { } } - ImGui::TextColored(textcolor,"%s",thislist[n].name.c_str()); + std::string nametext = Utility::truncateLen(thislist[n].name,87); + ImGui::TextColored(textcolor,"%s",nametext.c_str()); + if(item.selectionstate == FILE_SELECTION_CHECKBOX && thislist[n].type == FS::FileEntryType::File){ ImGui::Dummy(ImVec2(0.0,0.0)); ImGui::SetCursorPos({ImGui::GetCursorPos().x, ImGui::GetCursorPos().y-1.0f}); @@ -371,7 +374,151 @@ namespace Windows { } ImGui::SameLine(); - ImGui::TextColored(textcolor,"%s",thislist[n].name.c_str()); + std::string nametext = Utility::truncateLen(thislist[n].name,87); + ImGui::TextColored(textcolor,"%s",nametext.c_str()); + + if(thislist[n].type == FS::FileEntryType::File){ + ImGui::SameLine(total_w-150); + ImGui::TextColored(textcolor,"%s",Utility::humanSize(thislist[n].size).c_str()); + } + + + if(item.selectionstate == FILE_SELECTION_CHECKBOX && thislist[n].type == FS::FileEntryType::File){ + ImGui::Dummy(ImVec2(0.0,0.0)); + ImGui::SetCursorPos({ImGui::GetCursorPos().x, ImGui::GetCursorPos().y-1.0f}); + + } + if (selected) + ImGui::SetItemDefaultFocus(); + } + if (*first_item && thislist.size() >0) { + std::string itemid = "##" + std::to_string(0); + ImGui::SetFocusID(ImGui::GetID(itemid.c_str()), ImGui::GetCurrentWindow()); + ImGui::SetScrollY(0.0f); + *first_item = false; + } + if(*focus){ + if(item.selectionstate == FILE_SELECTION_NONE){ + std::string itemid = "##" + std::to_string(item.fileHoveredidx); + ImGui::SetFocusID(ImGui::GetID((itemid.c_str())), ImGui::GetCurrentWindow()); + } + if(item.selectionstate == FILE_SELECTION_CHECKBOX){ + std::string checkitemid = "##check" + std::to_string(item.fileHoveredidx); + ImGui::SetFocusID(ImGui::GetID((checkitemid.c_str())), ImGui::GetCurrentWindow()); + } + *focus = false; + } + if(triggerselect == true){ + *first_item = true; + } + } + //ImGuiContext& g = *GImGui; + //ImGui::NavMoveRequestTryWrapping(g.CurrentWindow, ImGuiNavMoveFlags_LoopY); + ImGui::EndListBox(); + + } + Windows::ExitWindow(); + } + + void NFSWindow(bool *focus, bool *first_item) { + Windows::SetupWindow(); + std::vector topmenu = {"Local Files","Network","Enigma2"}; + static unsigned int lastfocus = 0; + + if (ImGui::Begin("SSH Browser", nullptr, ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_MenuBar)) { + if(item.popupstate == POPUP_STATE_NONE){ + ImGui::SetNextWindowFocus(); + } + + if (ImGui::BeginMenuBar()) { + ImGui::Text("current path: %s",nfsdir->getCurrPath().c_str()); + ImGui::EndMenuBar(); + } + float total_w = ImGui::GetContentRegionAvail().x; + float total_h = ImGui::GetContentRegionAvail().y; + if (ImGui::BeginListBox("SSH Browser Menu",ImVec2(total_w, total_h))){ + + urlschema thisurl = Utility::parseUrl(nfsdir->getUrl()); + std::vector thislist = nfsdir->getCurrList(); + bool triggerselect = false; + for (unsigned int n = 0; n < thislist.size(); n++){ + + static int selected = -1; + + if(thislist[n].type == FS::FileEntryType::Directory){ + ImGui::Image((void*)(intptr_t)FolderTexture.id, ImVec2(40,40)); + }else{ + ImGui::Image((void*)(intptr_t)FileTexture.id, ImVec2(40,40)); + } + ImGui::SameLine(); + std::string itemid = "##" + std::to_string(n); + if(item.selectionstate == FILE_SELECTION_CHECKBOX){ + if(thislist[n].type == FS::FileEntryType::Directory){ + ImGui::SetCursorPos({ImGui::GetCursorPos().x, ImGui::GetCursorPos().y + (40 - ImGui::GetFont()->FontSize) / 2}); + if (ImGui::Selectable(itemid.c_str(), selected == n)){ + triggerselect = true; + nfsdir->DirList(thislist[n].path,configini->getshowHidden(false),Utility::getMediaExtensions()); + } + }else{ + ImGui::SetCursorPos({ImGui::GetCursorPos().x, ImGui::GetCursorPos().y + (40 - ImGui::GetFont()->FontSize - ImGui::GetStyle().FramePadding.y * 2) / 2}); + std::string checkitemid = "##check" + std::to_string(n); + if(thislist[n].type != FS::FileEntryType::Directory){ + if(ImGui::Checkbox(checkitemid.c_str(), nfsdir->checked(n))){ + std::string openurl = thisurl.scheme + std::string("://") + thisurl.server + thislist[n].path; + if(*nfsdir->checked(n)){ + playlist->appendFile(thislist[n].name,openurl); + } else { + playlist->removeFile(thislist[n].name,openurl); + } + } + } + } + if (ImGui::IsItemHovered()){ + item.fileHoveredidx = n; + } + } + if(item.selectionstate == FILE_SELECTION_NONE){ + ImGui::SetCursorPos({ImGui::GetCursorPos().x, ImGui::GetCursorPos().y + (40 - ImGui::GetFont()->FontSize) / 2}); + if (ImGui::Selectable(itemid.c_str(), selected == n)){ + if(nfsdir->getCurrList()[n].type == FS::FileEntryType::Directory){ + triggerselect = true; + nfsdir->DirList(thislist[n].path,configini->getshowHidden(false),Utility::getMediaExtensions()); + } + else{ + item.laststate = item.state; + playlist->clearPlaylist(); + nfsdir->clearChecked(); + std::string openurl = thisurl.scheme + std::string("://") + thisurl.server + thislist[n].path; + libmpv->loadFile(openurl); + if(configini->getDbActive(true)){ + libmpv->getFileInfo()->resume = sqlitedb->getResume(openurl); + if(libmpv->getFileInfo()->resume>0){ + item.popupstate = POPUP_STATE_RESUME; + } + } + } + } + if (ImGui::IsItemHovered()){ + item.fileHoveredidx = n; + } + } + ImVec4 textcolor = ImVec4(1.0f,1.0f,1.0f,1.0f); + if(sqlitedb != nullptr){ + std::string openurl = thisurl.scheme + std::string("://") + thisurl.server + thislist[n].path; + int dbfilestatus = sqlitedb->getFileDbStatus(openurl); + if(dbfilestatus == 2){ + textcolor = ImVec4(0.0f,1.0f,0.0f,1.0f); + } + if(dbfilestatus == 1){ + textcolor = ImVec4(0.0f,1.0f,1.0f,1.0f); + } + + } + ImGui::SameLine(); + + std::string nametext = Utility::truncateLen(thislist[n].name,87); + ImGui::TextColored(textcolor,"%s",nametext.c_str()); + if(thislist[n].type == FS::FileEntryType::File){ ImGui::SameLine(total_w-150); ImGui::TextColored(textcolor,"%s",Utility::humanSize(thislist[n].size).c_str()); @@ -568,19 +715,22 @@ namespace Windows { static int selected = -1; urlschema thisurl = Utility::parseUrl(item.networksources[n].url); if(thisurl.scheme == "ftp"){ - ImGui::Image((void*)(intptr_t)FTPTexture.id, ImVec2(40,40)); + ImGui::Image((void*)(intptr_t)FTPTexture.id, ImVec2(50,50)); } if(thisurl.scheme == "http"){ - ImGui::Image((void*)(intptr_t)HTTPTexture.id, ImVec2(40,40)); + ImGui::Image((void*)(intptr_t)HTTPTexture.id, ImVec2(50,50)); } if(thisurl.scheme == "sftp"){ - ImGui::Image((void*)(intptr_t)SFTPTexture.id, ImVec2(40,40)); + ImGui::Image((void*)(intptr_t)SFTPTexture.id, ImVec2(50,50)); } if(thisurl.scheme == "smb"){ - ImGui::Image((void*)(intptr_t)SMBTexture.id, ImVec2(40,40)); + ImGui::Image((void*)(intptr_t)SMBTexture.id, ImVec2(50,50)); + } + if(thisurl.scheme == "nfs"){ + ImGui::Image((void*)(intptr_t)NFSTexture.id, ImVec2(50,50)); } ImGui::SameLine(); - ImGui::SetCursorPos({ImGui::GetCursorPos().x, ImGui::GetCursorPos().y + (40 - ImGui::GetFont()->FontSize) / 2}); + ImGui::SetCursorPos({ImGui::GetCursorPos().x, ImGui::GetCursorPos().y + (50 - ImGui::GetFont()->FontSize) / 2}); if (ImGui::Selectable(item.networksources[n].name.c_str(), selected == n)){ item.first_item = true; @@ -606,6 +756,11 @@ namespace Windows { sambadir->DirList(sambadir->getBasePath(),configini->getshowHidden(false),Utility::getMediaExtensions()); item.state = MENU_STATE_SAMBABROWSER; } + if(thisurl.scheme == "nfs"){ + nfsdir = new nfsDir(item.networksources[n].url,playlist); + nfsdir->DirList(nfsdir->getBasePath(),configini->getshowHidden(false),Utility::getMediaExtensions()); + item.state = MENU_STATE_NFSBROWSER; + } if (selected) ImGui::SetItemDefaultFocus(); } @@ -621,4 +776,5 @@ namespace Windows { } Windows::ExitWindow(); } +#endif } \ No newline at end of file diff --git a/source/UI/playerWindows.cpp b/source/UI/playerWindows.cpp index 393d25c..5b81bcc 100644 --- a/source/UI/playerWindows.cpp +++ b/source/UI/playerWindows.cpp @@ -429,6 +429,12 @@ namespace playerWindows{ if(ImGui::DragInt("Volume", &drag_volume, 0.5f, 0, 200, "%d", ImGuiSliderFlags_NoInput)){ libmpv->setVolume(drag_volume,item.playershowcontrols); } + + bool audionrombool = libmpv->getAudioNormalize(); + if(ImGui::Checkbox("Normalize", &audionrombool)){ + libmpv->setAudioNormalize(audionrombool); + } + ImGui::SetCursorPosX((windowWidth - ImGui::CalcTextSize("Audio Delay", NULL, true).x) * 0.5f); ImGui::Text("Audio Delay"); if(ImGui::DragFloat("Audio Delay", &drag_audiodelay, 0.100f, -5.0f, 5.0f, "%.3f", ImGuiSliderFlags_NoInput)){ @@ -447,6 +453,7 @@ namespace playerWindows{ drag_audiodelay = 0.0f; libmpv->setVolume(drag_volume,false); libmpv->setAudioDelay(drag_audiodelay,false); + libmpv->setAudioNormalize(false); } } playerWindows::ExitWindow(); diff --git a/source/UI/upnpBrowser.cpp b/source/UI/upnpBrowser.cpp new file mode 100644 index 0000000..369b78f --- /dev/null +++ b/source/UI/upnpBrowser.cpp @@ -0,0 +1,156 @@ +#include "gui.h" +#include "imgui.h" +#include "imgui_internal.h" +#include "appwindows.h" +#include "utils.h" +#include "localfiles.h" + + +namespace Windows { +#ifdef NXMP_UPNPSUPPORT + void UPNPBrowserWindow(bool *focus, bool *first_item) { + Windows::SetupWindow(); + if (ImGui::Begin("UPNP Browser", nullptr, ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_MenuBar)) { + ImGui::SetNextWindowFocus(); + float total_w = ImGui::GetContentRegionAvail().x; + float total_h = ImGui::GetContentRegionAvail().y; + if(nxupnp->getSelDevice() == -1){ + if (ImGui::BeginListBox("UPNP Browser Menu",ImVec2(total_w, total_h))){ + static int selected = -1; + std::vector thislist = nxupnp->getDevicesList(); + for (unsigned int n = 0; n < thislist.size(); n++){ + std::string itemid = "##" + std::to_string(n); + + if(thislist[n]->devIcon.id == 0){ + + MemoryStruct *chunk = (MemoryStruct *)malloc(sizeof(MemoryStruct)); + curlDownloader * curldownload = new curlDownloader(); + curldownload->Download(thislist[n]->iconUrl.c_str(),chunk); + Utility::TxtLoadFromMemory(chunk->memory,chunk->size,&thislist[n]->devIcon.id,&thislist[n]->devIcon.width,&thislist[n]->devIcon.height); + free(chunk->memory); + free(chunk); + delete curldownload; + + } + ImGui::Image((void*)(intptr_t)thislist[n]->devIcon.id, ImVec2(80,80)); + ImGui::SameLine(); + if (ImGui::Selectable(itemid.c_str(), selected == n,0,ImVec2(0,80))){ + thislist[n]->parentList.push_back("0"); + thislist[n]->browseOID(); + nxupnp->setSelDevice(n); + + } + ImGui::SameLine(); + float currstartpos = ImGui::GetCursorPosX(); + + ImGui::Text("%s",thislist[n]->getfriendlyName().c_str()); + ImGui::SetCursorPosX(currstartpos); + ImGui::SetCursorPosY(ImGui::GetCursorPosY()-40.0f); + ImGui::Text("%s",thislist[n]->getIP().c_str()); + ImGui::Dummy(ImVec2(0,20)); + } + } + ImGui::EndListBox(); + + } else { + if (ImGui::BeginListBox("USB Browser Menu",ImVec2(total_w, total_h))){ + bool triggerselect = false; + int total_w = ImGui::GetContentRegionAvail().x; + static int selected = -1; + std::vector thislist = nxupnp->getDevice(nxupnp->getSelDevice())->currentlist; + for (unsigned int n = 0; n < thislist.size(); n++){ + if(thislist[n].type == UPNPTYPE::UPNPContainer){ + ImGui::Image((void*)(intptr_t)FolderTexture.id, ImVec2(40,40)); + }else{ + if(thislist[n].albumart.id ==0){ + ImGui::Image((void*)(intptr_t)FileTexture.id, ImVec2(40,40)); + }else{ + ImGui::Image((void*)(intptr_t)thislist[n].albumart.id, ImVec2(thislist[n].albumart.width/2,thislist[n].albumart.height/2)); + } + } + ImGui::SameLine(); + std::string itemid = "##" + std::to_string(n); + + ImGui::SetCursorPos({ImGui::GetCursorPos().x, ImGui::GetCursorPos().y + (40 - ImGui::GetFont()->FontSize) / 2}); + + if (ImGui::Selectable(itemid.c_str(), selected == n)){ + + if(thislist[n].type == UPNPTYPE::UPNPContainer){ + nxupnp->getDevice(nxupnp->getSelDevice())->parentList.push_back(thislist[n].uri); + nxupnp->getDevice(nxupnp->getSelDevice())->browseOID(); + triggerselect = true; + }else if(thislist[n].type == UPNPTYPE::UPNPItem){ + item.laststate = item.state; + libmpv->loadFileLive(thislist[n].uri,thislist[n].name); + //libmpv->loadFile(thislist[n].path); + /* + if(configini->getDbActive(true)){ + libmpv->getFileInfo()->resume = sqlitedb->getResume(thislist[n].path); + if(libmpv->getFileInfo()->resume>0){ + item.popupstate = POPUP_STATE_RESUME; + } + } + */ + } + } + /* + if(ImGui::IsItemVisible()){ + if(thislist[n].albumart.id ==0 && thislist[n].type == UPNPTYPE::UPNPItem){ + MemoryStruct *chunk = (MemoryStruct *)malloc(sizeof(MemoryStruct)); + curlDownloader * curldownload = new curlDownloader(); + curldownload->Download(thislist[n].albumarturi.c_str(),chunk); + Utility::TxtLoadFromMemory(chunk->memory,chunk->size,&thislist[n].albumart.id,&thislist[n].albumart.width,&thislist[n].albumart.height); + free(chunk->memory); + free(chunk); + delete curldownload; + } + + } + */ + + ImGui::SameLine(); + ImVec4 textcolor = ImVec4(1.0f,1.0f,1.0f,1.0f); + /* + if(sqlitedb != nullptr){ + int dbfilestatus = sqlitedb->getFileDbStatus(thislist[n].uri); + if(dbfilestatus == 2){ + textcolor = ImVec4(0.0f,1.0f,0.0f,1.0f); + } + if(dbfilestatus == 1){ + textcolor = ImVec4(0.0f,1.0f,1.0f,1.0f); + } + + } + */ + std::string nametext = Utility::truncateLen(thislist[n].name,87); + ImGui::TextColored(textcolor,"%s",nametext.c_str()); + + if(thislist[n].type == UPNPTYPE::UPNPItem){ + ImGui::SameLine(total_w-150); + ImGui::TextColored(textcolor,"%s",Utility::humanSize(thislist[n].size).c_str()); + } + + + if (selected) + ImGui::SetItemDefaultFocus(); + } + if (*first_item && thislist.size() >0) { + std::string itemid = "##" + std::to_string(0); + ImGui::SetFocusID(ImGui::GetID(itemid.c_str()), ImGui::GetCurrentWindow()); + ImGui::SetScrollY(0.0f); + *first_item = false; + } + if(triggerselect == true){ + *first_item = true; + } + + } + ImGui::EndListBox(); + } + + } + + Windows::ExitWindow(); + } +#endif +} \ No newline at end of file diff --git a/source/config.cpp b/source/config.cpp index 9aeed56..9b19629 100644 --- a/source/config.cpp +++ b/source/config.cpp @@ -113,16 +113,23 @@ Config::Config(std::string inifile){ topmenu.push_back("Local Files"); +#ifdef NXMP_USBSUPPORT topmenu.push_back("USB"); - +#endif +#ifdef NXMP_NETWORKSUPPORT ini->GetAllValues("Network", "source", values); if(getNetworks().size() != 0){ topmenu.push_back("Network"); } - +#endif +#ifdef NXMP_UPNPSUPPORT + topmenu.push_back("UPNP"); +#endif +#ifdef NXMP_ENIGMASUPPORT if(getEnigma() != ""){ topmenu.push_back("Enigma2"); } +#endif topmenu.push_back("Playlist"); topmenu.push_back("Settings"); topmenu.push_back("Info"); diff --git a/source/curldownloader/curldownloader.cpp b/source/curldownloader/curldownloader.cpp new file mode 100644 index 0000000..58cc8b9 --- /dev/null +++ b/source/curldownloader/curldownloader.cpp @@ -0,0 +1,99 @@ + +#include "curldownloader.h" + +static size_t +WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) +{ + size_t realsize = size * nmemb; + struct MemoryStruct *mem = (struct MemoryStruct *)userp; + + char *ptr = (char *)realloc(mem->memory, mem->size + realsize + 1); + if(!ptr) { + /* out of memory! */ + printf("not enough memory (realloc returned NULL)\n"); + return 0; + } + + mem->memory = ptr; + memcpy(&(mem->memory[mem->size]), contents, realsize); + mem->size += realsize; + mem->memory[mem->size] = 0; + + return realsize; +} + +curlDownloader::curlDownloader(){ + +} + +void curlDownloader::Download(char * url ,MemoryStruct * chunk){ + + CURL *curl_handle; + CURLcode res; + chunk->memory = (char *)malloc(1); + chunk->size = 0; + curl_global_init(CURL_GLOBAL_ALL); + curl_handle = curl_easy_init(); + curl_easy_setopt(curl_handle, CURLOPT_URL, url); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)chunk); + curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); + res = curl_easy_perform(curl_handle); + if(res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + } + else { + + + } + curl_easy_cleanup(curl_handle); + curl_global_cleanup(); +} + +SOAPcurlDownloader::SOAPcurlDownloader(){ + + +} + +void SOAPcurlDownloader::Download(char * url ,MemoryStruct * chunk, char * OID){ + struct curl_slist *headerchunk = NULL; + CURL *curl_handle; + CURLcode res; + chunk->memory = (char *)malloc(1); + chunk->size = 0; + curl_global_init(CURL_GLOBAL_ALL); + curl_handle = curl_easy_init(); + char soapdata[4096]; + + char * soapdatafmt = " \ + \ + \ + \ + %s \ + BrowseDirectChildren \ + * \ + 0 \ + 10000 \ + \ + \ + \ + "; + sprintf(soapdata,soapdatafmt,OID); + + + headerchunk = curl_slist_append(headerchunk, "Content-Type: text/xml; charset=\"utf-8\""); + headerchunk = curl_slist_append(headerchunk, "SOAPACTION: \"urn:schemas-upnp-org:service:ContentDirectory:1#Browse\""); + curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headerchunk); + curl_easy_setopt(curl_handle, CURLOPT_URL, url); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)chunk); + curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, soapdata); + curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, strlen(soapdata)); + + res = curl_easy_perform(curl_handle); + + curl_easy_cleanup(curl_handle); + curl_global_cleanup(); + +} diff --git a/source/curldownloader/curldownloader.h b/source/curldownloader/curldownloader.h new file mode 100644 index 0000000..9370da2 --- /dev/null +++ b/source/curldownloader/curldownloader.h @@ -0,0 +1,33 @@ +#ifndef NXMP_CURLDOWNLOADER_H +#define NXMP_CURLDOWNLOADER_H + +#include +#include +#include + + +struct MemoryStruct { + char *memory; + size_t size; +}; + + +class curlDownloader{ +public: + curlDownloader(); + void Download(char * url ,MemoryStruct * chunk); + +}; + + +class SOAPcurlDownloader{ +public: + SOAPcurlDownloader(); + void Download(char * url ,MemoryStruct * chunk, char * OID); + +}; + + + + +#endif diff --git a/source/gui.cpp b/source/gui.cpp index 05c904d..5cc7a29 100644 --- a/source/gui.cpp +++ b/source/gui.cpp @@ -1,6 +1,8 @@ #include #include +#include "platforms.h" + #include "gui.h" #include "localfiles.h" @@ -51,7 +53,7 @@ namespace GUI { SDL_Event event; while (SDL_PollEvent(&event)) { ImGui_ImplSDL2_ProcessEvent(&event); -#ifndef __SWITCH__ +#ifdef NXMP_WIN32 if (event.type == SDL_KEYDOWN) { Uint8 keycode = event.key.keysym.sym; //printf("Key code :%d\n",keycode); @@ -140,6 +142,17 @@ namespace GUI { SDL_PushEvent(&sdlevent); } + if(keycode == SDLK_f){ + if(fullscreen){ + SDL_SetWindowFullscreen(window,0); + fullscreen = false; + }else{ + SDL_SetWindowFullscreen(window,SDL_WINDOW_FULLSCREEN); + fullscreen = true; + } + + } + if(keycode == 45){ SDL_Event sdlevent; @@ -315,7 +328,7 @@ namespace GUI { } if (button == SDL_KEY_DLEFT){ - if((item.state == MENU_STATE_FILEBROWSER || item.state == MENU_STATE_USB || item.state == MENU_STATE_FTPBROWSER || item.state == MENU_STATE_HTTPBROWSER || item.state == MENU_STATE_SSHBROWSER || item.state == MENU_STATE_SAMBABROWSER) && item.popupstate == POPUP_STATE_NONE){ + if((item.state == MENU_STATE_FILEBROWSER || item.state == MENU_STATE_USB || item.state == MENU_STATE_FTPBROWSER || item.state == MENU_STATE_HTTPBROWSER || item.state == MENU_STATE_SSHBROWSER || item.state == MENU_STATE_SAMBABROWSER || item.state == MENU_STATE_NFSBROWSER) && item.popupstate == POPUP_STATE_NONE){ if(item.selectionstate == FILE_SELECTION_CHECKBOX){ item.focus = true; item.selectionstate = FILE_SELECTION_NONE; @@ -383,7 +396,7 @@ namespace GUI { } if (button == SDL_KEY_X){ - if(item.state == MENU_STATE_FILEBROWSER || item.state == MENU_STATE_FTPBROWSER || item.state == MENU_STATE_HTTPBROWSER || item.state == MENU_STATE_USB || item.state == MENU_STATE_SSHBROWSER || item.state == MENU_STATE_SAMBABROWSER){ + if(item.state == MENU_STATE_FILEBROWSER || item.state == MENU_STATE_FTPBROWSER || item.state == MENU_STATE_HTTPBROWSER || item.state == MENU_STATE_USB || item.state == MENU_STATE_SSHBROWSER || item.state == MENU_STATE_SAMBABROWSER || item.state == MENU_STATE_NFSBROWSER){ item.popupstate = POPUP_STATE_STARTPLAYLIST; } @@ -415,12 +428,13 @@ namespace GUI { delete localdir; localdir = nullptr; } -#ifdef __SWITCH__ +#ifdef NXMP_USBSUPPORT if(usbmounter != nullptr && libmpv->Stopped() && !usbmounter->haveIteminPlaylist()){ delete usbmounter; usbmounter = nullptr; } #endif +#ifdef NXMP_NETWORKSUPPORT if(ftpdir != nullptr){ delete ftpdir; ftpdir = nullptr; @@ -437,14 +451,23 @@ namespace GUI { delete sambadir; sambadir = nullptr; } +#endif +#ifdef NXMP_UPNPSUPPORT + if(nxupnp != nullptr){ + delete nxupnp; + nxupnp = nullptr; + } +#endif +#ifdef NXMP_ENIGMASUPPORT if(enigma2 != nullptr){ delete enigma2; enigma2 = nullptr; } +#endif item.networkselect = true; item.first_item = true; item.state = MENU_STATE_HOME; -#ifdef __SWITCH__ +#ifdef NXMP_USBSUPPORT if(usbmounter != nullptr && usbmounter->haveIteminPlaylist()){ usbmounter->setBasePath(""); } @@ -454,11 +477,13 @@ namespace GUI { } if (button == SDL_KEY_B){ +#ifdef NXMP_ENIGMASUPPORT if(item.state == MENU_STATE_ENIGMABROWSER){ item.first_item = true; enigma2->backToTop(); } - +#endif +#ifdef NXMP_NETWORKSUPPORT if(item.state == MENU_STATE_FTPBROWSER){ item.first_item = true; ftpdir->backDir(); @@ -483,9 +508,25 @@ namespace GUI { sambadir->DirList(sambadir->getCurrPath(),configini->getshowHidden(false),Utility::getMediaExtensions()); } - + if(item.state == MENU_STATE_NFSBROWSER){ + item.first_item = true; + nfsdir->backDir(); + nfsdir->DirList(nfsdir->getCurrPath(),configini->getshowHidden(false),Utility::getMediaExtensions()); + + } +#endif +#ifdef NXMP_UPNPSUPPORT + if(item.state == MENU_STATE_UPNPBROWSER){ + item.first_item = true; + if(nxupnp->getSelDevice()>-1){ + nxupnp->getDevice(nxupnp->getSelDevice())->back(); + + nxupnp->getDevice(nxupnp->getSelDevice())->browseOID(); + } + } +#endif if(item.state == MENU_STATE_USB){ -#ifdef __SWITCH__ +#ifdef NXMP_USBSUPPORT if(usbmounter->getBasePath() != ""){ item.first_item = true; usbmounter->backPath(); @@ -584,7 +625,7 @@ namespace GUI { } if (mp_event->event_id == MPV_EVENT_START_FILE) { -#ifdef __SWITCH__ +#ifdef NXMP_SWITCH appletSetMediaPlaybackState(true); #endif printf("START FILE\n"); @@ -593,7 +634,7 @@ namespace GUI { } //printf("Event id: %d\n",mp_event->event_id); if (mp_event->event_id == MPV_EVENT_END_FILE) { -#ifdef __SWITCH__ +#ifdef NXMP_SWITCH appletSetMediaPlaybackState(false); #endif struct mpv_event_end_file *eof = (struct mpv_event_end_file *)mp_event->data; @@ -699,40 +740,70 @@ namespace GUI { } break; case MENU_STATE_USB: +#ifdef NXMP_USBSUPPORT Windows::USBBrowserWindow(&item.focus, &item.first_item); if(item.popupstate == POPUP_STATE_STARTPLAYLIST){ Popups::PlaylistStartPlaylist(); } +#endif break; case MENU_STATE_NETWORKBROWSER: +#ifdef NXMP_NETWORKSUPPORT Windows::NetworkWindow(&item.focus, &item.first_item); +#endif break; case MENU_STATE_FTPBROWSER: +#ifdef NXMP_NETWORKSUPPORT Windows::FtpWindow(&item.focus, &item.first_item); if(item.popupstate == POPUP_STATE_STARTPLAYLIST){ Popups::PlaylistStartPlaylist(); } +#endif break; case MENU_STATE_HTTPBROWSER: +#ifdef NXMP_NETWORKSUPPORT Windows::HttpWindow(&item.focus, &item.first_item); if(item.popupstate == POPUP_STATE_STARTPLAYLIST){ Popups::PlaylistStartPlaylist(); } +#endif break; case MENU_STATE_SSHBROWSER: +#ifdef NXMP_NETWORKSUPPORT Windows::SSHWindow(&item.focus, &item.first_item); if(item.popupstate == POPUP_STATE_STARTPLAYLIST){ Popups::PlaylistStartPlaylist(); } +#endif break; case MENU_STATE_SAMBABROWSER: +#ifdef NXMP_NETWORKSUPPORT Windows::SambaWindow(&item.focus, &item.first_item); if(item.popupstate == POPUP_STATE_STARTPLAYLIST){ Popups::PlaylistStartPlaylist(); } +#endif + break; + case MENU_STATE_NFSBROWSER: +#ifdef NXMP_NETWORKSUPPORT + Windows::NFSWindow(&item.focus, &item.first_item); + if(item.popupstate == POPUP_STATE_STARTPLAYLIST){ + Popups::PlaylistStartPlaylist(); + } +#endif + break; + case MENU_STATE_UPNPBROWSER: +#ifdef NXMP_UPNPSUPPORT + Windows::UPNPBrowserWindow(&item.focus, &item.first_item); + //if(item.popupstate == POPUP_STATE_STARTPLAYLIST){ + // Popups::PlaylistStartPlaylist(); + //} +#endif break; case MENU_STATE_ENIGMABROWSER: +#ifdef NXMP_ENIGMASUPPORT Windows::EnigmaWindow(&item.focus, &item.first_item); +#endif break; case MENU_STATE_SETTINGS: Windows::SettingsMenuWindow(&item.focus, &item.first_item); @@ -859,10 +930,10 @@ namespace GUI { item.popupstate = POPUP_STATE_DBUPDATED; } item.first_item = true; -#ifdef __SWITCH__ +#ifdef NXMP_SWITCH while (!renderloopdone && appletMainLoop()) #endif -#ifdef _WIN32 +#ifdef NXMP_WIN32 while (!renderloopdone) #endif { diff --git a/source/libmpv.cpp b/source/libmpv.cpp index c1c2adf..f5f55ae 100644 --- a/source/libmpv.cpp +++ b/source/libmpv.cpp @@ -605,6 +605,26 @@ bool libMpv::getLoop(){ return loop; } +void libMpv::setAudioNormalize(bool val){ + if(val){ + std::string cmd = "no-osd af add @dynaudnorm:dynaudnorm=f=75:g=25:p=0.5"; + mpv_command_string(handle, cmd.c_str()); + audionorm = val; + + + }else{ + std::string cmd = "no-osd af remove @dynaudnorm"; + mpv_command_string(handle, cmd.c_str()); + audionorm = val; + } +} + +bool libMpv::getAudioNormalize(){ + return audionorm; +} + + + void libMpv::setShader(std::string _filename){ std::string command = std::string("no-osd change-list glsl-shaders set ") + _filename; mpv_command_string(handle,command.c_str()); diff --git a/source/localfs/localFs.cpp b/source/localfs/localFs.cpp index 67c1e93..1518956 100644 --- a/source/localfs/localFs.cpp +++ b/source/localfs/localFs.cpp @@ -55,9 +55,16 @@ void localFs::DirList(const std::string &path,bool showHidden,const std::vector< file.path = FS::removeLastSlash(path) + "/" + file.name; file.checked = playlist->isPresent(file.name,file.path); + + #ifdef _WIN32 + struct stat64 st{}; + if (stat64(file.path.c_str(), &st) == 0) { + file.size = (size_t) st.st_size; + #else struct stat st{}; if (stat(file.path.c_str(), &st) == 0) { file.size = (size_t) st.st_size; + #endif file.type = S_ISDIR(st.st_mode) ? FS::FileEntryType::Directory : FS::FileEntryType::File; } if(file.type == FS::FileEntryType::File){ diff --git a/source/localfs/usb/usbfs.cpp b/source/localfs/usb/usbfs.cpp index 8d45713..cefeca4 100644 --- a/source/localfs/usb/usbfs.cpp +++ b/source/localfs/usb/usbfs.cpp @@ -1,7 +1,10 @@ #include #include "usbfs.h" -#ifdef __SWITCH__ + +#include "platforms.h" + +#ifdef NXMP_USBSUPPORT static UEvent *g_statusChangeEvent, g_exitEvent; static UsbHsFsDevice *g_usbDevices; diff --git a/source/localfs/usb/usbfs.h b/source/localfs/usb/usbfs.h index a7a838a..ee44227 100644 --- a/source/localfs/usb/usbfs.h +++ b/source/localfs/usb/usbfs.h @@ -1,7 +1,10 @@ -#ifdef __SWITCH__ #ifndef NXMP_USBFS_H #define NXMP_USBFS_H +#include "platforms.h" + +#ifdef NXMP_USBSUPPORT + #include #include #include diff --git a/source/main.cpp b/source/main.cpp index 63a3a3b..213adbf 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,13 +1,18 @@ -#ifdef __SWITCH__ + +#include "platforms.h" + +#ifdef NXMP_SWITCH #include #endif + #include #include #include #include + #include "config.h" #include "gui.h" #include "utils.h" @@ -17,6 +22,9 @@ #include "FTPDir.h" #include "sshDir.h" #include "sambaDir.h" +#include "NX-UPNP.h" +#include "nfsDir.h" + @@ -32,26 +40,43 @@ #include "shaderMania.h" -//#define NDEBUG 1 +#define NDEBUG 1 static bool init(); SDL_Window *window; +#ifdef NXMP_WIN32 +bool fullscreen = false; +#endif + SDL_GLContext context; libMpv *libmpv = nullptr; localFs *localdir = nullptr; +#ifdef NXMP_NETWORKSUPPORT FTPDir *ftpdir = nullptr; HTTPDir *httpdir = nullptr; sshDir *sshdir = nullptr; sambaDir *sambadir = nullptr; -#ifdef __SWITCH__ +nfsDir *nfsdir = nullptr; +#endif + +#ifdef NXMP_UPNPSUPPORT +NXUPnP *nxupnp = nullptr; +#endif + +#ifdef NXMP_USBSUPPORT USBMounter *usbmounter = nullptr; #endif + +#ifdef NXMP_ENIGMASUPPORT +Enigma2 *enigma2 = nullptr; +#endif + Config *configini = nullptr; EQPreset *eqpreset = nullptr; SQLiteDB *sqlitedb = nullptr; bool dbUpdated = false; -Enigma2 *enigma2 = nullptr; + Playlist *playlist = nullptr; uint32_t wakeup_on_mpv_render_update; @@ -79,6 +104,8 @@ Tex FTPTexture; Tex HTTPTexture; Tex SFTPTexture; Tex SMBTexture; +Tex NFSTexture; +Tex UPNPTexture; Tex FFMPEGTexture; Tex MPVTexture; @@ -102,6 +129,33 @@ const GLuint WIDTH = 1280, HEIGHT = 720; std::string nxmpTitle = std::string("NXMP v") + std::to_string(VERSION_MAJOR) + std::string(".") + std::to_string(VERSION_MINOR) + std::string(".") + std::to_string(VERSION_MICRO); +void deinitTextures(){ + + glDeleteTextures(1, &SdCardTexture.id); + glDeleteTextures(1, &UsbTexture.id); + glDeleteTextures(1, &NetworkTexture.id); + glDeleteTextures(1, &Enigma2Texture.id); + glDeleteTextures(1, &FileTexture.id); + glDeleteTextures(1, &PlaylistTexture.id); + glDeleteTextures(1, &InfoTexture.id); + glDeleteTextures(1, &FFMPEGTexture.id); + glDeleteTextures(1, &HTTPTexture.id); + glDeleteTextures(1, &FTPTexture.id); + glDeleteTextures(1, &SFTPTexture.id); + glDeleteTextures(1, &SMBTexture.id); + glDeleteTextures(1, &MPVTexture.id); + glDeleteTextures(1, &ExitTexture.id); + glDeleteTextures(1, &NXMPBannerTexture.id); + glDeleteTextures(1, &PlayIcon.id); + glDeleteTextures(1, &StopIcon.id); + glDeleteTextures(1, &PauseIcon.id); + glDeleteTextures(1, &MuteIcon.id); + glDeleteTextures(1, &VolumeIcon.id); + glDeleteTextures(1, &LoopIcon.id); + glDeleteTextures(1, &NoLoopIcon.id); + +} + static bool init() { bool success = true; @@ -143,7 +197,7 @@ static bool init() { return success; } -#ifdef __SWITCH__ +#ifdef NXMP_SWITCH int main() { appletLockExit(); #else @@ -151,7 +205,7 @@ int main(int argc,char *argv[]){ #endif -#ifdef __SWITCH__ +#ifdef NXMP_SWITCH socketInitializeDefault(); #ifdef NDEBUG nxlinkStdio(); @@ -179,7 +233,7 @@ int main(int argc,char *argv[]){ shadermania = new shaderMania(); -#ifdef __SWITCH__ +#ifdef NXMP_SWITCH Result ret; if (R_FAILED(ret = romfsInit())) { printf("romfsInit() failed: 0x%x\n", ret); @@ -207,14 +261,14 @@ int main(int argc,char *argv[]){ ImGui::StyleColorsDark(); printf("Init MPV\n"); - GUI::initMpv(); + printf("Init SDL\n"); ImGui_ImplSDL2_InitForOpenGL(window, context); printf("Init OPENGL\n"); ImGui_ImplOpenGL3_Init("#version 430 core"); -#ifdef __SWITCH__ +#ifdef NXMP_SWITCH plInitialize(PlServiceType_System); if (R_FAILED(ret = nifmInitialize(NifmServiceType_User))) { printf("nifmInitialize(NifmServiceType_User) failed: 0x%x\n", ret); @@ -235,16 +289,20 @@ int main(int argc,char *argv[]){ int width = 0, height = 0, bpp = 0; ImFontConfig font_cfg; - font_cfg.FontDataOwnedByAtlas = false; + //font_cfg.FontDataOwnedByAtlas = false; printf("Loading TTF\n"); -#ifdef __SWITCH__ - io.Fonts->AddFontFromFileTTF("romfs:/DejaVuSans.ttf", 24.0f,&font_cfg, io.Fonts->GetGlyphRangesDefault()); - fontSmall = io.Fonts->AddFontFromFileTTF("romfs:/DejaVuSans.ttf", 16.0f,&font_cfg, io.Fonts->GetGlyphRangesDefault()); + + + +#ifdef NXMP_SWITCH + io.Fonts->AddFontFromFileTTF("romfs:/DejaVuSans.ttf", 24.0f,&font_cfg); + fontSmall = io.Fonts->AddFontFromFileTTF("romfs:/DejaVuSans.ttf", 16.0f,&font_cfg); #else - io.Fonts->AddFontFromFileTTF("./romfs/DejaVuSans.ttf", 24.0f,&font_cfg, io.Fonts->GetGlyphRangesDefault()); - fontSmall = io.Fonts->AddFontFromFileTTF("./romfs/DejaVuSans.ttf", 16.0f,&font_cfg, io.Fonts->GetGlyphRangesDefault());; + io.Fonts->AddFontFromFileTTF("./romfs/DejaVuSans.ttf", 24.0f,&font_cfg); + fontSmall = io.Fonts->AddFontFromFileTTF("./romfs/DejaVuSans.ttf", 16.0f,&font_cfg); #endif - font_cfg.MergeMode = true; + //font_cfg.MergeMode = true; + //io.Fonts->AddFontFromFileTTF("./romfs/DejaVuSans.ttf", 24.0f,&font_cfg, io.Fonts->GetGlyphRangesJapanese()); //io.Fonts->AddFontFromMemoryTTF(standard.address, standard.size, 28.0f, &font_cfg, io.Fonts->GetGlyphRangesJapanese()); /* static const ImWchar ranges[] = @@ -275,9 +333,12 @@ int main(int argc,char *argv[]){ }; - printf("Loading Extended Chars\n"); + static ImWchar ranges[] = { 0x1, 0x1FFFF, 0 }; + font_cfg.OversampleH = font_cfg.OversampleV = 1; + font_cfg.MergeMode = true; + //printf("Loading Extended Chars\n"); //io.Fonts->AddFontFromMemoryTTF (fonts_ext.address, fonts_ext.size, 24.0f, &font_cfg, ranges); -#ifdef __SWITCH__ +#ifdef NXMP_SWITCH io.Fonts->AddFontFromFileTTF("romfs:/DejaVuSans.ttf", 24.0f,&font_cfg, tmranges); fontSmall = io.Fonts->AddFontFromFileTTF("romfs:/DejaVuSans.ttf", 16.0f,&font_cfg, tmranges); #else @@ -292,7 +353,7 @@ int main(int argc,char *argv[]){ printf("Loading Textures\n"); -#ifdef __SWITCH__ +#ifdef NXMP_SWITCH Utility::TxtLoadFromFile("romfs:/sdcard.png",&SdCardTexture.id,&SdCardTexture.width,&SdCardTexture.height); Utility::TxtLoadFromFile("romfs:/usb.png",&UsbTexture.id,&UsbTexture.width,&UsbTexture.height); Utility::TxtLoadFromFile("romfs:/network.png",&NetworkTexture.id,&NetworkTexture.width,&NetworkTexture.height); @@ -307,6 +368,8 @@ int main(int argc,char *argv[]){ Utility::TxtLoadFromFile("romfs:/ftp.png",&FTPTexture.id,&FTPTexture.width,&FTPTexture.height); Utility::TxtLoadFromFile("romfs:/sftp.png",&SFTPTexture.id,&SFTPTexture.width,&SFTPTexture.height); Utility::TxtLoadFromFile("romfs:/smb.png",&SMBTexture.id,&SMBTexture.width,&SMBTexture.height); + Utility::TxtLoadFromFile("romfs:/nfs.png",&NFSTexture.id,&NFSTexture.width,&NFSTexture.height); + Utility::TxtLoadFromFile("romfs:/upnp.png",&UPNPTexture.id,&UPNPTexture.width,&UPNPTexture.height); Utility::TxtLoadFromFile("romfs:/mpv.png",&MPVTexture.id,&MPVTexture.width,&MPVTexture.height); Utility::TxtLoadFromFile("romfs:/exit.png",&ExitTexture.id,&ExitTexture.width,&ExitTexture.height); Utility::TxtLoadFromFile("romfs:/nxmp-banner.jpg",&NXMPBannerTexture.id,&NXMPBannerTexture.width,&NXMPBannerTexture.height); @@ -333,6 +396,8 @@ int main(int argc,char *argv[]){ Utility::TxtLoadFromFile("./romfs/ftp.png",&FTPTexture.id,&FTPTexture.width,&FTPTexture.height); Utility::TxtLoadFromFile("./romfs/sftp.png",&SFTPTexture.id,&SFTPTexture.width,&SFTPTexture.height); Utility::TxtLoadFromFile("./romfs/smb.png",&SMBTexture.id,&SMBTexture.width,&SMBTexture.height); + Utility::TxtLoadFromFile("./romfs/nfs.png",&NFSTexture.id,&NFSTexture.width,&NFSTexture.height); + Utility::TxtLoadFromFile("./romfs/upnp.png",&UPNPTexture.id,&UPNPTexture.width,&UPNPTexture.height); Utility::TxtLoadFromFile("./romfs/mpv.png",&MPVTexture.id,&MPVTexture.width,&MPVTexture.height); Utility::TxtLoadFromFile("./romfs/exit.png",&ExitTexture.id,&ExitTexture.width,&ExitTexture.height); Utility::TxtLoadFromFile("./romfs/nxmp-banner.jpg",&NXMPBannerTexture.id,&NXMPBannerTexture.width,&NXMPBannerTexture.height); @@ -347,26 +412,26 @@ int main(int argc,char *argv[]){ #endif - printf("Init Enigma2\n"); - + GUI::initMpv(); GUI::RenderLoop(); printf("Ending Render Loop\n"); delete libmpv; libmpv = nullptr; - + printf("Ending MPV\n"); if(localdir != nullptr){ delete localdir; localdir = nullptr; } -#ifdef __SWITCH__ +#ifdef NXMP_USBSUPPORT if(usbmounter != nullptr){ delete usbmounter; usbmounter = nullptr; } #endif +#ifdef NXMP_NETWORKSUPPORT if(ftpdir != nullptr){ delete ftpdir; ftpdir = nullptr; @@ -375,10 +440,19 @@ int main(int argc,char *argv[]){ delete httpdir; httpdir = nullptr; } +#endif +#ifdef NXMP_ENIGMASUPPORT if(enigma2 != nullptr){ delete enigma2; enigma2 = nullptr; } +#endif +#ifdef NXMP_UPNPSUPPORT + if(nxupnp != nullptr){ + delete nxupnp; + nxupnp = nullptr; + } +#endif if(sqlitedb != nullptr){ delete sqlitedb; @@ -389,6 +463,7 @@ int main(int argc,char *argv[]){ ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); SDL_GL_DeleteContext(context); + deinitTextures(); } SDL_DestroyWindow(window); window = NULL; @@ -398,7 +473,7 @@ int main(int argc,char *argv[]){ printf("Exit Services\n"); -#ifdef __SWITCH__ +#ifdef NXMP_SWITCH ncmExit(); plExit(); romfsExit(); diff --git a/source/remotefs/Enigma2/Enigma2.cpp b/source/remotefs/Enigma2/Enigma2.cpp index 40758f3..50b1c72 100644 --- a/source/remotefs/Enigma2/Enigma2.cpp +++ b/source/remotefs/Enigma2/Enigma2.cpp @@ -1,26 +1,8 @@ #include "Enigma2.h" +#ifdef NXMP_ENIGMASUPPORT #include "utils.h" -static size_t -WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) -{ - size_t realsize = size * nmemb; - struct MemoryStruct *mem = (struct MemoryStruct *)userp; - - char *ptr = (char *)realloc(mem->memory, mem->size + realsize + 1); - if(!ptr) { - /* out of memory! */ - printf("not enough memory (realloc returned NULL)\n"); - return 0; - } - - mem->memory = ptr; - memcpy(&(mem->memory[mem->size]), contents, realsize); - mem->size += realsize; - mem->memory[mem->size] = 0; - - return realsize; -} + string urlencode(string s) { @@ -47,31 +29,6 @@ string urlencode(string s) } -void Enigma2::curlDownload(char * url ,MemoryStruct * chunk){ - - CURL *curl_handle; - CURLcode res; - chunk->memory = (char *)malloc(1); - chunk->size = 0; - curl_global_init(CURL_GLOBAL_ALL); - curl_handle = curl_easy_init(); - curl_easy_setopt(curl_handle, CURLOPT_URL, url); - curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); - curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)chunk); - curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0"); - res = curl_easy_perform(curl_handle); - if(res != CURLE_OK) { - fprintf(stderr, "curl_easy_perform() failed: %s\n", - curl_easy_strerror(res)); - } - else { - - - } - curl_easy_cleanup(curl_handle); - curl_global_cleanup(); -} - vector Enigma2::parseBouquet(char * data){ @@ -109,7 +66,10 @@ void Enigma2::m3uParser(char * url){ m3uurl.append("/web/services.m3u?bRef="); m3uurl.append(urlencode(url)); MemoryStruct *chunk = (MemoryStruct *)malloc(sizeof(MemoryStruct)); - curlDownload((char *)m3uurl.c_str(),chunk); + //curlDownload((char *)m3uurl.c_str(),chunk); + + curlDownloader * curldownloader = new curlDownloader(); + curldownloader->Download((char *)m3uurl.c_str(),chunk); string s = chunk->memory; @@ -136,8 +96,8 @@ void Enigma2::m3uParser(char * url){ epguurl.append("/web/epgnow?bRef="); epguurl.append(urlencode(url)); MemoryStruct *chunk2 = (MemoryStruct *)malloc(sizeof(MemoryStruct)); - curlDownload((char *)epguurl.c_str(),chunk2); - + curldownloader->Download((char *)epguurl.c_str(),chunk2); + XMLDocument doc; doc.Parse( chunk2->memory ); XMLElement * pRootElement = doc.RootElement(); @@ -181,7 +141,8 @@ bool Enigma2::getServices(){ downurl.append(":"); downurl.append(e2schema.port); downurl.append("/web/getservices"); - curlDownload((char *)downurl.c_str(),chunk); + curlDownloader * curldownloader = new curlDownloader(); + curldownloader->Download((char *)downurl.c_str(),chunk); e2services = parseBouquet(chunk->memory); free(chunk->memory); free(chunk); @@ -204,4 +165,5 @@ void Enigma2::backToTop(){ Enigma2::Enigma2(std::string _url){ enigmaurl = _url; } - \ No newline at end of file + +#endif \ No newline at end of file diff --git a/source/remotefs/Enigma2/Enigma2.h b/source/remotefs/Enigma2/Enigma2.h index 1a51e64..70e1a29 100644 --- a/source/remotefs/Enigma2/Enigma2.h +++ b/source/remotefs/Enigma2/Enigma2.h @@ -1,9 +1,13 @@ #ifndef NXMP_ENIGMA2_H #define NXMP_ENIGMA2_H +#include "platforms.h" + +#ifdef NXMP_ENIGMASUPPORT + #include #include -#include +#include "curldownloader.h" #include #include #include @@ -12,10 +16,6 @@ using namespace std; using namespace tinyxml2; -struct MemoryStruct { - char *memory; - size_t size; -}; struct epgStruct{ string title; @@ -45,7 +45,7 @@ class Enigma2{ string enigma2ip; vector parseBouquet(char * data); void m3uParser(char * url); - void curlDownload(char * url ,MemoryStruct * chunk); + //void curlDownload(char * url ,MemoryStruct * chunk); void backToTop(); EnigmaServices getCurrBouquet(); @@ -65,3 +65,4 @@ class Enigma2{ }; #endif +#endif diff --git a/source/remotefs/HTTPDir/HTTPDir.cpp b/source/remotefs/HTTPDir/HTTPDir.cpp index 5d00f7b..bf53586 100644 --- a/source/remotefs/HTTPDir/HTTPDir.cpp +++ b/source/remotefs/HTTPDir/HTTPDir.cpp @@ -1,5 +1,7 @@ #include "HTTPDir.h" +#ifdef NXMP_NETWORKSUPPORT + static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; @@ -133,4 +135,6 @@ void HTTPDir::backDir(){ if(currentpath == basepath)return; currentpath = currentpath.substr(0, currentpath.find_last_of("\\/")); currentpath = currentpath.substr(0, currentpath.find_last_of("\\/")+1); -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/source/remotefs/HTTPDir/HTTPDir.h b/source/remotefs/HTTPDir/HTTPDir.h index 2fce2ff..583f4ec 100644 --- a/source/remotefs/HTTPDir/HTTPDir.h +++ b/source/remotefs/HTTPDir/HTTPDir.h @@ -1,6 +1,10 @@ #ifndef NXMP_HTTPDIR_H #define NXMP_HTTPDIR_H +#include + +#ifdef NXMP_NETWORKSUPPORT + #include #include #include @@ -41,4 +45,5 @@ class HTTPDir{ std::string currentpath = ""; }; +#endif #endif \ No newline at end of file diff --git a/source/remotefs/UPNP/NX-UPNP.cpp b/source/remotefs/UPNP/NX-UPNP.cpp new file mode 100644 index 0000000..2781635 --- /dev/null +++ b/source/remotefs/UPNP/NX-UPNP.cpp @@ -0,0 +1,537 @@ +#include "NX-UPNP.h" +#ifdef NXMP_UPNPSUPPORT + +#ifdef __SWITCH__ + +#include +#include + +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 + +#endif + +#include "tinyxml2.h" + +#ifdef _WIN32 +#ifndef __MSXML_LIBRARY_DEFINED__ +#define __MSXML_LIBRARY_DEFINED__ +#endif +#endif + +using namespace tinyxml2; + +#ifdef _WIN32 +#include +#include +#endif + + +#define MAX_DGRAM_SIZE 1024 + +const char *searchMediaServer = "M-SEARCH * HTTP/1.1\r\n" \ + "HOST: 239.255.255.250:1900\r\n" \ + "MAN: \"ssdp:discover\"\r\n" \ + "MX: 5\r\n" \ + "ST: urn:schemas-upnp-org:device:MediaServer:1\r\n" \ + "USER-AGENT: NXMP\r\n\r\n"; + +const char *searchContentServer = "M-SEARCH * HTTP/1.1\r\n" \ + "HOST: 239.255.255.250:1900\r\n" \ + "MAN: \"ssdp:discover\"\r\n" \ + "MX: 5\r\n" \ + "ST: urn:schemas-upnp-org:service:ContentDirectory:1\r\n" \ + "USER-AGENT: NXMP\r\n\r\n"; + +extern "C" size_t decode_html_entities_utf8(char *dest, const char *src); + +void *DiscoverThreadFunc(void* arg) +{ + NXUPnP* nxupnp = (NXUPnP *)arg; + nxupnp->ListenSSDPResponse(); + return (void *)0; +} + +std::string Device::getIP(){ + return serversddress; +} + +Device::Device(std::string _location){ + urlschema thisurl = Utility::parseUrl(_location); + serversddress = thisurl.server; + MemoryStruct *chunk = (MemoryStruct *)malloc(sizeof(MemoryStruct)); + curlDownloader * curldown = new curlDownloader(); + curldown->Download(_location.c_str(),chunk); + printf("%s\n",chunk->memory); + fflush(stdout); + tinyxml2::XMLDocument doc; + doc.Parse( chunk->memory ); + XMLElement * pRootElement = doc.RootElement(); + if (NULL != pRootElement) { + XMLElement * devicenode = pRootElement->FirstChildElement("device"); + XMLElement * UDNNameTag = devicenode->FirstChildElement("UDN"); + XMLElement * friendlyNameTag = devicenode->FirstChildElement("friendlyName"); + XMLElement * iconListTag = devicenode->FirstChildElement("iconList"); + XMLElement * iconnode = iconListTag->FirstChildElement("icon"); + while(iconnode){ + XMLElement * mymetag = iconnode->FirstChildElement("mimetype"); + XMLElement * urltag = iconnode->FirstChildElement("url"); + std::string mimetype = mymetag->GetText(); + std::string tmpiconurl = urltag->GetText(); + if(mimetype == "image/png"){ + iconUrl = thisurl.scheme + std::string("://") + thisurl.server + ":" +thisurl.port + tmpiconurl; + printf("Icon Url: %s\n",iconUrl.c_str()); + + break; + } + + + iconnode = iconnode->NextSiblingElement("icon"); + } + XMLElement * serviceListTag = devicenode->FirstChildElement("serviceList"); + XMLElement * servicenode = serviceListTag->FirstChildElement("service"); + while(servicenode){ + XMLElement * serviceTypetag = servicenode->FirstChildElement("serviceType"); + std::string serviceTypestring = serviceTypetag->GetText(); + if(serviceTypestring == "urn:schemas-upnp-org:service:ContentDirectory:1"){ + XMLElement * controlURLtag = servicenode->FirstChildElement("controlURL"); + controlUrl = thisurl.scheme + std::string("://") + thisurl.server + ":" +thisurl.port + std::string(controlURLtag->GetText()); + printf("Control URL: %s\n",controlUrl.c_str()); + fflush(stdout); + } + servicenode = servicenode->NextSiblingElement("service"); + } + + + friendlyName = friendlyNameTag->GetText(); + devUDN = UDNNameTag->GetText(); + printf("UDN: %s\n",friendlyName.c_str()); + printf("friendlyName: %s\n",devUDN.c_str()); + fflush(stdout); + } + + free(chunk->memory); + free(chunk); + + delete curldown; + +} + +std::string Device::getUDN(){ + return devUDN; +} + + +int NXUPnP::getSelDevice(){ + return seldevice; + +} + +Device * NXUPnP::getDevice(int idx){ + return deviceslist[idx]; + +} + +void NXUPnP::Discovery(){ + + +} + +NXUPnP::~NXUPnP(){ + if(searchthreadexit == 0){ + searchthreadexit = 1; + close(discoverSocket); + } + pthread_join(DiscoverThreadID,NULL); + +} + +void NXUPnP::addDevice(Device * _dev){ + bool found = false; + for(int i=0;igetUDN() == deviceslist[i]->getUDN()){ + found = true; + } + } + if(!found){ + deviceslist.push_back(_dev); + } +} + + +void Device::browseOID(){ + currentlist.clear(); + SOAPcurlDownloader *testcurl = new SOAPcurlDownloader(); + MemoryStruct *chunk = (MemoryStruct *)malloc(sizeof(MemoryStruct)); + testcurl->Download(controlUrl.c_str(),chunk,parentList.back().c_str()); + printf("CHUNK MEM\n"); + printf("RET: %s\n",chunk->memory); + fflush(stdout); + char * testret = NULL; + decode_html_entities_utf8(chunk->memory,0); + printf("RET2: %s\n",chunk->memory); + fflush(stdout); + tinyxml2::XMLDocument doc; + doc.Parse( chunk->memory ,strlen(chunk->memory)); + + XMLElement * BrowseResponseEle = doc.FirstChildElement( "s:Envelope" )->FirstChildElement( "s:Body" )->FirstChildElement( "u:BrowseResponse" ); + + + XMLElement * DIDEle = BrowseResponseEle->FirstChildElement( "Result" )->FirstChildElement( "DIDL-Lite" ); + + XMLElement * containernode = DIDEle->FirstChildElement("container"); + while(containernode){ + XMLElement * conttitleele = containernode->FirstChildElement("dc:title"); + + upnpres_struct tmpentry; + tmpentry.name = conttitleele->GetText(); + tmpentry.uri = containernode->Attribute("id"); + tmpentry.type = UPNPTYPE::UPNPContainer; + currentlist.push_back(tmpentry); + containernode = containernode->NextSiblingElement("container"); + + } + XMLElement * itemnode = DIDEle->FirstChildElement("item"); + while(itemnode){ + XMLElement * itemele = itemnode->FirstChildElement("dc:title"); + upnpres_struct tmpentry; + tmpentry.name = itemele->GetText(); + XMLElement * itemres = itemnode->FirstChildElement("res"); + tmpentry.uri = itemres->GetText(); +#ifdef __SWITCH__ + tmpentry.size = atoi(itemres->Attribute("size")); +#endif +#ifdef __WIN32 + tmpentry.size = _atoi64(itemres->Attribute("size")); +#endif + tmpentry.type = UPNPTYPE::UPNPItem; + currentlist.push_back(tmpentry); + + + itemnode = itemnode->NextSiblingElement("item"); + } + + fflush(stdout); + free(chunk->memory); + free(chunk); + + delete testcurl; + +} + +std::string Device::getfriendlyName(){ + return friendlyName; +} + +void NXUPnP::setSelDevice(int idx){ + if(searchthreadexit == 0){ + searchthreadexit = 1; + close(discoverSocket); + } + seldevice = idx; + +} + + +void Device::back(){ + if(parentList.size()>1){ + parentList.pop_back(); + } + } + +std::vector NXUPnP::getDevicesList(){ + return deviceslist; +} + +void NXUPnP::InitDiscoverThread(){ + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 0x10000000); + printf("InitDiscoverThread pthread_create\n"); + pthread_create(&DiscoverThreadID, &attr, DiscoverThreadFunc, this); + +} + +NXUPnP::NXUPnP(){ + +#ifdef _WIN32 + WSADATA wsadata; + int err; + + err = WSAStartup(MAKEWORD(2, 0), &wsadata); + if(err != 0) { + printf("WSAStartup failed with error: %d\n", err); + + }else{ + printf("WSAStartup ok\n"); + } +#endif + + struct sockaddr_in upnpControl, broadcast_addr; + discoverSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (discoverSocket == INVALID_SOCKET) + { + printf("socked failed INVALID_SOCKET\n"); + return; + } + + int reuse = 1; + setsockopt(discoverSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)); + +#ifdef _WIN32 + char ttl = 4; +#else + int ret = -1; + socklen_t ttl = 4; +#endif +char ipv4address[INET_ADDRSTRLEN]; +#ifdef _WIN32 + /* CODE TAKEN FROM LIBUPNP*/ + /* ---------------------------------------------------- */ + /* WIN32 implementation will use the IpHlpAPI library. */ + /* ---------------------------------------------------- */ + PIP_ADAPTER_ADDRESSES adapts = NULL; + PIP_ADAPTER_ADDRESSES adapts_item; + PIP_ADAPTER_UNICAST_ADDRESS uni_addr; + SOCKADDR *ip_addr; + struct in_addr v4_addr; + struct in6_addr v6_addr; + ULONG adapts_sz = 0; + ULONG ret; + int ifname_found = 0; + int valid_addr_found = 0; + char inet_addr4[INET_ADDRSTRLEN]; + char inet_addr6[INET6_ADDRSTRLEN]; + + char ipv4_ifname[180]; + int ipv4_ifindex; + + /* Get Adapters addresses required size. */ + ret = GetAdaptersAddresses(AF_UNSPEC, + GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_DNS_SERVER, + NULL, + adapts, + &adapts_sz); + if (ret != ERROR_BUFFER_OVERFLOW) { + + } + /* Allocate enough memory. */ + adapts = (PIP_ADAPTER_ADDRESSES)malloc(adapts_sz); + if (adapts == NULL) { + + } + /* Do the call that will actually return the info. */ + ret = GetAdaptersAddresses(AF_UNSPEC, + GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_DNS_SERVER, + NULL, + adapts, + &adapts_sz); + if (ret != ERROR_SUCCESS) { + free(adapts); + + } + /* Copy interface name, if it was provided. */ + + for (adapts_item = adapts; adapts_item != NULL; + adapts_item = adapts_item->Next) { + if (adapts_item->Flags & IP_ADAPTER_NO_MULTICAST || + adapts_item->OperStatus != IfOperStatusUp) { + continue; + } + /* + * Partial fix for Windows: Friendly name is wchar + * string, but currently p->gIF_NAME is char string. For + * now try to convert it, which will work with many (but + * not all) adapters. A full fix would require a lot of + * big changes (p->gIF_NAME to wchar string?). + */ + if (!ifname_found) { + /* We have found a valid interface name. Keep + * it. */ + char tmpIfName[180]; + wcstombs(tmpIfName, + adapts_item->FriendlyName, + 180); + strcpy(ipv4_ifname, tmpIfName); + ifname_found = 1; + } else { + char tmpIfName[180] = {0}; + wcstombs(tmpIfName, + adapts_item->FriendlyName, + sizeof(tmpIfName)); + if (strncmp(ipv4_ifname, + tmpIfName, + 180) != 0) { + /* This is not the interface we're + * looking for. + */ + continue; + } + } + /* Loop thru this adapter's unicast IP addresses. */ + uni_addr = adapts_item->FirstUnicastAddress; + while (uni_addr) { + ip_addr = uni_addr->Address.lpSockaddr; + switch (ip_addr->sa_family) { + case AF_INET: + memcpy(&v4_addr, + &((struct sockaddr_in *)ip_addr) + ->sin_addr, + sizeof(v4_addr)); + /* TODO: Retrieve IPv4 netmask */ + valid_addr_found = 1; + break; + case AF_INET6: + /* TODO: Retrieve IPv6 ULA or GUA + * address and its prefix */ + /* Only keep IPv6 link-local addresses. + */ + if (IN6_IS_ADDR_LINKLOCAL( + &((struct sockaddr_in6 *)ip_addr) + ->sin6_addr)) { + memcpy(&v6_addr, + &((struct sockaddr_in6 *) + ip_addr) + ->sin6_addr, + sizeof(v6_addr)); + /* TODO: Retrieve IPv6 LLA + * prefix */ + valid_addr_found = 1; + } + break; + default: + if (valid_addr_found == 0) { + /* Address is not IPv4 or IPv6 + * and no valid address has */ + /* yet been found for this + * interface. + * Discard interface name. */ + ifname_found = 0; + } + break; + } + /* Next address. */ + uni_addr = uni_addr->Next; + } + if (valid_addr_found == 1) { + ipv4_ifindex = adapts_item->IfIndex; + break; + } + } + free(adapts); + /* Failed to find a valid interface, or valid address. */ + if (ifname_found == 0 || valid_addr_found == 0) { + return; + } + inet_ntop(AF_INET, &v4_addr, inet_addr4, sizeof inet_addr4); + strcpy(ipv4address,inet_addr4); + + +#endif + + memset((char *) &upnpControl, 0, sizeof(upnpControl)); + upnpControl.sin_family = AF_INET; + upnpControl.sin_port = htons(0); + upnpControl.sin_addr.s_addr = htonl(INADDR_ANY); + if (bind(discoverSocket, (sockaddr*)&upnpControl, sizeof(upnpControl)) == SOCKET_ERROR) + { + printf("bind failed\n"); + fflush(stdout); + close(discoverSocket); + return; + } + +#ifdef __SWITCH__ + + nifmInitialize(NifmServiceType_User); + uint32_t currentIp; + nifmGetCurrentIpAddress(¤tIp); + inet_ntop(AF_INET, ¤tIp, ipv4address, INET_ADDRSTRLEN); +#endif + +#ifdef _WIN32 + + + struct in_addr addr; + memset((void *)&addr, 0, sizeof(struct in_addr)); + addr.s_addr = inet_addr(ipv4address); + ret = setsockopt(discoverSocket, + IPPROTO_IP, + IP_MULTICAST_IF, + (char *)&addr, + sizeof addr); + if (ret == -1) { + printf("Failed to set Multicast IF\n"); + } + +#endif + + struct timeval tv; + tv.tv_sec = 1; + tv.tv_usec = 0; + setsockopt(discoverSocket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); + + + int onOff = 1; + ret = setsockopt(discoverSocket, + SOL_SOCKET, + SO_BROADCAST, + (char *)&onOff, + sizeof(onOff)); + + InitDiscoverThread(); + +} + +void NXUPnP::ListenSSDPResponse() +{ + char buf[MAX_DGRAM_SIZE]; + memset(buf, 0, MAX_DGRAM_SIZE); + + struct sockaddr_in broadcast_addr; + memset((char *) &broadcast_addr, 0, sizeof(broadcast_addr)); + broadcast_addr.sin_family = AF_INET; + broadcast_addr.sin_port = htons(1900); + broadcast_addr.sin_addr.s_addr = inet_addr("239.255.255.250"); + int bcLen = sizeof(broadcast_addr); + + if(sendto(discoverSocket, searchMediaServer, strlen(searchMediaServer), 0, (sockaddr *)&broadcast_addr, sizeof(broadcast_addr)) == SOCKET_ERROR) + { + printf("Error Sending SSDP\n"); + }else{ + printf("Sent SSDP\n"); + } + + while(searchthreadexit == 0){ + + + while (recvfrom(discoverSocket, buf, MAX_DGRAM_SIZE, 0, (struct sockaddr*)&broadcast_addr, (socklen_t*)&bcLen) >0) + { + + printf("buf:%s\n",buf); + if(strstr(buf, "LOCATION:")){ + std::ostringstream ss; + int i = 0; + char *deviceIp = strstr(buf, "LOCATION:") + 9; + while(deviceIp[i] == ' ') i++; + while(!isspace(deviceIp[i])) + { + ss << deviceIp[i]; + ++i; + } + std::string location = ss.str().c_str(); + printf("location:%s\n",location.c_str()); + Device *tmpdev = new Device(location); + addDevice(tmpdev); + } + memset(buf, 0, MAX_DGRAM_SIZE); + + } + + } + printf("Exit SSDP Read Thread\n"); + fflush(stdout); +} + +#endif \ No newline at end of file diff --git a/source/remotefs/UPNP/NX-UPNP.h b/source/remotefs/UPNP/NX-UPNP.h new file mode 100644 index 0000000..31fbb6a --- /dev/null +++ b/source/remotefs/UPNP/NX-UPNP.h @@ -0,0 +1,104 @@ +#ifndef NXMP_UPNP_H +#define NXMP_UPNP_H + +#include "platforms.h" + +#ifdef NXMP_UPNPSUPPORT + +#include +#include +#include +#include +#include +#include +#include + + + +#include "curldownloader.h" +#include "localfiles.h" + +#include +#include + +#ifdef __SWITCH__ +#include + +#endif + +using namespace tinyxml2; + + +#define _GNU_SOURCE + + +typedef enum UPNPTYPE { + UPNPItem, + UPNPContainer +} UPNPTYPE; + + +struct upnpres_struct{ + std::string name; + std::string uri; + int64_t size; + UPNPTYPE type; + Tex albumart; + std::string albumarturi; +}; + +class Device{ +public: + Device(std::string _location); + ~Device(); + std::string getIP(); + std::string getfriendlyName(); + std::string getUDN(); + Tex devIcon; + std::string iconUrl; + void browseOID(); + void back(); + std::vector currentlist; + std::vector parentList; + +private: + std::string devUDN; + std::string friendlyName; + std::string currentID = "0"; + std::string parentID = "0"; + + char * baseUrl = NULL; + std::string serversddress; + std::string controlUrl; + + + +}; + +class NXUPnP{ +public: + NXUPnP(); + ~NXUPnP(); + void InitDiscoverThread(); + void ListenSSDPResponse(); + void Discovery(); + void addDevice(Device * _dev); + Device * getDevice(int idx); + std::vector getDevicesList(); + //UpnpClient_Handle CtrlPnt; + int getSelDevice(); + void setSelDevice(int idx); + +private: + pthread_t DiscoverThreadID; + int discoverSocket = -1; + //UpnpLib * libpointer = NULL; + std::vector deviceslist; + int seldevice = -1; + + int searchthreadexit = 0; + +}; + +#endif +#endif \ No newline at end of file diff --git a/source/remotefs/UPNP/entities.c b/source/remotefs/UPNP/entities.c new file mode 100644 index 0000000..f1f8049 --- /dev/null +++ b/source/remotefs/UPNP/entities.c @@ -0,0 +1,388 @@ +/* Copyright 2012, 2016 Christoph Gärtner + Distributed under the Boost Software License, Version 1.0 +*/ + +#include "entities.h" + +#include +#include +#include +#include + +#define UNICODE_MAX 0x10FFFFul + +static const char *const NAMED_ENTITIES[][2] = { + { "AElig;", "Æ" }, + { "Aacute;", "Á" }, + { "Acirc;", "Â" }, + { "Agrave;", "À" }, + { "Alpha;", "Α" }, + { "Aring;", "Ã…" }, + { "Atilde;", "Ã" }, + { "Auml;", "Ä" }, + { "Beta;", "Î’" }, + { "Ccedil;", "Ç" }, + { "Chi;", "Χ" }, + { "Dagger;", "‡" }, + { "Delta;", "Δ" }, + { "ETH;", "Ð" }, + { "Eacute;", "É" }, + { "Ecirc;", "Ê" }, + { "Egrave;", "È" }, + { "Epsilon;", "Ε" }, + { "Eta;", "Η" }, + { "Euml;", "Ë" }, + { "Gamma;", "Γ" }, + { "Iacute;", "Í" }, + { "Icirc;", "ÃŽ" }, + { "Igrave;", "ÃŒ" }, + { "Iota;", "Ι" }, + { "Iuml;", "Ï" }, + { "Kappa;", "Κ" }, + { "Lambda;", "Λ" }, + { "Mu;", "Îœ" }, + { "Ntilde;", "Ñ" }, + { "Nu;", "Ν" }, + { "OElig;", "Å’" }, + { "Oacute;", "Ó" }, + { "Ocirc;", "Ô" }, + { "Ograve;", "Ã’" }, + { "Omega;", "Ω" }, + { "Omicron;", "Ο" }, + { "Oslash;", "Ø" }, + { "Otilde;", "Õ" }, + { "Ouml;", "Ö" }, + { "Phi;", "Φ" }, + { "Pi;", "Î " }, + { "Prime;", "″" }, + { "Psi;", "Ψ" }, + { "Rho;", "Ρ" }, + { "Scaron;", "Å " }, + { "Sigma;", "Σ" }, + { "THORN;", "Þ" }, + { "Tau;", "Τ" }, + { "Theta;", "Θ" }, + { "Uacute;", "Ú" }, + { "Ucirc;", "Û" }, + { "Ugrave;", "Ù" }, + { "Upsilon;", "Î¥" }, + { "Uuml;", "Ãœ" }, + { "Xi;", "Ξ" }, + { "Yacute;", "Ý" }, + { "Yuml;", "Ÿ" }, + { "Zeta;", "Ζ" }, + { "aacute;", "á" }, + { "acirc;", "â" }, + { "acute;", "´" }, + { "aelig;", "æ" }, + { "agrave;", "à " }, + { "alefsym;", "ℵ" }, + { "alpha;", "α" }, + { "amp;", "&" }, + { "and;", "∧" }, + { "ang;", "∠" }, + { "apos;", "'" }, + { "aring;", "Ã¥" }, + { "asymp;", "≈" }, + { "atilde;", "ã" }, + { "auml;", "ä" }, + { "bdquo;", "„" }, + { "beta;", "β" }, + { "brvbar;", "¦" }, + { "bull;", "•" }, + { "cap;", "∩" }, + { "ccedil;", "ç" }, + { "cedil;", "¸" }, + { "cent;", "¢" }, + { "chi;", "χ" }, + { "circ;", "ˆ" }, + { "clubs;", "♣" }, + { "cong;", "≅" }, + { "copy;", "©" }, + { "crarr;", "↵" }, + { "cup;", "∪" }, + { "curren;", "¤" }, + { "dArr;", "⇓" }, + { "dagger;", "†" }, + { "darr;", "↓" }, + { "deg;", "°" }, + { "delta;", "δ" }, + { "diams;", "♦" }, + { "divide;", "÷" }, + { "eacute;", "é" }, + { "ecirc;", "ê" }, + { "egrave;", "è" }, + { "empty;", "∅" }, + { "emsp;", "\xE2\x80\x83" }, + { "ensp;", "\xE2\x80\x82" }, + { "epsilon;", "ε" }, + { "equiv;", "≡" }, + { "eta;", "η" }, + { "eth;", "ð" }, + { "euml;", "ë" }, + { "euro;", "€" }, + { "exist;", "∃" }, + { "fnof;", "Æ’" }, + { "forall;", "∀" }, + { "frac12;", "½" }, + { "frac14;", "¼" }, + { "frac34;", "¾" }, + { "frasl;", "⁄" }, + { "gamma;", "γ" }, + { "ge;", "≥" }, + { "gt;", ">" }, + { "hArr;", "⇔" }, + { "harr;", "↔" }, + { "hearts;", "♥" }, + { "hellip;", "…" }, + { "iacute;", "í" }, + { "icirc;", "î" }, + { "iexcl;", "¡" }, + { "igrave;", "ì" }, + { "image;", "â„‘" }, + { "infin;", "∞" }, + { "int;", "∫" }, + { "iota;", "ι" }, + { "iquest;", "¿" }, + { "isin;", "∈" }, + { "iuml;", "ï" }, + { "kappa;", "κ" }, + { "lArr;", "⇐" }, + { "lambda;", "λ" }, + { "lang;", "〈" }, + { "laquo;", "«" }, + { "larr;", "←" }, + { "lceil;", "⌈" }, + { "ldquo;", "“" }, + { "le;", "≤" }, + { "lfloor;", "⌊" }, + { "lowast;", "∗" }, + { "loz;", "â—Š" }, + { "lrm;", "\xE2\x80\x8E" }, + { "lsaquo;", "‹" }, + { "lsquo;", "‘" }, + { "lt;", "<" }, + { "macr;", "¯" }, + { "mdash;", "—" }, + { "micro;", "µ" }, + { "middot;", "·" }, + { "minus;", "−" }, + { "mu;", "μ" }, + { "nabla;", "∇" }, + { "nbsp;", "\xC2\xA0" }, + { "ndash;", "–" }, + { "ne;", "≠" }, + { "ni;", "∋" }, + { "not;", "¬" }, + { "notin;", "∉" }, + { "nsub;", "⊄" }, + { "ntilde;", "ñ" }, + { "nu;", "ν" }, + { "oacute;", "ó" }, + { "ocirc;", "ô" }, + { "oelig;", "Å“" }, + { "ograve;", "ò" }, + { "oline;", "‾" }, + { "omega;", "ω" }, + { "omicron;", "ο" }, + { "oplus;", "⊕" }, + { "or;", "∨" }, + { "ordf;", "ª" }, + { "ordm;", "º" }, + { "oslash;", "ø" }, + { "otilde;", "õ" }, + { "otimes;", "⊗" }, + { "ouml;", "ö" }, + { "para;", "¶" }, + { "part;", "∂" }, + { "permil;", "‰" }, + { "perp;", "⊥" }, + { "phi;", "φ" }, + { "pi;", "Ï€" }, + { "piv;", "Ï–" }, + { "plusmn;", "±" }, + { "pound;", "£" }, + { "prime;", "′" }, + { "prod;", "∏" }, + { "prop;", "∝" }, + { "psi;", "ψ" }, + { "quot;", "\"" }, + { "rArr;", "⇒" }, + { "radic;", "√" }, + { "rang;", "〉" }, + { "raquo;", "»" }, + { "rarr;", "→" }, + { "rceil;", "⌉" }, + { "rdquo;", "”" }, + { "real;", "â„œ" }, + { "reg;", "®" }, + { "rfloor;", "⌋" }, + { "rho;", "ρ" }, + { "rlm;", "\xE2\x80\x8F" }, + { "rsaquo;", "›" }, + { "rsquo;", "’" }, + { "sbquo;", "‚" }, + { "scaron;", "Å¡" }, + { "sdot;", "â‹…" }, + { "sect;", "§" }, + { "shy;", "\xC2\xAD" }, + { "sigma;", "σ" }, + { "sigmaf;", "Ï‚" }, + { "sim;", "∼" }, + { "spades;", "â™ " }, + { "sub;", "⊂" }, + { "sube;", "⊆" }, + { "sum;", "∑" }, + { "sup1;", "¹" }, + { "sup2;", "²" }, + { "sup3;", "³" }, + { "sup;", "⊃" }, + { "supe;", "⊇" }, + { "szlig;", "ß" }, + { "tau;", "Ï„" }, + { "there4;", "∴" }, + { "theta;", "θ" }, + { "thetasym;", "Ï‘" }, + { "thinsp;", "\xE2\x80\x89" }, + { "thorn;", "þ" }, + { "tilde;", "Ëœ" }, + { "times;", "×" }, + { "trade;", "â„¢" }, + { "uArr;", "⇑" }, + { "uacute;", "ú" }, + { "uarr;", "↑" }, + { "ucirc;", "û" }, + { "ugrave;", "ù" }, + { "uml;", "¨" }, + { "upsih;", "Ï’" }, + { "upsilon;", "Ï…" }, + { "uuml;", "ü" }, + { "weierp;", "℘" }, + { "xi;", "ξ" }, + { "yacute;", "ý" }, + { "yen;", "Â¥" }, + { "yuml;", "ÿ" }, + { "zeta;", "ζ" }, + { "zwj;", "\xE2\x80\x8D" }, + { "zwnj;", "\xE2\x80\x8C" } +}; + +static int cmp(const void *key, const void *value) +{ + return strncmp((const char *)key, *(const char *const *)value, + strlen(*(const char *const *)value)); +} + +static const char *get_named_entity(const char *name) +{ + const char *const *entity = (const char *const *)bsearch(name, + NAMED_ENTITIES, sizeof NAMED_ENTITIES / sizeof *NAMED_ENTITIES, + sizeof *NAMED_ENTITIES, cmp); + + return entity ? entity[1] : NULL; +} + +static size_t putc_utf8(unsigned long cp, char *buffer) +{ + unsigned char *bytes = (unsigned char *)buffer; + + if(cp <= 0x007Ful) + { + bytes[0] = (unsigned char)cp; + return 1; + } + + if(cp <= 0x07FFul) + { + bytes[1] = (unsigned char)((2 << 6) | (cp & 0x3F)); + bytes[0] = (unsigned char)((6 << 5) | (cp >> 6)); + return 2; + } + + if(cp <= 0xFFFFul) + { + bytes[2] = (unsigned char)(( 2 << 6) | ( cp & 0x3F)); + bytes[1] = (unsigned char)(( 2 << 6) | ((cp >> 6) & 0x3F)); + bytes[0] = (unsigned char)((14 << 4) | (cp >> 12)); + return 3; + } + + if(cp <= 0x10FFFFul) + { + bytes[3] = (unsigned char)(( 2 << 6) | ( cp & 0x3F)); + bytes[2] = (unsigned char)(( 2 << 6) | ((cp >> 6) & 0x3F)); + bytes[1] = (unsigned char)(( 2 << 6) | ((cp >> 12) & 0x3F)); + bytes[0] = (unsigned char)((30 << 3) | (cp >> 18)); + return 4; + } + + return 0; +} + +static bool parse_entity( + const char *current, char **to, const char **from) +{ + const char *end = strchr(current, ';'); + if(!end) return 0; + + if(current[1] == '#') + { + char *tail = NULL; + int errno_save = errno; + bool hex = current[2] == 'x' || current[2] == 'X'; + + errno = 0; + unsigned long cp = strtoul( + current + (hex ? 3 : 2), &tail, hex ? 16 : 10); + + bool fail = errno || tail != end || cp > UNICODE_MAX; + errno = errno_save; + if(fail) return 0; + + *to += putc_utf8(cp, *to); + *from = end + 1; + + return 1; + } + else + { + const char *entity = get_named_entity(¤t[1]); + if(!entity) return 0; + + size_t len = strlen(entity); + memcpy(*to, entity, len); + + *to += len; + *from = end + 1; + + return 1; + } +} + +size_t decode_html_entities_utf8(char *dest, const char *src) +{ + if(!src) src = dest; + + char *to = dest; + const char *from = src; + + for(const char *current; (current = strchr(from, '&'));) + { + memmove(to, from, (size_t)(current - from)); + to += current - from; + + if(parse_entity(current, &to, &from)) + continue; + + from = current; + *to++ = *from++; + } + + size_t remaining = strlen(from); + + memmove(to, from, remaining); + to += remaining; + *to = 0; + + return (size_t)(to - dest); +} \ No newline at end of file diff --git a/source/remotefs/UPNP/entities.h b/source/remotefs/UPNP/entities.h new file mode 100644 index 0000000..606956a --- /dev/null +++ b/source/remotefs/UPNP/entities.h @@ -0,0 +1,20 @@ +/* Copyright 2012 Christoph Gärtner + Distributed under the Boost Software License, Version 1.0 +*/ + +#ifndef DECODE_HTML_ENTITIES_UTF8_ +#define DECODE_HTML_ENTITIES_UTF8_ + +#include + +extern size_t decode_html_entities_utf8(char *dest, const char *src); +/* Takes input from and decodes into , which should be a buffer + large enough to hold characters. + + If is , input will be taken from , decoding + the entities in-place. + + The function returns the length of the decoded string. +*/ + +#endif \ No newline at end of file diff --git a/source/remotefs/ftplib/FTPDir.cpp b/source/remotefs/ftplib/FTPDir.cpp index 115f096..43fbb6f 100644 --- a/source/remotefs/ftplib/FTPDir.cpp +++ b/source/remotefs/ftplib/FTPDir.cpp @@ -1,5 +1,7 @@ #include "FTPDir.h" +#ifdef NXMP_NETWORKSUPPORT + FTPDir::FTPDir(std::string _url,Playlist *_playlist){ url = _url; urlschema thisurl = Utility::parseUrl(url); @@ -71,4 +73,6 @@ void FTPDir::backDir(){ if(currpath == basepath)return; currpath = FS::removeLastSlash(currpath); currpath = currpath.substr(0, currpath.find_last_of("/")); -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/source/remotefs/ftplib/FTPDir.h b/source/remotefs/ftplib/FTPDir.h index e55880c..37c4b5a 100644 --- a/source/remotefs/ftplib/FTPDir.h +++ b/source/remotefs/ftplib/FTPDir.h @@ -1,6 +1,10 @@ #ifndef NXMP_FTPDIR_H #define NXMP_FTPDIR_H +#include "platforms.h" + +#ifdef NXMP_NETWORKSUPPORT + #include #include @@ -34,4 +38,5 @@ class FTPDir{ }; +#endif #endif \ No newline at end of file diff --git a/source/remotefs/ftplib/ftplib.h b/source/remotefs/ftplib/ftplib.h index e5fd277..563f94a 100644 --- a/source/remotefs/ftplib/ftplib.h +++ b/source/remotefs/ftplib/ftplib.h @@ -58,6 +58,11 @@ #define FTPLIB_CALLBACKARG 4 #define FTPLIB_CALLBACKBYTES 5 +#ifdef _WIN32 +#define __UINT64_MAX 1 +#endif + + #ifdef __cplusplus extern "C" { #endif diff --git a/source/remotefs/nfsDir/nfsDir.cpp b/source/remotefs/nfsDir/nfsDir.cpp new file mode 100644 index 0000000..2f42e86 --- /dev/null +++ b/source/remotefs/nfsDir/nfsDir.cpp @@ -0,0 +1,182 @@ +#include "nfsDir.h" + +#ifdef _WIN32 +#include +#endif + +#include +#include +#include +#include + +struct client { + char *nfsserver; + char *nfsexport; + uint32_t mount_port; + int is_finished; +}; + +nfsDir::nfsDir(std::string _url,Playlist * _playlist){ +#ifdef _WIN32 + WSADATA wsadata; + int err; + + err = WSAStartup(MAKEWORD(2, 0), &wsadata); + if(err != 0) { + printf("WSAStartup failed with error: %d\n", err); + + }else{ + printf("WSAStartup ok\n"); + } +#endif + + url = _url; + urlschema thisurl = Utility::parseUrl(url); + basepath = std::string("/") + thisurl.path; + size_t pos = basepath.find_last_of("/"); + if(pos == basepath.length()-1) + { + basepath.erase(basepath.length()-1); + } + printf("Base Path = %s\n",basepath.c_str()); + currentpath = basepath; + playlist = _playlist; + +} + +void nfsDir::DirList(std::string _path,bool showHidden,const std::vector &extensions){ + struct nfs_context *nfs = NULL; + struct client client; + struct nfs_url *_url = NULL; + int ret; + urlschema thisurl = Utility::parseUrl(url); + currentpath = _path; + struct nfsdirent *nfsdirent; + struct nfsdir *nfsdir; + + + + nfs = nfs_init_context(); + if (nfs == NULL) { + printf("failed to init context\n"); + goto finished; + } + + _url = nfs_parse_url_dir(nfs, url.c_str()); + if (_url == NULL) { + printf("%s\n", nfs_get_error(nfs)); + goto finished; + } + + printf("DIR PATH %s\n",_path.c_str()); + client.nfsserver = _url->server; + client.nfsexport = (char *)_path.c_str(); + client.is_finished = 0; + + + + if ((ret = nfs_mount(nfs, client.nfsserver, client.nfsexport)) != 0) { + printf("Failed to mount nfs share : %s\n", nfs_get_error(nfs)); + goto finished; + } + + + + ret = nfs_opendir(nfs, "", &nfsdir); + if (ret != 0) { + printf("Failed to opendir(\"%s\") %s\n", "", nfs_get_error(nfs)); + goto finished; + } + currentlist.clear(); + while((nfsdirent = nfs_readdir(nfs, nfsdir)) != NULL) { + char path[1024]; + FS::FileEntry tmpentry; + + if(!showHidden){ + if (nfsdirent->name[0] == '.') { + continue; + } + } + if (!strcmp(nfsdirent->name, ".") || !strcmp(nfsdirent->name, "..")) { + continue; + } + snprintf(path, 1024, "%s/%s", "", nfsdirent->name); + + switch (nfsdirent->mode & S_IFMT) { + + case S_IFREG: + tmpentry.type = FS::FileEntryType::File; + break; + case S_IFDIR: + tmpentry.type = FS::FileEntryType::Directory; + break; + case S_IFCHR: + break; + case S_IFBLK: + break; + } + + tmpentry.name = nfsdirent->name; + tmpentry.path = _path + std::string("/") + std::string(nfsdirent->name); + tmpentry.size = nfsdirent->size; + currentlist.push_back(tmpentry); + + } + nfs_closedir(nfs, nfsdir); + std::sort(currentlist.begin(), currentlist.end(), FS::Sort); + + currentlist.erase( + std::remove_if(currentlist.begin(), currentlist.end(), [extensions](const FS::FileEntry &file) { + for (auto &ext : extensions) { + if (Utility::endsWith(file.name, ext, false)) { + return false; + } + } + return file.type == FS::FileEntryType::File; + }), currentlist.end()); + + +finished: + + if (nfs != NULL) { + nfs_destroy_context(nfs); + } + +} + + +std::vector nfsDir::getCurrList(){ + return currentlist; +} + +bool *nfsDir::checked(int pos){ + return ¤tlist[pos].checked; +} + +std::string nfsDir::getUrl(){ + return url; +} + +std::string nfsDir::getCurrPath(){ + return currentpath; +} + +std::string nfsDir::getBasePath(){ + return basepath; +} + +void nfsDir::clearChecked(){ + for(int i=0;i +#include +#include +#include +#include "localfiles.h" +#include "playlist.h" + + + +class nfsDir{ +public: + nfsDir(std::string _url,Playlist * _playlist); + + void DirList(std::string path,bool showHidden,const std::vector &extensions); + std::vector getCurrList(); + + void backDir(); + std::string getUrl(); + std::string getCurrPath(); + std::string getBasePath(); + + bool *checked(int pos); + void clearChecked(); + +private: + std::vector currentlist; + std::string url; + std::string basepath = ""; + std::string currentpath = ""; + Playlist *playlist; + +}; + +#endif +#endif \ No newline at end of file diff --git a/source/remotefs/smb2/sambaDir.h b/source/remotefs/smb2/sambaDir.h index 9e9b348..d255731 100644 --- a/source/remotefs/smb2/sambaDir.h +++ b/source/remotefs/smb2/sambaDir.h @@ -1,6 +1,10 @@ #ifndef NXMP_SAMBADIR_H #define NXMP_SAMBADIR_H +#include "platforms.h" + +#ifdef NXMP_NETWORKSUPPORT + #include #ifdef __SWITCH__ #include @@ -40,4 +44,5 @@ class sambaDir{ Playlist *playlist; }; +#endif #endif \ No newline at end of file diff --git a/source/remotefs/sshDir/sshDir.cpp b/source/remotefs/sshDir/sshDir.cpp index e8d6783..5f58634 100644 --- a/source/remotefs/sshDir/sshDir.cpp +++ b/source/remotefs/sshDir/sshDir.cpp @@ -1,5 +1,7 @@ #include "sshDir.h" +#ifdef NXMP_NETWORKSUPPORT + #ifdef __SWITCH__ #include #include @@ -224,4 +226,6 @@ void sshDir::backDir(){ if(currentpath == basepath)return; currentpath = FS::removeLastSlash(currentpath); currentpath = currentpath.substr(0, currentpath.find_last_of("/")); -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/source/remotefs/sshDir/sshDir.h b/source/remotefs/sshDir/sshDir.h index acc5ee6..57c3f57 100644 --- a/source/remotefs/sshDir/sshDir.h +++ b/source/remotefs/sshDir/sshDir.h @@ -1,6 +1,10 @@ #ifndef NXMP_SSHDIR_H #define NXMP_SSHDIR_H +#include "platforms.h" + +#ifdef NXMP_NETWORKSUPPORT + #include #include #include @@ -33,5 +37,5 @@ class sshDir{ Playlist *playlist; }; - +#endif #endif \ No newline at end of file diff --git a/source/utils.cpp b/source/utils.cpp index 163ac51..593144b 100644 --- a/source/utils.cpp +++ b/source/utils.cpp @@ -81,6 +81,39 @@ namespace Utility{ return true; } + bool TxtLoadFromMemory(unsigned char* data,size_t image_size, GLuint* out_texture, int* out_width, int* out_height){ + int image_width = 0; + int image_height = 0; + int comp = 0; + + unsigned char* image_data = stbi_load_from_memory((const stbi_uc *)data, image_size, &image_width, &image_height, &comp, 0); + + if (image_data == NULL){ + printf("Failed to load IMG from memory\n"); + return false; + } + GLuint id = 0; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + + if(comp == 3) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image_width, image_height, 0, GL_RGB, GL_UNSIGNED_BYTE, image_data); + else if(comp == 4) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_width, image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data); + + + + *out_texture = id; + *out_width = image_width; + *out_height = image_height; + + return true; + } + std::string humanSize(size_t bytes) { std::vectorsuffix = {"B", "KB", "MB", "GB", "TB"};