Compare commits

..

38 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
19251a253f debug settings for gdb in vscode; steamtest now reports which apps have bottles managed by steam automatically 2025-01-25 19:15:05 -08:00
9ad87ba79b build system spruce-up 2025-01-25 17:42:48 -08:00
83b7de3a5f doxyfile improvements 2025-01-20 16:32:11 -08:00
0f679dc345 cellar is now capable of finding your steam libraries 2025-01-20 16:31:48 -08:00
ce8c26e58c stubbing out for big things ahead :) 2025-01-20 15:42:44 -08:00
2d620be9d8 copyright update 2025-01-20 15:33:16 -08:00
2de0b66e4e fix config reading errors from not touching this project in a few eons 2025-01-20 14:49:17 -08:00
b2c3508e8f build system updates 2025-01-20 14:48:54 -08:00
d1960506ac config cli fixes 2025-01-18 23:18:12 -08:00
1f9a78956b version bump, update for modern boost, default cellar bottle path baked in 2025-01-18 23:09:28 -08:00
81732eab03 fix incorrect license value in RPM 2024-03-18 18:35:58 -07:00
c73cf7d67e partial debian and rpm packaging files; gitea has package registries for these types anyway, might as well take advantage of them 2024-03-18 18:35:19 -07:00
eec5abdeb2 honestly the mirror line is both inaccurate and silly 2023-06-30 17:48:20 -07:00
8c9a66632c cellar create will now create ~/.local/share/cellar if it does not already exist 2023-06-30 17:34:46 -07:00
fec22d16f5 fixed occurrence of deadname 2023-06-30 17:34:16 -07:00
d7c50a1941 fix *.in files being mistakenly ignored by git, fixed missing cmake.hpp.in 2023-06-30 17:18:19 -07:00
Nicole O'Connor
aec4d311b6 i changed my github username 2020-09-03 13:44:55 -07:00
Nicholas O'Connor
85cec3444c cellar translate will now use resolve_drive_letter 2019-05-15 18:09:22 -07:00
Nicholas O'Connor
b305cf56f4 Update copyright years in man page 2019-05-15 18:08:38 -07:00
Nicholas O'Connor
66c8175423 translates windows drive letters to canonical paths 2019-05-15 18:03:15 -07:00
Nicholas O'Connor
2e1217adf3 fix active_bottle being set to a bogus bottle location 2019-05-15 18:02:19 -07:00
Nicholas O'Connor
54a1d35556 rename drive letter regex to make room for expansion 2019-05-15 17:16:54 -07:00
43 changed files with 4504 additions and 298 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,3 @@
*.in
*.o *.o
*.so *.so
*/.deps */.deps

28
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,28 @@
{
"configurations": [
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/cellar",
"args": ["steamtest"],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
}
]
}

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

@@ -1,9 +1,9 @@
cmake_minimum_required(VERSION 3.7.2) cmake_minimum_required(VERSION 3.27.2)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/Modules) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
include(Colours) include(Colours)
include(Platform) include(Platform)
project(cellar CXX) project(cellar VERSION 0.5 LANGUAGES CXX)
string(TIMESTAMP BUILD_DATE "%Y.%m.%d %H:%M:%S UTC" UTC) string(TIMESTAMP BUILD_DATE "%Y.%m.%d %H:%M:%S UTC" UTC)
# local cmake modules # local cmake modules
@@ -14,6 +14,9 @@ SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib/cellar")
# which point to directories outside the build tree to the install RPATH # which point to directories outside the build tree to the install RPATH
SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
# for build time config
set(ENABLE_STEAM TRUE CACHE BOOL "Whether or not to support bottles managed by Steam.")
include(Git) include(Git)
git_init() git_init()
@@ -37,24 +40,32 @@ http://tclap.sourceforge.net and put the headers in ./include
(wink, nudge)") (wink, nudge)")
endif(NOT TCLAP_FOUND) endif(NOT TCLAP_FOUND)
include(LavaTargets) include(Binaries)
include_directories(include) include_directories(include)
configure_file("${CMAKE_SOURCE_DIR}/include/cmake.hpp.in" configure_file("${CMAKE_SOURCE_DIR}/include/cmake.hpp.in"
"${CMAKE_CURRENT_BINARY_DIR}/include/cmake.hpp") "${CMAKE_CURRENT_BINARY_DIR}/include/cmake.hpp")
configure_file("${CMAKE_SOURCE_DIR}/Doxyfile.in"
"${CMAKE_CURRENT_BINARY_DIR}/Doxyfile")
include_directories("${CMAKE_CURRENT_BINARY_DIR}/include") include_directories("${CMAKE_CURRENT_BINARY_DIR}/include")
set(src "${CMAKE_SOURCE_DIR}/src") set(src "${CMAKE_SOURCE_DIR}/src")
lava_create_gutlib( set(libcellar_subdirs bottles config launch paths)
SUBDIRS bottles config launch if(ENABLE_STEAM)
list(APPEND libcellar_subdirs steam)
endif()
build_library(TARGET libcellar
SUBDIRS ${libcellar_subdirs}
DEPENDS cog) DEPENDS cog)
set_target_properties(libcellar PROPERTIES PREFIX "") # prevent "liblibcellar"
lava_create_executable(TARGET cellar build_executable(TARGET cellar
SUBDIRS core help SUBDIRS core help
LIBRARIES gutlib Boost::filesystem Boost::system LIBRARIES libcellar Boost::filesystem Boost::system
DEPENDS cog) DEPENDS cog)
install(TARGETS cellar gutlib install(TARGETS cellar libcellar
RUNTIME DESTINATION bin RUNTIME DESTINATION bin
LIBRARY DESTINATION lib/cellar LIBRARY DESTINATION lib/cellar
ARCHIVE DESTINATION share/cellar) ARCHIVE DESTINATION share/cellar)
message(STATUS "If you have Doxygen installed, you can run it from this directory to generate documentation.")

2917
Doxyfile.in Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
Unless otherwise specified, any and all code in this repository is Unless otherwise specified, any and all code in this repository is
provided under the terms of the MIT license, as below: provided under the terms of the MIT license, as below:
Copyright (c) 2017 Nicholas O'Connor Copyright (c) 2017-2025 Nicole O'Connor
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the

View File

