Compare commits
No commits in common. "main" and "windows" have entirely different histories.
|
@ -5,18 +5,14 @@ AlignConsecutiveDeclarations: true
|
|||
AllowShortBlocksOnASingleLine: Always
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
BasedOnStyle: Chromium
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
ColumnLimit: 120
|
||||
ConstructorInitializerIndentWidth: 2
|
||||
ContinuationIndentWidth: 2
|
||||
Cpp11BracedListStyle: false
|
||||
FixNamespaceComments: false
|
||||
IndentAccessModifiers: false
|
||||
IndentExternBlock: Indent
|
||||
IndentWidth: 2
|
||||
LineEnding: LF
|
||||
NamespaceIndentation: All
|
||||
SpaceBeforeCpp11BracedList: true
|
||||
SpacesBeforeTrailingComments: 1
|
||||
|
|
49
.clang-tidy
49
.clang-tidy
|
@ -1,32 +1,25 @@
|
|||
# noinspection SpellCheckingInspection
|
||||
Checks: >
|
||||
*,
|
||||
-ctad-maybe-unsupported,
|
||||
-abseil-*,
|
||||
-altera-*,
|
||||
-boost-*,
|
||||
-bugprone-easily-swappable-parameters,
|
||||
-bugprone-implicit-widening-of-multiplication-result,
|
||||
-cert-env33-c,
|
||||
-concurrency-mt-unsafe,
|
||||
-cppcoreguidelines-avoid-magic-numbers,
|
||||
-cppcoreguidelines-macro-usage,
|
||||
-cppcoreguidelines-owning-memory,
|
||||
-cppcoreguidelines-pro-type-member-init,
|
||||
-cppcoreguidelines-pro-type-vararg,
|
||||
-fuchsia-*,
|
||||
-google-*,
|
||||
-hicpp-*,
|
||||
-llvm-include-order,
|
||||
-llvm-include-order,
|
||||
-llvm-namespace-comment,
|
||||
-llvmlibc-*,
|
||||
-misc-non-private-member-variables-in-classes,
|
||||
-readability-braces-around-statements,
|
||||
-readability-function-cognitive-complexity,
|
||||
-readability-implicit-bool-conversion,
|
||||
-readability-isolate-declaration,
|
||||
-readability-magic-numbers
|
||||
*,
|
||||
-abseil-*,
|
||||
-altera-*,
|
||||
-bugprone-easily-swappable-parameters,
|
||||
-bugprone-implicit-widening-of-multiplication-result,
|
||||
-concurrency-mt-unsafe,
|
||||
-cppcoreguidelines-avoid-magic-numbers,
|
||||
-cppcoreguidelines-owning-memory,
|
||||
-cppcoreguidelines-pro-type-member-init,
|
||||
-fuchsia-*,
|
||||
-google-*,
|
||||
-hicpp-*,
|
||||
-llvm-include-order,
|
||||
-llvm-include-order,
|
||||
-llvmlibc-*,
|
||||
-misc-non-private-member-variables-in-classes,
|
||||
-modernize-use-trailing-return-type,
|
||||
-readability-braces-around-statements,
|
||||
-readability-implicit-bool-conversion,
|
||||
-readability-isolate-declaration,
|
||||
-readability-magic-numbers
|
||||
CheckOptions:
|
||||
readability-identifier-naming.ClassCase: CamelCase
|
||||
readability-identifier-naming.EnumCase: CamelCase
|
||||
|
|
4
.clangd
4
.clangd
|
@ -1,4 +0,0 @@
|
|||
Diagnostics:
|
||||
Suppress: >
|
||||
-Wmissing-template-arg-list-after-template-kw,
|
||||
-Wctad-maybe-unsupported
|
28
.gitignore
vendored
28
.gitignore
vendored
|
@ -1,28 +1,2 @@
|
|||
.cache/
|
||||
.cmake/
|
||||
.direnv/
|
||||
.idea/
|
||||
.vs/
|
||||
.vscode/
|
||||
.xmake/
|
||||
.DS_Store
|
||||
|
||||
bin/
|
||||
build/
|
||||
cmake-build-debug/
|
||||
CMakeFiles/
|
||||
out/
|
||||
Testing/
|
||||
|
||||
build.ninja
|
||||
CMakeCache.txt
|
||||
cmake_install.cmake
|
||||
CMakeSettings.json
|
||||
compile_commands.json
|
||||
config.toml
|
||||
draconis++
|
||||
Makefile
|
||||
result
|
||||
/include/
|
||||
subprojects/*
|
||||
!subprojects/*.wrap
|
||||
cmake-build-*/
|
10
.gitmodules
vendored
10
.gitmodules
vendored
|
@ -1,10 +0,0 @@
|
|||
[submodule "subprojects/reflectcpp"]
|
||||
path = subprojects/reflectcpp
|
||||
url = https://github.com/getml/reflect-cpp
|
||||
[submodule "subprojects/sdbus-c++"]
|
||||
path = subprojects/sdbus-c++
|
||||
url = https://github.com/Kistler-Group/sdbus-cpp
|
||||
[submodule "subprojects/systemd"]
|
||||
path = subprojects/systemd
|
||||
url = https://github.com/systemd/systemd
|
||||
branch = v257-stable
|
15
CMakeLists.txt
Normal file
15
CMakeLists.txt
Normal file
|
@ -0,0 +1,15 @@
|
|||
cmake_minimum_required(VERSION 3.28)
|
||||
|
||||
project(draconis__)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 26)
|
||||
set(CMAKE_MAKE_PROGRAM "Ninja")
|
||||
|
||||
add_executable(draconis__ main.cpp
|
||||
util.h)
|
||||
|
||||
find_package(fmt CONFIG REQUIRED)
|
||||
find_package(cppwinrt CONFIG REQUIRED)
|
||||
|
||||
target_link_libraries(draconis__ PRIVATE fmt::fmt-header-only)
|
||||
target_link_libraries(draconis__ PRIVATE WindowsApp)
|
|
@ -1,107 +0,0 @@
|
|||
{
|
||||
"fmt": {
|
||||
"cargoLocks": null,
|
||||
"date": null,
|
||||
"extract": null,
|
||||
"name": "fmt",
|
||||
"passthru": null,
|
||||
"pinned": false,
|
||||
"src": {
|
||||
"deepClone": false,
|
||||
"fetchSubmodules": false,
|
||||
"leaveDotGit": false,
|
||||
"name": null,
|
||||
"owner": "fmtlib",
|
||||
"repo": "fmt",
|
||||
"rev": "11.1.3",
|
||||
"sha256": "sha256-6r9D/csVSgS+T/H0J8cSR+YszxnH/h2V2odi2s6VYN8=",
|
||||
"sparseCheckout": [],
|
||||
"type": "github"
|
||||
},
|
||||
"version": "11.1.3"
|
||||
},
|
||||
"reflect-cpp": {
|
||||
"cargoLocks": null,
|
||||
"date": null,
|
||||
"extract": null,
|
||||
"name": "reflect-cpp",
|
||||
"passthru": null,
|
||||
"pinned": false,
|
||||
"src": {
|
||||
"deepClone": false,
|
||||
"fetchSubmodules": false,
|
||||
"leaveDotGit": false,
|
||||
"name": null,
|
||||
"owner": "getml",
|
||||
"repo": "reflect-cpp",
|
||||
"rev": "v0.17.0",
|
||||
"sha256": "sha256-ugMop4Gsw46zFA7mESNzDTAZVzIg3szAX8ND1kUiF2A=",
|
||||
"sparseCheckout": [],
|
||||
"type": "github"
|
||||
},
|
||||
"version": "v0.17.0"
|
||||
},
|
||||
"sdbus-cpp": {
|
||||
"cargoLocks": null,
|
||||
"date": null,
|
||||
"extract": null,
|
||||
"name": "sdbus-cpp",
|
||||
"passthru": null,
|
||||
"pinned": false,
|
||||
"src": {
|
||||
"deepClone": false,
|
||||
"fetchSubmodules": false,
|
||||
"leaveDotGit": false,
|
||||
"name": null,
|
||||
"owner": "kistler-group",
|
||||
"repo": "sdbus-cpp",
|
||||
"rev": "v2.1.0",
|
||||
"sha256": "sha256-JnjabBr7oELLsUV9a+dAAaRyUzaMIriu90vkaVJg2eY=",
|
||||
"sparseCheckout": [],
|
||||
"type": "github"
|
||||
},
|
||||
"version": "v2.1.0"
|
||||
},
|
||||
"tomlplusplus": {
|
||||
"cargoLocks": null,
|
||||
"date": null,
|
||||
"extract": null,
|
||||
"name": "tomlplusplus",
|
||||
"passthru": null,
|
||||
"pinned": false,
|
||||
"src": {
|
||||
"deepClone": false,
|
||||
"fetchSubmodules": false,
|
||||
"leaveDotGit": false,
|
||||
"name": null,
|
||||
"owner": "marzer",
|
||||
"repo": "tomlplusplus",
|
||||
"rev": "v3.4.0",
|
||||
"sha256": "sha256-h5tbO0Rv2tZezY58yUbyRVpsfRjY3i+5TPkkxr6La8M=",
|
||||
"sparseCheckout": [],
|
||||
"type": "github"
|
||||
},
|
||||
"version": "v3.4.0"
|
||||
},
|
||||
"yyjson": {
|
||||
"cargoLocks": null,
|
||||
"date": null,
|
||||
"extract": null,
|
||||
"name": "yyjson",
|
||||
"passthru": null,
|
||||
"pinned": false,
|
||||
"src": {
|
||||
"deepClone": false,
|
||||
"fetchSubmodules": false,
|
||||
"leaveDotGit": false,
|
||||
"name": null,
|
||||
"owner": "ibireme",
|
||||
"repo": "yyjson",
|
||||
"rev": "0.10.0",
|
||||
"sha256": "sha256-mp9Oz08qTyhj3P6F1d81SX96vamUY/JWpD2DTYR+v04=",
|
||||
"sparseCheckout": [],
|
||||
"type": "github"
|
||||
},
|
||||
"version": "0.10.0"
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
# This file was generated by nvfetcher, please do not modify it manually.
|
||||
{ fetchgit, fetchurl, fetchFromGitHub, dockerTools }:
|
||||
{
|
||||
fmt = {
|
||||
pname = "fmt";
|
||||
version = "11.1.3";
|
||||
src = fetchFromGitHub {
|
||||
owner = "fmtlib";
|
||||
repo = "fmt";
|
||||
rev = "11.1.3";
|
||||
fetchSubmodules = false;
|
||||
sha256 = "sha256-6r9D/csVSgS+T/H0J8cSR+YszxnH/h2V2odi2s6VYN8=";
|
||||
};
|
||||
};
|
||||
reflect-cpp = {
|
||||
pname = "reflect-cpp";
|
||||
version = "v0.17.0";
|
||||
src = fetchFromGitHub {
|
||||
owner = "getml";
|
||||
repo = "reflect-cpp";
|
||||
rev = "v0.17.0";
|
||||
fetchSubmodules = false;
|
||||
sha256 = "sha256-ugMop4Gsw46zFA7mESNzDTAZVzIg3szAX8ND1kUiF2A=";
|
||||
};
|
||||
};
|
||||
sdbus-cpp = {
|
||||
pname = "sdbus-cpp";
|
||||
version = "v2.1.0";
|
||||
src = fetchFromGitHub {
|
||||
owner = "kistler-group";
|
||||
repo = "sdbus-cpp";
|
||||
rev = "v2.1.0";
|
||||
fetchSubmodules = false;
|
||||
sha256 = "sha256-JnjabBr7oELLsUV9a+dAAaRyUzaMIriu90vkaVJg2eY=";
|
||||
};
|
||||
};
|
||||
tomlplusplus = {
|
||||
pname = "tomlplusplus";
|
||||
version = "v3.4.0";
|
||||
src = fetchFromGitHub {
|
||||
owner = "marzer";
|
||||
repo = "tomlplusplus";
|
||||
rev = "v3.4.0";
|
||||
fetchSubmodules = false;
|
||||
sha256 = "sha256-h5tbO0Rv2tZezY58yUbyRVpsfRjY3i+5TPkkxr6La8M=";
|
||||
};
|
||||
};
|
||||
yyjson = {
|
||||
pname = "yyjson";
|
||||
version = "0.10.0";
|
||||
src = fetchFromGitHub {
|
||||
owner = "ibireme";
|
||||
repo = "yyjson";
|
||||
rev = "0.10.0";
|
||||
fetchSubmodules = false;
|
||||
sha256 = "sha256-mp9Oz08qTyhj3P6F1d81SX96vamUY/JWpD2DTYR+v04=";
|
||||
};
|
||||
};
|
||||
}
|
96
flake.lock
96
flake.lock
|
@ -1,96 +0,0 @@
|
|||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1739863612,
|
||||
"narHash": "sha256-UbtgxplOhFcyjBcNbTVO8+HUHAl/WXFDOb6LvqShiZo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "632f04521e847173c54fa72973ec6c39a371211c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1735554305,
|
||||
"narHash": "sha256-zExSA1i/b+1NMRhGGLtNfFGXgLtgo+dcuzHzaWA6w3Q=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "0e82ab234249d8eee3e8c91437802b32c74bb3fd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"treefmt-nix": "treefmt-nix",
|
||||
"utils": "utils"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1739829690,
|
||||
"narHash": "sha256-mL1szCeIsjh6Khn3nH2cYtwO5YXG6gBiTw1A30iGeDU=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "3d0579f5cc93436052d94b73925b48973a104204",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
175
flake.nix
175
flake.nix
|
@ -1,175 +0,0 @@
|
|||
{
|
||||
description = "C/C++ environment";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
treefmt-nix.url = "github:numtide/treefmt-nix";
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
treefmt-nix,
|
||||
utils,
|
||||
...
|
||||
}:
|
||||
utils.lib.eachDefaultSystem (
|
||||
system: let
|
||||
pkgs = import nixpkgs {inherit system;};
|
||||
|
||||
llvmPackages = with pkgs;
|
||||
if hostPlatform.isLinux
|
||||
then llvmPackages_20
|
||||
else llvmPackages_19;
|
||||
|
||||
stdenv = with pkgs;
|
||||
(
|
||||
if hostPlatform.isLinux
|
||||
then stdenvAdapters.useMoldLinker
|
||||
else lib.id
|
||||
)
|
||||
llvmPackages.stdenv;
|
||||
|
||||
sources = import ./_sources/generated.nix {
|
||||
inherit (pkgs) fetchFromGitHub fetchgit fetchurl dockerTools;
|
||||
};
|
||||
|
||||
mkPkg = name:
|
||||
pkgs.pkgsStatic.${name}.overrideAttrs {
|
||||
inherit (sources.${name}) pname version src;
|
||||
};
|
||||
|
||||
fmt = mkPkg "fmt";
|
||||
|
||||
tomlplusplus = pkgs.pkgsStatic.tomlplusplus.overrideAttrs {
|
||||
inherit (sources.tomlplusplus) pname version src;
|
||||
doCheck = false;
|
||||
};
|
||||
|
||||
sdbus-cpp = pkgs.sdbus-cpp.overrideAttrs {
|
||||
inherit (sources.sdbus-cpp) pname version src;
|
||||
|
||||
cmakeFlags = [
|
||||
(pkgs.lib.cmakeBool "BUILD_CODE_GEN" true)
|
||||
(pkgs.lib.cmakeBool "BUILD_SHARED_LIBS" false)
|
||||
];
|
||||
};
|
||||
|
||||
yyjson = pkgs.pkgsStatic.stdenv.mkDerivation {
|
||||
inherit (sources.yyjson) pname version src;
|
||||
|
||||
nativeBuildInputs = with pkgs; [cmake ninja pkg-config];
|
||||
};
|
||||
|
||||
reflect-cpp = stdenv.mkDerivation rec {
|
||||
inherit (sources.reflect-cpp) pname version src;
|
||||
|
||||
buildInputs = [tomlplusplus yyjson];
|
||||
nativeBuildInputs = buildInputs ++ (with pkgs; [cmake ninja pkg-config]);
|
||||
|
||||
cmakeFlags = [
|
||||
"-DCMAKE_TOOLCHAIN_FILE=OFF"
|
||||
"-DREFLECTCPP_TOML=ON"
|
||||
"-DREFLECTCPP_JSON=ON"
|
||||
"-DREFLECTCPP_USE_STD_EXPECTED=ON"
|
||||
];
|
||||
};
|
||||
|
||||
deps = with pkgs.pkgsStatic;
|
||||
[
|
||||
# curl
|
||||
# fmt
|
||||
# libiconv
|
||||
# tomlplusplus
|
||||
# yyjson
|
||||
# reflect-cpp
|
||||
# sqlitecpp
|
||||
# ftxui
|
||||
]
|
||||
++ linuxPkgs;
|
||||
|
||||
linuxPkgs = nixpkgs.lib.optionals stdenv.isLinux (with pkgs;
|
||||
[
|
||||
valgrind
|
||||
]
|
||||
++ (with pkgsStatic; [
|
||||
dbus
|
||||
xorg.libX11
|
||||
wayland
|
||||
]));
|
||||
in
|
||||
with pkgs; {
|
||||
packages = rec {
|
||||
draconisplusplus = stdenv.mkDerivation {
|
||||
name = "draconis++";
|
||||
version = "0.1.0";
|
||||
src = self;
|
||||
|
||||
nativeBuildInputs = [
|
||||
cmake
|
||||
meson
|
||||
ninja
|
||||
pkg-config
|
||||
];
|
||||
|
||||
buildInputs = deps;
|
||||
|
||||
configurePhase = ''
|
||||
meson setup build
|
||||
'';
|
||||
|
||||
buildPhase = ''
|
||||
meson compile -C build
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
mv build/draconis++ $out/bin/draconis++
|
||||
'';
|
||||
};
|
||||
|
||||
default = draconisplusplus;
|
||||
};
|
||||
|
||||
formatter = treefmt-nix.lib.mkWrapper pkgs {
|
||||
projectRootFile = "flake.nix";
|
||||
programs = {
|
||||
alejandra.enable = true;
|
||||
deadnix.enable = true;
|
||||
|
||||
clang-format = {
|
||||
enable = true;
|
||||
package = pkgs.llvmPackages.clang-tools;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
devShell = mkShell.override {inherit stdenv;} {
|
||||
packages =
|
||||
[
|
||||
alejandra
|
||||
bear
|
||||
llvmPackages.clang-tools
|
||||
cmake
|
||||
lldb
|
||||
hyperfine
|
||||
meson
|
||||
ninja
|
||||
nvfetcher
|
||||
pkg-config
|
||||
unzip
|
||||
|
||||
(writeScriptBin "build" "meson compile -C build")
|
||||
(writeScriptBin "clean" "meson setup build --wipe")
|
||||
(writeScriptBin "run" "meson compile -C build && build/draconis++")
|
||||
]
|
||||
++ deps;
|
||||
|
||||
LD_LIBRARY_PATH = "${lib.makeLibraryPath deps}";
|
||||
|
||||
name = "C++";
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
199
main.cpp
Normal file
199
main.cpp
Normal file
|
@ -0,0 +1,199 @@
|
|||
#include <fmt/chrono.h>
|
||||
#include <fmt/core.h>
|
||||
#include <iostream>
|
||||
#include <windows.h>
|
||||
#include <winrt/Windows.Foundation.h> // ReSharper disable once CppUnusedIncludeDirective
|
||||
#include <winrt/Windows.Media.Control.h>
|
||||
#include <winrt/base.h>
|
||||
#include <winrt/impl/windows.media.control.2.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Windows::Media::Control;
|
||||
|
||||
struct KBToGiB {
|
||||
u64 value;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<KBToGiB> : formatter<double> {
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator
|
||||
format(const KBToGiB KTG, FormatContext& ctx) {
|
||||
typename FormatContext::iterator out = formatter<double>::format(
|
||||
static_cast<double>(KTG.value) / pow(1024, 2), ctx
|
||||
);
|
||||
|
||||
*out++ = 'G';
|
||||
*out++ = 'i';
|
||||
*out++ = 'B';
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
enum DateNum : u8 { Ones, Twos, Threes, Default };
|
||||
|
||||
DateNum ParseDate(std::string const& input) {
|
||||
if (input == "1" || input == "21" || input == "31") return Ones;
|
||||
if (input == "2" || input == "22") return Twos;
|
||||
if (input == "3" || input == "23") return Threes;
|
||||
return Default;
|
||||
}
|
||||
|
||||
std::string GetNowPlaying() {
|
||||
using namespace winrt::Windows::Media::Control;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
|
||||
using MediaProperties =
|
||||
GlobalSystemMediaTransportControlsSessionMediaProperties;
|
||||
using Session = GlobalSystemMediaTransportControlsSession;
|
||||
using SessionManager = GlobalSystemMediaTransportControlsSessionManager;
|
||||
|
||||
try {
|
||||
// Request the session manager asynchronously
|
||||
const IAsyncOperation<SessionManager> sessionManagerOp =
|
||||
SessionManager::RequestAsync();
|
||||
const SessionManager sessionManager = sessionManagerOp.get();
|
||||
|
||||
if (const Session currentSession = sessionManager.GetCurrentSession()) {
|
||||
// Try to get the media properties asynchronously
|
||||
const IAsyncOperation<MediaProperties> mediaPropertiesOp =
|
||||
currentSession.TryGetMediaPropertiesAsync();
|
||||
const MediaProperties mediaProperties = mediaPropertiesOp.get();
|
||||
|
||||
// Convert the hstring title to std::string
|
||||
return to_string(mediaProperties.Title());
|
||||
}
|
||||
|
||||
// If we reach this point, there is no current session
|
||||
return "No current media session.";
|
||||
} catch (...) { return "Failed to get media properties."; }
|
||||
}
|
||||
|
||||
std::string GetRegistryValue(
|
||||
const HKEY& hKey,
|
||||
const std::string& subKey,
|
||||
const std::string& valueName
|
||||
) {
|
||||
HKEY key = nullptr;
|
||||
if (RegOpenKeyExA(hKey, subKey.c_str(), 0, KEY_READ, &key) != ERROR_SUCCESS)
|
||||
return "";
|
||||
|
||||
DWORD dataSize = 0;
|
||||
if (RegQueryValueExA(
|
||||
key, valueName.c_str(), nullptr, nullptr, nullptr, &dataSize
|
||||
) != ERROR_SUCCESS) {
|
||||
RegCloseKey(key);
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string value(dataSize, '\0');
|
||||
if (RegQueryValueExA(
|
||||
key,
|
||||
valueName.c_str(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
reinterpret_cast<LPBYTE>(value.data()), // NOLINT(*-reinterpret-cast)
|
||||
&dataSize
|
||||
) != ERROR_SUCCESS) {
|
||||
RegCloseKey(key);
|
||||
return "";
|
||||
}
|
||||
|
||||
RegCloseKey(key);
|
||||
// Remove null terminator if present
|
||||
if (!value.empty() && value.back() == '\0') value.pop_back();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
std::string GetPrettyWindowsName() {
|
||||
std::string productName = GetRegistryValue(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)",
|
||||
"ProductName"
|
||||
);
|
||||
|
||||
const std::string displayVersion = GetRegistryValue(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)",
|
||||
"DisplayVersion"
|
||||
);
|
||||
|
||||
const std::string releaseId = GetRegistryValue(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)",
|
||||
"ReleaseId"
|
||||
);
|
||||
|
||||
const int buildNumber = stoi(GetRegistryValue(
|
||||
HKEY_LOCAL_MACHINE,
|
||||
R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)",
|
||||
"CurrentBuildNumber"
|
||||
));
|
||||
|
||||
fmt::println("Build number: {}", buildNumber);
|
||||
|
||||
// Check if the build number is 22000 or higher
|
||||
if (buildNumber >= 22000 &&
|
||||
productName.find("Windows 10") != std::string::npos)
|
||||
productName.replace(productName.find("Windows 10"), 10, "Windows 11");
|
||||
|
||||
if (!productName.empty()) {
|
||||
std::string result = productName;
|
||||
|
||||
if (!displayVersion.empty())
|
||||
result += " " + displayVersion;
|
||||
else if (!releaseId.empty())
|
||||
result += " " + releaseId;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
int main() {
|
||||
init_apartment();
|
||||
|
||||
u64 memInfo = 0;
|
||||
GetPhysicallyInstalledSystemMemory(&memInfo);
|
||||
fmt::println("Installed RAM: {:.2f}", KBToGiB {memInfo});
|
||||
|
||||
fmt::println("Now playing: {}", GetNowPlaying());
|
||||
|
||||
const std::tm localTime = fmt::localtime(time(nullptr));
|
||||
|
||||
std::string date = fmt::format("{:%e}", localTime);
|
||||
|
||||
auto start = date.begin();
|
||||
while (start != date.end() && std::isspace(*start)) ++start;
|
||||
date.erase(date.begin(), start);
|
||||
|
||||
switch (ParseDate(date)) {
|
||||
case Ones:
|
||||
date += "st";
|
||||
break;
|
||||
|
||||
case Twos:
|
||||
date += "nd";
|
||||
break;
|
||||
|
||||
case Threes:
|
||||
date += "rd";
|
||||
break;
|
||||
|
||||
case Default:
|
||||
date += "th";
|
||||
break;
|
||||
}
|
||||
|
||||
fmt::println("{:%B} {}, {:%-I:%0M %p}", localTime, date, localTime);
|
||||
|
||||
fmt::println("Version: {}", GetPrettyWindowsName());
|
||||
|
||||
uninit_apartment();
|
||||
return 0;
|
||||
}
|
150
meson.build
150
meson.build
|
@ -1,150 +0,0 @@
|
|||
project(
|
||||
'draconis++',
|
||||
'cpp',
|
||||
version: '0.1.0',
|
||||
default_options: [
|
||||
'default_library=static',
|
||||
'warning_level=everything',
|
||||
'buildtype=release',
|
||||
'cpp_args=-fvisibility=hidden',
|
||||
],
|
||||
)
|
||||
|
||||
cpp = meson.get_compiler('cpp')
|
||||
|
||||
if host_machine.system() == 'darwin'
|
||||
add_languages('objcpp')
|
||||
objcpp = meson.get_compiler('objcpp')
|
||||
add_project_arguments(
|
||||
objcpp.get_supported_arguments(
|
||||
[
|
||||
'-std=c++2b',
|
||||
'-Wno-c++20-compat',
|
||||
'-Wno-c++20-extensions',
|
||||
'-Wno-c++98-compat',
|
||||
'-Wno-c++98-compat-pedantic',
|
||||
'-Wno-disabled-macro-expansion',
|
||||
'-Wno-missing-prototypes',
|
||||
'-Wno-padded',
|
||||
'-Wno-pre-c++20-compat-pedantic',
|
||||
'-Wno-switch-default',
|
||||
'-Wunused-function',
|
||||
],
|
||||
),
|
||||
language: 'objcpp',
|
||||
)
|
||||
endif
|
||||
|
||||
common_cpp_args = [
|
||||
'-std=c++26',
|
||||
'-Wno-c++20-compat',
|
||||
'-Wno-c++20-extensions',
|
||||
'-Wno-c++98-compat',
|
||||
'-Wno-c++98-compat-pedantic',
|
||||
'-Wno-disabled-macro-expansion',
|
||||
'-Wno-missing-prototypes',
|
||||
'-Wno-padded',
|
||||
'-Wno-pre-c++20-compat-pedantic',
|
||||
'-Wno-switch-default',
|
||||
'-Wunused-function',
|
||||
'-fno-strict-enums',
|
||||
'-nostdlib++',
|
||||
]
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
common_cpp_args += ['-DCURL_STATICLIB']
|
||||
endif
|
||||
|
||||
add_project_arguments(cpp.get_supported_arguments(common_cpp_args), language: 'cpp')
|
||||
|
||||
source_file_names = ['src/main.cpp', 'src/config/config.cpp', 'src/config/weather.cpp']
|
||||
|
||||
if host_machine.system() == 'linux'
|
||||
source_file_names += ['src/os/linux.cpp', 'src/os/linux/issetugid_stub.cpp']
|
||||
elif host_machine.system() == 'freebsd'
|
||||
source_file_names += ['src/os/freebsd.cpp']
|
||||
elif host_machine.system() == 'darwin'
|
||||
source_file_names += [
|
||||
'src/os/macos.cpp',
|
||||
'src/os/macos/bridge.mm',
|
||||
]
|
||||
elif host_machine.system() == 'windows'
|
||||
source_file_names += ['src/os/windows.cpp']
|
||||
endif
|
||||
|
||||
sources = []
|
||||
|
||||
foreach file : source_file_names
|
||||
sources += files(file)
|
||||
endforeach
|
||||
|
||||
deps = [
|
||||
dependency('fmt', include_type: 'system', static: true),
|
||||
dependency('openssl', include_type: 'system', static: true),
|
||||
dependency('libcurl', include_type: 'system', static: true),
|
||||
dependency('tomlplusplus', include_type: 'system', static: true),
|
||||
]
|
||||
|
||||
if host_machine.system() == 'darwin'
|
||||
deps += dependency(
|
||||
'appleframeworks',
|
||||
modules: ['foundation', 'mediaplayer', 'systemconfiguration'],
|
||||
static: true,
|
||||
)
|
||||
elif host_machine.system() == 'windows'
|
||||
deps += [
|
||||
cpp.find_library('dwmapi'),
|
||||
cpp.find_library('windowsapp'),
|
||||
]
|
||||
elif host_machine.system() == 'linux' or host_machine.system() == 'freebsd'
|
||||
deps += [
|
||||
dependency('SQLiteCpp'),
|
||||
dependency('sdbus-c++'),
|
||||
dependency('x11'),
|
||||
dependency('xcb'),
|
||||
dependency('xau'),
|
||||
dependency('xdmcp'),
|
||||
dependency('wayland-client'),
|
||||
dependency('dbus-1'),
|
||||
]
|
||||
endif
|
||||
|
||||
cmake = import('cmake')
|
||||
|
||||
ftxui_dep = dependency(
|
||||
'ftxui',
|
||||
modules: ['ftxui::screen', 'ftxui::dom', 'ftxui::component'],
|
||||
include_type: 'system',
|
||||
static: true,
|
||||
required: false,
|
||||
)
|
||||
|
||||
if not ftxui_dep.found()
|
||||
ftxui_dom = dependency('ftxui-dom', fallback: ['ftxui', 'dom_dep'])
|
||||
ftxui_screen = dependency('ftxui-screen', fallback: ['ftxui', 'screen_dep'])
|
||||
ftxui_component = dependency('ftxui-component', fallback: ['ftxui', 'component_dep'])
|
||||
ftxui_dep = declare_dependency(dependencies: [ftxui_dom, ftxui_screen, ftxui_component])
|
||||
endif
|
||||
|
||||
deps += ftxui_dep
|
||||
|
||||
reflectcpp_proj = cmake.subproject('reflectcpp')
|
||||
reflectcpp_dep = reflectcpp_proj.dependency('reflectcpp')
|
||||
deps += reflectcpp_dep
|
||||
|
||||
objc_args = []
|
||||
link_args = []
|
||||
|
||||
if host_machine.system() == 'darwin'
|
||||
objc_args += ['-fobjc-arc']
|
||||
elif host_machine.system() == 'linux'
|
||||
link_args += ['-static-libgcc', '-static-libstdc++']
|
||||
endif
|
||||
|
||||
executable(
|
||||
'draconis++',
|
||||
sources,
|
||||
objc_args: objc_args,
|
||||
link_args: link_args,
|
||||
dependencies: deps,
|
||||
)
|
|
@ -1,44 +0,0 @@
|
|||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fmt/core.h>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
using rfl::Result;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
inline fn GetConfigPath() -> fs::path {
|
||||
#ifdef _WIN32
|
||||
const char* localAppData = std::getenv("LOCALAPPDATA");
|
||||
|
||||
if (!localAppData)
|
||||
throw std::runtime_error("Environment variable LOCALAPPDATA is not set");
|
||||
|
||||
return fs::path(localAppData);
|
||||
#else
|
||||
const char* home = std::getenv("HOME");
|
||||
|
||||
if (!home)
|
||||
throw std::runtime_error("Environment variable HOME is not set");
|
||||
|
||||
return fs::path(home) / ".config";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
fn Config::getInstance() -> Config {
|
||||
fs::path configPath = GetConfigPath();
|
||||
configPath /= "draconis++/config.toml";
|
||||
|
||||
const Result<Config> result = rfl::toml::load<Config>(configPath.string());
|
||||
|
||||
if (!result) {
|
||||
ERROR_LOG("Failed to load config file: {}", result.error().what());
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return result.value();
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
#include <rfl.hpp>
|
||||
#include <rfl/Field.hpp>
|
||||
|
||||
#include "../util/macros.h"
|
||||
#include "../util/types.h"
|
||||
#include "weather.h"
|
||||
|
||||
using Location = std::variant<string, Coords>;
|
||||
|
||||
struct General {
|
||||
rfl::Field<"name", string> name = "user";
|
||||
};
|
||||
|
||||
struct NowPlaying {
|
||||
bool enabled = false;
|
||||
};
|
||||
|
||||
struct Weather {
|
||||
bool enabled = false;
|
||||
bool show_town_name = false;
|
||||
|
||||
Location location;
|
||||
string api_key;
|
||||
string units;
|
||||
|
||||
[[nodiscard]] fn getWeatherInfo() const -> WeatherOutput;
|
||||
};
|
||||
|
||||
struct Config {
|
||||
rfl::Field<"general", General> general = General { .name = "user" };
|
||||
rfl::Field<"now_playing", NowPlaying> now_playing = NowPlaying();
|
||||
rfl::Field<"weather", Weather> weather = Weather();
|
||||
|
||||
static fn getInstance() -> Config;
|
||||
};
|
|
@ -1,176 +0,0 @@
|
|||
#include <chrono>
|
||||
#include <curl/curl.h>
|
||||
#include <expected>
|
||||
#include <filesystem>
|
||||
#include <fmt/core.h>
|
||||
#include <rfl/json.hpp>
|
||||
#include <rfl/json/load.hpp>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using namespace std::string_literals;
|
||||
|
||||
// Alias for cleaner error handling
|
||||
template <typename T>
|
||||
using Result = std::expected<T, std::string>;
|
||||
|
||||
namespace {
|
||||
// Common function to get cache path
|
||||
fn GetCachePath() -> Result<fs::path> {
|
||||
std::error_code errc;
|
||||
fs::path cachePath = fs::temp_directory_path(errc);
|
||||
|
||||
if (errc)
|
||||
return std::unexpected("Failed to get temp directory: "s + errc.message());
|
||||
|
||||
cachePath /= "weather_cache.json";
|
||||
return cachePath;
|
||||
}
|
||||
|
||||
// Function to read cache from file
|
||||
fn ReadCacheFromFile() -> Result<WeatherOutput> {
|
||||
Result<fs::path> cachePath = GetCachePath();
|
||||
if (!cachePath)
|
||||
return std::unexpected(cachePath.error());
|
||||
|
||||
std::ifstream ifs(*cachePath, std::ios::binary);
|
||||
if (!ifs.is_open())
|
||||
return std::unexpected("Cache file not found: "s + cachePath->string());
|
||||
|
||||
DEBUG_LOG("Reading from cache file...");
|
||||
|
||||
std::string content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
|
||||
|
||||
rfl::Result<WeatherOutput> result = rfl::json::read<WeatherOutput>(content);
|
||||
if (!result)
|
||||
return std::unexpected(result.error().what());
|
||||
|
||||
DEBUG_LOG("Successfully read from cache file.");
|
||||
return *result;
|
||||
}
|
||||
|
||||
// Function to write cache to file
|
||||
fn WriteCacheToFile(const WeatherOutput& data) -> Result<void> {
|
||||
Result<fs::path> cachePath = GetCachePath();
|
||||
if (!cachePath)
|
||||
return std::unexpected(cachePath.error());
|
||||
|
||||
DEBUG_LOG("Writing to cache file...");
|
||||
|
||||
fs::path tempPath = *cachePath;
|
||||
tempPath += ".tmp";
|
||||
|
||||
{
|
||||
std::ofstream ofs(tempPath, std::ios::binary | std::ios::trunc);
|
||||
if (!ofs.is_open())
|
||||
return std::unexpected("Failed to open temp file: "s + tempPath.string());
|
||||
|
||||
std::string json = rfl::json::write(data);
|
||||
ofs << json;
|
||||
|
||||
if (!ofs)
|
||||
return std::unexpected("Failed to write to temp file");
|
||||
}
|
||||
|
||||
std::error_code errc;
|
||||
fs::rename(tempPath, *cachePath, errc);
|
||||
|
||||
if (errc) {
|
||||
fs::remove(tempPath, errc);
|
||||
return std::unexpected("Failed to replace cache file: "s + errc.message());
|
||||
}
|
||||
|
||||
DEBUG_LOG("Successfully wrote to cache file.");
|
||||
return {};
|
||||
}
|
||||
|
||||
fn WriteCallback(void* contents, size_t size, size_t nmemb, std::string* str) -> size_t {
|
||||
const size_t totalSize = size * nmemb;
|
||||
str->append(static_cast<char*>(contents), totalSize);
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
// Function to make API request
|
||||
fn MakeApiRequest(const std::string& url) -> Result<WeatherOutput> {
|
||||
DEBUG_LOG("Making API request to URL: {}", url);
|
||||
|
||||
CURL* curl = curl_easy_init();
|
||||
std::string responseBuffer;
|
||||
|
||||
if (!curl)
|
||||
return std::unexpected("Failed to initialize cURL");
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseBuffer);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10);
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5);
|
||||
|
||||
const CURLcode res = curl_easy_perform(curl);
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (res != CURLE_OK)
|
||||
return std::unexpected(fmt::format("cURL error: {}", curl_easy_strerror(res)));
|
||||
|
||||
DEBUG_LOG("API response size: {}", responseBuffer.size());
|
||||
|
||||
DEBUG_LOG("API response: {}", responseBuffer);
|
||||
|
||||
rfl::Result<WeatherOutput> output = rfl::json::read<WeatherOutput>(responseBuffer);
|
||||
if (!output)
|
||||
return std::unexpected(output.error().what());
|
||||
|
||||
return *output;
|
||||
}
|
||||
}
|
||||
|
||||
// Core function to get weather information
|
||||
fn Weather::getWeatherInfo() const -> WeatherOutput {
|
||||
using namespace std::chrono;
|
||||
|
||||
if (Result<WeatherOutput> data = ReadCacheFromFile()) {
|
||||
const WeatherOutput& dataVal = *data;
|
||||
const duration<double> cacheAge = system_clock::now() - system_clock::time_point(seconds(dataVal.dt));
|
||||
|
||||
if (cacheAge < 10min) {
|
||||
DEBUG_LOG("Using valid cache");
|
||||
return dataVal;
|
||||
}
|
||||
DEBUG_LOG("Cache expired");
|
||||
} else {
|
||||
DEBUG_LOG("Cache error: {}", data.error());
|
||||
}
|
||||
|
||||
fn handleApiResult = [](const Result<WeatherOutput>& result) -> WeatherOutput {
|
||||
if (!result)
|
||||
ERROR_LOG("API request failed: {}", result.error());
|
||||
|
||||
// Fix for second warning: Check the write result
|
||||
if (Result<void> writeResult = WriteCacheToFile(*result); !writeResult)
|
||||
ERROR_LOG("Failed to write cache: {}", writeResult.error());
|
||||
|
||||
return *result;
|
||||
};
|
||||
|
||||
if (std::holds_alternative<std::string>(location)) {
|
||||
const auto& city = std::get<std::string>(location);
|
||||
char* escaped = curl_easy_escape(nullptr, city.c_str(), static_cast<int>(city.length()));
|
||||
|
||||
DEBUG_LOG("Requesting city: {}", escaped);
|
||||
const std::string apiUrl =
|
||||
fmt::format("https://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units={}", escaped, api_key, units);
|
||||
|
||||
curl_free(escaped);
|
||||
return handleApiResult(MakeApiRequest(apiUrl));
|
||||
}
|
||||
|
||||
const auto& [lat, lon] = std::get<Coords>(location);
|
||||
DEBUG_LOG("Requesting coordinates: lat={:.3f}, lon={:.3f}", lat, lon);
|
||||
|
||||
const std::string apiUrl = fmt::format(
|
||||
"https://api.openweathermap.org/data/2.5/weather?lat={:.3f}&lon={:.3f}&appid={}&units={}", lat, lon, api_key, units
|
||||
);
|
||||
|
||||
return handleApiResult(MakeApiRequest(apiUrl));
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <rfl.hpp>
|
||||
#include <rfl/toml.hpp>
|
||||
|
||||
#include "../util/types.h"
|
||||
|
||||
using degrees = rfl::Validator<u16, rfl::Minimum<0>, rfl::Maximum<360>>;
|
||||
using percentage = rfl::Validator<u8, rfl::Minimum<0>, rfl::Maximum<100>>;
|
||||
|
||||
struct Condition {
|
||||
string description;
|
||||
string icon;
|
||||
string main;
|
||||
usize id;
|
||||
};
|
||||
|
||||
struct Main {
|
||||
f64 feels_like;
|
||||
f64 temp;
|
||||
f64 temp_max;
|
||||
f64 temp_min;
|
||||
isize pressure;
|
||||
percentage humidity;
|
||||
std::optional<isize> grnd_level;
|
||||
std::optional<isize> sea_level;
|
||||
};
|
||||
|
||||
struct Wind {
|
||||
degrees deg;
|
||||
f64 speed;
|
||||
std::optional<f64> gust;
|
||||
};
|
||||
|
||||
struct Precipitation {
|
||||
rfl::Rename<"1h", std::optional<f64>> one_hour;
|
||||
rfl::Rename<"3h", std::optional<f64>> three_hours;
|
||||
};
|
||||
|
||||
struct Sys {
|
||||
string country;
|
||||
usize id;
|
||||
usize sunrise;
|
||||
usize sunset;
|
||||
usize type;
|
||||
};
|
||||
|
||||
struct Clouds {
|
||||
percentage all;
|
||||
};
|
||||
|
||||
struct Coords {
|
||||
double lat;
|
||||
double lon;
|
||||
};
|
||||
|
||||
struct WeatherOutput {
|
||||
Clouds clouds;
|
||||
isize timezone;
|
||||
isize visibility;
|
||||
Main main;
|
||||
rfl::Rename<"coord", Coords> coords;
|
||||
std::optional<Precipitation> rain;
|
||||
std::optional<Precipitation> snow;
|
||||
string base;
|
||||
string name;
|
||||
std::vector<Condition> weather;
|
||||
Sys sys;
|
||||
usize cod;
|
||||
usize dt;
|
||||
usize id;
|
||||
Wind wind;
|
||||
};
|
312
src/main.cpp
312
src/main.cpp
|
@ -1,312 +0,0 @@
|
|||
#include <ctime>
|
||||
#include <fmt/chrono.h>
|
||||
#include <fmt/color.h>
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/format.h>
|
||||
#include <ftxui/dom/elements.hpp>
|
||||
#include <ftxui/screen/screen.hpp>
|
||||
#include <future>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
#include "config/config.h"
|
||||
#include "ftxui/screen/color.hpp"
|
||||
#include "os/os.h"
|
||||
|
||||
constexpr const bool SHOW_ICONS = true;
|
||||
|
||||
struct BytesToGiB {
|
||||
u64 value;
|
||||
};
|
||||
|
||||
// 1024^3 (size of 1 GiB)
|
||||
constexpr u64 GIB = 1'073'741'824;
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<BytesToGiB> : fmt::formatter<double> {
|
||||
template <typename FmtCtx>
|
||||
constexpr fn format(const BytesToGiB& BTG, FmtCtx& ctx) const -> typename FmtCtx::iterator {
|
||||
return fmt::format_to(fmt::formatter<double>::format(static_cast<double>(BTG.value) / GIB, ctx), "GiB");
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
fn GetDate() -> std::string {
|
||||
// Get current local time
|
||||
std::time_t now = std::time(nullptr);
|
||||
std::tm localTime;
|
||||
|
||||
#ifdef __WIN32__
|
||||
if (localtime_s(&localTime, &now) != 0)
|
||||
ERROR_LOG("localtime_s failed");
|
||||
#else
|
||||
if (localtime_r(&now, &localTime) == nullptr)
|
||||
ERROR_LOG("localtime_r failed");
|
||||
#endif
|
||||
|
||||
// Format the date using fmt::format
|
||||
std::string date = fmt::format("{:%e}", localTime);
|
||||
|
||||
// Remove leading whitespace
|
||||
if (!date.empty() && std::isspace(date.front()))
|
||||
date.erase(date.begin());
|
||||
|
||||
// Append appropriate suffix for the date
|
||||
if (date.ends_with("1") && date != "11")
|
||||
date += "st";
|
||||
else if (date.ends_with("2") && date != "12")
|
||||
date += "nd";
|
||||
else if (date.ends_with("3") && date != "13")
|
||||
date += "rd";
|
||||
else
|
||||
date += "th";
|
||||
|
||||
return fmt::format("{:%B} {}", localTime, date);
|
||||
}
|
||||
|
||||
struct SystemData {
|
||||
std::string date;
|
||||
std::string host;
|
||||
std::string kernel_version;
|
||||
std::expected<string, string> os_version;
|
||||
std::expected<u64, std::string> mem_info;
|
||||
std::optional<string> desktop_environment;
|
||||
std::string window_manager;
|
||||
std::optional<std::expected<string, NowPlayingError>> now_playing;
|
||||
std::optional<WeatherOutput> weather_info;
|
||||
|
||||
static fn fetchSystemData(const Config& config) -> SystemData {
|
||||
SystemData data;
|
||||
|
||||
// Group tasks by dependency/type
|
||||
auto [date, host, kernel] = std::tuple(
|
||||
std::async(std::launch::async, GetDate),
|
||||
std::async(std::launch::async, GetHost),
|
||||
std::async(std::launch::async, GetKernelVersion)
|
||||
);
|
||||
|
||||
auto [osVer, mem, desktop, winManager] = std::tuple(
|
||||
std::async(std::launch::async, GetOSVersion),
|
||||
std::async(std::launch::async, GetMemInfo),
|
||||
std::async(std::launch::async, GetDesktopEnvironment),
|
||||
std::async(std::launch::async, GetWindowManager)
|
||||
);
|
||||
|
||||
// Conditional async tasks
|
||||
std::future<WeatherOutput> weather;
|
||||
std::future<std::expected<string, NowPlayingError>> nowPlaying;
|
||||
|
||||
if (config.weather.get().enabled)
|
||||
weather = std::async(std::launch::async, [&] { return config.weather.get().getWeatherInfo(); });
|
||||
|
||||
if (config.now_playing.get().enabled)
|
||||
nowPlaying = std::async(std::launch::async, GetNowPlaying);
|
||||
|
||||
// Ordered wait for fastest completion
|
||||
data.date = date.get();
|
||||
data.host = host.get();
|
||||
data.kernel_version = kernel.get();
|
||||
data.os_version = osVer.get();
|
||||
data.mem_info = mem.get();
|
||||
data.desktop_environment = desktop.get();
|
||||
data.window_manager = winManager.get();
|
||||
|
||||
if (weather.valid())
|
||||
data.weather_info = weather.get();
|
||||
if (nowPlaying.valid())
|
||||
data.now_playing = nowPlaying.get();
|
||||
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
using namespace ftxui;
|
||||
|
||||
fn CreateColorCircles() -> Element {
|
||||
Elements circles(16);
|
||||
|
||||
std::generate_n(circles.begin(), 16, [colorIndex = 0]() mutable {
|
||||
return hbox({ text("◯") | bold | color(Color::Palette256(colorIndex++)), text(" ") });
|
||||
});
|
||||
|
||||
return hbox(circles);
|
||||
}
|
||||
|
||||
fn SystemInfoBox(const Config& config, const SystemData& data) -> Element {
|
||||
// Fetch data
|
||||
const string& name = config.general.get().name.get();
|
||||
const Weather weather = config.weather.get();
|
||||
const bool nowPlayingEnabled = config.now_playing.get().enabled;
|
||||
|
||||
const char *calendarIcon = "", *hostIcon = "", *kernelIcon = "", *osIcon = "", *memoryIcon = "", *weatherIcon = "",
|
||||
*musicIcon = "";
|
||||
|
||||
if (SHOW_ICONS) {
|
||||
calendarIcon = " ";
|
||||
hostIcon = " ";
|
||||
kernelIcon = " ";
|
||||
osIcon = " ";
|
||||
memoryIcon = " ";
|
||||
weatherIcon = " ";
|
||||
musicIcon = " ";
|
||||
}
|
||||
|
||||
const Color::Palette16 labelColor = Color::Yellow;
|
||||
const Color::Palette16 valueColor = Color::White;
|
||||
const Color::Palette16 borderColor = Color::GrayLight;
|
||||
const Color::Palette16 iconColor = Color::Cyan;
|
||||
|
||||
Elements content;
|
||||
|
||||
content.push_back(text(" Hello " + name + "! ") | bold | color(Color::Cyan));
|
||||
content.push_back(separator() | color(borderColor));
|
||||
content.push_back(hbox(
|
||||
{
|
||||
text(" ") | color(iconColor), // Palette icon
|
||||
CreateColorCircles(),
|
||||
}
|
||||
));
|
||||
content.push_back(separator() | color(borderColor));
|
||||
|
||||
// Helper function for aligned rows
|
||||
fn createRow = [&](const std::string& icon, const std::string& label, const std::string& value) {
|
||||
return hbox(
|
||||
{
|
||||
text(icon) | color(iconColor),
|
||||
text(label) | color(labelColor),
|
||||
filler(),
|
||||
text(value) | color(valueColor),
|
||||
text(" "),
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// System info rows
|
||||
content.push_back(createRow(calendarIcon, "Date", data.date));
|
||||
|
||||
// Weather row
|
||||
if (weather.enabled && data.weather_info.has_value()) {
|
||||
const WeatherOutput& weatherInfo = data.weather_info.value();
|
||||
|
||||
if (weather.show_town_name)
|
||||
content.push_back(hbox(
|
||||
{
|
||||
text(weatherIcon) | color(iconColor),
|
||||
text("Weather") | color(labelColor),
|
||||
filler(),
|
||||
|
||||
hbox(
|
||||
{
|
||||
text(fmt::format("{}°F ", std::lround(weatherInfo.main.temp))),
|
||||
text("in "),
|
||||
text(weatherInfo.name),
|
||||
text(" "),
|
||||
}
|
||||
) |
|
||||
color(valueColor),
|
||||
}
|
||||
));
|
||||
else
|
||||
content.push_back(hbox(
|
||||
{
|
||||
text(weatherIcon) | color(iconColor),
|
||||
text("Weather") | color(labelColor),
|
||||
filler(),
|
||||
|
||||
hbox(
|
||||
{
|
||||
text(fmt::format("{}°F, {}", std::lround(weatherInfo.main.temp), weatherInfo.weather[0].description)),
|
||||
text(" "),
|
||||
}
|
||||
) |
|
||||
color(valueColor),
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
content.push_back(separator() | color(borderColor));
|
||||
|
||||
if (!data.host.empty())
|
||||
content.push_back(createRow(hostIcon, "Host", data.host));
|
||||
|
||||
if (!data.kernel_version.empty())
|
||||
content.push_back(createRow(kernelIcon, "Kernel", data.kernel_version));
|
||||
|
||||
if (data.os_version.has_value())
|
||||
content.push_back(createRow(osIcon, "OS", *data.os_version));
|
||||
else
|
||||
ERROR_LOG("Failed to get OS version: {}", data.os_version.error());
|
||||
|
||||
if (data.mem_info.has_value())
|
||||
content.push_back(createRow(memoryIcon, "RAM", fmt::format("{:.2f}", BytesToGiB { *data.mem_info })));
|
||||
else
|
||||
ERROR_LOG("Failed to get memory info: {}", data.mem_info.error());
|
||||
|
||||
content.push_back(separator() | color(borderColor));
|
||||
|
||||
if (data.desktop_environment.has_value() && *data.desktop_environment != data.window_manager)
|
||||
content.push_back(createRow(" ", "DE", *data.desktop_environment));
|
||||
|
||||
if (!data.window_manager.empty())
|
||||
content.push_back(createRow(" ", "WM", data.window_manager));
|
||||
|
||||
// Now Playing row
|
||||
if (nowPlayingEnabled && data.now_playing.has_value()) {
|
||||
const std::expected<string, NowPlayingError>& nowPlayingResult = *data.now_playing;
|
||||
|
||||
if (nowPlayingResult.has_value()) {
|
||||
const std::string& npText = *nowPlayingResult;
|
||||
|
||||
content.push_back(separator() | color(borderColor));
|
||||
content.push_back(hbox(
|
||||
{
|
||||
text(musicIcon) | color(iconColor),
|
||||
text("Playing") | color(labelColor),
|
||||
text(" "),
|
||||
filler(),
|
||||
paragraph(npText) | color(Color::Magenta) | size(WIDTH, LESS_THAN, 30),
|
||||
text(" "),
|
||||
}
|
||||
));
|
||||
} else {
|
||||
const NowPlayingError& error = nowPlayingResult.error();
|
||||
|
||||
if (std::holds_alternative<NowPlayingCode>(error))
|
||||
switch (std::get<NowPlayingCode>(error)) {
|
||||
case NowPlayingCode::NoPlayers:
|
||||
DEBUG_LOG("No players found");
|
||||
break;
|
||||
case NowPlayingCode::NoActivePlayer:
|
||||
DEBUG_LOG("No active player found");
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
if (std::holds_alternative<LinuxError>(error))
|
||||
DEBUG_LOG("DBus error: {}", std::get<LinuxError>(error));
|
||||
#endif
|
||||
|
||||
#ifdef __WIN32__
|
||||
if (std::holds_alternative<WindowsError>(error))
|
||||
DEBUG_LOG("WinRT error: {}", to_string(std::get<WindowsError>(error).message()));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
return vbox(content) | borderRounded | color(Color::White);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> i32 {
|
||||
const Config& config = Config::getInstance();
|
||||
|
||||
SystemData data = SystemData::fetchSystemData(config);
|
||||
|
||||
Element document = hbox({ SystemInfoBox(config, data), filler() });
|
||||
|
||||
Screen screen = Screen::Create(Dimension::Full(), Dimension::Fit(document));
|
||||
Render(screen, document);
|
||||
screen.Print();
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
#ifdef __FreeBSD__
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#include "os.h"
|
||||
|
||||
fn GetMemInfo() -> u64 {
|
||||
u64 mem = 0;
|
||||
usize size = sizeof(mem);
|
||||
|
||||
sysctlbyname("hw.physmem", &mem, &size, nullptr, 0);
|
||||
|
||||
return mem;
|
||||
}
|
||||
|
||||
fn GetOSVersion() -> string {
|
||||
std::ifstream file("/etc/os-release");
|
||||
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Failed to open /etc/os-release" << std::endl;
|
||||
return ""; // Return empty string indicating failure
|
||||
}
|
||||
|
||||
string line;
|
||||
const string prefix = "PRETTY_NAME=";
|
||||
|
||||
while (std::getline(file, line)) {
|
||||
if (line.find(prefix) == 0) {
|
||||
string prettyName = line.substr(prefix.size());
|
||||
|
||||
// Remove surrounding quotes if present
|
||||
if (!prettyName.empty() && prettyName.front() == '"' && prettyName.back() == '"')
|
||||
prettyName = prettyName.substr(1, prettyName.size() - 2);
|
||||
|
||||
return prettyName;
|
||||
}
|
||||
}
|
||||
|
||||
return ""; // Return empty string if PRETTY_NAME= line not found
|
||||
}
|
||||
|
||||
fn GetMprisPlayers(sdbus::IConnection& connection) -> std::vector<string> {
|
||||
const sdbus::ServiceName dbusInterface = sdbus::ServiceName("org.freedesktop.DBus");
|
||||
const sdbus::ObjectPath dbusObjectPath = sdbus::ObjectPath("/org/freedesktop/DBus");
|
||||
const char* dbusMethodListNames = "ListNames";
|
||||
|
||||
const std::unique_ptr<sdbus::IProxy> dbusProxy =
|
||||
createProxy(connection, dbusInterface, dbusObjectPath);
|
||||
|
||||
std::vector<string> names;
|
||||
|
||||
dbusProxy->callMethod(dbusMethodListNames).onInterface(dbusInterface).storeResultsTo(names);
|
||||
|
||||
std::vector<string> mprisPlayers;
|
||||
|
||||
for (const std::basic_string<char>& name : names)
|
||||
if (const char* mprisInterfaceName = "org.mpris.MediaPlayer2";
|
||||
name.find(mprisInterfaceName) != std::string::npos)
|
||||
mprisPlayers.push_back(name);
|
||||
|
||||
return mprisPlayers;
|
||||
}
|
||||
|
||||
fn GetActivePlayer(const std::vector<string>& mprisPlayers) -> string {
|
||||
if (!mprisPlayers.empty())
|
||||
return mprisPlayers.front();
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
fn GetNowPlaying() -> string {
|
||||
try {
|
||||
const char *playerObjectPath = "/org/mpris/MediaPlayer2",
|
||||
*playerInterfaceName = "org.mpris.MediaPlayer2.Player";
|
||||
|
||||
std::unique_ptr<sdbus::IConnection> connection = sdbus::createSessionBusConnection();
|
||||
|
||||
std::vector<string> mprisPlayers = GetMprisPlayers(*connection);
|
||||
|
||||
if (mprisPlayers.empty())
|
||||
return "";
|
||||
|
||||
string activePlayer = GetActivePlayer(mprisPlayers);
|
||||
|
||||
if (activePlayer.empty())
|
||||
return "";
|
||||
|
||||
auto playerProxy = sdbus::createProxy(
|
||||
*connection, sdbus::ServiceName(activePlayer), sdbus::ObjectPath(playerObjectPath)
|
||||
);
|
||||
|
||||
sdbus::Variant metadataVariant =
|
||||
playerProxy->getProperty("Metadata").onInterface(playerInterfaceName);
|
||||
|
||||
if (metadataVariant.containsValueOfType<std::map<std::string, sdbus::Variant>>()) {
|
||||
const auto& metadata = metadataVariant.get<std::map<std::string, sdbus::Variant>>();
|
||||
|
||||
auto iter = metadata.find("xesam:title");
|
||||
|
||||
if (iter != metadata.end() && iter->second.containsValueOfType<std::string>())
|
||||
return iter->second.get<std::string>();
|
||||
}
|
||||
} catch (const sdbus::Error& e) {
|
||||
if (e.getName() != "com.github.altdesktop.playerctld.NoActivePlayer")
|
||||
return fmt::format("Error: {}", e.what());
|
||||
|
||||
return "No active player";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
#endif
|
694
src/os/linux.cpp
694
src/os/linux.cpp
|
@ -1,694 +0,0 @@
|
|||
#ifdef __linux__
|
||||
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <dbus/dbus.h>
|
||||
#include <dirent.h>
|
||||
#include <expected>
|
||||
#include <filesystem>
|
||||
#include <fmt/format.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "os.h"
|
||||
#include "src/util/macros.h"
|
||||
|
||||
using std::errc, std::expected, std::from_chars, std::getline, std::istreambuf_iterator, std::less, std::lock_guard,
|
||||
std::mutex, std::ofstream, std::pair, std::string_view, std::vector, std::nullopt, std::array, std::optional,
|
||||
std::bit_cast, std::to_string, std::ifstream, std::getenv, std::string, std::unexpected, std::ranges::is_sorted,
|
||||
std::ranges::lower_bound, std::ranges::replace, std::ranges::subrange, std::ranges::transform;
|
||||
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
enum SessionType : u8 { Wayland, X11, TTY, Unknown };
|
||||
|
||||
namespace {
|
||||
fn GetX11WindowManager() -> string {
|
||||
Display* display = XOpenDisplay(nullptr);
|
||||
|
||||
// If XOpenDisplay fails, likely in a TTY
|
||||
if (!display)
|
||||
return "";
|
||||
|
||||
Atom supportingWmCheck = XInternAtom(display, "_NET_SUPPORTING_WM_CHECK", False);
|
||||
Atom wmName = XInternAtom(display, "_NET_WM_NAME", False);
|
||||
Atom utf8String = XInternAtom(display, "UTF8_STRING", False);
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wold-style-cast"
|
||||
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
|
||||
Window root = DefaultRootWindow(display); // NOLINT
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
Window wmWindow = 0;
|
||||
Atom actualType = 0;
|
||||
int actualFormat = 0;
|
||||
unsigned long nitems = 0, bytesAfter = 0;
|
||||
unsigned char* data = nullptr;
|
||||
|
||||
if (XGetWindowProperty(
|
||||
display,
|
||||
root,
|
||||
supportingWmCheck,
|
||||
0,
|
||||
1,
|
||||
False,
|
||||
// XA_WINDOW
|
||||
static_cast<Atom>(33),
|
||||
&actualType,
|
||||
&actualFormat,
|
||||
&nitems,
|
||||
&bytesAfter,
|
||||
&data
|
||||
) == Success &&
|
||||
data) {
|
||||
wmWindow = *bit_cast<Window*>(data);
|
||||
XFree(data);
|
||||
data = nullptr;
|
||||
|
||||
if (XGetWindowProperty(
|
||||
display,
|
||||
wmWindow,
|
||||
wmName,
|
||||
0,
|
||||
1024,
|
||||
False,
|
||||
utf8String,
|
||||
&actualType,
|
||||
&actualFormat,
|
||||
&nitems,
|
||||
&bytesAfter,
|
||||
&data
|
||||
) == Success &&
|
||||
data) {
|
||||
string name(bit_cast<char*>(data));
|
||||
XFree(data);
|
||||
XCloseDisplay(display);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
XCloseDisplay(display);
|
||||
|
||||
return "Unknown (X11)"; // Changed to empty string
|
||||
}
|
||||
|
||||
fn TrimHyprlandWrapper(const string& input) -> string {
|
||||
if (input.contains("hyprland"))
|
||||
return "Hyprland";
|
||||
return input;
|
||||
}
|
||||
|
||||
fn ReadProcessCmdline(int pid) -> string {
|
||||
string path = "/proc/" + to_string(pid) + "/cmdline";
|
||||
ifstream cmdlineFile(path);
|
||||
string cmdline;
|
||||
if (getline(cmdlineFile, cmdline)) {
|
||||
// Replace null bytes with spaces
|
||||
replace(cmdline, '\0', ' ');
|
||||
return cmdline;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
fn DetectHyprlandSpecific() -> string {
|
||||
// Check environment variables first
|
||||
const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP");
|
||||
if (xdgCurrentDesktop && strcasestr(xdgCurrentDesktop, "hyprland"))
|
||||
return "Hyprland";
|
||||
|
||||
// Check for Hyprland's specific environment variable
|
||||
if (getenv("HYPRLAND_INSTANCE_SIGNATURE"))
|
||||
return "Hyprland";
|
||||
|
||||
// Check for Hyprland socket
|
||||
if (fs::exists("/run/user/" + to_string(getuid()) + "/hypr"))
|
||||
return "Hyprland";
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
fn GetWaylandCompositor() -> string {
|
||||
// First try Hyprland-specific detection
|
||||
string hypr = DetectHyprlandSpecific();
|
||||
if (!hypr.empty())
|
||||
return hypr;
|
||||
|
||||
// Then try the standard Wayland detection
|
||||
wl_display* display = wl_display_connect(nullptr);
|
||||
|
||||
if (!display)
|
||||
return "";
|
||||
|
||||
int fileDescriptor = wl_display_get_fd(display);
|
||||
|
||||
struct ucred cred;
|
||||
socklen_t len = sizeof(cred);
|
||||
|
||||
if (getsockopt(fileDescriptor, SOL_SOCKET, SO_PEERCRED, &cred, &len) == -1) {
|
||||
wl_display_disconnect(display);
|
||||
return "";
|
||||
}
|
||||
|
||||
// Read both comm and cmdline
|
||||
string compositorName;
|
||||
|
||||
// 1. Check comm (might be wrapped)
|
||||
string commPath = "/proc/" + to_string(cred.pid) + "/comm";
|
||||
ifstream commFile(commPath);
|
||||
if (commFile >> compositorName) {
|
||||
subrange removedRange = std::ranges::remove(compositorName, '\n');
|
||||
compositorName.erase(removedRange.begin(), compositorName.end());
|
||||
}
|
||||
|
||||
// 2. Check cmdline for actual binary reference
|
||||
string cmdline = ReadProcessCmdline(cred.pid);
|
||||
if (cmdline.contains("hyprland")) {
|
||||
wl_display_disconnect(display);
|
||||
return "Hyprland";
|
||||
}
|
||||
|
||||
// 3. Check exe symlink
|
||||
string exePath = "/proc/" + to_string(cred.pid) + "/exe";
|
||||
array<char, PATH_MAX> buf;
|
||||
ssize_t lenBuf = readlink(exePath.c_str(), buf.data(), buf.size() - 1);
|
||||
if (lenBuf != -1) {
|
||||
buf.at(static_cast<usize>(lenBuf)) = '\0';
|
||||
string exe(buf.data());
|
||||
if (exe.contains("hyprland")) {
|
||||
wl_display_disconnect(display);
|
||||
return "Hyprland";
|
||||
}
|
||||
}
|
||||
|
||||
wl_display_disconnect(display);
|
||||
|
||||
// Final cleanup of wrapper names
|
||||
return TrimHyprlandWrapper(compositorName);
|
||||
}
|
||||
|
||||
fn DetectFromEnvVars() -> optional<string> {
|
||||
// Use RAII to guard against concurrent env modifications
|
||||
static mutex EnvMutex;
|
||||
lock_guard<mutex> lock(EnvMutex);
|
||||
|
||||
// XDG_CURRENT_DESKTOP
|
||||
if (const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP")) {
|
||||
const string_view sview(xdgCurrentDesktop);
|
||||
const size_t colon = sview.find(':');
|
||||
return string(sview.substr(0, colon)); // Direct construct from view
|
||||
}
|
||||
|
||||
// DESKTOP_SESSION
|
||||
if (const char* desktopSession = getenv("DESKTOP_SESSION"))
|
||||
return string(string_view(desktopSession)); // Avoid intermediate view storage
|
||||
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
fn DetectFromSessionFiles() -> optional<string> {
|
||||
static constexpr array<pair<string_view, string_view>, 12> DE_PATTERNS = {
|
||||
// clang-format off
|
||||
pair { "Budgie"sv, "budgie"sv },
|
||||
pair { "Cinnamon"sv, "cinnamon"sv },
|
||||
pair { "LXQt"sv, "lxqt"sv },
|
||||
pair { "MATE"sv, "mate"sv },
|
||||
pair { "Unity"sv, "unity"sv },
|
||||
pair { "gnome"sv, "GNOME"sv },
|
||||
pair { "gnome-wayland"sv, "GNOME"sv },
|
||||
pair { "gnome-xorg"sv, "GNOME"sv },
|
||||
pair { "kde"sv, "KDE"sv },
|
||||
pair { "plasma"sv, "KDE"sv },
|
||||
pair { "plasmax11"sv, "KDE"sv },
|
||||
pair { "xfce"sv, "XFCE"sv },
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
static_assert(is_sorted(DE_PATTERNS, {}, &pair<string_view, string_view>::first));
|
||||
|
||||
// Precomputed session paths
|
||||
static constexpr array<string_view, 2> SESSION_PATHS = { "/usr/share/xsessions", "/usr/share/wayland-sessions" };
|
||||
|
||||
// Single memory reserve for lowercase conversions
|
||||
string lowercaseStem;
|
||||
lowercaseStem.reserve(32);
|
||||
|
||||
for (const auto& path : SESSION_PATHS) {
|
||||
if (!fs::exists(path))
|
||||
continue;
|
||||
|
||||
for (const auto& entry : fs::directory_iterator(path)) {
|
||||
if (!entry.is_regular_file())
|
||||
continue;
|
||||
|
||||
// Reuse buffer
|
||||
lowercaseStem = entry.path().stem().string();
|
||||
transform(lowercaseStem, lowercaseStem.begin(), ::tolower);
|
||||
|
||||
// Modern ranges version
|
||||
const pair<string_view, string_view>* const patternIter = lower_bound(
|
||||
DE_PATTERNS, lowercaseStem, less {}, &pair<string_view, string_view>::first // Projection
|
||||
);
|
||||
|
||||
if (patternIter != DE_PATTERNS.end() && patternIter->first == lowercaseStem)
|
||||
return string(patternIter->second);
|
||||
}
|
||||
}
|
||||
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
fn DetectFromProcesses() -> optional<string> {
|
||||
const array processChecks = {
|
||||
// clang-format off
|
||||
pair { "plasmashell"sv, "KDE"sv },
|
||||
pair { "gnome-shell"sv, "GNOME"sv },
|
||||
pair { "xfce4-session"sv, "XFCE"sv },
|
||||
pair { "mate-session"sv, "MATE"sv },
|
||||
pair { "cinnamon-sessio"sv, "Cinnamon"sv },
|
||||
pair { "budgie-wm"sv, "Budgie"sv },
|
||||
pair { "lxqt-session"sv, "LXQt"sv },
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
ifstream cmdline("/proc/self/environ");
|
||||
string envVars((istreambuf_iterator<char>(cmdline)), istreambuf_iterator<char>());
|
||||
|
||||
for (const auto& [process, deName] : processChecks)
|
||||
if (envVars.contains(process))
|
||||
return string(deName);
|
||||
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wold-style-cast"
|
||||
fn GetMprisPlayers(DBusConnection* connection) -> vector<string> {
|
||||
vector<string> mprisPlayers;
|
||||
DBusError err;
|
||||
dbus_error_init(&err);
|
||||
|
||||
// Create a method call to org.freedesktop.DBus.ListNames
|
||||
DBusMessage* msg = dbus_message_new_method_call(
|
||||
"org.freedesktop.DBus", // target service
|
||||
"/org/freedesktop/DBus", // object path
|
||||
"org.freedesktop.DBus", // interface name
|
||||
"ListNames" // method name
|
||||
);
|
||||
|
||||
if (!msg) {
|
||||
DEBUG_LOG("Failed to create message for ListNames.");
|
||||
return mprisPlayers;
|
||||
}
|
||||
|
||||
// Send the message and block until we get a reply.
|
||||
DBusMessage* reply = dbus_connection_send_with_reply_and_block(connection, msg, -1, &err);
|
||||
dbus_message_unref(msg);
|
||||
|
||||
if (dbus_error_is_set(&err)) {
|
||||
DEBUG_LOG("DBus error in ListNames: {}", err.message);
|
||||
dbus_error_free(&err);
|
||||
return mprisPlayers;
|
||||
}
|
||||
|
||||
if (!reply) {
|
||||
DEBUG_LOG("No reply received for ListNames.");
|
||||
return mprisPlayers;
|
||||
}
|
||||
|
||||
// The expected reply signature is "as" (an array of strings)
|
||||
DBusMessageIter iter;
|
||||
|
||||
if (!dbus_message_iter_init(reply, &iter)) {
|
||||
DEBUG_LOG("Reply has no arguments.");
|
||||
dbus_message_unref(reply);
|
||||
return mprisPlayers;
|
||||
}
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) {
|
||||
DEBUG_LOG("Reply argument is not an array.");
|
||||
dbus_message_unref(reply);
|
||||
return mprisPlayers;
|
||||
}
|
||||
|
||||
// Iterate over the array of strings
|
||||
DBusMessageIter subIter;
|
||||
dbus_message_iter_recurse(&iter, &subIter);
|
||||
|
||||
while (dbus_message_iter_get_arg_type(&subIter) != DBUS_TYPE_INVALID) {
|
||||
if (dbus_message_iter_get_arg_type(&subIter) == DBUS_TYPE_STRING) {
|
||||
const char* name = nullptr;
|
||||
dbus_message_iter_get_basic(&subIter, static_cast<void*>(&name));
|
||||
if (name && std::string_view(name).contains("org.mpris.MediaPlayer2"))
|
||||
mprisPlayers.emplace_back(name);
|
||||
}
|
||||
dbus_message_iter_next(&subIter);
|
||||
}
|
||||
|
||||
dbus_message_unref(reply);
|
||||
return mprisPlayers;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
fn GetActivePlayer(const vector<string>& mprisPlayers) -> optional<string> {
|
||||
if (!mprisPlayers.empty())
|
||||
return mprisPlayers.front();
|
||||
return nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
fn GetOSVersion() -> expected<string, string> {
|
||||
constexpr const char* path = "/etc/os-release";
|
||||
|
||||
ifstream file(path);
|
||||
|
||||
if (!file.is_open())
|
||||
return unexpected("Failed to open " + string(path));
|
||||
|
||||
string line;
|
||||
const string prefix = "PRETTY_NAME=";
|
||||
|
||||
while (getline(file, line))
|
||||
if (line.starts_with(prefix)) {
|
||||
string prettyName = line.substr(prefix.size());
|
||||
|
||||
if (!prettyName.empty() && prettyName.front() == '"' && prettyName.back() == '"')
|
||||
return prettyName.substr(1, prettyName.size() - 2);
|
||||
|
||||
return prettyName;
|
||||
}
|
||||
|
||||
return unexpected("PRETTY_NAME line not found in " + string(path));
|
||||
}
|
||||
|
||||
fn GetMemInfo() -> expected<u64, string> {
|
||||
constexpr const char* path = "/proc/meminfo";
|
||||
|
||||
ifstream input(path);
|
||||
|
||||
if (!input.is_open())
|
||||
return unexpected("Failed to open " + string(path));
|
||||
|
||||
string line;
|
||||
while (getline(input, line)) {
|
||||
if (line.starts_with("MemTotal")) {
|
||||
const size_t colonPos = line.find(':');
|
||||
|
||||
if (colonPos == string::npos)
|
||||
return unexpected("Invalid MemTotal line: no colon found");
|
||||
|
||||
string_view view(line);
|
||||
view.remove_prefix(colonPos + 1);
|
||||
|
||||
// Trim leading whitespace
|
||||
const size_t firstNonSpace = view.find_first_not_of(' ');
|
||||
|
||||
if (firstNonSpace == string_view::npos)
|
||||
return unexpected("No number found after colon in MemTotal line");
|
||||
|
||||
view.remove_prefix(firstNonSpace);
|
||||
|
||||
// Find the end of the numeric part
|
||||
const size_t end = view.find_first_not_of("0123456789");
|
||||
if (end != string_view::npos)
|
||||
view = view.substr(0, end);
|
||||
|
||||
// Get pointers via iterators
|
||||
const char* startPtr = &*view.begin();
|
||||
const char* endPtr = &*view.end();
|
||||
|
||||
u64 value = 0;
|
||||
const auto result = from_chars(startPtr, endPtr, value);
|
||||
|
||||
if (result.ec != errc() || result.ptr != endPtr)
|
||||
return unexpected("Failed to parse number in MemTotal line");
|
||||
|
||||
return value * 1024;
|
||||
}
|
||||
}
|
||||
|
||||
return unexpected("MemTotal line not found in " + string(path));
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wold-style-cast"
|
||||
fn GetNowPlaying() -> expected<string, NowPlayingError> {
|
||||
DBusError err;
|
||||
dbus_error_init(&err);
|
||||
|
||||
// Connect to the session bus
|
||||
DBusConnection* connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
|
||||
|
||||
if (!connection)
|
||||
if (dbus_error_is_set(&err)) {
|
||||
ERROR_LOG("DBus connection error: {}", err.message);
|
||||
|
||||
NowPlayingError error = LinuxError(err.message);
|
||||
dbus_error_free(&err);
|
||||
|
||||
return unexpected(error);
|
||||
}
|
||||
|
||||
vector<string> mprisPlayers = GetMprisPlayers(connection);
|
||||
|
||||
if (mprisPlayers.empty()) {
|
||||
dbus_connection_unref(connection);
|
||||
return unexpected(NowPlayingError { NowPlayingCode::NoPlayers });
|
||||
}
|
||||
|
||||
optional<string> activePlayer = GetActivePlayer(mprisPlayers);
|
||||
|
||||
if (!activePlayer.has_value()) {
|
||||
dbus_connection_unref(connection);
|
||||
return unexpected(NowPlayingError { NowPlayingCode::NoActivePlayer });
|
||||
}
|
||||
|
||||
// Prepare a call to the Properties.Get method to fetch "Metadata"
|
||||
DBusMessage* msg = dbus_message_new_method_call(
|
||||
activePlayer->c_str(), // target service (active player)
|
||||
"/org/mpris/MediaPlayer2", // object path
|
||||
"org.freedesktop.DBus.Properties", // interface
|
||||
"Get" // method name
|
||||
);
|
||||
|
||||
if (!msg) {
|
||||
dbus_connection_unref(connection);
|
||||
return unexpected(NowPlayingError { /* error creating message */ });
|
||||
}
|
||||
|
||||
const char* interfaceName = "org.mpris.MediaPlayer2.Player";
|
||||
const char* propertyName = "Metadata";
|
||||
|
||||
if (!dbus_message_append_args(
|
||||
msg, DBUS_TYPE_STRING, &interfaceName, DBUS_TYPE_STRING, &propertyName, DBUS_TYPE_INVALID
|
||||
)) {
|
||||
dbus_message_unref(msg);
|
||||
dbus_connection_unref(connection);
|
||||
return unexpected(NowPlayingError { /* error appending arguments */ });
|
||||
}
|
||||
|
||||
// Call the method and block until reply is received.
|
||||
DBusMessage* reply = dbus_connection_send_with_reply_and_block(connection, msg, -1, &err);
|
||||
dbus_message_unref(msg);
|
||||
|
||||
if (dbus_error_is_set(&err)) {
|
||||
ERROR_LOG("DBus error in Properties.Get: {}", err.message);
|
||||
|
||||
NowPlayingError error = LinuxError(err.message);
|
||||
dbus_error_free(&err);
|
||||
dbus_connection_unref(connection);
|
||||
|
||||
return unexpected(error);
|
||||
}
|
||||
|
||||
if (!reply) {
|
||||
dbus_connection_unref(connection);
|
||||
return unexpected(NowPlayingError { /* no reply error */ });
|
||||
}
|
||||
|
||||
// The reply should contain a variant holding a dictionary ("a{sv}")
|
||||
DBusMessageIter iter;
|
||||
|
||||
if (!dbus_message_iter_init(reply, &iter)) {
|
||||
dbus_message_unref(reply);
|
||||
dbus_connection_unref(connection);
|
||||
return unexpected(NowPlayingError { /* no arguments in reply */ });
|
||||
}
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
|
||||
dbus_message_unref(reply);
|
||||
dbus_connection_unref(connection);
|
||||
return unexpected(NowPlayingError { /* unexpected argument type */ });
|
||||
}
|
||||
|
||||
// Recurse into the variant to get the dictionary
|
||||
DBusMessageIter variantIter;
|
||||
dbus_message_iter_recurse(&iter, &variantIter);
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&variantIter) != DBUS_TYPE_ARRAY) {
|
||||
dbus_message_unref(reply);
|
||||
dbus_connection_unref(connection);
|
||||
return unexpected(NowPlayingError { /* expected array type */ });
|
||||
}
|
||||
|
||||
string title;
|
||||
string artist;
|
||||
DBusMessageIter arrayIter;
|
||||
dbus_message_iter_recurse(&variantIter, &arrayIter);
|
||||
|
||||
// Iterate over each dictionary entry (each entry is of type dict entry)
|
||||
while (dbus_message_iter_get_arg_type(&arrayIter) != DBUS_TYPE_INVALID) {
|
||||
if (dbus_message_iter_get_arg_type(&arrayIter) == DBUS_TYPE_DICT_ENTRY) {
|
||||
DBusMessageIter dictEntry;
|
||||
dbus_message_iter_recurse(&arrayIter, &dictEntry);
|
||||
|
||||
// Get the key (a string)
|
||||
const char* key = nullptr;
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&dictEntry) == DBUS_TYPE_STRING)
|
||||
dbus_message_iter_get_basic(&dictEntry, static_cast<void*>(&key));
|
||||
|
||||
// Move to the value (a variant)
|
||||
dbus_message_iter_next(&dictEntry);
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&dictEntry) == DBUS_TYPE_VARIANT) {
|
||||
DBusMessageIter valueIter;
|
||||
dbus_message_iter_recurse(&dictEntry, &valueIter);
|
||||
|
||||
if (key && std::string_view(key) == "xesam:title") {
|
||||
if (dbus_message_iter_get_arg_type(&valueIter) == DBUS_TYPE_STRING) {
|
||||
const char* val = nullptr;
|
||||
dbus_message_iter_get_basic(&valueIter, static_cast<void*>(&val));
|
||||
|
||||
if (val)
|
||||
title = val;
|
||||
}
|
||||
} else if (key && std::string_view(key) == "xesam:artist") {
|
||||
// Expect an array of strings
|
||||
if (dbus_message_iter_get_arg_type(&valueIter) == DBUS_TYPE_ARRAY) {
|
||||
DBusMessageIter subIter;
|
||||
dbus_message_iter_recurse(&valueIter, &subIter);
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&subIter) == DBUS_TYPE_STRING) {
|
||||
const char* val = nullptr;
|
||||
dbus_message_iter_get_basic(&subIter, static_cast<void*>(&val));
|
||||
|
||||
if (val)
|
||||
artist = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dbus_message_iter_next(&arrayIter);
|
||||
}
|
||||
|
||||
dbus_message_unref(reply);
|
||||
dbus_connection_unref(connection);
|
||||
|
||||
string result;
|
||||
|
||||
if (!artist.empty() && !title.empty())
|
||||
result = artist + " - " + title;
|
||||
else if (!title.empty())
|
||||
result = title;
|
||||
else if (!artist.empty())
|
||||
result = artist;
|
||||
else
|
||||
result = "";
|
||||
|
||||
return result;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
fn GetWindowManager() -> string {
|
||||
// Check environment variables first
|
||||
const char* xdgSessionType = getenv("XDG_SESSION_TYPE");
|
||||
const char* waylandDisplay = getenv("WAYLAND_DISPLAY");
|
||||
|
||||
// Prefer Wayland detection if Wayland session
|
||||
if ((waylandDisplay != nullptr) || (xdgSessionType && string_view(xdgSessionType).contains("wayland"))) {
|
||||
string compositor = GetWaylandCompositor();
|
||||
if (!compositor.empty())
|
||||
return compositor;
|
||||
|
||||
// Fallback environment check
|
||||
const char* xdgCurrentDesktop = getenv("XDG_CURRENT_DESKTOP");
|
||||
if (xdgCurrentDesktop) {
|
||||
string desktop(xdgCurrentDesktop);
|
||||
transform(compositor, compositor.begin(), ::tolower);
|
||||
if (desktop.contains("hyprland"))
|
||||
return "hyprland";
|
||||
}
|
||||
}
|
||||
|
||||
// X11 detection
|
||||
string x11wm = GetX11WindowManager();
|
||||
if (!x11wm.empty())
|
||||
return x11wm;
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
fn GetDesktopEnvironment() -> optional<string> {
|
||||
// Try environment variables first
|
||||
if (auto desktopEnvironment = DetectFromEnvVars(); desktopEnvironment.has_value())
|
||||
return desktopEnvironment;
|
||||
|
||||
// Try session files next
|
||||
if (auto desktopEnvironment = DetectFromSessionFiles(); desktopEnvironment.has_value())
|
||||
return desktopEnvironment;
|
||||
|
||||
// Fallback to process detection
|
||||
return DetectFromProcesses();
|
||||
}
|
||||
|
||||
fn GetShell() -> string {
|
||||
const char* shell = getenv("SHELL");
|
||||
|
||||
return shell ? shell : "";
|
||||
}
|
||||
|
||||
fn GetHost() -> string {
|
||||
constexpr const char* path = "/sys/class/dmi/id/product_family";
|
||||
|
||||
ifstream file(path);
|
||||
if (!file.is_open()) {
|
||||
ERROR_LOG("Failed to open {}", path);
|
||||
return "";
|
||||
}
|
||||
|
||||
string productFamily;
|
||||
if (!getline(file, productFamily)) {
|
||||
ERROR_LOG("Failed to read from {}", path);
|
||||
return "";
|
||||
}
|
||||
|
||||
return productFamily;
|
||||
}
|
||||
|
||||
fn GetKernelVersion() -> string {
|
||||
struct utsname uts;
|
||||
|
||||
if (uname(&uts) == -1) {
|
||||
ERROR_LOG("uname() failed: {}", strerror(errno));
|
||||
return "";
|
||||
}
|
||||
|
||||
return static_cast<const char*>(uts.release);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,3 +0,0 @@
|
|||
#include "src/util/macros.h"
|
||||
|
||||
extern "C" fn issetugid() -> usize { return 0; } // NOLINT
|
|
@ -1,9 +0,0 @@
|
|||
#include "src/os/linux/pkg_count.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
fn GetApkPackageCount() -> optional<usize> {
|
||||
fs::path apkDbPath("/lib/apk/db/installed");
|
||||
|
||||
return std::nullopt;
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "src/util/macros.h"
|
||||
|
||||
using std::optional;
|
||||
|
||||
// Get package count from dpkg (Debian/Ubuntu)
|
||||
fn GetDpkgPackageCount() -> optional<usize>;
|
||||
|
||||
// Get package count from RPM (Red Hat/Fedora/CentOS)
|
||||
fn GetRpmPackageCount() -> std::optional<usize>;
|
||||
|
||||
// Get package count from pacman (Arch Linux)
|
||||
fn GetPacmanPackageCount() -> std::optional<usize>;
|
||||
|
||||
// Get package count from Portage (Gentoo)
|
||||
fn GetPortagePackageCount() -> std::optional<usize>;
|
||||
|
||||
// Get package count from zypper (openSUSE)
|
||||
fn GetZypperPackageCount() -> std::optional<usize>;
|
||||
|
||||
// Get package count from apk (Alpine)
|
||||
fn GetApkPackageCount() -> std::optional<usize>;
|
||||
|
||||
// Get package count from nix
|
||||
fn GetNixPackageCount() -> std::optional<usize>;
|
||||
|
||||
// Get package count from flatpak
|
||||
fn GetFlatpakPackageCount() -> std::optional<usize>;
|
||||
|
||||
// Get package count from snap
|
||||
fn GetSnapPackageCount() -> std::optional<usize>;
|
||||
|
||||
// Get package count from AppImage
|
||||
fn GetAppimagePackageCount() -> std::optional<usize>;
|
||||
|
||||
// Get total package count from all available package managers
|
||||
fn GetTotalPackageCount() -> std::optional<usize>;
|
197
src/os/macos.cpp
197
src/os/macos.cpp
|
@ -1,197 +0,0 @@
|
|||
#ifdef __APPLE__
|
||||
|
||||
#include <expected>
|
||||
#include <map>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#include "macos/bridge.h"
|
||||
#include "os.h"
|
||||
|
||||
fn GetMemInfo() -> expected<u64, string> {
|
||||
u64 mem = 0;
|
||||
usize size = sizeof(mem);
|
||||
|
||||
if (sysctlbyname("hw.memsize", &mem, &size, nullptr, 0) == -1)
|
||||
return std::unexpected(string("sysctlbyname failed: ") + strerror(errno));
|
||||
|
||||
return mem;
|
||||
}
|
||||
|
||||
fn GetNowPlaying() -> expected<string, NowPlayingError> { return GetCurrentPlayingInfo(); }
|
||||
|
||||
fn GetOSVersion() -> expected<string, string> { return GetMacOSVersion(); }
|
||||
|
||||
fn GetDesktopEnvironment() -> optional<string> { return std::nullopt; }
|
||||
|
||||
fn GetWindowManager() -> string { return "Yabai"; }
|
||||
|
||||
fn GetKernelVersion() -> string {
|
||||
std::array<char, 256> kernelVersion;
|
||||
usize kernelVersionLen = sizeof(kernelVersion);
|
||||
|
||||
sysctlbyname("kern.osrelease", kernelVersion.data(), &kernelVersionLen, nullptr, 0);
|
||||
|
||||
return kernelVersion.data();
|
||||
}
|
||||
|
||||
fn GetHost() -> string {
|
||||
std::array<char, 256> hwModel;
|
||||
size_t hwModelLen = sizeof(hwModel);
|
||||
|
||||
sysctlbyname("hw.model", hwModel.data(), &hwModelLen, nullptr, 0);
|
||||
|
||||
// taken from https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/host/host_mac.c
|
||||
// shortened a lot of the entries to remove unnecessary info
|
||||
std::map<std::string, std::string> modelNameByHwModel = {
|
||||
// MacBook Pro
|
||||
{ "MacBookPro18,3", "MacBook Pro (14-inch, 2021)" },
|
||||
{ "MacBookPro18,4", "MacBook Pro (14-inch, 2021)" },
|
||||
{ "MacBookPro18,1", "MacBook Pro (16-inch, 2021)" },
|
||||
{ "MacBookPro18,2", "MacBook Pro (16-inch, 2021)" },
|
||||
{ "MacBookPro17,1", "MacBook Pro (13-inch, M1, 2020)" },
|
||||
{ "MacBookPro16,3", "MacBook Pro (13-inch, 2020)" },
|
||||
{ "MacBookPro16,2", "MacBook Pro (13-inch, 2020)" },
|
||||
{ "MacBookPro16,4", "MacBook Pro (16-inch, 2019)" },
|
||||
{ "MacBookPro16,1", "MacBook Pro (16-inch, 2019)" },
|
||||
{ "MacBookPro15,4", "MacBook Pro (13-inch, 2019)" },
|
||||
{ "MacBookPro15,3", "MacBook Pro (15-inch, 2019)" },
|
||||
{ "MacBookPro15,2", "MacBook Pro (13-inch, 2018/2019)" },
|
||||
{ "MacBookPro15,1", "MacBook Pro (15-inch, 2018/2019)" },
|
||||
{ "MacBookPro14,3", "MacBook Pro (15-inch, 2017)" },
|
||||
{ "MacBookPro14,2", "MacBook Pro (13-inch, 2017)" },
|
||||
{ "MacBookPro14,1", "MacBook Pro (13-inch, 2017)" },
|
||||
{ "MacBookPro13,3", "MacBook Pro (15-inch, 2016)" },
|
||||
{ "MacBookPro13,2", "MacBook Pro (13-inch, 2016)" },
|
||||
{ "MacBookPro13,1", "MacBook Pro (13-inch, 2016)" },
|
||||
{ "MacBookPro12,1", "MacBook Pro (13-inch, 2015)" },
|
||||
{ "MacBookPro11,4", "MacBook Pro (15-inch, 2015)" },
|
||||
{ "MacBookPro11,5", "MacBook Pro (15-inch, 2015)" },
|
||||
{ "MacBookPro11,2", "MacBook Pro (15-inch, 2013/2014)" },
|
||||
{ "MacBookPro11,3", "MacBook Pro (15-inch, 2013/2014)" },
|
||||
{ "MacBookPro11,1", "MacBook Pro (13-inch, 2013/2014)" },
|
||||
{ "MacBookPro10,2", "MacBook Pro (13-inch, 2012/2013)" },
|
||||
{ "MacBookPro10,1", "MacBook Pro (15-inch, 2012/2013)" },
|
||||
{ "MacBookPro9,2", "MacBook Pro (13-inch, 2012)" },
|
||||
{ "MacBookPro9,1", "MacBook Pro (15-inch, 2012)" },
|
||||
{ "MacBookPro8,3", "MacBook Pro (17-inch, 2011)" },
|
||||
{ "MacBookPro8,2", "MacBook Pro (15-inch, 2011)" },
|
||||
{ "MacBookPro8,1", "MacBook Pro (13-inch, 2011)" },
|
||||
{ "MacBookPro7,1", "MacBook Pro (13-inch, 2010)" },
|
||||
{ "MacBookPro6,2", "MacBook Pro (15-inch, 2010)" },
|
||||
{ "MacBookPro6,1", "MacBook Pro (17-inch, 2010)" },
|
||||
{ "MacBookPro5,5", "MacBook Pro (13-inch, 2009)" },
|
||||
{ "MacBookPro5,3", "MacBook Pro (15-inch, 2009)" },
|
||||
{ "MacBookPro5,2", "MacBook Pro (17-inch, 2009)" },
|
||||
{ "MacBookPro5,1", "MacBook Pro (15-inch, 2008)" },
|
||||
{ "MacBookPro4,1", "MacBook Pro (17/15-inch, 2008)" },
|
||||
|
||||
// MacBook Air
|
||||
{ "MacBookAir10,1", "MacBook Air (M1, 2020)" },
|
||||
{ "MacBookAir9,1", "MacBook Air (13-inch, 2020)" },
|
||||
{ "MacBookAir8,2", "MacBook Air (13-inch, 2019)" },
|
||||
{ "MacBookAir8,1", "MacBook Air (13-inch, 2018)" },
|
||||
{ "MacBookAir7,2", "MacBook Air (13-inch, 2015/2017)" },
|
||||
{ "MacBookAir7,1", "MacBook Air (11-inch, 2015)" },
|
||||
{ "MacBookAir6,2", "MacBook Air (13-inch, 2013/2014)" },
|
||||
{ "MacBookAir6,1", "MacBook Air (11-inch, 2013/2014)" },
|
||||
{ "MacBookAir5,2", "MacBook Air (13-inch, 2012)" },
|
||||
{ "MacBookAir5,1", "MacBook Air (11-inch, 2012)" },
|
||||
{ "MacBookAir4,2", "MacBook Air (13-inch, 2011)" },
|
||||
{ "MacBookAir4,1", "MacBook Air (11-inch, 2011)" },
|
||||
{ "MacBookAir3,2", "MacBook Air (13-inch, 2010)" },
|
||||
{ "MacBookAir3,1", "MacBook Air (11-inch, 2010)" },
|
||||
{ "MacBookAir2,1", "MacBook Air (2009)" },
|
||||
|
||||
// Mac mini
|
||||
{ "Macmini9,1", "Mac mini (M1, 2020)" },
|
||||
{ "Macmini8,1", "Mac mini (2018)" },
|
||||
{ "Macmini7,1", "Mac mini (2014)" },
|
||||
{ "Macmini6,1", "Mac mini (2012)" },
|
||||
{ "Macmini6,2", "Mac mini (2012)" },
|
||||
{ "Macmini5,1", "Mac mini (2011)" },
|
||||
{ "Macmini5,2", "Mac mini (2011)" },
|
||||
{ "Macmini4,1", "Mac mini (2010)" },
|
||||
{ "Macmini3,1", "Mac mini (2009)" },
|
||||
|
||||
// MacBook
|
||||
{ "MacBook10,1", "MacBook (12-inch, 2017)" },
|
||||
{ "MacBook9,1", "MacBook (12-inch, 2016)" },
|
||||
{ "MacBook8,1", "MacBook (12-inch, 2015)" },
|
||||
{ "MacBook7,1", "MacBook (13-inch, 2010)" },
|
||||
{ "MacBook6,1", "MacBook (13-inch, 2009)" },
|
||||
{ "MacBook5,2", "MacBook (13-inch, 2009)" },
|
||||
|
||||
// Mac Pro
|
||||
{ "MacPro7,1", "Mac Pro (2019)" },
|
||||
{ "MacPro6,1", "Mac Pro (2013)" },
|
||||
{ "MacPro5,1", "Mac Pro (2010 - 2012)" },
|
||||
{ "MacPro4,1", "Mac Pro (2009)" },
|
||||
|
||||
// Mac (Generic)
|
||||
{ "Mac16,3", "iMac (24-inch, 2024)" },
|
||||
{ "Mac16,2", "iMac (24-inch, 2024)" },
|
||||
{ "Mac16,1", "MacBook Pro (14-inch, 2024)" },
|
||||
{ "Mac16,6", "MacBook Pro (14-inch, 2024)" },
|
||||
{ "Mac16,8", "MacBook Pro (14-inch, 2024)" },
|
||||
{ "Mac16,7", "MacBook Pro (16-inch, 2024)" },
|
||||
{ "Mac16,5", "MacBook Pro (16-inch, 2024)" },
|
||||
{ "Mac16,15", "Mac mini (2024)" },
|
||||
{ "Mac16,10", "Mac mini (2024)" },
|
||||
{ "Mac15,13", "MacBook Air (15-inch, M3, 2024)" },
|
||||
{ "Mac15,2", "MacBook Air (13-inch, M3, 2024)" },
|
||||
{ "Mac15,3", "MacBook Pro (14-inch, Nov 2023)" },
|
||||
{ "Mac15,4", "iMac (24-inch, 2023)" },
|
||||
{ "Mac15,5", "iMac (24-inch, 2023)" },
|
||||
{ "Mac15,6", "MacBook Pro (14-inch, Nov 2023)" },
|
||||
{ "Mac15,8", "MacBook Pro (14-inch, Nov 2023)" },
|
||||
{ "Mac15,10", "MacBook Pro (14-inch, Nov 2023)" },
|
||||
{ "Mac15,7", "MacBook Pro (16-inch, Nov 2023)" },
|
||||
{ "Mac15,9", "MacBook Pro (16-inch, Nov 2023)" },
|
||||
{ "Mac15,11", "MacBook Pro (16-inch, Nov 2023)" },
|
||||
{ "Mac14,15", "MacBook Air (15-inch, M2, 2023)" },
|
||||
{ "Mac14,14", "Mac Studio (M2 Ultra, 2023)" },
|
||||
{ "Mac14,13", "Mac Studio (M2 Max, 2023)" },
|
||||
{ "Mac14,8", "Mac Pro (2023)" },
|
||||
{ "Mac14,6", "MacBook Pro (16-inch, 2023)" },
|
||||
{ "Mac14,10", "MacBook Pro (16-inch, 2023)" },
|
||||
{ "Mac14,5", "MacBook Pro (14-inch, 2023)" },
|
||||
{ "Mac14,9", "MacBook Pro (14-inch, 2023)" },
|
||||
{ "Mac14,3", "Mac mini (M2, 2023)" },
|
||||
{ "Mac14,12", "Mac mini (M2, 2023)" },
|
||||
{ "Mac14,7", "MacBook Pro (13-inch, M2, 2022)" },
|
||||
{ "Mac14,2", "MacBook Air (M2, 2022)" },
|
||||
{ "Mac13,1", "Mac Studio (M1 Max, 2022)" },
|
||||
{ "Mac13,2", "Mac Studio (M1 Ultra, 2022)" },
|
||||
|
||||
// iMac
|
||||
{ "iMac21,1", "iMac (24-inch, M1, 2021)" },
|
||||
{ "iMac21,2", "iMac (24-inch, M1, 2021)" },
|
||||
{ "iMac20,1", "iMac (27-inch, 2020)" },
|
||||
{ "iMac20,2", "iMac (27-inch, 2020)" },
|
||||
{ "iMac19,1", "iMac (27-inch, 2019)" },
|
||||
{ "iMac19,2", "iMac (21.5-inch, 2019)" },
|
||||
{ "iMacPro1,1", "iMac Pro (2017)" },
|
||||
{ "iMac18,3", "iMac (27-inch, 2017)" },
|
||||
{ "iMac18,2", "iMac (21.5-inch, 2017)" },
|
||||
{ "iMac18,1", "iMac (21.5-inch, 2017)" },
|
||||
{ "iMac17,1", "iMac (27-inch, 2015)" },
|
||||
{ "iMac16,2", "iMac (21.5-inch, 2015)" },
|
||||
{ "iMac16,1", "iMac (21.5-inch, 2015)" },
|
||||
{ "iMac15,1", "iMac (27-inch, 2014/2015)" },
|
||||
{ "iMac14,4", "iMac (21.5-inch, 2014)" },
|
||||
{ "iMac14,2", "iMac (27-inch, 2013)" },
|
||||
{ "iMac14,1", "iMac (21.5-inch, 2013)" },
|
||||
{ "iMac13,2", "iMac (27-inch, 2012)" },
|
||||
{ "iMac13,1", "iMac (21.5-inch, 2012)" },
|
||||
{ "iMac12,2", "iMac (27-inch, 2011)" },
|
||||
{ "iMac12,1", "iMac (21.5-inch, 2011)" },
|
||||
{ "iMac11,3", "iMac (27-inch, 2010)" },
|
||||
{ "iMac11,2", "iMac (21.5-inch, 2010)" },
|
||||
{ "iMac10,1", "iMac (27/21.5-inch, 2009)" },
|
||||
{ "iMac9,1", "iMac (24/20-inch, 2009)" },
|
||||
};
|
||||
|
||||
return modelNameByHwModel[hwModel.data()];
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,27 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include <expected>
|
||||
#include <string>
|
||||
|
||||
#include "../../util/macros.h"
|
||||
|
||||
#ifdef __OBJC__
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface Bridge : NSObject
|
||||
+ (void)fetchCurrentPlayingMetadata:(void (^)(std::expected<NSDictionary*, const char*>))completion;
|
||||
+ (std::expected<string, string>)macOSVersion;
|
||||
@end
|
||||
|
||||
#else
|
||||
|
||||
extern "C++" {
|
||||
fn GetCurrentPlayingInfo() -> std::expected<std::string, NowPlayingError>;
|
||||
fn GetMacOSVersion() -> std::expected<std::string, const char*>;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
|
@ -1,121 +0,0 @@
|
|||
#ifdef __APPLE__
|
||||
|
||||
#import <dispatch/dispatch.h>
|
||||
#include <expected>
|
||||
#import <objc/runtime.h>
|
||||
#include <string>
|
||||
|
||||
#import "bridge.h"
|
||||
|
||||
using MRMediaRemoteGetNowPlayingInfoFunction =
|
||||
void (*)(dispatch_queue_t queue, void (^handler)(NSDictionary* information));
|
||||
|
||||
@implementation Bridge
|
||||
+ (void)fetchCurrentPlayingMetadata:(void (^)(std::expected<NSDictionary*, const char*>))completion {
|
||||
CFURLRef ref = CFURLCreateWithFileSystemPath(
|
||||
kCFAllocatorDefault, CFSTR("/System/Library/PrivateFrameworks/MediaRemote.framework"), kCFURLPOSIXPathStyle, false
|
||||
);
|
||||
|
||||
if (!ref) {
|
||||
completion(std::unexpected("Failed to create CFURL for MediaRemote framework"));
|
||||
return;
|
||||
}
|
||||
|
||||
CFBundleRef bundle = CFBundleCreate(kCFAllocatorDefault, ref);
|
||||
CFRelease(ref);
|
||||
|
||||
if (!bundle) {
|
||||
completion(std::unexpected("Failed to create bundle for MediaRemote framework"));
|
||||
return;
|
||||
}
|
||||
|
||||
auto mrMediaRemoteGetNowPlayingInfo = std::bit_cast<MRMediaRemoteGetNowPlayingInfoFunction>(
|
||||
CFBundleGetFunctionPointerForName(bundle, CFSTR("MRMediaRemoteGetNowPlayingInfo"))
|
||||
);
|
||||
|
||||
if (!mrMediaRemoteGetNowPlayingInfo) {
|
||||
CFRelease(bundle);
|
||||
completion(std::unexpected("Failed to get MRMediaRemoteGetNowPlayingInfo function pointer"));
|
||||
return;
|
||||
}
|
||||
|
||||
mrMediaRemoteGetNowPlayingInfo(
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
|
||||
^(NSDictionary* information) {
|
||||
NSDictionary* nowPlayingInfo = information; // Immutable, no copy needed
|
||||
CFRelease(bundle);
|
||||
completion(
|
||||
nowPlayingInfo ? std::expected<NSDictionary*, const char*>(nowPlayingInfo)
|
||||
: std::unexpected("No now playing information")
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
+ (std::expected<string, string>)macOSVersion {
|
||||
NSProcessInfo* processInfo = [NSProcessInfo processInfo];
|
||||
NSOperatingSystemVersion osVersion = [processInfo operatingSystemVersion];
|
||||
|
||||
NSString* versionNumber = nil;
|
||||
if (osVersion.patchVersion == 0) {
|
||||
versionNumber = [NSString stringWithFormat:@"%ld.%ld", osVersion.majorVersion, osVersion.minorVersion];
|
||||
} else {
|
||||
versionNumber = [NSString
|
||||
stringWithFormat:@"%ld.%ld.%ld", osVersion.majorVersion, osVersion.minorVersion, osVersion.patchVersion];
|
||||
}
|
||||
|
||||
NSDictionary* versionNames =
|
||||
@{ @11 : @"Big Sur", @12 : @"Monterey", @13 : @"Ventura", @14 : @"Sonoma", @15 : @"Sequoia" };
|
||||
|
||||
NSNumber* majorVersion = @(osVersion.majorVersion);
|
||||
NSString* versionName = versionNames[majorVersion] ? versionNames[majorVersion] : @"Unknown";
|
||||
|
||||
NSString* fullVersion = [NSString stringWithFormat:@"macOS %@ %@", versionNumber, versionName];
|
||||
return std::string([fullVersion UTF8String]);
|
||||
}
|
||||
@end
|
||||
|
||||
extern "C++" {
|
||||
// NOLINTBEGIN(misc-use-internal-linkage)
|
||||
fn GetCurrentPlayingInfo() -> std::expected<std::string, NowPlayingError> {
|
||||
__block std::expected<std::string, NowPlayingError> result;
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
|
||||
[Bridge fetchCurrentPlayingMetadata:^(std::expected<NSDictionary*, const char*> metadataResult) {
|
||||
if (!metadataResult) {
|
||||
result = std::unexpected(NowPlayingError { metadataResult.error() });
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
return;
|
||||
}
|
||||
|
||||
NSDictionary* metadata = *metadataResult;
|
||||
if (!metadata) {
|
||||
result = std::unexpected(NowPlayingError { NowPlayingCode::NoPlayers });
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
return;
|
||||
}
|
||||
|
||||
NSString* title = [metadata objectForKey:@"kMRMediaRemoteNowPlayingInfoTitle"];
|
||||
NSString* artist = [metadata objectForKey:@"kMRMediaRemoteNowPlayingInfoArtist"];
|
||||
|
||||
if (!title && !artist)
|
||||
result = std::unexpected("No metadata");
|
||||
else if (!title)
|
||||
result = std::string([artist UTF8String]);
|
||||
else if (!artist)
|
||||
result = std::string([title UTF8String]);
|
||||
else
|
||||
result = std::string([[NSString stringWithFormat:@"%@ - %@", title, artist] UTF8String]);
|
||||
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
|
||||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
||||
return result;
|
||||
}
|
||||
|
||||
fn GetMacOSVersion() -> std::expected<string, string> { return [Bridge macOSVersion]; }
|
||||
// NOLINTEND(misc-use-internal-linkage)
|
||||
}
|
||||
|
||||
#endif
|
53
src/os/os.h
53
src/os/os.h
|
@ -1,53 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <expected>
|
||||
|
||||
#include "../util/macros.h"
|
||||
#include "../util/types.h"
|
||||
|
||||
using std::optional, std::expected;
|
||||
|
||||
/**
|
||||
* @brief Get the amount of installed RAM in bytes.
|
||||
*/
|
||||
fn GetMemInfo() -> expected<u64, string>;
|
||||
|
||||
/**
|
||||
* @brief Get the currently playing song metadata.
|
||||
*/
|
||||
fn GetNowPlaying() -> expected<string, NowPlayingError>;
|
||||
|
||||
/**
|
||||
* @brief Get the OS version.
|
||||
*/
|
||||
fn GetOSVersion() -> expected<string, string>;
|
||||
|
||||
/**
|
||||
* @brief Get the current desktop environment.
|
||||
*/
|
||||
fn GetDesktopEnvironment() -> optional<string>;
|
||||
|
||||
/**
|
||||
* @brief Get the current window manager.
|
||||
*/
|
||||
fn GetWindowManager() -> string;
|
||||
|
||||
/**
|
||||
* @brief Get the current shell.
|
||||
*/
|
||||
fn GetShell() -> string;
|
||||
|
||||
/**
|
||||
* @brief Get the product family
|
||||
*/
|
||||
fn GetHost() -> string;
|
||||
|
||||
/**
|
||||
* @brief Get the kernel version.
|
||||
*/
|
||||
fn GetKernelVersion() -> string;
|
||||
|
||||
/**
|
||||
* @brief Get the number of installed packages.
|
||||
*/
|
||||
fn GetPackageCount() -> u64;
|
|
@ -1,240 +0,0 @@
|
|||
#ifdef __WIN32__
|
||||
|
||||
#include <iostream>
|
||||
#include <windows.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Media.Control.h>
|
||||
#include <winrt/base.h>
|
||||
#include <winrt/impl/Windows.Media.Control.2.h>
|
||||
|
||||
// clang-format off
|
||||
#include <dwmapi.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
// clang-format on
|
||||
|
||||
#include "os.h"
|
||||
|
||||
using RtlGetVersionPtr = NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW);
|
||||
|
||||
namespace {
|
||||
fn GetRegistryValue(const HKEY& hKey, const string& subKey, const string& valueName) -> string {
|
||||
HKEY key = nullptr;
|
||||
if (RegOpenKeyExA(hKey, subKey.c_str(), 0, KEY_READ, &key) != ERROR_SUCCESS)
|
||||
return "";
|
||||
|
||||
DWORD dataSize = 0;
|
||||
if (RegQueryValueExA(key, valueName.c_str(), nullptr, nullptr, nullptr, &dataSize) != ERROR_SUCCESS) {
|
||||
RegCloseKey(key);
|
||||
return "";
|
||||
}
|
||||
|
||||
string value(dataSize, '\0');
|
||||
if (RegQueryValueExA(key, valueName.c_str(), nullptr, nullptr, std::bit_cast<LPBYTE>(value.data()), &dataSize) !=
|
||||
ERROR_SUCCESS) {
|
||||
RegCloseKey(key);
|
||||
return "";
|
||||
}
|
||||
|
||||
RegCloseKey(key);
|
||||
// Remove null terminator if present
|
||||
if (!value.empty() && value.back() == '\0')
|
||||
value.pop_back();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// Add these function implementations
|
||||
fn GetRunningProcesses() -> std::vector<string> {
|
||||
std::vector<string> processes;
|
||||
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (hSnapshot == INVALID_HANDLE_VALUE)
|
||||
return processes;
|
||||
|
||||
PROCESSENTRY32 pe32;
|
||||
pe32.dwSize = sizeof(PROCESSENTRY32);
|
||||
|
||||
if (!Process32First(hSnapshot, &pe32)) {
|
||||
CloseHandle(hSnapshot);
|
||||
return processes;
|
||||
}
|
||||
|
||||
while (Process32Next(hSnapshot, &pe32)) { processes.emplace_back(pe32.szExeFile); }
|
||||
|
||||
CloseHandle(hSnapshot);
|
||||
return processes;
|
||||
}
|
||||
|
||||
fn IsProcessRunning(const std::vector<string>& processes, const string& name) -> bool {
|
||||
return std::ranges::any_of(processes, [&name](const string& proc) {
|
||||
return _stricmp(proc.c_str(), name.c_str()) == 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn GetMemInfo() -> expected<u64, string> {
|
||||
u64 mem = 0;
|
||||
|
||||
if (!GetPhysicallyInstalledSystemMemory(&mem))
|
||||
return std::unexpected("Failed to get physical system memory.");
|
||||
|
||||
return mem * 1024;
|
||||
}
|
||||
|
||||
fn GetNowPlaying() -> expected<string, NowPlayingError> {
|
||||
using namespace winrt::Windows::Media::Control;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
|
||||
using MediaProperties = GlobalSystemMediaTransportControlsSessionMediaProperties;
|
||||
using Session = GlobalSystemMediaTransportControlsSession;
|
||||
using SessionManager = GlobalSystemMediaTransportControlsSessionManager;
|
||||
|
||||
try {
|
||||
// Request the session manager asynchronously
|
||||
const IAsyncOperation<SessionManager> sessionManagerOp = SessionManager::RequestAsync();
|
||||
const SessionManager sessionManager = sessionManagerOp.get();
|
||||
|
||||
if (const Session currentSession = sessionManager.GetCurrentSession()) {
|
||||
// Try to get the media properties asynchronously
|
||||
const MediaProperties mediaProperties = currentSession.TryGetMediaPropertiesAsync().get();
|
||||
|
||||
// Convert the hstring title to string
|
||||
return to_string(mediaProperties.Title());
|
||||
}
|
||||
|
||||
// If we reach this point, there is no current session
|
||||
return std::unexpected(NowPlayingError { NowPlayingCode::NoActivePlayer });
|
||||
} catch (const winrt::hresult_error& e) { return std::unexpected(NowPlayingError { e }); }
|
||||
}
|
||||
|
||||
fn GetOSVersion() -> expected<string, string> {
|
||||
string productName =
|
||||
GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "ProductName");
|
||||
|
||||
const string displayVersion =
|
||||
GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "DisplayVersion");
|
||||
|
||||
const string releaseId =
|
||||
GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "ReleaseId");
|
||||
|
||||
// Check for Windows 11
|
||||
if (const i32 buildNumber = stoi(
|
||||
GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "CurrentBuildNumber")
|
||||
);
|
||||
buildNumber >= 22000 && productName.find("Windows 10") != string::npos)
|
||||
productName.replace(productName.find("Windows 10"), 10, "Windows 11");
|
||||
|
||||
if (!productName.empty()) {
|
||||
string result = productName;
|
||||
|
||||
if (!displayVersion.empty())
|
||||
result += " " + displayVersion;
|
||||
else if (!releaseId.empty())
|
||||
result += " " + releaseId;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return std::unexpected("Failed to get OS version.");
|
||||
}
|
||||
|
||||
fn GetHost() -> string {
|
||||
string hostName = GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SYSTEM\HardwareConfig\Current)", "SystemFamily");
|
||||
|
||||
if (hostName.empty())
|
||||
hostName = GetRegistryValue(
|
||||
HKEY_LOCAL_MACHINE, R"(SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName)", "ComputerName"
|
||||
);
|
||||
|
||||
return hostName;
|
||||
}
|
||||
|
||||
fn GetKernelVersion() -> string {
|
||||
std::stringstream versionStream;
|
||||
HMODULE ntdllHandle = GetModuleHandleW(L"ntdll.dll");
|
||||
|
||||
if (ntdllHandle) {
|
||||
auto rtlGetVersion = std::bit_cast<RtlGetVersionPtr>(GetProcAddress(ntdllHandle, "RtlGetVersion"));
|
||||
if (rtlGetVersion) {
|
||||
RTL_OSVERSIONINFOW osInfo = {};
|
||||
osInfo.dwOSVersionInfoSize = sizeof(osInfo);
|
||||
if (rtlGetVersion(&osInfo) == 0)
|
||||
versionStream << osInfo.dwMajorVersion << "." << osInfo.dwMinorVersion << "." << osInfo.dwBuildNumber << "."
|
||||
<< osInfo.dwPlatformId;
|
||||
}
|
||||
}
|
||||
|
||||
return versionStream.str();
|
||||
}
|
||||
|
||||
fn GetWindowManager() -> string {
|
||||
const std::vector<string> processes = GetRunningProcesses();
|
||||
string windowManager;
|
||||
|
||||
// Check for third-party WMs
|
||||
if (IsProcessRunning(processes, "glazewm.exe")) {
|
||||
windowManager = "GlazeWM";
|
||||
} else if (IsProcessRunning(processes, "fancywm.exe")) {
|
||||
windowManager = "FancyWM";
|
||||
} else if (IsProcessRunning(processes, "komorebi.exe") || IsProcessRunning(processes, "komorebic.exe")) {
|
||||
windowManager = "Komorebi";
|
||||
}
|
||||
|
||||
// Fallback to DWM detection
|
||||
if (windowManager.empty()) {
|
||||
BOOL compositionEnabled = FALSE;
|
||||
if (SUCCEEDED(DwmIsCompositionEnabled(&compositionEnabled))) {
|
||||
windowManager = compositionEnabled ? "Desktop Window Manager" : "Windows Manager (Basic)";
|
||||
} else {
|
||||
windowManager = "Windows Manager";
|
||||
}
|
||||
}
|
||||
|
||||
return windowManager;
|
||||
}
|
||||
|
||||
fn GetDesktopEnvironment() -> optional<string> {
|
||||
// Get version information from registry
|
||||
const string buildStr =
|
||||
GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "CurrentBuildNumber");
|
||||
|
||||
if (buildStr.empty())
|
||||
return std::nullopt;
|
||||
|
||||
try {
|
||||
const i32 build = stoi(buildStr);
|
||||
|
||||
// Windows 11+ (Fluent)
|
||||
if (build >= 22000)
|
||||
return "Fluent (Windows 11)";
|
||||
|
||||
// Windows 10 Fluent Era
|
||||
if (build >= 15063)
|
||||
return "Fluent (Windows 10)";
|
||||
|
||||
// Windows 8.1/10 Metro Era
|
||||
if (build >= 9200) { // Windows 8+
|
||||
// Distinguish between Windows 8 and 10
|
||||
const string productName =
|
||||
GetRegistryValue(HKEY_LOCAL_MACHINE, R"(SOFTWARE\Microsoft\Windows NT\CurrentVersion)", "ProductName");
|
||||
|
||||
if (productName.find("Windows 10") != string::npos)
|
||||
return "Metro (Windows 10)";
|
||||
|
||||
if (build >= 9600)
|
||||
return "Metro (Windows 8.1)";
|
||||
return "Metro (Windows 8)";
|
||||
}
|
||||
|
||||
// Windows 7 Aero
|
||||
if (build >= 7600)
|
||||
return "Aero (Windows 7)";
|
||||
|
||||
// Older versions
|
||||
return "Classic";
|
||||
} catch (...) { return std::nullopt; }
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,71 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
// probably stupid but it fixes the issue with windows.h defining ERROR
|
||||
#undef ERROR
|
||||
|
||||
#include <filesystem>
|
||||
#include <fmt/chrono.h>
|
||||
#include <fmt/color.h>
|
||||
#include <fmt/format.h>
|
||||
#include <source_location>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#define fn auto // Rust-style function shorthand
|
||||
|
||||
namespace log_colors {
|
||||
using fmt::terminal_color;
|
||||
constexpr fmt::terminal_color debug = terminal_color::cyan, info = terminal_color::green,
|
||||
warn = terminal_color::yellow, error = terminal_color::red,
|
||||
timestamp = terminal_color::bright_white, file_info = terminal_color::bright_white;
|
||||
}
|
||||
|
||||
enum class LogLevel : u8 { DEBUG, INFO, WARN, ERROR };
|
||||
|
||||
template <typename... Args>
|
||||
void LogImpl(LogLevel level, const std::source_location& loc, fmt::format_string<Args...> fmt, Args&&... args) {
|
||||
const time_t now = std::time(nullptr);
|
||||
const auto [color, levelStr] = [&] {
|
||||
switch (level) {
|
||||
case LogLevel::DEBUG:
|
||||
return std::make_pair(log_colors::debug, "DEBUG");
|
||||
case LogLevel::INFO:
|
||||
return std::make_pair(log_colors::info, "INFO ");
|
||||
case LogLevel::WARN:
|
||||
return std::make_pair(log_colors::warn, "WARN ");
|
||||
case LogLevel::ERROR:
|
||||
return std::make_pair(log_colors::error, "ERROR");
|
||||
}
|
||||
}();
|
||||
|
||||
const string filename = std::filesystem::path(loc.file_name()).lexically_normal().string();
|
||||
const struct tm time = *std::localtime(&now);
|
||||
|
||||
// Timestamp and level
|
||||
fmt::print(fg(log_colors::timestamp), "[{:%H:%M:%S}] ", time);
|
||||
fmt::print(fmt::emphasis::bold | fg(color), "{} ", levelStr);
|
||||
|
||||
// Message
|
||||
fmt::print(fmt, std::forward<Args>(args)...);
|
||||
|
||||
// File info (debug builds only)
|
||||
#ifndef NDEBUG
|
||||
fmt::print(fg(log_colors::file_info), "\n{:>14} ", "╰──");
|
||||
fmt::print(fmt::emphasis::italic | fg(log_colors::file_info), "{}:{}", filename, loc.line());
|
||||
#endif
|
||||
|
||||
fmt::print("\n");
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-macros"
|
||||
#ifdef NDEBUG
|
||||
#define DEBUG_LOG(...) static_cast<void>(0)
|
||||
#else
|
||||
#define DEBUG_LOG(...) LogImpl(LogLevel::DEBUG, std::source_location::current(), __VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#define INFO_LOG(...) LogImpl(LogLevel::INFO, std::source_location::current(), __VA_ARGS__)
|
||||
#define WARN_LOG(...) LogImpl(LogLevel::WARN, std::source_location::current(), __VA_ARGS__)
|
||||
#define ERROR_LOG(...) LogImpl(LogLevel::ERROR, std::source_location::current(), __VA_ARGS__)
|
||||
#pragma clang diagnostic pop
|
176
src/util/types.h
176
src/util/types.h
|
@ -1,176 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#ifdef __WIN32__
|
||||
#include <winrt/base.h>
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <variant>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @typedef u8
|
||||
* @brief Represents an 8-bit unsigned integer.
|
||||
*
|
||||
* This type alias is used for 8-bit unsigned integers, ranging from 0 to 255.
|
||||
* It is based on the std::uint8_t type.
|
||||
*/
|
||||
using u8 = std::uint8_t;
|
||||
|
||||
/**
|
||||
* @typedef u16
|
||||
* @brief Represents a 16-bit unsigned integer.
|
||||
*
|
||||
* This type alias is used for 16-bit unsigned integers, ranging from 0 to 65,535.
|
||||
* It is based on the std::uint16_t type.
|
||||
*/
|
||||
using u16 = std::uint16_t;
|
||||
|
||||
/**
|
||||
* @typedef u32
|
||||
* @brief Represents a 32-bit unsigned integer.
|
||||
*
|
||||
* This type alias is used for 32-bit unsigned integers, ranging from 0 to 4,294,967,295.
|
||||
* It is based on the std::uint32_t type.
|
||||
*/
|
||||
using u32 = std::uint32_t;
|
||||
|
||||
/**
|
||||
* @typedef u64
|
||||
* @brief Represents a 64-bit unsigned integer.
|
||||
*
|
||||
* This type alias is used for 64-bit unsigned integers, ranging from 0 to
|
||||
* 18,446,744,073,709,551,615. It is based on the std::uint64_t type.
|
||||
*/
|
||||
using u64 = std::uint64_t;
|
||||
|
||||
// Type Aliases for Signed Integers
|
||||
|
||||
/**
|
||||
* @typedef i8
|
||||
* @brief Represents an 8-bit signed integer.
|
||||
*
|
||||
* This type alias is used for 8-bit signed integers, ranging from -128 to 127.
|
||||
* It is based on the std::int8_t type.
|
||||
*/
|
||||
using i8 = std::int8_t;
|
||||
|
||||
/**
|
||||
* @typedef i16
|
||||
* @brief Represents a 16-bit signed integer.
|
||||
*
|
||||
* This type alias is used for 16-bit signed integers, ranging from -32,768 to 32,767.
|
||||
* It is based on the std::int16_t type.
|
||||
*/
|
||||
using i16 = std::int16_t;
|
||||
|
||||
/**
|
||||
* @typedef i32
|
||||
* @brief Represents a 32-bit signed integer.
|
||||
*
|
||||
* This type alias is used for 32-bit signed integers, ranging from -2,147,483,648 to 2,147,483,647.
|
||||
* It is based on the std::int32_t type.
|
||||
*/
|
||||
using i32 = std::int32_t;
|
||||
|
||||
/**
|
||||
* @typedef i64
|
||||
* @brief Represents a 64-bit signed integer.
|
||||
*
|
||||
* This type alias is used for 64-bit signed integers, ranging from -9,223,372,036,854,775,808 to
|
||||
* 9,223,372,036,854,775,807. It is based on the std::int64_t type.
|
||||
*/
|
||||
using i64 = std::int64_t;
|
||||
|
||||
// Type Aliases for Floating-Point Numbers
|
||||
|
||||
/**
|
||||
* @typedef f32
|
||||
* @brief Represents a 32-bit floating-point number.
|
||||
*
|
||||
* This type alias is used for 32-bit floating-point numbers, which follow the IEEE 754 standard.
|
||||
* It is based on the float type.
|
||||
*/
|
||||
using f32 = float;
|
||||
|
||||
/**
|
||||
* @typedef f64
|
||||
* @brief Represents a 64-bit floating-point number.
|
||||
*
|
||||
* This type alias is used for 64-bit floating-point numbers, which follow the IEEE 754 standard.
|
||||
* It is based on the double type.
|
||||
*/
|
||||
using f64 = double;
|
||||
|
||||
// Type Aliases for Size Types
|
||||
|
||||
/**
|
||||
* @typedef usize
|
||||
* @brief Represents an unsigned size type.
|
||||
*
|
||||
* This type alias is used for representing the size of objects in bytes.
|
||||
* It is based on the std::size_t type, which is the result type of the sizeof operator.
|
||||
*/
|
||||
using usize = std::size_t;
|
||||
|
||||
/**
|
||||
* @typedef isize
|
||||
* @brief Represents a signed size type.
|
||||
*
|
||||
* This type alias is used for representing pointer differences.
|
||||
* It is based on the std::ptrdiff_t type, which is the signed integer type returned when
|
||||
* subtracting two pointers.
|
||||
*/
|
||||
using isize = std::ptrdiff_t;
|
||||
|
||||
/**
|
||||
* @typedef string
|
||||
* @brief Represents a string.
|
||||
*/
|
||||
using string = std::string;
|
||||
|
||||
/**
|
||||
* @enum NowPlayingCode
|
||||
* @brief Represents error codes for Now Playing functionality.
|
||||
*/
|
||||
enum class NowPlayingCode : u8 {
|
||||
NoPlayers,
|
||||
NoActivePlayer,
|
||||
};
|
||||
|
||||
// Platform-specific error details
|
||||
#ifdef __linux__
|
||||
/**
|
||||
* @typedef LinuxError
|
||||
* @brief Represents a Linux-specific error.
|
||||
*/
|
||||
using LinuxError = std::string;
|
||||
#elif defined(__APPLE__)
|
||||
/**
|
||||
* @typedef MacError
|
||||
* @brief Represents a macOS-specific error.
|
||||
*/
|
||||
using MacError = std::string;
|
||||
#elif defined(__WIN32__)
|
||||
/**
|
||||
* @typedef WindowsError
|
||||
* @brief Represents a Windows-specific error.
|
||||
*/
|
||||
using WindowsError = winrt::hresult_error;
|
||||
#endif
|
||||
|
||||
// Unified error type
|
||||
using NowPlayingError = std::variant<
|
||||
NowPlayingCode,
|
||||
#ifdef __linux__
|
||||
LinuxError
|
||||
#elif defined(__APPLE__)
|
||||
MacError
|
||||
#elif defined(__WIN32__)
|
||||
WindowsError
|
||||
#endif
|
||||
>;
|
|
@ -1,13 +0,0 @@
|
|||
[wrap-file]
|
||||
directory = curl-8.10.1
|
||||
source_url = https://github.com/curl/curl/releases/download/curl-8_10_1/curl-8.10.1.tar.xz
|
||||
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/curl_8.10.1-1/curl-8.10.1.tar.xz
|
||||
source_filename = curl-8.10.1.tar.xz
|
||||
source_hash = 73a4b0e99596a09fa5924a4fb7e4b995a85fda0d18a2c02ab9cf134bebce04ee
|
||||
patch_filename = curl_8.10.1-1_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/curl_8.10.1-1/get_patch
|
||||
patch_hash = 707c28f35fc9b0e8d68c0c2800712007612f922a31da9637ce706a2159f3ddd8
|
||||
wrapdb_version = 8.10.1-1
|
||||
|
||||
[provide]
|
||||
dependency_names = libcurl
|
|
@ -1,13 +0,0 @@
|
|||
[wrap-file]
|
||||
directory = fmt-11.1.1
|
||||
source_url = https://github.com/fmtlib/fmt/archive/11.1.1.tar.gz
|
||||
source_filename = fmt-11.1.1.tar.gz
|
||||
source_hash = 482eed9efbc98388dbaee5cb5f368be5eca4893456bb358c18b7ff71f835ae43
|
||||
patch_filename = fmt_11.1.1-2_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/fmt_11.1.1-2/get_patch
|
||||
patch_hash = eee2e90d5d43061a0a1f0b9f8eb188c5b8820ef3e1b15e4b8a4eb791ef82b325
|
||||
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_11.1.1-2/fmt-11.1.1.tar.gz
|
||||
wrapdb_version = 11.1.1-2
|
||||
|
||||
[provide]
|
||||
fmt = fmt_dep
|
|
@ -1,15 +0,0 @@
|
|||
[wrap-file]
|
||||
directory = FTXUI-5.0.0
|
||||
source_url = https://github.com/ArthurSonzogni/FTXUI/archive/refs/tags/v5.0.0.tar.gz
|
||||
source_filename = FTXUI-5.0.0.tar.gz
|
||||
source_hash = a2991cb222c944aee14397965d9f6b050245da849d8c5da7c72d112de2786b5b
|
||||
patch_filename = ftxui_5.0.0-1_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/ftxui_5.0.0-1/get_patch
|
||||
patch_hash = 21c654e82739b90b95bd98c1d321264608d37c50d29fbcc3487f790fd5412909
|
||||
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/ftxui_5.0.0-1/FTXUI-5.0.0.tar.gz
|
||||
wrapdb_version = 5.0.0-1
|
||||
|
||||
[provide]
|
||||
ftxui-screen = screen_dep
|
||||
ftxui-dom = dom_dep
|
||||
ftxui-component = component_dep
|
|
@ -1,15 +0,0 @@
|
|||
[wrap-file]
|
||||
directory = openssl-3.0.8
|
||||
source_url = https://www.openssl.org/source/openssl-3.0.8.tar.gz
|
||||
source_filename = openssl-3.0.8.tar.gz
|
||||
source_hash = 6c13d2bf38fdf31eac3ce2a347073673f5d63263398f1f69d0df4a41253e4b3e
|
||||
patch_filename = openssl_3.0.8-3_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/openssl_3.0.8-3/get_patch
|
||||
patch_hash = 300da189e106942347d61a4a4295aa2edbcf06184f8d13b4cee0bed9fb936963
|
||||
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/openssl_3.0.8-3/openssl-3.0.8.tar.gz
|
||||
wrapdb_version = 3.0.8-3
|
||||
|
||||
[provide]
|
||||
libcrypto = libcrypto_dep
|
||||
libssl = libssl_dep
|
||||
openssl = openssl_dep
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 33eac03e0d2e87d778e7a8d7ad1f0e1c40b581a5
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 48ea775531523ac290abec91107c628f00156543
|
|
@ -1,2 +0,0 @@
|
|||
[wrap-redirect]
|
||||
filename = SQLiteCpp-3.3.2/subprojects/sqlite3.wrap
|
|
@ -1,10 +0,0 @@
|
|||
[wrap-file]
|
||||
directory = SQLiteCpp-3.3.2
|
||||
source_url = https://github.com/SRombauts/SQLiteCpp/archive/refs/tags/3.3.2.zip
|
||||
source_filename = sqlitecpp-3.3.2.zip
|
||||
source_hash = 1f41ef7322da573fdfca95655bd1329282638b4d9d3dc16a48f4bad16008eda8
|
||||
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/sqlitecpp_3.3.2-1/sqlitecpp-3.3.2.zip
|
||||
wrapdb_version = 3.3.2-1
|
||||
|
||||
[provide]
|
||||
sqlitecpp = sqlitecpp_dep
|
|
@ -1,10 +0,0 @@
|
|||
[wrap-file]
|
||||
directory = tomlplusplus-3.4.0
|
||||
source_url = https://github.com/marzer/tomlplusplus/archive/v3.4.0.tar.gz
|
||||
source_filename = tomlplusplus-3.4.0.tar.gz
|
||||
source_hash = 8517f65938a4faae9ccf8ebb36631a38c1cadfb5efa85d9a72e15b9e97d25155
|
||||
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/tomlplusplus_3.4.0-1/tomlplusplus-3.4.0.tar.gz
|
||||
wrapdb_version = 3.4.0-1
|
||||
|
||||
[provide]
|
||||
dependency_names = tomlplusplus
|
23
util.h
Normal file
23
util.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// Unsigned integers
|
||||
using u8 = std::uint8_t;
|
||||
using u16 = std::uint16_t;
|
||||
using u32 = std::uint32_t;
|
||||
using u64 = std::uint64_t;
|
||||
|
||||
// Signed integers
|
||||
using i8 = std::int8_t;
|
||||
using i16 = std::int16_t;
|
||||
using i32 = std::int32_t;
|
||||
using i64 = std::int64_t;
|
||||
|
||||
// Floating-points
|
||||
using f32 = float;
|
||||
using f64 = double;
|
||||
|
||||
// Size types
|
||||
using usize = std::size_t;
|
||||
using isize = std::ptrdiff_t;
|
12
vcpkg.json
Normal file
12
vcpkg.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name" : "draconispp",
|
||||
"version-string" : "1.0.0",
|
||||
"builtin-baseline" : "14b91796a68c87bc8d5cb35911b39287ccb7bd95",
|
||||
"dependencies" : [ {
|
||||
"name" : "fmt",
|
||||
"version>=" : "10.2.1#2"
|
||||
}, {
|
||||
"name" : "cppwinrt",
|
||||
"version>=" : "2.0.240111.5"
|
||||
} ]
|
||||
}
|
Loading…
Reference in a new issue