Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ jobs:
cmake -S . -B build
export MAKEFLAGS=-j$(nproc)
cmake --build build --verbose
- name: Test
run: ctest --test-dir build --output-on-failure
21 changes: 21 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ add_executable(${PROJECT_NAME}
main.cpp
Sources/audioMenu.cpp
Sources/config.cpp
Sources/directoryReader.cpp
Sources/fileBrowser.cpp
Sources/fileBrowserMenu.cpp
Sources/fileBrowserModel.cpp
Sources/font.cpp
Sources/ftpConnection.cpp
Sources/ftpServer.cpp
Expand All @@ -35,6 +39,7 @@ add_executable(${PROJECT_NAME}
Sources/sntpClient.cpp
Sources/subAppRouter.cpp
Sources/subsystems.cpp
Sources/textLayout.cpp
Sources/timing.cpp
Sources/timeMenu.cpp
Sources/videoMenu.cpp
Expand All @@ -58,5 +63,21 @@ else()
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -pedantic)
endif()

include(CTest)

if(BUILD_TESTING)
add_executable(FileBrowserModelTests
Tests/fileBrowserModelTests.cpp
Sources/directoryReader.cpp
Sources/fileBrowserModel.cpp
Sources/textLayout.cpp)
target_include_directories(FileBrowserModelTests PRIVATE
${CMAKE_SOURCE_DIR}
${PROJECT_SOURCE_DIR}/Includes)
set_property(TARGET FileBrowserModelTests PROPERTY CXX_STANDARD 11)
set_property(TARGET FileBrowserModelTests PROPERTY CXX_STANDARD_REQUIRED ON)
add_test(NAME FileBrowserModelTests COMMAND FileBrowserModelTests)
endif()

#set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
#set (CMAKE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address")
51 changes: 51 additions & 0 deletions Includes/directoryReader.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#ifndef NEVOLUTIONX_INCLUDES_DIRECTORYREADER_HPP_
#define NEVOLUTIONX_INCLUDES_DIRECTORYREADER_HPP_

#include <cstddef>
#include <cstdint>
#include <string>
#include <utility>
#include <vector>

enum class DirectoryEntryKind
{
DIRECTORY,
FILE,
OTHER
};

struct DirectoryEntry {
DirectoryEntry() = default;
DirectoryEntry(std::string entryName,
DirectoryEntryKind entryKind,
uint64_t entrySize = 0) :
name(std::move(entryName)), kind(entryKind), size(entrySize) {}

std::string name;
DirectoryEntryKind kind{ DirectoryEntryKind::OTHER };
uint64_t size{ 0 };
};

struct DirectoryReadResult {
bool succeeded{ false };
std::vector<DirectoryEntry> entries;
std::string error;
};

class DirectoryReader {
public:
virtual ~DirectoryReader() = default;

virtual DirectoryReadResult read(const std::string& path) const = 0;
virtual char separator() const = 0;
virtual size_t maxPathLength() const = 0;
};

class PlatformDirectoryReader : public DirectoryReader {
public:
DirectoryReadResult read(const std::string& path) const override;
char separator() const override;
size_t maxPathLength() const override;
};

#endif // NEVOLUTIONX_INCLUDES_DIRECTORYREADER_HPP_
41 changes: 41 additions & 0 deletions Includes/fileBrowser.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#ifndef NEVOLUTIONX_INCLUDES_FILEBROWSER_HPP_
#define NEVOLUTIONX_INCLUDES_FILEBROWSER_HPP_

#include <memory>
#include <string>
#include <vector>
#include "fileBrowserModel.hpp"
#include "renderer.hpp"
#include "subApp.hpp"

class FileBrowser : public SubApp {
public:
FileBrowser(Renderer& renderer, const std::vector<std::string>& roots);

void render(Font& font) override;

void onUpPressed() override;
void onDownPressed() override;
void onLeftPressed() override;
void onRightPressed() override;
void onAPressed() override;
void onBPressed() override;
void onBackPressed() override;
void onXPressed() override;

void onLeftStickDigitalUpPressed() override { onUpPressed(); }
void onLeftStickDigitalDownPressed() override { onDownPressed(); }
void onLeftStickDigitalLeftPressed() override { onLeftPressed(); }
void onLeftStickDigitalRightPressed() override { onRightPressed(); }

private:
void navigateBack();
std::string displayLabel(const DirectoryEntry& entry) const;

Renderer& renderer;
std::shared_ptr<DirectoryReader> reader;
FileBrowserModel model;
size_t visibleRows{ 1 };
};

#endif // NEVOLUTIONX_INCLUDES_FILEBROWSER_HPP_
20 changes: 20 additions & 0 deletions Includes/fileBrowserMenu.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#ifndef NEVOLUTIONX_INCLUDES_FILEBROWSERMENU_HPP_
#define NEVOLUTIONX_INCLUDES_FILEBROWSERMENU_HPP_

#include <string>
#include <vector>
#include "menu.hpp"

class FileBrowserMenu : public MenuItem {
public:
FileBrowserMenu(MenuNode* parent,
const std::string& label,
std::vector<std::string> roots);

void execute(Menu* menu) override;

private:
std::vector<std::string> roots;
};

#endif // NEVOLUTIONX_INCLUDES_FILEBROWSERMENU_HPP_
73 changes: 73 additions & 0 deletions Includes/fileBrowserModel.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#ifndef NEVOLUTIONX_INCLUDES_FILEBROWSERMODEL_HPP_
#define NEVOLUTIONX_INCLUDES_FILEBROWSERMODEL_HPP_

#include <memory>
#include <string>
#include <vector>
#include "directoryReader.hpp"

class FileBrowserModel {
public:
enum class ActivateResult
{
NONE,
ENTERED_DIRECTORY,
FILE_SELECTED,
FAILED
};

FileBrowserModel(std::vector<std::string> configuredRoots,
std::shared_ptr<DirectoryReader> reader);

const std::vector<DirectoryEntry>& getEntries() const { return entries; }
size_t getSelected() const { return selected; }
const std::string& getStatus() const { return status; }
std::string getLocationLabel() const;
bool isAtVirtualRoot() const { return atVirtualRoot; }

void moveSelection(int delta, bool allowWrap = true);
void pageSelection(int delta);
ActivateResult activateSelected();
bool navigateBack();
bool refresh();
void setStatus(const std::string& newStatus) { status = newStatus; }

static bool isSafeChildName(const std::string& name);
static bool entryLess(const DirectoryEntry& lhs, const DirectoryEntry& rhs);

private:
struct SelectionIdentity {
bool valid{ false };
std::string name;
DirectoryEntryKind kind{ DirectoryEntryKind::OTHER };
};

struct HistoryFrame {
bool virtualRoot{ true };
size_t rootIndex{ 0 };
std::vector<std::string> components;
SelectionIdentity selection;
};

static std::string trim(const std::string& value);
static bool equalsIgnoreCase(const std::string& lhs, const std::string& rhs);
std::vector<std::string> normalizeRoots(const std::vector<std::string>& roots) const;
bool normalizeRoot(const std::string& input, std::string& output) const;
std::string buildCurrentPath() const;
bool loadCurrentDirectory(bool preserveOldListing);
void showVirtualRoot();
SelectionIdentity selectedIdentity() const;
void restoreSelection(const SelectionIdentity& identity);

std::shared_ptr<DirectoryReader> reader;
std::vector<std::string> roots;
std::vector<DirectoryEntry> entries;
std::vector<std::string> components;
std::vector<HistoryFrame> history;
size_t rootIndex{ 0 };
size_t selected{ 0 };
bool atVirtualRoot{ true };
std::string status;
};

#endif // NEVOLUTIONX_INCLUDES_FILEBROWSERMODEL_HPP_
13 changes: 13 additions & 0 deletions Includes/textLayout.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#ifndef NEVOLUTIONX_INCLUDES_TEXTLAYOUT_HPP_
#define NEVOLUTIONX_INCLUDES_TEXTLAYOUT_HPP_

#include <functional>
#include <string>

std::string sanitizeUtf8ForDisplay(const std::string& text);

std::string ellipsizeText(const std::string& text,
float maximumWidth,
const std::function<float(const std::string&)>& measure);

#endif // NEVOLUTIONX_INCLUDES_TEXTLAYOUT_HPP_
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ SRCS += \
$(SRCDIR)/audioMenu.cpp \
$(SRCDIR)/config.cpp \
$(SRCDIR)/font.cpp \
$(SRCDIR)/directoryReader.cpp \
$(SRCDIR)/fileBrowser.cpp \
$(SRCDIR)/fileBrowserMenu.cpp \
$(SRCDIR)/fileBrowserModel.cpp \
$(SRCDIR)/ftpConnection.cpp \
$(SRCDIR)/ftpServer.cpp \
$(SRCDIR)/infoLog.cpp \
Expand All @@ -24,6 +28,7 @@ SRCS += \
$(SRCDIR)/subsystems.cpp \
$(SRCDIR)/timeMenu.cpp \
$(SRCDIR)/timing.cpp \
$(SRCDIR)/textLayout.cpp \
$(SRCDIR)/videoMenu.cpp \
$(SRCDIR)/wipeCache.cpp \
$(SRCDIR)/xbeLauncher.cpp \
Expand Down
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,28 @@ As the XboxDev community grew, the need for an open-source, nxdk based dashboard
- [ ] TLS
- [x] Application launcher
- [x] DVD launcher
- [x] Read-only file browser
- [ ] Themes

### File browser

The file browser is opt-in. Add a `file_browser` entry to the `menu` array in
`config.json` and explicitly list the roots it may expose:

```json
{
"label": "File browser",
"type": "file_browser",
"roots": ["C:\\", "E:\\", "F:\\", "G:\\"]
}
```

The configured roots are a safety boundary; cache and dashboard-private drives
are not discovered automatically. The browser can enter directories and view
files, but it cannot launch, copy, rename, or delete anything. Use D-pad
up/down to select, left/right to page, A to enter a directory, X to refresh,
and B or Back to move up or close the browser.

## Building with nxdk
### Build
In order to build NevolutionX you'll need to install [nxdk](https://github.com/XboxDev/nxdk) first.
Expand All @@ -41,6 +61,7 @@ There is no further configuration required. The FTP-server will start automatica
## Building with CMake (Linux target)
TODO: Document what parts of the Linux target are not supported.
TODO: Document installation for the Linux target, if applicable.

`cmake -S . -B build && cmake --build build --verbose`

## Credits
Expand All @@ -51,4 +72,3 @@ This software is built on top of other awesome projects:
## License
NevolutionX is published under the MIT License. See [LICENSE](LICENSE) for more information.
MIT © 2019 Lucas Eriksson

Loading