@@ -2,7 +2,11 @@
### bottle management tool for WINE connoisseurs ### bottle management tool for WINE connoisseurs
*(this software is considered unfinished, use at own risk)* *(this software is considered unfinished, use at own risk)*
(notice: https://github.com/lavacano201014/cellar is a mirror. The upstream repository is at [vcs.lavacano.net/cellar.git](http://vcs.lavacano.net/?p=cellar.git), and bugs are officially tracked at [mantis.lavacano.net](http://mantis.lavacano.net)) ## Dependencies
* Boost
* Cog for code generation (`pip install cogapp`)
* TCLAP (either system-wide or included in `include`)
* Ronn for man page
## Installation ## Installation
@@ -11,25 +15,23 @@
$ make -j4 $ make -j4
$ sudo make install $ 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 ## Quick Usage Primer
$ cellar create steam $ cellar create foobar2000
$ cellar -b steam winetricks vcrun2012 $ cellar -b foobar2000 winetricks vcrun2012
# without the -b argument, cellar assumes you want to deal with ~/.wine # without the -b argument, cellar assumes you want to deal with ~/.wine
# you can manage which bottle that points to with this command # 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 # arguments passed to "cellar launch" are passed to wine
$ cellar launch /mnt/windows/Steam/Steam.exe $ cellar launch /mnt/windows/foobar2000/foobar2000.exe
## Features ## 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. * **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 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. 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 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. 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 * **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

82
cmake/Binaries.cmake Executable file
View File

@@ -0,0 +1,82 @@
include(GenerateExportHeader)
link_directories(${CMAKE_CURRENT_BINARY_DIR})
set(BUILD_SHARED_LIBS ON)
if (MSVC)
add_compile_options("/utf-8")
endif()
function(build_library)
set(multiValueArgs SUBDIRS DEPENDS LIBRARIES)
set(oneValueArgs TARGET)
cmake_parse_arguments(build_library "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(target ${build_library_TARGET})
set(targetsources)
foreach(subdir ${build_library_SUBDIRS})
set(found_files)
#file(GLOB_RECURSE found_files RELATIVE "${CMAKE_SOURCE_DIR}" "${CMAKE_SOURCE_DIR}/src/${subdir}/*.cpp")
cog_sources("src/${subdir}/*.cpp" found_files)
set(targetsources ${targetsources} ${found_files})
endforeach()
if (MSVC AND EXISTS "${CMAKE_SOURCE_DIR}/res/${target}.rc")
set(targetsources ${targetsources} "res/${target}.rc")
endif()
add_library(${target} SHARED ${targetsources})
if (MSVC)
set_target_properties(${target} PROPERTIES PREFIX "")
set_target_properties(${target} PROPERTIES LINK_FLAGS "/MAP")
GENERATE_EXPORT_HEADER(${target}
BASE_NAME ${target}
EXPORT_MACRO_NAME ${target}_EXPORT
EXPORT_FILE_NAME include/${target}_Export.h
STATIC_DEFINE ${target}_BUILT_AS_STATIC)
endif()
if (build_library_LIBRARIES)
foreach (library ${build_library_LIBRARIES})
target_link_libraries(${target} ${library})
endforeach()
endif()
if (build_library_DEPENDS)
add_dependencies(${target} ${build_library_DEPENDS})
endif()
endfunction()
function(build_executable)
set(multiValueArgs SUBDIRS DEPENDS LIBRARIES)
set(oneValueArgs TARGET)
cmake_parse_arguments(build_executable "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(target ${build_executable_TARGET})
set(targetsources)
foreach(subdir ${build_executable_SUBDIRS})
set(found_files)
#file(GLOB_RECURSE found_files RELATIVE "${CMAKE_SOURCE_DIR}" "${CMAKE_SOURCE_DIR}/src/${subdir}/*.cpp")
cog_sources("src/${subdir}/*.cpp" found_files)
set(targetsources ${targetsources} ${found_files})
endforeach()
if (MSVC AND EXISTS "${CMAKE_SOURCE_DIR}/res/${target}.rc")
set(targetsources ${targetsources} "res/${target}.rc")
endif()
add_executable(${target} ${targetsources})
if (MSVC)
# tells MSVC to use int main() but still hide the console window
set_target_properties(${target} PROPERTIES LINK_FLAGS "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
endif()
if (build_executable_LIBRARIES)
foreach (library ${build_executable_LIBRARIES})
target_link_libraries(${target} ${library})
endforeach()
endif()
if (build_executable_DEPENDS)
add_dependencies(${target} ${build_executable_DEPENDS})
endif()
endfunction()

View File

@@ -1,4 +1,4 @@
find_package(PythonInterp) find_package(Python3 COMPONENTS Interpreter)
find_package(PythonModule) find_package(PythonModule)
find_python_module(cogapp REQUIRED) find_python_module(cogapp REQUIRED)
@@ -31,7 +31,7 @@ macro(cog_target)
set(thisfile "${CMAKE_CURRENT_BINARY_DIR}/${outfile}") set(thisfile "${CMAKE_CURRENT_BINARY_DIR}/${outfile}")
add_custom_command(OUTPUT "${thisfile}" PRE_BUILD add_custom_command(OUTPUT "${thisfile}" PRE_BUILD
COMMAND ${PYTHON_EXECUTABLE} -m cogapp -d -o "${thisfile}" "${cogfile}" COMMAND ${Python3_EXECUTABLE} -m cogapp -d -o "${thisfile}" "${cogfile}"
DEPENDS ${cogfile} DEPENDS ${cogfile}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Greasing the cog for ${BoldCyan}${outfile}${ColourReset}") COMMENT "Greasing the cog for ${BoldCyan}${outfile}${ColourReset}")

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

@@ -1,4 +1,4 @@
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(PLATFORM_FOREIGN_ENV FALSE) set(PLATFORM_FOREIGN_ENV FALSE)

3
cogrc
View File

@@ -1,5 +1,6 @@
[version] [version]
release_version=0.4 release_version=0.5
[defaults] [defaults]
wine-path=wine wine-path=wine
cellar-bottle-path=~/.local/share/cellar/bottles

View File

@@ -34,4 +34,4 @@ TODO: Generate this.
## COPYRIGHT ## COPYRIGHT
Copyright © 2017 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

@@ -1,6 +1,6 @@
/* This file contains string definitions for ANSI color sequences /* This file contains string definitions for ANSI color sequences
* it was written by Nicholas "Lavacano" O'Connor and is available * it was written by Nicole O'Connor and is available to all
* to all under the MIT license. * under the MIT license.
* *
* Usage: * Usage:
* std::cout << ansicol::red_bold << "I am bold red text!" << ansicol::reset << std::endl; * std::cout << ansicol::red_bold << "I am bold red text!" << ansicol::reset << std::endl;

View File

@@ -18,12 +18,28 @@ namespace cellar {
bottle_error, bottle_error,
bottle_anonymous, bottle_anonymous,
bottle_labelled, 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 { class Bottle {
public: public:
// public members // public members
bottle_type type; bottle_type type;
bottle_manager manager;
json config; json config;
string path; string path;
string canonical_path; string canonical_path;

6
include/cmake.hpp.in Executable file
View File

@@ -0,0 +1,6 @@
#pragma once
#cmakedefine IS_GIT_REPO "@IS_GIT_REPO@"
#cmakedefine GIT_COMMIT_HASH "@GIT_COMMIT_HASH@"
#cmakedefine GIT_BRANCH "@GIT_BRANCH@"
#cmakedefine ENABLE_STEAM "@ENABLE_STEAM@"

View File

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

@@ -5,5 +5,6 @@
namespace cellar { namespace cellar {
namespace paths { namespace paths {
extern std::string translate(std::string in_path, bool lazy = false); extern std::string translate(std::string in_path, bool lazy = false);
extern std::string resolve_drive_letter(std::string in_path);
} }
} }

11
include/steam.hpp Normal file
View File

@@ -0,0 +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();
}
}

855
include/vdf_parser.hpp Normal file
View File

@@ -0,0 +1,855 @@
// source: https://github.com/TinyTinni/ValveFileVDF/releases/tag/v1.1.0
// MIT License
//
// Copyright(c) 2016 Matthias Moeller
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#ifndef __TYTI_STEAM_VDF_PARSER_H__
#define __TYTI_STEAM_VDF_PARSER_H__
#include <algorithm>
#include <fstream>
#include <functional>
#include <iterator>
#include <map>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include <exception>
#include <system_error>
// for wstring support
#include <cwchar>
#include <string>
// internal
#include <stack>
// VS < 2015 has only partial C++11 support
#if defined(_MSC_VER) && _MSC_VER < 1900
#ifndef CONSTEXPR
#define CONSTEXPR
#endif
#ifndef NOEXCEPT
#define NOEXCEPT
#endif
#else
#ifndef CONSTEXPR
#define CONSTEXPR constexpr
#define TYTI_UNDEF_CONSTEXPR
#endif
#ifndef NOEXCEPT
#define NOEXCEPT noexcept
#define TYTI_UNDEF_NOEXCEPT
#endif
#endif
namespace tyti
{
namespace vdf
{
namespace detail
{
///////////////////////////////////////////////////////////////////////////
// Helper functions selecting the right encoding (char/wchar_T)
///////////////////////////////////////////////////////////////////////////
template <typename T> struct literal_macro_help
{
static CONSTEXPR const char *result(const char *c, const wchar_t *) NOEXCEPT
{
return c;
}
static CONSTEXPR char result(const char c, const wchar_t) NOEXCEPT
{
return c;
}
};
template <> struct literal_macro_help<wchar_t>
{
static CONSTEXPR const wchar_t *result(const char *,
const wchar_t *wc) NOEXCEPT
{
return wc;
}
static CONSTEXPR wchar_t result(const char, const wchar_t wc) NOEXCEPT
{
return wc;
}
};
#define TYTI_L(type, text) \
vdf::detail::literal_macro_help<type>::result(text, L##text)
inline std::string string_converter(const std::string &w) NOEXCEPT { return w; }
inline std::string string_converter(const std::wstring &w) NOEXCEPT
{
std::mbstate_t state = std::mbstate_t();
auto wstr = w.data();
// unsafe: ignores any error handling
// and disables warning that wcsrtombs_s should be used
#ifdef WIN32
#pragma warning(push)
#pragma warning(disable : 4996)
#endif
std::size_t len = 1 + std::wcsrtombs(nullptr, &wstr, 0, &state);
std::string mbstr(len, '\0');
std::wcsrtombs(&mbstr[0], &wstr, mbstr.size(), &state);
#ifdef WIN32
#pragma warning(pop)
#endif
return mbstr;
}
///////////////////////////////////////////////////////////////////////////
// Writer helper functions
///////////////////////////////////////////////////////////////////////////
template <typename charT> class tabs
{
const size_t t;
public:
explicit CONSTEXPR tabs(size_t i) NOEXCEPT : t(i) {}
std::basic_string<charT> print() const
{
return std::basic_string<charT>(t, TYTI_L(charT, '\t'));
}
inline CONSTEXPR tabs operator+(size_t i) const NOEXCEPT
{
return tabs(t + i);
}
};
template <typename oStreamT>
oStreamT &operator<<(oStreamT &s, const tabs<typename oStreamT::char_type> t)
{
s << t.print();
return s;
}
template <typename charT>
std::basic_string<charT> escape(std::basic_string<charT> in)
{
// std::replace_if(in.begin(), end.begin(), [](const charT))
for (size_t i = 0; i < in.size(); ++i)
{
if (in[i] == TYTI_L(charT, '\"') || in[i] == TYTI_L(charT, '\\'))
{
in.insert(i, TYTI_L(charT, "\\"));
++i;
}
}
return in;
}
} // end namespace detail
///////////////////////////////////////////////////////////////////////////
// Interface
///////////////////////////////////////////////////////////////////////////
/// custom objects and their corresponding write functions
/// basic object node. Every object has a name and can contains attributes saved
/// as key_value pairs or childrens
template <typename CharT> struct basic_object
{
typedef CharT char_type;
std::basic_string<char_type> name;
std::unordered_map<std::basic_string<char_type>,
std::basic_string<char_type>>
attribs;
std::unordered_map<std::basic_string<char_type>,
std::shared_ptr<basic_object<char_type>>>
childs;
void add_attribute(std::basic_string<char_type> key,
std::basic_string<char_type> value)
{
attribs.emplace(std::move(key), std::move(value));
}
void add_child(std::unique_ptr<basic_object<char_type>> child)
{
std::shared_ptr<basic_object<char_type>> obj{child.release()};
childs.emplace(obj->name, obj);
}
void set_name(std::basic_string<char_type> n) { name = std::move(n); }
};
template <typename CharT> struct basic_multikey_object
{
typedef CharT char_type;
std::basic_string<char_type> name;
std::unordered_multimap<std::basic_string<char_type>,
std::basic_string<char_type>>
attribs;
std::unordered_multimap<std::basic_string<char_type>,
std::shared_ptr<basic_multikey_object<char_type>>>
childs;
void add_attribute(std::basic_string<char_type> key,
std::basic_string<char_type> value)
{
attribs.emplace(std::move(key), std::move(value));
}
void add_child(std::unique_ptr<basic_multikey_object<char_type>> child)
{
std::shared_ptr<basic_multikey_object<char_type>> obj{child.release()};
childs.emplace(obj->name, obj);
}
void set_name(std::basic_string<char_type> n) { name = std::move(n); }
};
typedef basic_object<char> object;
typedef basic_object<wchar_t> wobject;
typedef basic_multikey_object<char> multikey_object;
typedef basic_multikey_object<wchar_t> wmultikey_object;
struct Options
{
bool strip_escape_symbols;
bool ignore_all_platform_conditionals;
bool ignore_includes;
Options()
: strip_escape_symbols(true), ignore_all_platform_conditionals(false),
ignore_includes(false)
{
}
};
struct WriteOptions
{
bool escape_symbols;
WriteOptions() : escape_symbols(true) {}
};
// forward decls
// forward decl
template <typename OutputT, typename iStreamT>
OutputT read(iStreamT &inStream, const Options &opt = Options{});
/** \brief writes given object tree in vdf format to given stream.
Output is prettyfied, using tabs
*/
template <typename oStreamT, typename T>
void write(oStreamT &s, const T &r, const WriteOptions &opts = {},
const detail::tabs<typename oStreamT::char_type> tab =
detail::tabs<typename oStreamT::char_type>(0))
{
typedef typename oStreamT::char_type charT;
using namespace detail;
auto escapeFunction = [&opts](const std::basic_string<charT> &in)
{
if (opts.escape_symbols)
return escape(std::move(in));
return in;
};
s << tab << TYTI_L(charT, '"') << escapeFunction(r.name)
<< TYTI_L(charT, "\"\n") << tab << TYTI_L(charT, "{\n");
for (const auto &i : r.attribs)
s << tab + 1 << TYTI_L(charT, '"') << escapeFunction(i.first)
<< TYTI_L(charT, "\"\t\t\"") << escapeFunction(i.second)
<< TYTI_L(charT, "\"\n");
for (const auto &i : r.childs)
if (i.second)
write(s, *i.second, opts, tab + 1);
s << tab << TYTI_L(charT, "}\n");
}
namespace detail
{
template <typename iStreamT>
std::basic_string<typename iStreamT::char_type> read_file(iStreamT &inStream)
{
// cache the file
typedef typename iStreamT::char_type charT;
std::basic_string<charT> str;
inStream.seekg(0, std::ios::end);
str.resize(static_cast<size_t>(inStream.tellg()));
if (str.empty())
return str;
inStream.seekg(0, std::ios::beg);
inStream.read(&str[0], static_cast<std::streamsize>(str.size()));
return str;
}
/** \brief Read VDF formatted sequences defined by the range [first, last).
If the file is mailformatted, parser will try to read it until it can.
@param first begin iterator
@param end end iterator
@param exclude_files list of files which cant be included anymore.
prevents circular includes
can thow:
- "std::runtime_error" if a parsing error occured
- "std::bad_alloc" if not enough memory coup be allocated
*/
template <typename OutputT, typename IterT>
std::vector<std::unique_ptr<OutputT>> read_internal(
IterT first, const IterT last,
std::unordered_set<
std::basic_string<typename std::iterator_traits<IterT>::value_type>>
&exclude_files,
const Options &opt)
{
static_assert(std::is_default_constructible<OutputT>::value,
"Output Type must be default constructible (provide "
"constructor without arguments)");
static_assert(std::is_move_constructible<OutputT>::value,
"Output Type must be move constructible");
typedef typename std::iterator_traits<IterT>::value_type charT;
const std::basic_string<charT> comment_end_str = TYTI_L(charT, "*/");
const std::basic_string<charT> whitespaces = TYTI_L(charT, " \n\v\f\r\t");
#ifdef WIN32
std::function<bool(const std::basic_string<charT> &)> is_platform_str =
[](const std::basic_string<charT> &in)
{
return in == TYTI_L(charT, "$WIN32") || in == TYTI_L(charT, "$WINDOWS");
};
#elif __APPLE__
// WIN32 stands for pc in general
std::function<bool(const std::basic_string<charT> &)> is_platform_str =
[](const std::basic_string<charT> &in)
{
return in == TYTI_L(charT, "$WIN32") || in == TYTI_L(charT, "$POSIX") ||
in == TYTI_L(charT, "$OSX");
};
#elif __linux__
// WIN32 stands for pc in general
std::function<bool(const std::basic_string<charT> &)> is_platform_str =
[](const std::basic_string<charT> &in)
{
return in == TYTI_L(charT, "$WIN32") || in == TYTI_L(charT, "$POSIX") ||
in == TYTI_L(charT, "$LINUX");
};
#else
std::function<bool(const std::basic_string<charT> &)> is_platform_str =
[](const std::basic_string<charT> &in) { return false; };
#endif
if (opt.ignore_all_platform_conditionals)
is_platform_str = [](const std::basic_string<charT> &)
{ return false; };
// function for skipping a comment block
// iter: iterator poition to the position after a '/'
auto skip_comments = [&comment_end_str](IterT iter,
const IterT &last) -> IterT
{
++iter;
if (iter == last)
return last;
if (*iter == TYTI_L(charT, '/'))
{
// line comment, skip whole line
iter = std::find(iter + 1, last, TYTI_L(charT, '\n'));
if (iter == last)
return last;
}
if (*iter == '*')
{
// block comment, skip until next occurance of "*\"
iter = std::search(iter + 1, last, std::begin(comment_end_str),
std::end(comment_end_str));
if (std::distance(iter, last) <= 2)
return last;
iter += 2;
}
return iter;
};
auto end_quote = [opt](IterT iter, const IterT &last) -> IterT
{
const auto begin = iter;
auto last_esc = iter;
if (iter == last)
throw std::runtime_error{"quote was opened but not closed."};
do
{
++iter;
iter = std::find(iter, last, TYTI_L(charT, '\"'));
if (iter == last)
break;
last_esc = std::prev(iter);
if (opt.strip_escape_symbols)
{
while (last_esc != begin && *last_esc == '\\')
--last_esc;
}
} while (!(std::distance(last_esc, iter) % 2) && iter != last);
if (iter == last)
throw std::runtime_error{"quote was opened but not closed."};
return iter;
};
auto end_word = [&whitespaces](IterT iter, const IterT &last) -> IterT
{
const auto begin = iter;
auto last_esc = iter;
if (iter == last)
throw std::runtime_error{"quote was opened but not closed."};
do
{
++iter;
iter = std::find_first_of(iter, last, std::begin(whitespaces),
std::end(whitespaces));
if (iter == last)
break;
last_esc = std::prev(iter);
while (last_esc != begin && *last_esc == '\\')
--last_esc;
} while (!(std::distance(last_esc, iter) % 2) && iter != last);
if (iter == last)
throw std::runtime_error{"word wasnt properly ended"};
return iter;
};
auto skip_whitespaces = [&whitespaces](IterT iter,
const IterT &last) -> IterT
{
if (iter == last)
return iter;
iter = std::find_if_not(iter, last,
[&whitespaces](charT c)
{
// return true if whitespace
return std::any_of(std::begin(whitespaces),
std::end(whitespaces),
[c](charT pc)
{ return pc == c; });
});
return iter;
};
std::function<void(std::basic_string<charT> &)> strip_escape_symbols =
[](std::basic_string<charT> &s)
{
auto quote_searcher = [&s](size_t pos)
{ return s.find(TYTI_L(charT, "\\\""), pos); };
auto p = quote_searcher(0);
while (p != s.npos)
{
s.replace(p, 2, TYTI_L(charT, "\""));
p = quote_searcher(p + 1);
}
auto searcher = [&s](size_t pos)
{ return s.find(TYTI_L(charT, "\\\\"), pos); };
p = searcher(0);
while (p != s.npos)
{
s.replace(p, 2, TYTI_L(charT, "\\"));
p = searcher(p + 1);
}
};
if (!opt.strip_escape_symbols)
strip_escape_symbols = [](std::basic_string<charT> &) {};
auto conditional_fullfilled =
[&skip_whitespaces, &is_platform_str](IterT &iter, const IterT &last)
{
iter = skip_whitespaces(iter, last);
if (iter == last)
return true;
if (*iter == '[')
{
++iter;
if (iter == last)
throw std::runtime_error("conditional not closed");
const auto end = std::find(iter, last, ']');
if (end == last)
throw std::runtime_error("conditional not closed");
const bool negate = *iter == '!';
if (negate)
++iter;
auto conditional = std::basic_string<charT>(iter, end);
const bool is_platform = is_platform_str(conditional);
iter = end + 1;
return static_cast<bool>(is_platform ^ negate);
}
return true;
};
// read header
// first, quoted name
std::unique_ptr<OutputT> curObj = nullptr;
std::vector<std::unique_ptr<OutputT>> roots;
std::stack<std::unique_ptr<OutputT>> lvls;
auto curIter = first;
while (curIter != last && *curIter != '\0')
{
// find first starting attrib/child, or ending
curIter = skip_whitespaces(curIter, last);
if (curIter == last || *curIter == '\0')
break;
if (*curIter == TYTI_L(charT, '/'))
{
curIter = skip_comments(curIter, last);
if (curIter == last || *curIter == '\0')
throw std::runtime_error("Unexpected eof");
}
else if (*curIter != TYTI_L(charT, '}'))
{
// get key
const auto keyEnd = (*curIter == TYTI_L(charT, '\"'))
? end_quote(curIter, last)
: end_word(curIter, last);
if (*curIter == TYTI_L(charT, '\"'))
++curIter;
std::basic_string<charT> key(curIter, keyEnd);
strip_escape_symbols(key);
curIter = keyEnd + ((*keyEnd == TYTI_L(charT, '\"')) ? 1 : 0);
if (curIter == last)
throw std::runtime_error{"key opened, but never closed"};
curIter = skip_whitespaces(curIter, last);
if (!conditional_fullfilled(curIter, last))
continue;
if (curIter == last)
throw std::runtime_error{"key declared, but no value"};
while (*curIter == TYTI_L(charT, '/'))
{
curIter = skip_comments(curIter, last);
if (curIter == last || *curIter == '}')
throw std::runtime_error{"key declared, but no value"};
curIter = skip_whitespaces(curIter, last);
if (curIter == last || *curIter == '}')
throw std::runtime_error{"key declared, but no value"};
}
// get value
if (*curIter != '{')
{
if (curIter == last)
throw std::runtime_error{"key declared, but no value"};
const auto valueEnd = (*curIter == TYTI_L(charT, '\"'))
? end_quote(curIter, last)
: end_word(curIter, last);
if (valueEnd == last)
throw std::runtime_error("No closed word");
if (*curIter == TYTI_L(charT, '\"'))
++curIter;
if (curIter == last)
throw std::runtime_error("No closed word");
auto value = std::basic_string<charT>(curIter, valueEnd);
strip_escape_symbols(value);
curIter =
valueEnd + ((*valueEnd == TYTI_L(charT, '\"')) ? 1 : 0);
if (!conditional_fullfilled(curIter, last))
continue;
// process value
if (key != TYTI_L(charT, "#include") &&
key != TYTI_L(charT, "#base"))
{
if (curObj)
{
curObj->add_attribute(std::move(key), std::move(value));
}
else
{
throw std::runtime_error{
"unexpected key without object"};
}
}
else
{
if (!opt.ignore_includes &&
exclude_files.find(value) == exclude_files.end())
{
exclude_files.insert(value);
std::basic_ifstream<charT> i(
detail::string_converter(value));
auto str = read_file(i);
auto file_objs = read_internal<OutputT>(
str.begin(), str.end(), exclude_files, opt);
for (auto &n : file_objs)
{
if (curObj)
curObj->add_child(std::move(n));
else
roots.push_back(std::move(n));
}
exclude_files.erase(value);
}
}
}
else if (*curIter == '{')
{
if (curObj)
lvls.push(std::move(curObj));
curObj = std::make_unique<OutputT>();
curObj->set_name(std::move(key));
++curIter;
}
}
// end of new object
else if (curObj && *curIter == TYTI_L(charT, '}'))
{
if (!lvls.empty())
{
// get object before
std::unique_ptr<OutputT> prev{std::move(lvls.top())};
lvls.pop();
// add finished obj to obj before and release it from processing
prev->add_child(std::move(curObj));
curObj = std::move(prev);
}
else
{
roots.push_back(std::move(curObj));
curObj.reset();
}
++curIter;
}
else
{
throw std::runtime_error{"unexpected '}'"};
}
}
if (curObj != nullptr || !lvls.empty())
{
throw std::runtime_error{"object is not closed with '}'"};
}
return roots;
}
} // namespace detail
/** \brief Read VDF formatted sequences defined by the range [first, last).
If the file is mailformatted, parser will try to read it until it can.
@param first begin iterator
@param end end iterator
can thow:
- "std::runtime_error" if a parsing error occured
- "std::bad_alloc" if not enough memory coup be allocated
*/
template <typename OutputT, typename IterT>
OutputT read(IterT first, const IterT last, const Options &opt = Options{})
{
auto exclude_files = std::unordered_set<
std::basic_string<typename std::iterator_traits<IterT>::value_type>>{};
auto roots =
detail::read_internal<OutputT>(first, last, exclude_files, opt);
OutputT result;
if (roots.size() > 1)
{
for (auto &i : roots)
result.add_child(std::move(i));
}
else if (roots.size() == 1)
result = std::move(*roots[0]);
return result;
}
/** \brief Read VDF formatted sequences defined by the range [first, last).
If the file is mailformatted, parser will try to read it until it can.
@param first begin iterator
@param end end iterator
@param ec output bool. 0 if ok, otherwise, holds an system error code
Possible error codes:
std::errc::protocol_error: file is mailformatted
std::errc::not_enough_memory: not enough space
std::errc::invalid_argument: iterators throws e.g. out of range
*/
template <typename OutputT, typename IterT>
OutputT read(IterT first, IterT last, std::error_code &ec,
const Options &opt = Options{}) NOEXCEPT
{
ec.clear();
OutputT r{};
try
{
r = read<OutputT>(first, last, opt);
}
catch (std::runtime_error &)
{
ec = std::make_error_code(std::errc::protocol_error);
}
catch (std::bad_alloc &)
{
ec = std::make_error_code(std::errc::not_enough_memory);
}
catch (...)
{
ec = std::make_error_code(std::errc::invalid_argument);
}
return r;
}
/** \brief Read VDF formatted sequences defined by the range [first, last).
If the file is mailformatted, parser will try to read it until it can.
@param first begin iterator
@param end end iterator
@param ok output bool. true, if parser successed, false, if parser failed
*/
template <typename OutputT, typename IterT>
OutputT read(IterT first, const IterT last, bool *ok,
const Options &opt = Options{}) NOEXCEPT
{
std::error_code ec;
auto r = read<OutputT>(first, last, ec, opt);
if (ok)
*ok = !ec;
return r;
}
template <typename IterT>
inline auto read(IterT first, const IterT last, bool *ok,
const Options &opt = Options{}) NOEXCEPT
-> basic_object<typename std::iterator_traits<IterT>::value_type>
{
return read<basic_object<typename std::iterator_traits<IterT>::value_type>>(
first, last, ok, opt);
}
template <typename IterT>
inline auto read(IterT first, IterT last, std::error_code &ec,
const Options &opt = Options{}) NOEXCEPT
-> basic_object<typename std::iterator_traits<IterT>::value_type>
{
return read<basic_object<typename std::iterator_traits<IterT>::value_type>>(
first, last, ec, opt);
}
template <typename IterT>
inline auto read(IterT first, const IterT last, const Options &opt = Options{})
-> basic_object<typename std::iterator_traits<IterT>::value_type>
{
return read<basic_object<typename std::iterator_traits<IterT>::value_type>>(
first, last, opt);
}
/** \brief Loads a stream (e.g. filestream) into the memory and parses the vdf
formatted data. throws "std::bad_alloc" if file buffer could not be allocated
*/
template <typename OutputT, typename iStreamT>
OutputT read(iStreamT &inStream, std::error_code &ec,
const Options &opt = Options{})
{
// cache the file
typedef typename iStreamT::char_type charT;
std::basic_string<charT> str = detail::read_file(inStream);
// parse it
return read<OutputT>(str.begin(), str.end(), ec, opt);
}
template <typename iStreamT>
inline basic_object<typename iStreamT::char_type>
read(iStreamT &inStream, std::error_code &ec, const Options &opt = Options{})
{
return read<basic_object<typename iStreamT::char_type>>(inStream, ec, opt);
}
/** \brief Loads a stream (e.g. filestream) into the memory and parses the vdf
formatted data. throws "std::bad_alloc" if file buffer could not be allocated
ok == false, if a parsing error occured
*/
template <typename OutputT, typename iStreamT>
OutputT read(iStreamT &inStream, bool *ok, const Options &opt = Options{})
{
std::error_code ec;
const auto r = read<OutputT>(inStream, ec, opt);
if (ok)
*ok = !ec;
return r;
}
template <typename iStreamT>
inline basic_object<typename iStreamT::char_type>
read(iStreamT &inStream, bool *ok, const Options &opt = Options{})
{
return read<basic_object<typename iStreamT::char_type>>(inStream, ok, opt);
}
/** \brief Loads a stream (e.g. filestream) into the memory and parses the vdf
formatted data. throws "std::bad_alloc" if file buffer could not be allocated
throws "std::runtime_error" if a parsing error occured
*/
template <typename OutputT, typename iStreamT>
OutputT read(iStreamT &inStream, const Options &opt)
{
// cache the file
typedef typename iStreamT::char_type charT;
std::basic_string<charT> str = detail::read_file(inStream);
// parse it
return read<OutputT>(str.begin(), str.end(), opt);
}
template <typename iStreamT>
inline basic_object<typename iStreamT::char_type>
read(iStreamT &inStream, const Options &opt = Options{})
{
return read<basic_object<typename iStreamT::char_type>>(inStream, opt);
}
} // namespace vdf
} // namespace tyti
#ifndef TYTI_NO_L_UNDEF
#undef TYTI_L
#endif
#ifdef TYTI_UNDEF_CONSTEXPR
#undef CONSTEXPR
#undef TYTI_NO_L_UNDEF
#endif
#ifdef TYTI_UNDEF_NOTHROW
#undef NOTHROW
#undef TYTI_UNDEF_NOTHROW
#endif
#endif //__TYTI_STEAM_VDF_PARSER_H__

15
packaging/debian/control Normal file
View File

@@ -0,0 +1,15 @@
## incomplete: need to double check syntax of [Build-]Depends and add libboost-{file,}system-dev to it, among other packages
Source: cellar
Maintainer: Nicole O'Connor <nicole@otl-hga.net>
Section: main
Priority: optional
Standards-Version: 4.6.2
Build-Depends: debhelper-compat (= 13)
Package: cellar
Architecture: amd64
Depends: ${shlibs:Depends} ${misc:Depends}
Description: WINE bottle management tool
Helps users manage multiple instances of WINE across
multiple configuration prefixes (or, bottles). Comes
with some advanced features.

11
packaging/rpm/cellar.spec Normal file
View File

@@ -0,0 +1,11 @@
# VERY INCOMPLETE
Name: cellar
Version: 0.4
Release: 1%{?dist}
Summary: WINE bottle management tool
License: MIT
URL: https://vcs.otl-hga.net/nicole/cellar/
Source0: https://vcs.otl-hga.net/nicole/cellar/archive/v0.4.tar.gz
BuildRequires: build-essential cmake python3-pip

View File

@@ -3,8 +3,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <boost/filesystem/operations.hpp> #include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp>
#include "bottles.hpp" #include "bottles.hpp"
#include "cellar.hpp" #include "cellar.hpp"
@@ -20,9 +19,9 @@ void cellar::bottles::switch_active_bottle(int argc, vector<string> argv) {
return; return;
} }
string homepath = getenv("HOME"); // /home/nick string homepath = getenv("HOME");
string bottlepath = homepath + "/.wine"; // /home/nick/.wine string bottlepath = homepath + "/.wine";
string targetpath = homepath + "/.local/share/cellar/bottles/" + argv[1]; // /home/nick/.wine.example string targetpath = cellar::bottles::resolve_bottle(argv[1]);
file_status targetstatus = symlink_status(targetpath); file_status targetstatus = symlink_status(targetpath);
if (!exists(targetstatus)) { 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; string bottlepath = active_bottle.canonical_path;
stringstream outstr; stringstream outstr;
bool cellar_managed = true; bool cellar_managed = true;
bool external_managed = false;
if (active_bottle.type == bottle_symlink) { if (active_bottle.type == bottle_symlink) {
outstr << "symlink to "; outstr << "symlink to ";
string homedir = getenv("HOME"); 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) { 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" bottlepath.replace(0, bottlerack.length() + 1, ""); // should convert "/home/someone/.wine.example" to ".wine.example"
active_bottle = bottlemap[bottlepath]; active_bottle = bottlemap[bottlepath];
active_bottle.set_config("manager", "cellar");
active_bottle.save_config();
} else { } else {
outstr << active_bottle.canonical_path; outstr << active_bottle.canonical_path;
cellar_managed = false; cellar_managed = false;
return;
} }
} }

View File

@@ -1,18 +1,22 @@
#include <cstdlib> #include <cstdlib>
#include <filesystem>
#include <fstream> #include <fstream>
#include <map> #include <map>
#include <string> #include <string>
#include <sstream> #include <sstream>
#include <vector> #include <vector>
#include <boost/filesystem/operations.hpp> #include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp>
#include "nlohmann/json.hpp" #include "nlohmann/json.hpp"
#include "bottles.hpp" #include "bottles.hpp"
#include "cmake.hpp"
#include "internal/bottles.hpp" #include "internal/bottles.hpp"
#include "fs.hpp" #include "fs.hpp"
#include "output.hpp" #include "output.hpp"
#ifdef ENABLE_STEAM
#include "steam.hpp"
#endif
using namespace std; using namespace std;
using namespace cellar; using namespace cellar;
@@ -21,32 +25,75 @@ using namespace cellar::bottles;
using CommandFunction = cellar::commands::CommandFunction; using CommandFunction = cellar::commands::CommandFunction;
using json = nlohmann::json; 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() { Bottle::Bottle() {
// define a null bottle // define a null bottle
// strings handle themselves // strings handle themselves
config = json({}); config = json({});
type = bottle_anonymous; 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) { Bottle::Bottle(string patharg) {
output::statement("loading bottle from " + patharg, true); output::statement("loading bottle from " + patharg, true);
config = json({}); config = json({});
path = patharg; path = patharg;
boost::filesystem::file_status path_status = boost::filesystem::symlink_status(path); //boost::filesystem::file_status path_status = boost::filesystem::symlink_status(path);
bool symlink = boost::filesystem::is_symlink(path_status); //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) { if (std::filesystem::is_symlink(path_canon)) {
boost::filesystem::path realpath = boost::filesystem::canonical(path);
canonical_path = realpath.string();
type = bottle_symlink; type = bottle_symlink;
} else { } else {
canonical_path = path;
try { try {
if (load_config()) { load_config();
type = bottle_labelled; auto cur_manager = get_config("manager");
} else { if (cur_manager == "cellar") {
type = bottle_anonymous; 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) { catch (const exception &exc) {
@@ -55,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> cellar::bottles::get_bottles() {
map<string, Bottle> result; map<string, Bottle> result;
@@ -67,29 +122,59 @@ map<string, Bottle> cellar::bottles::get_bottles() {
result[item] = output; 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; 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 cellar::bottles::resolve_bottle(string bottlechoice) {
string result; string result;
if (bottlechoice.substr(0,1) == "/" || bottlechoice.substr(0,1) == ".") { // absolute or relative path if (bottlechoice.substr(0,1) == "/" || bottlechoice.substr(0,1) == ".") { // absolute or relative path
result = bottlechoice; 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) } 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 // i'm figuring at that point if you're doing that, you'll also recognize if your shell
// isn't actually expanding your path... // isn't actually expanding your path...
bottlechoice.replace(0,1,getenv("HOME")); 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 // 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); output::warning("your shell didn't expand your given path properly, doing a naive replacement", true);
result = bottlechoice; 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 { } else {
string homepath = getenv("HOME"); string homepath = getenv("HOME");
string fullbottlepath = homepath + "/.local/share/cellar/bottles" + bottlechoice; string fullbottlepath = homepath + "/.local/share/cellar/bottles/" + bottlechoice;
result = fullbottlepath; result = fullbottlepath;
} }
return result; return result;
} }
/**
* @brief Prints bottles. Used as a command in CLI.
*/
void cellar::bottles::print_bottles(int argc, vector<string> argv) { void cellar::bottles::print_bottles(int argc, vector<string> argv) {
map<string, Bottle> bottles = get_bottles(); map<string, Bottle> bottles = get_bottles();
@@ -113,6 +198,9 @@ void cellar::bottles::print_bottles(int argc, vector<string> argv) {
case bottle_labelled: case bottle_labelled:
outstr << bottle.config["name"]; outstr << bottle.config["name"];
break; break;
case bottle_steam:
outstr << "Steam managed bottle for " << bottle.config["name"];
break;
default: default:
outstr << "broken or unsupported wine bottle"; outstr << "broken or unsupported wine bottle";
} }

View File

@@ -30,14 +30,16 @@ void cellar::bottles::create_bottle(int argc, vector<string> argv) {
cmdparse.parse(argv); cmdparse.parse(argv);
// sanity check: make sure ~/.local/share/cellar/bottles exists
string homepath = getenv("HOME"); string homepath = getenv("HOME");
if (!boost::filesystem::exists(homepath + "/.local/share/cellar/bottles")) { boost::filesystem::create_directories(homepath + "/.local/share/cellar/bottles"); }
string bottlechoice = bottlearg.getValue(); string bottlechoice = bottlearg.getValue();
string fullbottlepath; string fullbottlepath;
if (bottlechoice.substr(0,1) == "/" || bottlechoice.substr(0,1) == ".") { // absolute or relative path if (bottlechoice.substr(0,1) == "/" || bottlechoice.substr(0,1) == ".") { // absolute or relative path
fullbottlepath = bottlechoice; 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) } 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 // i'm figuring at that point if you're doing that, you'll also recognize if your shell
// isn't actually expanding your path... // isn't actually expanding your path...
bottlechoice.replace(0,1,getenv("HOME")); bottlechoice.replace(0,1,getenv("HOME"));

View File

@@ -5,6 +5,7 @@
#include "bottles.hpp" #include "bottles.hpp"
#include "cellar.hpp" #include "cellar.hpp"
#include "config.hpp"
#include "internal/config.hpp" #include "internal/config.hpp"
#include "output.hpp" #include "output.hpp"
#include "version.hpp" #include "version.hpp"
@@ -46,7 +47,15 @@ void cellar::config::config_command(int argc, vector<string> argv) {
return; return;
} }
string myval = active_bottle.get_config(key); string myval;
if (global) {
if (config::global_config.find(key) != config::global_config.end()) {
myval = config::global_config[key];
} else if (config::compiled_config.find(key) != config::compiled_config.end()) {
myval = config::compiled_config[key];
}
}
else { myval = active_bottle.get_config(key); }
if (myval != "") { if (myval != "") {
output::statement(key + ": " + myval); output::statement(key + ": " + myval);
@@ -60,7 +69,14 @@ void cellar::config::config_command(int argc, vector<string> argv) {
} }
string newvalue = value; string newvalue = value;
string oldvalue = active_bottle.get_config(key); string oldvalue;
if (global) {
if (config::global_config.find(key) != config::global_config.end()) {
oldvalue = config::global_config[key];
} else if (config::compiled_config.find(key) != config::compiled_config.end()) {
oldvalue = config::compiled_config[key];
}
} else { oldvalue = active_bottle.get_config(key); }
if (active_bottle.set_config(key, newvalue)) { if (active_bottle.set_config(key, newvalue)) {
output::statement(key + ": " + newvalue + " (was " + oldvalue + ")"); output::statement(key + ": " + newvalue + " (was " + oldvalue + ")");

View File

@@ -10,6 +10,7 @@
#include "nlohmann/json.hpp" #include "nlohmann/json.hpp"
#include "bottles.hpp" #include "bottles.hpp"
#include "internal/bottles.hpp"
#include "cellar.hpp" #include "cellar.hpp"
#include "commands.hpp" #include "commands.hpp"
#include "output.hpp" #include "output.hpp"
@@ -45,6 +46,7 @@ int main(int argc, char* argv[]) {
cout << "\n(try \"cellar help\" if you're confused)" << endl; cout << "\n(try \"cellar help\" if you're confused)" << endl;
return 0; return 0;
} }
cellar::bottles::setup_bottle_home();
try { try {
const string desc = "bottle management tool for WINE connoisseurs"; const string desc = "bottle management tool for WINE connoisseurs";
const string versionstr = version::short_version(); const string versionstr = version::short_version();
@@ -70,7 +72,7 @@ int main(int argc, char* argv[]) {
dryrun = dryrunarg.getValue(); dryrun = dryrunarg.getValue();
verbose = dryrun || verbosearg.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 /*[[[cog
import cog import cog
@@ -93,7 +95,7 @@ int main(int argc, char* argv[]) {
bottles::active_bottle = bottles::Bottle(env_wineprefix); bottles::active_bottle = bottles::Bottle(env_wineprefix);
} else { } else {
string homepath = getenv("HOME"); string homepath = getenv("HOME");
string fullbottlepath = homepath + "/.local/share/cellar/bottles/" + bottlearg.getValue(); string fullbottlepath = homepath + "/.wine";
bottles::active_bottle = bottles::Bottle(fullbottlepath); bottles::active_bottle = bottles::Bottle(fullbottlepath);
} }
@@ -103,7 +105,7 @@ int main(int argc, char* argv[]) {
if (bottlechoice.substr(0,1) == "/" || bottlechoice.substr(0,1) == ".") { // absolute or relative path if (bottlechoice.substr(0,1) == "/" || bottlechoice.substr(0,1) == ".") { // absolute or relative path
bottles::active_bottle = bottles::Bottle(bottlechoice); 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) } 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 // i'm figuring at that point if you're doing that, you'll also recognize if your shell
// isn't actually expanding your path... // isn't actually expanding your path...
bottlechoice.replace(0,1,getenv("HOME")); bottlechoice.replace(0,1,getenv("HOME"));

View File

@@ -2,15 +2,14 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <boost/filesystem/operations.hpp> #include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp>
#include "cellar.hpp" #include "cellar.hpp"
#include "fs.hpp" #include "fs.hpp"
#include "output.hpp" #include "output.hpp"
using namespace std; using namespace std;
using namespace boost; //using namespace boost;
using namespace cellar; using namespace cellar;
using namespace fs; using namespace fs;
@@ -22,10 +21,10 @@ void cbnull_remove(string src) { return; }
vector<string> fs::listdir(string path) { vector<string> fs::listdir(string path) {
vector<string> result; vector<string> result;
filesystem::path cwd(path); boost::filesystem::path cwd(path);
filesystem::directory_iterator iter_end; boost::filesystem::directory_iterator iter_end;
for (filesystem::directory_iterator iter_cwd(cwd); iter_cwd != iter_end; ++iter_cwd) { for (boost::filesystem::directory_iterator iter_cwd(cwd); iter_cwd != iter_end; ++iter_cwd) {
string item = iter_cwd->path().filename().native(); string item = iter_cwd->path().filename().native();
result.push_back(item); result.push_back(item);
@@ -34,10 +33,10 @@ vector<string> fs::listdir(string path) {
} }
bool fs::recursive_copy(string src, string dst, CopyCallbackFunc callback) { bool fs::recursive_copy(string src, string dst, CopyCallbackFunc callback) {
if (!filesystem::exists(dst)) { if (!boost::filesystem::exists(dst)) {
if (dryrun) { output::statement("mkdir: " + dst); } if (dryrun) { output::statement("mkdir: " + dst); }
else { else {
bool success = filesystem::create_directory(dst); bool success = boost::filesystem::create_directory(dst);
if (!success) { return false; } if (!success) { return false; }
} }
} }
@@ -46,17 +45,17 @@ bool fs::recursive_copy(string src, string dst, CopyCallbackFunc callback) {
string itemabs = src + "/" + itemrel; string itemabs = src + "/" + itemrel;
string targetabs = dst + "/" + itemrel; string targetabs = dst + "/" + itemrel;
auto itemstat = filesystem::symlink_status(itemabs); auto itemstat = boost::filesystem::symlink_status(itemabs);
if (filesystem::is_directory(itemstat)) { recursive_copy(itemabs, targetabs, callback); } if (boost::filesystem::is_directory(itemstat)) { recursive_copy(itemabs, targetabs, callback); }
else if (filesystem::is_symlink(itemstat)) { else if (boost::filesystem::is_symlink(itemstat)) {
auto symlinkpath = filesystem::read_symlink(itemabs); auto symlinkpath = boost::filesystem::read_symlink(itemabs);
if (dryrun) { output::statement("symlink: " + symlinkpath.native() + " => " + targetabs); } if (dryrun) { output::statement("symlink: " + symlinkpath.native() + " => " + targetabs); }
else { filesystem::create_symlink(symlinkpath, targetabs); } else { boost::filesystem::create_symlink(symlinkpath, targetabs); }
} }
else { else {
if (dryrun) { output::statement("copy: " + itemabs + " => " + targetabs); } if (dryrun) { output::statement("copy: " + itemabs + " => " + targetabs); }
else { filesystem::copy(itemabs, targetabs); } else { boost::filesystem::copy(itemabs, targetabs); }
} }
callback(itemabs, targetabs); callback(itemabs, targetabs);
@@ -68,24 +67,24 @@ bool fs::recursive_copy(string src, string dst, CopyCallbackFunc callback) {
bool fs::recursive_copy(string src, string dst) { return recursive_copy(src, dst, cbnull_copy); } bool fs::recursive_copy(string src, string dst) { return recursive_copy(src, dst, cbnull_copy); }
bool fs::recursive_remove(string target, RemoveCallbackFunc callback) { bool fs::recursive_remove(string target, RemoveCallbackFunc callback) {
if (!filesystem::exists(target)) { return false; } if (!boost::filesystem::exists(target)) { return false; }
for (string itemrel : listdir(target)) { for (string itemrel : listdir(target)) {
string itemabs = target + "/" + itemrel; string itemabs = target + "/" + itemrel;
auto itemstat = filesystem::symlink_status(itemabs); auto itemstat = boost::filesystem::symlink_status(itemabs);
if (filesystem::is_directory(itemstat)) { recursive_remove(itemabs, callback); } if (boost::filesystem::is_directory(itemstat)) { recursive_remove(itemabs, callback); }
else { else {
if (dryrun) { output::error("rm: " + itemabs); } if (dryrun) { output::error("rm: " + itemabs); }
else { filesystem::remove(itemabs); } else { boost::filesystem::remove(itemabs); }
} }
callback(itemabs); callback(itemabs);
} }
if (dryrun) { output::error("rm: " + target); } if (dryrun) { output::error("rm: " + target); }
else { filesystem::remove(target); } else { boost::filesystem::remove(target); }
return true; return true;
} }

View File

@@ -1,4 +1,5 @@
#include <exception> #include <exception>
#include <filesystem>
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -7,6 +8,7 @@
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include "tclap/CmdLine.h" #include "tclap/CmdLine.h"
#include "bottles.hpp"
#include "output.hpp" #include "output.hpp"
#include "paths.hpp" #include "paths.hpp"
#include "version.hpp" #include "version.hpp"
@@ -18,37 +20,73 @@ using namespace cellar;
string cellar::paths::translate(std::string in_path, bool lazy) { string cellar::paths::translate(std::string in_path, bool lazy) {
bool windows_input; bool windows_input;
static regex drive_letter(R"([a-zA-Z]:\\)"); static regex drive_letter_rgx(R"([a-zA-Z]:\\)");
windows_input = regex_match(in_path.substr(0, 3), drive_letter); 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 (windows_input) {
// lazy if (lazy) {
if (boost::algorithm::to_lower_copy(in_path.substr(0,1)) != "z") { if (boost::algorithm::to_lower_copy(in_path.substr(0,1)) != "z") {
throw invalid_argument("lazy path translation isn't possible for drive letters other than Z:"); throw invalid_argument("lazy path translation isn't possible for drive letters other than Z:");
} }
string out_path = in_path.substr(2, in_path.size() - 2); string out_path = in_path.substr(2, in_path.size() - 2);
size_t slashpos = out_path.find("\\"); size_t slashpos = out_path.find("\\");
while (slashpos != std::string::npos) { while (slashpos != std::string::npos) {
out_path.replace(slashpos, 1, "/"); out_path.replace(slashpos, 1, "/");
slashpos = out_path.find("\\"); slashpos = out_path.find("\\");
} }
return out_path; return out_path;
} else {
return paths::resolve_drive_letter(in_path);
}
} else { } else {
// lazy string out_path;
string out_path = "Z:"; string str_absolutepath = filesystem::canonical(in_path);
out_path.append(in_path);
size_t slashpos = out_path.find("/"); if (lazy) {
while (slashpos != std::string::npos) { out_path = "Z:";
out_path.replace(slashpos, 1, "\\"); out_path.append(str_absolutepath);
slashpos = out_path.find("/");
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; return out_path;

View File

@@ -0,0 +1,57 @@
#include <exception>
#include <filesystem>
#include <regex>
#include <string>
#include <unistd.h>
#include <boost/algorithm/string.hpp>
#include "bottles.hpp"
#include "output.hpp"
#include "paths.hpp"
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]:\\)");
windows_input = regex_match(in_path.substr(0, 3), drive_letter_rgx);
string out_path = "";
if (windows_input) {
string drive_letter = boost::algorithm::to_lower_copy(in_path.substr(0, 2));
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];
ssize_t bufflen = readlink(link_path.c_str(), stringbuffer, sizeof(stringbuffer) - 1);
if (bufflen != -1) {
stringbuffer[bufflen] = '\0';
string str_absolutepath = filesystem::canonical(stringbuffer);
out_path.append(str_absolutepath);
out_path.append("/");
} else {
throw runtime_error("readlink isn't having it");
}
string rest_of_path = in_path.substr(3, in_path.size() - 2);
size_t slashpos = rest_of_path.find("\\");
while (slashpos != std::string::npos) {
rest_of_path.replace(0, 1, "/");
slashpos = rest_of_path.find("\\");
}
out_path.append(rest_of_path);
}
filesystem::current_path(lastwd);
return out_path;
}

68
src/steam/bottles.cpp Normal file
View File

@@ -0,0 +1,68 @@
#include <filesystem>
#include <fstream>
#include <map>
#include <string>
#include "vdf_parser.hpp"
#include "bottles.hpp"
#include "steam.hpp"
#include "internal/steam.hpp"
using namespace tyti; //vdf
/**
* @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")) {
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; }
std::string str_gamename = "";
std::ifstream fd_appmanifest(pth_appmanifest);
auto hnd_appmanifest = vdf::read(fd_appmanifest);
for (auto hnd_appmanifest_def : hnd_appmanifest.attribs) {
std::string str_index = hnd_appmanifest_def.first;
std::string str_value = hnd_appmanifest_def.second;
if (str_index == "name") {
str_gamename = str_value;
break;
}
}
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);
}

57
src/steam/paths.cpp Normal file
View File

@@ -0,0 +1,57 @@
#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"
#include "steam.hpp"
#include "internal/steam.hpp"
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);
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;
result.push_back(hnd_library->attribs["path"]);
}
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 ()
}
}