Compare commits

...

16 Commits

Author SHA1 Message Date
09c70b0de4 initial proton code 2025-06-22 22:40:46 -07:00
7b7e71fdff resolve_bottle: fix missing path separator in fallback to default path 2025-06-03 12:26:30 -07:00
19a0e40977 improvements to cellar translate 2025-02-14 12:57:52 -08:00
2653ab65c3 cleanup 2025-02-07 18:38:14 -08:00
e77b61b1d9 more of cellar's internals are now aware of steam bottles 2025-02-03 18:50:02 -08:00
de049cea1a bottle_steam type introduced 2025-02-03 17:50:33 -08:00
d11b690708 doc a couple files 2025-02-03 17:28:18 -08:00
8dbb226251 got another one 2025-02-03 17:08:13 -08:00
870ec7345b find_steam_libraries now returns an empty result if it can't read libraryfolders.vdf for some reason 2025-02-03 17:01:38 -08:00
c633c96ad7 update readme to talk about foobar2000 instead of steam, given steam is VERY linux friendly these days 2025-02-02 15:49:50 -08:00
713bf30181 rip lavatargets.cmake, i barely knew ye 2025-02-02 15:47:20 -08:00
ea9514dba0 delete weird return in active 2025-02-02 15:36:13 -08:00
f53813d3c9 you can now activate a steam managed bottle using "steam:$APPID" 2025-02-02 15:24:17 -08:00
b2b2b362be include deps in readmwe 2025-01-27 16:56:42 -08:00
b32259c56d cellar list now includes proton bottles 2025-01-27 16:51:54 -08:00
3fc9c88230 update stale path in readme 2025-01-25 19:23:38 -08:00
25 changed files with 375 additions and 282 deletions

93
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,93 @@
{
"files.associations": {
"*.wsgi": "python",
"*.s": "nasm",
"*.cog": "cpp",
"algorithm": "cpp",
"cmath": "cpp",
"any": "cpp",
"array": "cpp",
"atomic": "cpp",
"hash_map": "cpp",
"strstream": "cpp",
"bit": "cpp",
"*.tcc": "cpp",
"bitset": "cpp",
"cctype": "cpp",
"cfenv": "cpp",
"charconv": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"codecvt": "cpp",
"compare": "cpp",
"complex": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"coroutine": "cpp",
"csetjmp": "cpp",
"csignal": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"forward_list": "cpp",
"list": "cpp",
"map": "cpp",
"set": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"exception": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"regex": "cpp",
"source_location": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"format": "cpp",
"fstream": "cpp",
"future": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"ranges": "cpp",
"semaphore": "cpp",
"shared_mutex": "cpp",
"span": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stdfloat": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"text_encoding": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"typeindex": "cpp",
"typeinfo": "cpp",
"valarray": "cpp",
"variant": "cpp"
}
}

13
.woodpecker/release.yaml Normal file
View File

@@ -0,0 +1,13 @@
when:
- event: tag
ref: refs/tags/v*
steps:
- name: Parse git metadata
image: build-runner
commands:
- git tag -n $CI_COMMIT_TAG --format "%(contents)" > ./.tag_message.txt
- name: Release
image: woodpeckerci/plugin-release
settings:
api_key:
from_secret: WOODPECKER_CI_TOKEN

View File

@@ -40,7 +40,6 @@ http://tclap.sourceforge.net and put the headers in ./include
(wink, nudge)")
endif(NOT TCLAP_FOUND)
#include(LavaTargets)
include(Binaries)
include_directories(include)
@@ -58,7 +57,7 @@ endif()
build_library(TARGET libcellar
SUBDIRS ${libcellar_subdirs}
DEPENDS cog)
set_target_properties(libcellar PROPERTIES PREFIX "")
set_target_properties(libcellar PROPERTIES PREFIX "") # prevent "liblibcellar"
build_executable(TARGET cellar
SUBDIRS core help

View File

@@ -2,6 +2,12 @@
### bottle management tool for WINE connoisseurs
*(this software is considered unfinished, use at own risk)*
## Dependencies
* Boost
* Cog for code generation (`pip install cogapp`)
* TCLAP (either system-wide or included in `include`)
* Ronn for man page
## Installation
$ mkdir build && cd build
@@ -9,25 +15,23 @@
$ make -j4
$ sudo make install
cellar also supports the use of clang as your compiler and/or ninja as your make system, for those of you who have opinions on such things.
## Quick Usage Primer
$ cellar create steam
$ cellar -b steam winetricks vcrun2012
$ cellar create foobar2000
$ cellar -b foobar2000 winetricks vcrun2012
# without the -b argument, cellar assumes you want to deal with ~/.wine
# you can manage which bottle that points to with this command
$ cellar activate steam
$ cellar activate foobar2000
# arguments passed to "cellar launch" are passed to wine
$ cellar launch /mnt/windows/Steam/Steam.exe
$ cellar launch /mnt/windows/foobar2000/foobar2000.exe
## Features
* **Corking**: Saves a bottle's configuration, including any pressed installers (see below) or installed winetricks, to a directory, then removes the WINE bottle from disk.
You can then easily rebuild that WINE bottle later by uncorking it, which will automatically rebuild the WINE bottle with your active (or specified) version of WINE, as
well as install any saved winetricks or run any pressed installers.
* **Pressed Installers**: Saves a copy of an installer to `~/.cellar`, writes it down in the bottle configuration, then runs it within your WINE bottle. If you choose to
* **Pressed Installers**: Saves a copy of an installer to `~/.local/share/cellar`, writes it down in the bottle configuration, then runs it within your WINE bottle. If you choose to
cork this bottle later, this installer will automatically be run after uncorking. If the installer comes with "unattended install" arguments, it's recommended you press
those in too.
* **Easy WINE and bottle management**: Need a specific bottle for a specific program? `cellar -b bottlename <command>`. Does the bottle need to run a specific instance of

View File

@@ -1,94 +0,0 @@
link_directories(${CMAKE_CURRENT_BINARY_DIR})
function(lava_create_library)
set(multiValueArgs SUBDIRS DEPENDS LIBRARIES)
set(oneValueArgs TARGET)
cmake_parse_arguments(lava_create_library "" "${oneValueArgs}"
"${multiValueArgs}" ${ARGN})
set(target ${lava_create_library_TARGET})
set(targetsources)
foreach(subdir ${lava_create_library_SUBDIRS})
cog_sources("src/${subdir}/*.cpp" subdirsources)
foreach(source ${subdirsources})
set(targetsources ${targetsources} ${source})
endforeach(source)
endforeach(subdir)
add_library(${target} SHARED ${targetsources})
if(MINGW)
# MinGW (or CMake?) does a stupid, redundant thing where it names all output DLLs
# "libblah.dll" and it's kinda stupid and redundant
# this removes the "lib" prefix because removing ".dll" causes things to break
set_target_properties(${target} PROPERTIES PREFIX "")
endif()
if(lava_create_library_LIBRARIES)
foreach(library ${lava_create_library_LIBRARIES})
target_link_libraries(${target} "${library}")
endforeach()
endif()
if(lava_create_library_DEPENDS)
add_dependencies(${target} ${lava_create_library_DEPENDS})
endif()
endfunction(lava_create_library)
function(lava_create_gutlib)
set(multiValueArgs SUBDIRS DEPENDS LIBRARIES LIBRARYVARS)
cmake_parse_arguments(lava_create_gutlib "" ""
"${multiValueArgs}" ${ARGN})
foreach(subdir ${lava_create_gutlib_SUBDIRS})
cog_sources("src/${subdir}/*.cpp" subdirsources)
foreach(source ${subdirsources})
set(targetsources ${targetsources} ${source})
endforeach(source)
endforeach(subdir)
add_library(gutlib SHARED ${targetsources})
set_target_properties(gutlib PROPERTIES OUTPUT_NAME ${CMAKE_PROJECT_NAME})
if(MINGW)
# MinGW (or CMake?) does a stupid, redundant thing where it names all output DLLs
# "libblah.dll" and it's kinda stupid and redundant
# this removes the "lib" prefix because removing ".dll" causes things to break
set_target_properties(gutlib PROPERTIES PREFIX "")
endif()
if(lava_create_gutlib_LIBRARIES)
foreach(library ${lava_create_gutlib_LIBRARIES})
target_link_libraries(gutlib "${library}")
endforeach()
endif()
if(lava_create_gutlib_DEPENDS)
add_dependencies(gutlib ${lava_create_gutlib_DEPENDS})
endif()
endfunction(lava_create_gutlib)
function(lava_create_executable)
set(multiValueArgs SUBDIRS LIBRARIES DEPENDS)
set(oneValueArgs TARGET)
cmake_parse_arguments(lava_create_executable "" "${oneValueArgs}"
"${multiValueArgs}" ${ARGN})
set(target ${lava_create_executable_TARGET})
set(targetsources)
foreach(subdir ${lava_create_executable_SUBDIRS})
cog_sources("src/${subdir}/*.cpp" subdirsources)
foreach(source ${subdirsources})
set(targetsources ${targetsources} ${source})
endforeach(source)
endforeach(subdir)
add_executable(${target} ${targetsources})
if(lava_create_executable_LIBRARIES)
foreach(library ${lava_create_executable_LIBRARIES})
target_link_libraries(${target} "${library}")
endforeach()
endif()
if(lava_create_executable_DEPENDS)
add_dependencies(${target} ${lava_create_executable_DEPENDS})
endif()
endfunction(lava_create_executable)

View File

@@ -1,68 +0,0 @@
find_package(Qt5 REQUIRED COMPONENTS Core Widgets)
include_directories(${Qt5_INCLUDES} ${Qt5_DIR})
file(READ "${CMAKE_SOURCE_DIR}/res/moc.txt" moclist)
string(REGEX REPLACE "\n" ";" moclist ${moclist}) # split into array by line
# note: i am aware of the existence of qt5_wrap_cpp and friends, but those functions
# aren't aware of my code generator so i have to do it myself
# also, qt5_wrap_ui just dumps headers in the root of the build dir. ew.
if(NOT QT_UIC_EXECUTABLE)
string(REPLACE moc uic QT_UIC_EXECUTABLE "${QT_MOC_EXECUTABLE}")
endif()
set(mocsources)
set(qtcogged FALSE)
if(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/src/qtgenerated")
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/src/qtgenerated")
endif()
foreach(mocfile ${moclist})
if(EXISTS ${CMAKE_SOURCE_DIR}/${mocfile})
string(REGEX REPLACE ".h\$" ".cpp" implfile_rel "${mocfile}")
string(REGEX REPLACE "include/" "src/qtgenerated/moc_" implfile_rel "${implfile_rel}")
set(implfile_abs "${CMAKE_CURRENT_BINARY_DIR}/${implfile_rel}")
add_custom_command(OUTPUT "${implfile_abs}" PRE_BUILD
COMMAND ${QT_MOC_EXECUTABLE} -o "${implfile_abs}" "${CMAKE_SOURCE_DIR}/${mocfile}"
DEPENDS "${CMAKE_SOURCE_DIR}/${mocfile}"
COMMENT "Qt MOC: ${BoldCyan}${mocfile}${ColourReset}")
elseif(EXISTS ${CMAKE_SOURCE_DIR}/${mocfile}.cog)
set(qtcogged TRUE)
string(REGEX REPLACE ".h\$" ".cpp" implfile_rel "${mocfile}")
string(REGEX REPLACE "include/" "src/qtgenerated/moc_" implfile_rel "${implfile_rel}")
set(implfile_abs "${CMAKE_CURRENT_BINARY_DIR}/${implfile_rel}")
add_custom_command(OUTPUT "${implfile_abs}" PRE_BUILD
COMMAND ${QT_MOC_EXECUTABLE} -o "${implfile_abs}" "${CMAKE_CURRENT_BINARY_DIR}/${mocfile}"
DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${mocfile}"
COMMENT "Qt MOC: ${BoldCyan}${mocfile}${ColourReset}")
endif()
set(mocsources ${mocsources} "${implfile_abs}")
endforeach()
file(GLOB_RECURSE uilist RELATIVE "${CMAKE_SOURCE_DIR}" "${CMAKE_SOURCE_DIR}/res/ui/*.ui")
set(uicsources)
foreach(uifile ${uilist})
string(REGEX REPLACE "res/ui/" "" uiname ${uifile})
string(REGEX REPLACE ".ui\$" "" uiname ${uiname})
string(REGEX REPLACE "/" "_" uiname ${uiname})
# res/ui/dlg/license.ui should result in ${uiname} being "dlg_license" at this point
set(headerfile "${CMAKE_CURRENT_BINARY_DIR}/include/ui_${uiname}.h")
add_custom_command(OUTPUT "${headerfile}" PRE_BUILD
# TODO: not hardcode this path
COMMAND ${QT_UIC_EXECUTABLE} -o "${headerfile}" "${CMAKE_SOURCE_DIR}/${uifile}"
DEPENDS "${CMAKE_SOURCE_DIR}/${uifile}"
COMMENT "Qt UIC: ${BoldCyan}${uifile}${ColourReset}")
set(uicsources ${uicsources} "${headerfile}")
endforeach()
add_library(qtgenerated STATIC ${mocsources} ${uicsources})
target_link_libraries(qtgenerated Qt5::Core Qt5::Widgets)
if (qtcogged)
add_dependencies(qtgenerated cog)
endif()
message(STATUS "Found Qt: ${Qt5_VERSION}")

View File

@@ -1,37 +0,0 @@
find_program(DVIPNG dvipng)
find_program(DVISVGM dvisvgm)
find_program(SPHINX_BUILD sphinx-build)
if(NOT DOCS_TOO)
set(DOCS_TOO 0)
endif()
if(NOT DVIPNG OR NOT DVISVGM OR NOT SPHINX_BUILD)
message(WARNING "${BoldYellow}Cannot build library documentation.${ColourReset}")
# report what's missing
if(NOT DVIPNG)
message(WARNING " dvipng not installed.")
endif()
if(NOT DVISVGM)
message(WARNING " dvisvgm not installed.")
endif()
if(NOT SPHINX_BUILD)
message(WARNING " Sphinx not installed.")
endif()
if (DOCS_TOO EQUAL 1)
message(FATAL_ERROR "${BoldRed}This is a problem, since you specified DOCS_TOO.${ColourReset}")
endif()
else()
if(DOCS_TOO EQUAL 1)
set(MAYBE_ALL ALL)
else()
message(STATUS "${Green}Not building docs automatically. Build them with: ${BoldGreen}${CMAKE_MAKE_PROGRAM} sphinx${ColourReset}
An alternative strategy would be to run CMake again with ${BoldYellow}-DDOCS_TOO=1${ColourReset}")
set(MAYBE_ALL "")
endif()
add_custom_target(sphinx ${MAYBE_ALL}
COMMAND ${SPHINX_BUILD} -b html "${CMAKE_SOURCE_DIR}/doc/sphinx" "${CMAKE_CURRENT_BINARY_DIR}/doc")
endif()

View File

@@ -34,4 +34,4 @@ TODO: Generate this.
## COPYRIGHT
Copyright © 2017-2019 Nicholas O'Connor. Provided under the terms of the MIT license: https://opensource.org/licenses/MIT
Copyright © 2017-2025 Nicole O'Connor. Provided under the terms of the MIT license: https://opensource.org/licenses/MIT

View File

@@ -18,12 +18,28 @@ namespace cellar {
bottle_error,
bottle_anonymous,
bottle_labelled,
bottle_symlink
bottle_symlink,
bottle_steam
};
enum bottle_manager {
manager_error,
manager_cellar,
manager_steam
};
extern std::string bottle_home;
/**
* Bottles are the internal name for WINE prefixes. In addition to standard WINE contents,
* bottles contain a configuration file managed by cellar, which labels the bottle as well
* as providing configuration settings.
*/
class Bottle {
public:
// public members
bottle_type type;
bottle_manager manager;
json config;
string path;
string canonical_path;

View File

@@ -21,6 +21,8 @@ namespace cellar {
cog.outl("extern void {0} (int, vector<string>);".format(item[1]))
]]]*/
//[[[end]]]
extern void setup_bottle_home();
}
namespace commands {
extern map<string, cellar::commands::CommandFunction> bottles_commands();

View File

@@ -0,0 +1,14 @@
#pragma once
#include <map>
#include <string>
#include <vector>
#include "commands.hpp"
namespace cellar {
namespace steam {
extern std::vector<std::string> find_steam_libraries();
extern std::map<std::string, std::string> find_steam_protons();
}
}

View File

@@ -1,26 +0,0 @@
#pragma once
#include <map>
#include <string>
#include <vector>
#include "commands.hpp"
namespace cellar {
namespace steam {
extern std::vector<std::string> find_steam_libraries();
extern void test_command(int argc, std::vector<std::string> argv);
/*[[[cog
import cog
with open("src/steam/commands.txt") as commandsfile:
for line in commandsfile:
item = line.strip().split(" ")
cog.outl("extern void {0} (int, vector<string>);".format(item[1]))
]]]*/
//[[[end]]]
}
namespace commands {
extern std::map<std::string,cellar::commands::CommandFunction> steam_commands();
}
}

View File

@@ -1,9 +1,11 @@
#pragma once
#include <map>
#include "bottles.hpp"
namespace cellar {
namespace steam {
extern cellar::bottles::Bottle app_bottle(unsigned appid);
extern std::map<std::string, cellar::bottles::Bottle> get_app_bottles();
}
}

View File

@@ -21,7 +21,7 @@ void cellar::bottles::switch_active_bottle(int argc, vector<string> argv) {
string homepath = getenv("HOME");
string bottlepath = homepath + "/.wine";
string targetpath = homepath + "/.local/share/cellar/bottles/" + argv[1];
string targetpath = cellar::bottles::resolve_bottle(argv[1]);
file_status targetstatus = symlink_status(targetpath);
if (!exists(targetstatus)) {

View File

@@ -17,6 +17,7 @@ void cellar::bottles::print_active_bottle(int argc, vector<string> argv) {
string bottlepath = active_bottle.canonical_path;
stringstream outstr;
bool cellar_managed = true;
bool external_managed = false;
if (active_bottle.type == bottle_symlink) {
outstr << "symlink to ";
string homedir = getenv("HOME");
@@ -24,10 +25,11 @@ void cellar::bottles::print_active_bottle(int argc, vector<string> argv) {
if (active_bottle.canonical_path.substr(0, bottlerack.length()) == bottlerack) {
bottlepath.replace(0, bottlerack.length() + 1, ""); // should convert "/home/someone/.wine.example" to ".wine.example"
active_bottle = bottlemap[bottlepath];
active_bottle.set_config("manager", "cellar");
active_bottle.save_config();
} else {
outstr << active_bottle.canonical_path;
cellar_managed = false;
return;
}
}

View File

@@ -1,4 +1,5 @@
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <map>
#include <string>
@@ -9,9 +10,13 @@
#include "nlohmann/json.hpp"
#include "bottles.hpp"
#include "cmake.hpp"
#include "internal/bottles.hpp"
#include "fs.hpp"
#include "output.hpp"
#ifdef ENABLE_STEAM
#include "steam.hpp"
#endif
using namespace std;
using namespace cellar;
@@ -20,33 +25,76 @@ using namespace cellar::bottles;
using CommandFunction = cellar::commands::CommandFunction;
using json = nlohmann::json;
/**
* @brief Construct an empty bottle object.
* This empty bottle object can then be used to help create new ones.
*/
Bottle::Bottle() {
// define a null bottle
// strings handle themselves
config = json({});
type = bottle_anonymous;
manager = manager_error;
}
std::string cellar::bottles::bottle_home;
/**
* @brief Sets up bottle home.
* Called once from early mainloop.
* @todo Have this not be an effectively hardcoded path. Respect xdg paths.
*/
void cellar::bottles::setup_bottle_home() {
stringstream sstr_bottle_home;
sstr_bottle_home << std::getenv("HOME");
sstr_bottle_home << "/.local/share/cellar/bottles";
bottle_home = sstr_bottle_home.str();
}
/**
* @brief Construct a bottle object from at a given path.
*
* @param patharg The path to load a bottle from.
*/
Bottle::Bottle(string patharg) {
output::statement("loading bottle from " + patharg, true);
config = json({});
path = patharg;
boost::filesystem::file_status path_status = boost::filesystem::symlink_status(path);
bool symlink = boost::filesystem::is_symlink(path_status);
//boost::filesystem::file_status path_status = boost::filesystem::symlink_status(path);
//bool symlink = boost::filesystem::is_symlink(path_status);
auto path_status = std::filesystem::path(path);
auto path_canon = std::filesystem::canonical(path_status);
canonical_path = path_canon.string();
if (symlink) {
boost::filesystem::path realpath = boost::filesystem::canonical(path);
canonical_path = realpath.string();
if (std::filesystem::is_symlink(path_canon)) {
type = bottle_symlink;
} else {
canonical_path = path;
try {
if (load_config()) {
load_config();
auto cur_manager = get_config("manager");
if (cur_manager == "cellar") {
manager = manager_cellar;
if (get_config("name") != "") {
type = bottle_labelled;
} else {
type = bottle_anonymous;
}
} else if (path_canon.parent_path() == bottle_home) {
manager = manager_cellar;
set_config("manager", "cellar"); // migrate from older cellar (or correct for something weird happening)
save_config();
if (get_config("name") != "") {
type = bottle_labelled;
} else {
type = bottle_anonymous;
}
} else if (cur_manager == "steam") {
type = bottle_steam;
manager = manager_steam;
}
}
catch (const exception &exc) {
type = bottle_error;
@@ -54,6 +102,14 @@ Bottle::Bottle(string patharg) {
}
}
/**
* @brief Lists all bottles cellar manages or is otherwise aware of.
* This includes bottles managed by other tools like Steam or Lutris,
* assuming support for those tools is compiled into cellar. (Steam
* support is present; Lutris support is planned for the future.)
*
* @return map<string, Bottle> All bottles. Bottles managed by other tools have prefixed keys, e.g. Steam bottles use "steam:"
*/
map<string, Bottle> cellar::bottles::get_bottles() {
map<string, Bottle> result;
@@ -66,29 +122,59 @@ map<string, Bottle> cellar::bottles::get_bottles() {
result[item] = output;
}
#ifdef ENABLE_STEAM
map<string, Bottle> bottles_proton = cellar::steam::get_app_bottles();
for (auto item : bottles_proton) {
result.insert_or_assign(item.first, item.second);
}
#endif
return result;
}
/**
* @brief Takes an input that refers to a bottle and returns a path to that bottle.
* This input can be any of the following:
* * A bottle name as managed by cellar
* * A prefixed bottle name, in the format `<tool>:<identifier>`. (e.g. `steam:78000`)
* * An absolute or relative path, in which case it is returned with no modification.
* * A path relative to (and starting with ~). cellar will throw a warning in verbose mode,
* since usually the shell expands this for us, but will do a naive replacement on its own
* and try to prevent confused users.
*
* @param bottlechoice Input, usually referring to a specific bottle known to cellar.
* @return string Path to referenced bottle.
*/
string cellar::bottles::resolve_bottle(string bottlechoice) {
string result;
if (bottlechoice.substr(0,1) == "/" || bottlechoice.substr(0,1) == ".") { // absolute or relative path
result = bottlechoice;
} else if (bottlechoice.substr(0,1) == "~") { // "absolute" path in home directory, not expanded by the shell for some reason (i've seen some shit)
// this is a naive replacement and will fail if the user tries something like ~nick/.wine
// this is a naive replacement and will fail if the user tries something like ~nicole/.wine
// i'm figuring at that point if you're doing that, you'll also recognize if your shell
// isn't actually expanding your path...
bottlechoice.replace(0,1,getenv("HOME"));
// or at least you'll think to use verbose mode to make sure it's loading the right directory
output::warning("your shell didn't expand your given path properly, doing a naive replacement", true);
result = bottlechoice;
#ifdef ENABLE_STEAM
} else if (bottlechoice.substr(0,6) == "steam:") { // steam bottles
string str_appid = bottlechoice.substr(6);
unsigned long uint_appid = std::stoul(str_appid);
auto steambottle = cellar::steam::app_bottle(uint_appid);
result = steambottle.path;
#endif
} else {
string homepath = getenv("HOME");
string fullbottlepath = homepath + "/.local/share/cellar/bottles" + bottlechoice;
string fullbottlepath = homepath + "/.local/share/cellar/bottles/" + bottlechoice;
result = fullbottlepath;
}
return result;
}
/**
* @brief Prints bottles. Used as a command in CLI.
*/
void cellar::bottles::print_bottles(int argc, vector<string> argv) {
map<string, Bottle> bottles = get_bottles();
@@ -112,6 +198,9 @@ void cellar::bottles::print_bottles(int argc, vector<string> argv) {
case bottle_labelled:
outstr << bottle.config["name"];
break;
case bottle_steam:
outstr << "Steam managed bottle for " << bottle.config["name"];
break;
default:
outstr << "broken or unsupported wine bottle";
}

View File

@@ -39,7 +39,7 @@ void cellar::bottles::create_bottle(int argc, vector<string> argv) {
if (bottlechoice.substr(0,1) == "/" || bottlechoice.substr(0,1) == ".") { // absolute or relative path
fullbottlepath = bottlechoice;
} else if (bottlechoice.substr(0,1) == "~") { // "absolute" path in home directory, not expanded by the shell for some reason (i've seen some shit)
// this is a naive replacement and will fail if the user tries something like ~nick/.wine
// this is a naive replacement and will fail if the user tries something like ~nicole/.wine
// i'm figuring at that point if you're doing that, you'll also recognize if your shell
// isn't actually expanding your path...
bottlechoice.replace(0,1,getenv("HOME"));

View File

@@ -10,6 +10,7 @@
#include "nlohmann/json.hpp"
#include "bottles.hpp"
#include "internal/bottles.hpp"
#include "cellar.hpp"
#include "commands.hpp"
#include "output.hpp"
@@ -45,6 +46,7 @@ int main(int argc, char* argv[]) {
cout << "\n(try \"cellar help\" if you're confused)" << endl;
return 0;
}
cellar::bottles::setup_bottle_home();
try {
const string desc = "bottle management tool for WINE connoisseurs";
const string versionstr = version::short_version();
@@ -70,7 +72,7 @@ int main(int argc, char* argv[]) {
dryrun = dryrunarg.getValue();
verbose = dryrun || verbosearg.getValue();
// BULLSHIT: trying to use str.format on this string causes bizarre compiler errors
// TODO: i'm sure stdlib has come a long way in formatting strings since this ancient hack was put in
/*[[[cog
import cog
@@ -103,7 +105,7 @@ int main(int argc, char* argv[]) {
if (bottlechoice.substr(0,1) == "/" || bottlechoice.substr(0,1) == ".") { // absolute or relative path
bottles::active_bottle = bottles::Bottle(bottlechoice);
} else if (bottlechoice.substr(0,1) == "~") { // "absolute" path in home directory, not expanded by the shell for some reason (i've seen some shit)
// this is a naive replacement and will fail if the user tries something like ~nick/.wine
// this is a naive replacement and will fail if the user tries something like ~nicole/.wine
// i'm figuring at that point if you're doing that, you'll also recognize if your shell
// isn't actually expanding your path...
bottlechoice.replace(0,1,getenv("HOME"));

View File

@@ -1,4 +1,5 @@
#include <exception>
#include <filesystem>
#include <iostream>
#include <string>
#include <vector>
@@ -7,6 +8,7 @@
#include <boost/algorithm/string.hpp>
#include "tclap/CmdLine.h"
#include "bottles.hpp"
#include "output.hpp"
#include "paths.hpp"
#include "version.hpp"
@@ -22,10 +24,6 @@ string cellar::paths::translate(std::string in_path, bool lazy) {
windows_input = regex_match(in_path.substr(0, 3), drive_letter_rgx);
if (!lazy) {
output::warning("non-lazy path translation is not implemented yet");
}
if (windows_input) {
if (lazy) {
if (boost::algorithm::to_lower_copy(in_path.substr(0,1)) != "z") {
@@ -44,15 +42,52 @@ string cellar::paths::translate(std::string in_path, bool lazy) {
return paths::resolve_drive_letter(in_path);
}
} else {
// lazy
string out_path = "Z:";
out_path.append(in_path);
string out_path;
string str_absolutepath = filesystem::canonical(in_path);
if (lazy) {
out_path = "Z:";
out_path.append(str_absolutepath);
size_t slashpos = out_path.find("/");
while (slashpos != std::string::npos) {
out_path.replace(slashpos, 1, "\\");
slashpos = out_path.find("/");
}
} else {
map<string, string> dct_drives;
for (auto hnd_curitem : filesystem::directory_iterator(filesystem::path(bottles::active_bottle.canonical_path) / "dosdevices")) {
auto pth_curitem = hnd_curitem.path();
dct_drives.insert_or_assign(pth_curitem.filename().string(), filesystem::canonical(pth_curitem));
}
for (auto hnd_curdrive : dct_drives) {
size_t sz_drivelen = hnd_curdrive.second.length();
size_t sz_findpos = in_path.rfind(hnd_curdrive.second, 0);
if (sz_findpos == 0) {
out_path.append(hnd_curdrive.first);
out_path.append(in_path.substr(sz_drivelen));
size_t slashpos = out_path.find("/");
while (slashpos != std::string::npos) {
out_path.replace(slashpos, 1, "\\");
slashpos = out_path.find("/");
}
break;
}
}
// if we're here, it's not on a drive letter. use Z:
out_path = "Z:";
out_path.append(str_absolutepath);
size_t slashpos = out_path.find("/");
while (slashpos != std::string::npos) {
out_path.replace(slashpos, 1, "\\");
slashpos = out_path.find("/");
}
}
return out_path;
}

View File

@@ -2,4 +2,3 @@ core
config
bottles
launch
steam

View File

@@ -1,4 +1,5 @@
#include <exception>
#include <filesystem>
#include <regex>
#include <string>
#include <unistd.h>
@@ -12,6 +13,7 @@
using namespace std;
string cellar::paths::resolve_drive_letter(string in_path) {
filesystem::path lastwd = filesystem::current_path();
bool windows_input;
static regex drive_letter_rgx(R"([a-zA-Z]:\\)");
@@ -25,6 +27,7 @@ string cellar::paths::resolve_drive_letter(string in_path) {
string link_path = "";
link_path.append(bottles::active_bottle.canonical_path);
link_path.append("/dosdevices/");
filesystem::current_path(filesystem::path(link_path));
link_path.append(drive_letter);
char stringbuffer[512];
@@ -32,7 +35,9 @@ string cellar::paths::resolve_drive_letter(string in_path) {
if (bufflen != -1) {
stringbuffer[bufflen] = '\0';
out_path.append(stringbuffer);
string str_absolutepath = filesystem::canonical(stringbuffer);
out_path.append(str_absolutepath);
out_path.append("/");
} else {
throw runtime_error("readlink isn't having it");
}
@@ -47,5 +52,6 @@ string cellar::paths::resolve_drive_letter(string in_path) {
out_path.append(rest_of_path);
}
filesystem::current_path(lastwd);
return out_path;
}

View File

@@ -1,30 +1,31 @@
#include <cstdlib>
#include <filesystem>
#include <format>
#include <fstream>
#include <sstream>
#include <map>
#include <string>
#include <vector>
#include "vdf_parser.hpp"
#include "bottles.hpp"
#include "output.hpp"
#include "steam.hpp"
#include "internal/steam.hpp"
using namespace tyti; //vdf
void cellar::steam::test_command(int argc, std::vector<std::string> argv) {
for (std::string str_path_library : cellar::steam::find_steam_libraries()) {
output::statement(str_path_library);
/**
* @brief Returns all app bottles managed by Steam.
*
* @return std::map<std::string, cellar::bottles::Bottle> Steam managed bottles. Keys are "steam:<appid>".
*/
std::map<std::string, cellar::bottles::Bottle> cellar::steam::get_app_bottles() {
std::map<std::string, cellar::bottles::Bottle> result;
for (std::string str_path_library : cellar::steam::find_steam_libraries()) {
std::filesystem::path pth_library(str_path_library);
std::filesystem::path pth_steam_cellar = pth_library / "steamapps/compatdata";
for (auto const& itm_appid : std::filesystem::directory_iterator(pth_steam_cellar)) {
auto pth_appid = itm_appid.path();
if (std::filesystem::is_directory(pth_appid / "pfx") && ! std::filesystem::is_empty(pth_appid / "pfx")) {
// \/ string-to-unsigned-long
std::string str_appid = pth_appid.filename().string(); // should become, e.g. 1124300
auto pth_appmanifest = pth_library / ("steamapps/appmanifest_" + str_appid + ".acf");
if (! std::filesystem::exists(pth_appmanifest) ) { continue; }
@@ -42,8 +43,26 @@ void cellar::steam::test_command(int argc, std::vector<std::string> argv) {
}
}
output::warning("Steam App #" + str_appid + " (" + str_gamename + ")");
auto curbottle = cellar::bottles::Bottle((pth_appid / "pfx").string());
curbottle.type = cellar::bottles::bottle_steam;
curbottle.set_config("name", str_gamename);
curbottle.set_config("manager", "steam");
curbottle.save_config();
result[std::string("steam:" + pth_appid.filename().string())] = curbottle;
}
}
}
return result;
}
cellar::bottles::Bottle cellar::steam::app_bottle(unsigned appid) {
string str_appid = std::to_string(appid);
string str_prefix = std::string("steam:") + str_appid;
auto steambottles = get_app_bottles();
if (steambottles.find(str_prefix) == steambottles.end()) {
throw std::range_error("steam is not currently managing a valid prefix for " + std::to_string(appid));
}
return steambottles.at(str_prefix);
}

View File

@@ -1 +0,0 @@
steamtest test_command Test Steam related thing.

View File

@@ -1,9 +1,12 @@
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <regex>
#include <sstream>
#include <string>
#include <vector>
#include "nlohmann/json.hpp"
#include "vdf_parser.hpp"
#include "bottles.hpp"
@@ -12,17 +15,23 @@
using namespace tyti;
/**
* @brief Reads Steam library settings and returns a list of Steam library paths.
* Returns an empty vector if it can't read ~/.steam/root/config/libraryfolders.vdf.
*
* @return std::vector<std::string> Steam library paths.
*/
std::vector<std::string> cellar::steam::find_steam_libraries() {
std::stringstream sstr_steam_library_config;
sstr_steam_library_config << std::getenv("HOME");
sstr_steam_library_config << "/.steam/root/config/libraryfolders.vdf";
std::string str_steam_library_config = sstr_steam_library_config.str();
std::vector<std::string> result = {};
std::ifstream fd_steam_library_config(str_steam_library_config);
if (fd_steam_library_config.fail()) { return result; } // return empty if something went wrong (should cover most problems)
auto hnd_steam_library_config = vdf::read(fd_steam_library_config);
std::vector<std::string> result;
for (auto hnd_library_def : hnd_steam_library_config.childs) {
std::string str_index = hnd_library_def.first;
auto hnd_library = hnd_library_def.second;
@@ -31,3 +40,18 @@ std::vector<std::string> cellar::steam::find_steam_libraries() {
return result;
}
/**
* @brief Gets available versions of Proton from Steam.
*
* @return std::vector<std::string> Proton versions.
*/
std::map<std::string, std::string> cellar::steam::find_steam_protons() {
std::map<std::string, std::string> result;
for (std::string str_library_path : find_steam_libraries()) {
std::filesystem::path pth_library(str_library_path);
for ()
}
}