Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b6923996b | |||
| 6673576e64 | |||
| 773a3c0d67 | |||
| f07196e611 | |||
| c814445673 | |||
| 090e529967 | |||
| 4694beb196 | |||
| 18f67bd670 | |||
| 96b19ab88a | |||
| dead2d1d5f | |||
| f516967a21 | |||
| f6c295101b | |||
| 47eae4d492 | |||
| d931e3e04a | |||
| 3515e5cca7 | |||
| b97e8c71ec | |||
| dac5577879 | |||
| 3f3e57db9a | |||
| 0814424360 | |||
| c3c0934f1b | |||
| f014003cf2 | |||
| 6ffae7d222 | |||
| 1e566473ec | |||
| 784e1f93df | |||
| f01ac12445 | |||
| b82e3deb17 |
@@ -1,8 +0,0 @@
|
||||
build
|
||||
cmake-build-*
|
||||
.DS_Store
|
||||
|
||||
*.swp
|
||||
*.kdev4
|
||||
|
||||
.idea
|
||||
@@ -1,12 +0,0 @@
|
||||
[submodule "moonlight-common-c"]
|
||||
path = moonlight-common-c
|
||||
url = https://github.com/moonlight-stream/moonlight-common-c.git
|
||||
[submodule "Simple-Web-Server"]
|
||||
path = Simple-Web-Server
|
||||
url = https://github.com/loki-47-6F-64/Simple-Web-Server.git
|
||||
[submodule "ViGEmClient"]
|
||||
path = ViGEmClient
|
||||
url = https://github.com/ViGEm/ViGEmClient
|
||||
[submodule "pre-compiled"]
|
||||
path = pre-compiled
|
||||
url = https://bitbucket.org/Loki-47-6F-64/pre-compiled.git
|
||||
@@ -0,0 +1,490 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<!-- Beautiful Jekyll 6.0.1 | Copyright Dean Attali 2023 -->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<title>404 - Page not found | LizardByte</title>
|
||||
|
||||
|
||||
|
||||
<meta name="author" content="LizardByte">
|
||||
|
||||
|
||||
<meta name="description" content="404
|
||||
There's nothing here.">
|
||||
|
||||
|
||||
<meta name="theme-color" content="#05FF3B">
|
||||
|
||||
|
||||
|
||||
<meta name="keywords" content="LizardByte,Sunshine,Moonlight,self-hosted,gamestreaming,gamestream,game,streaming,media server">
|
||||
|
||||
|
||||
|
||||
<link rel="alternate" type="application/rss+xml" title="LizardByte" href="http://app.lizardbyte.dev/feed.xml">
|
||||
|
||||
|
||||
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-SSW90X5YZX"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-SSW90X5YZX');
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="/assets/css/bootstrap-social.css">
|
||||
|
||||
<link rel="stylesheet" href="/assets/css/beautifuljekyll.css">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lizardbyte/shared-web@2025.326.11214/dist/crowdin-bootstrap-css.css">
|
||||
|
||||
<link rel="stylesheet" href="/assets/css/styles.css">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<meta property="og:site_name" content="LizardByte">
|
||||
<meta property="og:title" content="404 - Page not found | LizardByte">
|
||||
<meta property="og:description" content="404
|
||||
There's nothing here.">
|
||||
|
||||
|
||||
<meta property="og:image" content="http://app.lizardbyte.dev/Sunshine/assets/img/navbar-avatar.png">
|
||||
|
||||
|
||||
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="http://app.lizardbyte.dev/404.html">
|
||||
<link rel="canonical" href="http://app.lizardbyte.dev/404.html">
|
||||
|
||||
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
|
||||
<meta name="twitter:site" content="@LizardByteDev">
|
||||
<meta name="twitter:creator" content="@LizardByteDev">
|
||||
|
||||
<meta property="twitter:title" content="404 - Page not found | LizardByte">
|
||||
<meta property="twitter:description" content="404
|
||||
There's nothing here.">
|
||||
|
||||
|
||||
<meta name="twitter:image" content="http://app.lizardbyte.dev/Sunshine/assets/img/navbar-avatar.png">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
|
||||
<nav class="navbar navbar-expand-xl navbar-light fixed-top navbar-custom top-nav-regular">
|
||||
<div class="container-fluid"><a class="navbar-brand" href="http://app.lizardbyte.dev/">LizardByte</a><button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#main-navbar" aria-controls="main-navbar" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="main-navbar">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">❤ Donate</a>
|
||||
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
|
||||
<a class="dropdown-item" href="https://github.com/sponsors/LizardByte">GitHub</a>
|
||||
<a class="dropdown-item" href="https://patreon.com/LizardByte">Patreon</a>
|
||||
<a class="dropdown-item" href="https://www.paypal.com/paypalme/ReenigneArcher">PayPal</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://github.com/LizardByte">GitHub</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">Resources</a>
|
||||
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
|
||||
<a class="dropdown-item" href="/blog">Blog</a>
|
||||
<a class="dropdown-item" href="https://docs.lizardbyte.dev/latest/about/overview.html">Docs</a>
|
||||
<a class="dropdown-item" href="https://github.com/orgs/LizardByte/discussions">Community</a>
|
||||
<a class="dropdown-item" href="/discord">Discord</a>
|
||||
<a class="dropdown-item" href="/support">Support</a>
|
||||
<a class="dropdown-item" href="https://status.LizardByte.dev">Status</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">Tools</a>
|
||||
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
|
||||
<a class="dropdown-item" href="/gamepad-tester">Gamepad Tester</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">Development</a>
|
||||
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
|
||||
<a class="dropdown-item" href="https://docs.lizardbyte.dev/latest/developers/contributing.html">Contributing</a>
|
||||
<a class="dropdown-item" href="/roadmap">Roadmap</a>
|
||||
<a class="dropdown-item" href="https://app.lizardbyte.dev/dashboard">Dashboard</a>
|
||||
<a class="dropdown-item" href="https://status-dev.LizardByte.dev">Dev Status</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="nav-search-link" href="#" title="Search">
|
||||
<span id="nav-search-icon" class="fa fa-search"></span>
|
||||
<span id="nav-search-text">Search</span>
|
||||
</a>
|
||||
</li></ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="avatar-container">
|
||||
<div class="avatar-img-border">
|
||||
<a href="http://app.lizardbyte.dev/">
|
||||
<img alt="Navigation bar avatar" class="avatar-img" src="/Sunshine/assets/img/navbar-avatar.png" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
|
||||
<div id="beautifuljekyll-search-overlay">
|
||||
|
||||
<div id="nav-search-exit" title="Exit search">✕</div>
|
||||
<input type="text" id="nav-search-input" placeholder="Search">
|
||||
<ul id="search-results-container"></ul>
|
||||
|
||||
<script src="https://unpkg.com/simple-jekyll-search@latest/dest/simple-jekyll-search.min.js"></script>
|
||||
<script>
|
||||
SimpleJekyllSearch({
|
||||
searchInput: document.getElementById('nav-search-input'),
|
||||
resultsContainer: document.getElementById('search-results-container'),
|
||||
json: '/assets/data/searchcorpus.json'
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="intro-header"></div>
|
||||
|
||||
<main class=" container-md ">
|
||||
|
||||
|
||||
<div class="text-center">
|
||||
<h1>404</h1>
|
||||
<p>There's nothing here.</p>
|
||||
<br/>
|
||||
|
||||
<img src="/assets/img/404.gif" alt="Not found" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="after-content">
|
||||
|
||||
<!-- Donate section-->
|
||||
<section class="p-5" id="Donate">
|
||||
<div class="container mb-5 shadow border-0 card-custom rounded-0">
|
||||
<div class="d-table-row g-0">
|
||||
<div class="d-table-cell px-3 align-middle text-center">
|
||||
<h1>
|
||||
<i class="fa-fw fas fa-coins"></i>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="col-sm-auto border-white my-3 px-3 py-2 border-start">
|
||||
<div class="container">
|
||||
<h4 class="card-title mb-3 fw-bolder">Donate</h4>
|
||||
</div>
|
||||
<a
|
||||
class="text-decoration-none"
|
||||
href="https://github.com/sponsors/LizardByte"
|
||||
target="_blank">
|
||||
<img class="m-3 crowdin-ignore"
|
||||
alt="GitHub Sponsors"
|
||||
src="https://img.shields.io/github/sponsors/lizardbyte?label=Github%20Sponsors&style=for-the-badge&color=green&logo=githubsponsors"
|
||||
>
|
||||
</a>
|
||||
<a
|
||||
class="text-decoration-none"
|
||||
href="https://www.patreon.com/LizardByte"
|
||||
target="_blank">
|
||||
<img class="m-3 crowdin-ignore"
|
||||
alt="Patreon"
|
||||
src="https://img.shields.io/badge/dynamic/json?color=green&label=Patreon&style=for-the-badge&query=patron_count&url=https%3A%2F%2Fapp.lizardbyte.dev%2Fdashboard%2Fpatreon%2FLizardByte.json&logo=patreon"
|
||||
>
|
||||
</a>
|
||||
<a
|
||||
class="text-decoration-none"
|
||||
href="https://www.paypal.com/paypalme/ReenigneArcher"
|
||||
target="_blank">
|
||||
<img class="m-3 crowdin-ignore"
|
||||
alt="PayPal"
|
||||
src="https://img.shields.io/static/v1?style=for-the-badge&label=PayPal&message=Donate&color=green&logo=paypal"
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
|
||||
<footer>
|
||||
<div class="container-md beautiful-jekyll-footer">
|
||||
<div class="row">
|
||||
<div class="col-xl-8 offset-xl-2 col-lg-10 offset-lg-1">
|
||||
|
||||
<ul class="list-inline text-center footer-links"><li class="list-inline-item">
|
||||
<a href="/feed.xml" title="RSS">
|
||||
<span class="fa-stack fa-lg" aria-hidden="true">
|
||||
<i class="fas fa-circle fa-stack-2x"></i>
|
||||
<i class="fas fa-rss fa-stack-1x fa-inverse"></i>
|
||||
</span>
|
||||
<span class="sr-only">RSS</span>
|
||||
</a>
|
||||
</li><li class="list-inline-item">
|
||||
<a href="https://discord.com/invite/d6MpcrbYQs" title="Discord">
|
||||
<span class="fa-stack fa-lg" aria-hidden="true">
|
||||
<i class="fas fa-circle fa-stack-2x"></i>
|
||||
<i class="fab fa-discord fa-stack-1x fa-inverse"></i>
|
||||
</span>
|
||||
<span class="sr-only">Discord</span>
|
||||
</a>
|
||||
</li><li class="list-inline-item">
|
||||
<a href="https://www.facebook.com/LizardByteDev" title="Facebook">
|
||||
<span class="fa-stack fa-lg" aria-hidden="true">
|
||||
<i class="fas fa-circle fa-stack-2x"></i>
|
||||
<i class="fab fa-facebook fa-stack-1x fa-inverse"></i>
|
||||
</span>
|
||||
<span class="sr-only">Facebook</span>
|
||||
</a>
|
||||
</li><li class="list-inline-item">
|
||||
<a href="https://github.com/LizardByte" title="GitHub">
|
||||
<span class="fa-stack fa-lg" aria-hidden="true">
|
||||
<i class="fas fa-circle fa-stack-2x"></i>
|
||||
<i class="fab fa-github fa-stack-1x fa-inverse"></i>
|
||||
</span>
|
||||
<span class="sr-only">GitHub</span>
|
||||
</a>
|
||||
</li><li class="list-inline-item">
|
||||
<a href="https://patreon.com/LizardByte" title="Patreon">
|
||||
<span class="fa-stack fa-lg" aria-hidden="true">
|
||||
<i class="fas fa-circle fa-stack-2x"></i>
|
||||
<i class="fab fa-patreon fa-stack-1x fa-inverse"></i>
|
||||
</span>
|
||||
<span class="sr-only">Patreon</span>
|
||||
</a>
|
||||
</li><li class="list-inline-item">
|
||||
<a href="https://reddit.com/r/LizardByte" title="Reddit">
|
||||
<span class="fa-stack fa-lg" aria-hidden="true">
|
||||
<i class="fas fa-circle fa-stack-2x"></i>
|
||||
<i class="fab fa-reddit fa-stack-1x fa-inverse"></i>
|
||||
</span>
|
||||
<span class="sr-only">Reddit</span>
|
||||
</a>
|
||||
</li><li class="list-inline-item">
|
||||
<a href="https://twitter.com/LizardByteDev" title="X (Twitter)">
|
||||
<span class="fa-stack fa-lg" aria-hidden="true">
|
||||
<i class="fas fa-circle fa-stack-2x"></i>
|
||||
<i class="fab fa-x-twitter fa-stack-1x fa-inverse"></i>
|
||||
</span>
|
||||
<span class="sr-only">X (Twitter)</span>
|
||||
</a>
|
||||
</li><li class="list-inline-item">
|
||||
<a href="https://www.youtube.com/c/LizardByteDev" title="YouTube">
|
||||
<span class="fa-stack fa-lg" aria-hidden="true">
|
||||
<i class="fas fa-circle fa-stack-2x"></i>
|
||||
<i class="fab fa-youtube fa-stack-1x fa-inverse"></i>
|
||||
</span>
|
||||
<span class="sr-only">YouTube</span>
|
||||
</a>
|
||||
</li></ul>
|
||||
|
||||
|
||||
|
||||
<div class="footer-custom-content">
|
||||
|
||||
<script>
|
||||
//open external links in a new window
|
||||
function external_new_window() {
|
||||
for(let c = document.getElementsByTagName("a"), a = 0;a < c.length;a++) {
|
||||
let b = c[a];
|
||||
if(b.getAttribute("href") && b.hostname !== location.hostname) {
|
||||
b.target = "_blank";
|
||||
b.rel = "noopener";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//open PDF links in a new window
|
||||
function pdf_new_window ()
|
||||
{
|
||||
if (!document.getElementsByTagName) return false;
|
||||
let links = document.getElementsByTagName("a");
|
||||
for (let eleLink=0; eleLink < links.length; eleLink ++) {
|
||||
if ((links[eleLink].href.indexOf('.pdf') !== -1)||(links[eleLink].href.indexOf('.doc') !== -1)||(links[eleLink].href.indexOf('.docx') !== -1)) {
|
||||
links[eleLink].onclick =
|
||||
function() {
|
||||
window.open(this.href);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pdf_new_window();
|
||||
external_new_window();
|
||||
</script>
|
||||
|
||||
|
||||
<a title="Privacy Policy" href="/privacy" class="text_muted">Privacy</a>
|
||||
•
|
||||
<a title="Terms and Conditions" href="/terms" class="text_muted">Terms</a>
|
||||
•
|
||||
<a title="Licenses and Attribution" href="/licenses" class="text_muted">Licenses</a>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<p class="copyright text-muted">
|
||||
|
||||
LizardByte
|
||||
•
|
||||
|
||||
2025
|
||||
|
||||
|
||||
•
|
||||
<span class="author-site">
|
||||
<a href="http://app.lizardbyte.dev/">app.LizardByte.dev</a>
|
||||
</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
•
|
||||
<a title="Edit this page on GitHub" href="https://github.com/LizardByte/Sunshine/edit/gh-pages/404.html" class="text_muted">Edit page</a>
|
||||
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="/assets/js/beautifuljekyll.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/@lizardbyte/shared-web@2025.326.11214/dist/crowdin.js"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/@lizardbyte/shared-web@2025.326.11214/dist/discord.js"></script>
|
||||
|
||||
<script src="/assets/js/crowdin-init.js"></script>
|
||||
|
||||
<script src="/assets/js/discord-init.js"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,197 +0,0 @@
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
project(Sunshine)
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# On MSYS2, building a stand-alone binary that links with ffmpeg is not possible,
|
||||
# Therefore, ffmpeg, libx264 and libx265 must be build from source
|
||||
if(WIN32)
|
||||
option(SUNSHINE_STANDALONE "Compile stand-alone binary of Sunshine" OFF)
|
||||
if(SUNSHINE_STANDALONE)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
|
||||
|
||||
if(NOT DEFINED SUNSHINE_PREPARED_BINARIES)
|
||||
set(SUNSHINE_PREPARED_BINARIES "${CMAKE_CURRENT_SOURCE_DIR}/pre-compiled/windows")
|
||||
endif()
|
||||
list(PREPEND PLATFORM_LIBRARIES
|
||||
C:/msys64/mingw64/lib/gcc/x86_64-w64-mingw32/${CMAKE_CXX_COMPILER_VERSION}/libstdc++.a
|
||||
C:/msys64/mingw64/x86_64-w64-mingw32/lib/libwinpthread.a
|
||||
)
|
||||
|
||||
set(FFMPEG_INCLUDE_DIRS
|
||||
${SUNSHINE_PREPARED_BINARIES}/include)
|
||||
set(FFMPEG_LIBRARIES
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libavcodec.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libavdevice.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libavfilter.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libavformat.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libavutil.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libpostproc.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libswresample.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libswscale.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libx264.a
|
||||
${SUNSHINE_PREPARED_BINARIES}/lib/libx265.a
|
||||
z lzma bcrypt C:/msys64/mingw64/lib/libiconv.a)
|
||||
endif()
|
||||
else()
|
||||
set(SUNSHINE_STANDALONE OFF)
|
||||
endif()
|
||||
|
||||
add_subdirectory(Simple-Web-Server)
|
||||
add_subdirectory(moonlight-common-c/enet)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
if(NOT SUNSHINE_STANDALONE)
|
||||
find_package(FFmpeg REQUIRED)
|
||||
endif()
|
||||
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -fPIC -Wall -Wno-missing-braces -Wno-maybe-uninitialized -Wno-sign-compare)
|
||||
|
||||
if(WIN32)
|
||||
add_subdirectory(tools) #This is temporary, only tools for Windows are needed, for now
|
||||
|
||||
list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_windows.json")
|
||||
include_directories(
|
||||
ViGEmClient/include)
|
||||
set(PLATFORM_TARGET_FILES
|
||||
sunshine/platform/windows.cpp
|
||||
sunshine/platform/windows_dxgi.cpp
|
||||
sunshine/platform/windows_wasapi.cpp
|
||||
ViGEmClient/src/ViGEmClient.cpp
|
||||
ViGEmClient/include/ViGEm/Client.h
|
||||
ViGEmClient/include/ViGEm/Common.h
|
||||
ViGEmClient/include/ViGEm/Util.h
|
||||
ViGEmClient/include/ViGEm/km/BusShared.h)
|
||||
list(PREPEND PLATFORM_LIBRARIES
|
||||
winmm
|
||||
ksuser
|
||||
wsock32
|
||||
ws2_32
|
||||
iphlpapi
|
||||
windowsapp
|
||||
d3d11 dxgi
|
||||
setupapi
|
||||
)
|
||||
|
||||
set_source_files_properties(ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_DEFINITIONS "UNICODE=1;ERROR_INVALID_DEVICE_OBJECT_PARAMETER=650")
|
||||
set_source_files_properties(ViGEmClient/src/ViGEmClient.cpp PROPERTIES COMPILE_FLAGS "-Wno-unknown-pragmas -Wno-misleading-indentation -Wno-class-memaccess")
|
||||
else()
|
||||
list(APPEND SUNSHINE_DEFINITIONS APPS_JSON="apps_linux.json")
|
||||
|
||||
find_package(X11 REQUIRED)
|
||||
set(PLATFORM_TARGET_FILES
|
||||
sunshine/platform/linux.cpp
|
||||
sunshine/platform/linux_evdev.cpp)
|
||||
|
||||
set(PLATFORM_LIBRARIES
|
||||
Xfixes
|
||||
Xtst
|
||||
xcb
|
||||
xcb-shm
|
||||
xcb-xfixes
|
||||
${X11_LIBRARIES}
|
||||
evdev
|
||||
pulse
|
||||
pulse-simple
|
||||
)
|
||||
|
||||
set(PLATFORM_INCLUDE_DIRS
|
||||
${X11_INCLUDE_DIR}
|
||||
/usr/include/libevdev-1.0)
|
||||
|
||||
if(NOT DEFINED SUNSHINE_EXECUTABLE_PATH)
|
||||
set(SUNSHINE_EXECUTABLE_PATH "${CMAKE_CURRENT_BINARY_DIR}/sunshine")
|
||||
endif()
|
||||
configure_file(gen-deb.in gen-deb @ONLY)
|
||||
configure_file(sunshine.service.in sunshine.service @ONLY)
|
||||
endif()
|
||||
|
||||
set(Boost_USE_STATIC_LIBS ON)
|
||||
find_package(Boost COMPONENTS log filesystem REQUIRED)
|
||||
|
||||
set(SUNSHINE_TARGET_FILES
|
||||
moonlight-common-c/reedsolomon/rs.c
|
||||
moonlight-common-c/reedsolomon/rs.h
|
||||
moonlight-common-c/src/Input.h
|
||||
moonlight-common-c/src/Rtsp.h
|
||||
moonlight-common-c/src/RtspParser.c
|
||||
moonlight-common-c/src/Video.h
|
||||
sunshine/utility.h
|
||||
sunshine/uuid.h
|
||||
sunshine/config.h
|
||||
sunshine/config.cpp
|
||||
sunshine/main.cpp
|
||||
sunshine/main.h
|
||||
sunshine/crypto.cpp
|
||||
sunshine/crypto.h
|
||||
sunshine/nvhttp.cpp
|
||||
sunshine/nvhttp.h
|
||||
sunshine/stream.cpp
|
||||
sunshine/stream.h
|
||||
sunshine/video.cpp
|
||||
sunshine/video.h
|
||||
sunshine/thread_safe.h
|
||||
sunshine/input.cpp
|
||||
sunshine/input.h
|
||||
sunshine/audio.cpp
|
||||
sunshine/audio.h
|
||||
sunshine/platform/common.h
|
||||
sunshine/process.cpp
|
||||
sunshine/process.h
|
||||
sunshine/network.cpp
|
||||
sunshine/network.h
|
||||
sunshine/move_by_copy.h
|
||||
sunshine/task_pool.h
|
||||
sunshine/thread_pool.h
|
||||
${PLATFORM_TARGET_FILES})
|
||||
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Simple-Web-Server
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/moonlight-common-c/enet/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/moonlight-common-c/reedsolomon
|
||||
${FFMPEG_INCLUDE_DIRS}
|
||||
${PLATFORM_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
string(TOUPPER "x${CMAKE_BUILD_TYPE}" BUILD_TYPE)
|
||||
if("${BUILD_TYPE}" STREQUAL "XDEBUG")
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -O0 -pedantic -ggdb3)
|
||||
if(WIN32)
|
||||
set_source_files_properties(sunshine/nvhttp.cpp PROPERTIES COMPILE_FLAGS -O2)
|
||||
endif()
|
||||
else()
|
||||
add_definitions(-DNDEBUG)
|
||||
list(APPEND SUNSHINE_COMPILE_OPTIONS -O3)
|
||||
endif()
|
||||
|
||||
if(NOT SUNSHINE_ASSETS_DIR)
|
||||
set(SUNSHINE_ASSETS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/assets")
|
||||
endif()
|
||||
|
||||
if(SUNSHINE_STANDALONE)
|
||||
set(OPENSSL_LIBRARIES
|
||||
C:/msys64/mingw64/lib/libssl.a
|
||||
C:/msys64/mingw64/lib/libcrypto.a)
|
||||
endif()
|
||||
|
||||
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
stdc++fs
|
||||
enet
|
||||
opus
|
||||
${FFMPEG_LIBRARIES}
|
||||
${Boost_LIBRARIES}
|
||||
${OPENSSL_LIBRARIES}
|
||||
${PLATFORM_LIBRARIES})
|
||||
|
||||
list(APPEND SUNSHINE_DEFINITIONS SUNSHINE_ASSETS_DIR="${SUNSHINE_ASSETS_DIR}")
|
||||
add_executable(sunshine ${SUNSHINE_TARGET_FILES})
|
||||
target_link_libraries(sunshine ${SUNSHINE_EXTERNAL_LIBRARIES})
|
||||
target_compile_definitions(sunshine PUBLIC ${SUNSHINE_DEFINITIONS})
|
||||
set_target_properties(sunshine PROPERTIES CXX_STANDARD 17)
|
||||
|
||||
target_compile_options(sunshine PRIVATE ${SUNSHINE_COMPILE_OPTIONS})
|
||||
@@ -1,144 +0,0 @@
|
||||
# - Try to find FFMPEG
|
||||
# Once done this will define
|
||||
# FFMPEG_FOUND - System has FFMPEG
|
||||
# FFMPEG_INCLUDE_DIRS - The FFMPEG include directories
|
||||
# FFMPEG_LIBRARIES - The libraries needed to use FFMPEG
|
||||
# FFMPEG_LIBRARY_DIRS - The directory to find FFMPEG libraries
|
||||
#
|
||||
# written by Roy Shilkrot 2013 http://www.morethantechnical.com/
|
||||
#
|
||||
|
||||
find_package(PkgConfig)
|
||||
|
||||
|
||||
MACRO(FFMPEG_FIND varname shortname headername)
|
||||
|
||||
IF(NOT WIN32)
|
||||
PKG_CHECK_MODULES(PC_${varname} ${shortname})
|
||||
|
||||
FIND_PATH(${varname}_INCLUDE_DIR "${shortname}/${headername}"
|
||||
HINTS ${PC_${varname}_INCLUDEDIR} ${PC_${varname}_INCLUDE_DIRS}
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
ELSE()
|
||||
FIND_PATH(${varname}_INCLUDE_DIR "${shortname}/${headername}")
|
||||
ENDIF()
|
||||
|
||||
IF(${varname}_INCLUDE_DIR STREQUAL "${varname}_INCLUDE_DIR-NOTFOUND")
|
||||
message(STATUS "look for newer strcture")
|
||||
IF(NOT WIN32)
|
||||
PKG_CHECK_MODULES(PC_${varname} "lib${shortname}")
|
||||
|
||||
FIND_PATH(${varname}_INCLUDE_DIR "lib${shortname}/${headername}"
|
||||
HINTS ${PC_${varname}_INCLUDEDIR} ${PC_${varname}_INCLUDE_DIRS}
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
ELSE()
|
||||
FIND_PATH(${varname}_INCLUDE_DIR "lib${shortname}/${headername}")
|
||||
IF(${${varname}_INCLUDE_DIR} STREQUAL "${varname}_INCLUDE_DIR-NOTFOUND")
|
||||
#Desperate times call for desperate measures
|
||||
MESSAGE(STATUS "globbing...")
|
||||
FILE(GLOB_RECURSE ${varname}_INCLUDE_DIR "/ffmpeg*/${headername}")
|
||||
MESSAGE(STATUS "found: ${${varname}_INCLUDE_DIR}")
|
||||
IF(${varname}_INCLUDE_DIR)
|
||||
GET_FILENAME_COMPONENT(${varname}_INCLUDE_DIR "${${varname}_INCLUDE_DIR}" PATH)
|
||||
GET_FILENAME_COMPONENT(${varname}_INCLUDE_DIR "${${varname}_INCLUDE_DIR}" PATH)
|
||||
ELSE()
|
||||
SET(${varname}_INCLUDE_DIR "${varname}_INCLUDE_DIR-NOTFOUND")
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
|
||||
|
||||
IF(${${varname}_INCLUDE_DIR} STREQUAL "${varname}_INCLUDE_DIR-NOTFOUND")
|
||||
MESSAGE(STATUS "Can't find includes for ${shortname}...")
|
||||
ELSE()
|
||||
MESSAGE(STATUS "Found ${shortname} include dirs: ${${varname}_INCLUDE_DIR}")
|
||||
|
||||
#GET_DIRECTORY_PROPERTY(FFMPEG_PARENT DIRECTORY ${${varname}_INCLUDE_DIR} PARENT_DIRECTORY)
|
||||
GET_FILENAME_COMPONENT(FFMPEG_PARENT ${${varname}_INCLUDE_DIR} PATH)
|
||||
MESSAGE(STATUS "Using FFMpeg dir parent as hint: ${FFMPEG_PARENT}")
|
||||
|
||||
IF(NOT WIN32)
|
||||
FIND_LIBRARY(${varname}_LIBRARIES NAMES ${shortname}
|
||||
HINTS ${PC_${varname}_LIBDIR} ${PC_${varname}_LIBRARY_DIR} ${FFMPEG_PARENT})
|
||||
ELSE()
|
||||
FIND_PATH(${varname}_LIBRARIES "${shortname}.dll.a" HINTS ${FFMPEG_PARENT})
|
||||
# FILE(GLOB_RECURSE ${varname}_LIBRARIES "${FFMPEG_PARENT}/*${shortname}.lib")
|
||||
# GLOBing is very bad... but windows sux, this is the only thing that works
|
||||
ENDIF()
|
||||
|
||||
IF(${varname}_LIBRARIES STREQUAL "${varname}_LIBRARIES-NOTFOUND")
|
||||
MESSAGE(STATUS "look for newer structure for library")
|
||||
FIND_LIBRARY(${varname}_LIBRARIES NAMES lib${shortname}
|
||||
HINTS ${PC_${varname}_LIBDIR} ${PC_${varname}_LIBRARY_DIR} ${FFMPEG_PARENT})
|
||||
ENDIF()
|
||||
|
||||
|
||||
IF(${varname}_LIBRARIES STREQUAL "${varname}_LIBRARIES-NOTFOUND")
|
||||
MESSAGE(STATUS "Can't find lib for ${shortname}...")
|
||||
ELSE()
|
||||
MESSAGE(STATUS "Found ${shortname} libs: ${${varname}_LIBRARIES}")
|
||||
ENDIF()
|
||||
|
||||
|
||||
IF(NOT ${varname}_INCLUDE_DIR STREQUAL "${varname}_INCLUDE_DIR-NOTFOUND"
|
||||
AND NOT ${varname}_LIBRARIES STREQUAL ${varname}_LIBRARIES-NOTFOUND)
|
||||
|
||||
MESSAGE(STATUS "found ${shortname}: include ${${varname}_INCLUDE_DIR} lib ${${varname}_LIBRARIES}")
|
||||
SET(FFMPEG_${varname}_FOUND 1)
|
||||
SET(FFMPEG_${varname}_INCLUDE_DIRS ${${varname}_INCLUDE_DIR})
|
||||
SET(FFMPEG_${varname}_LIBS ${${varname}_LIBRARIES})
|
||||
ELSE()
|
||||
MESSAGE(STATUS "Can't find ${shortname}")
|
||||
ENDIF()
|
||||
|
||||
ENDIF()
|
||||
|
||||
ENDMACRO(FFMPEG_FIND)
|
||||
|
||||
FFMPEG_FIND(LIBAVFORMAT avformat avformat.h)
|
||||
FFMPEG_FIND(LIBAVDEVICE avdevice avdevice.h)
|
||||
FFMPEG_FIND(LIBAVCODEC avcodec avcodec.h)
|
||||
FFMPEG_FIND(LIBAVUTIL avutil avutil.h)
|
||||
FFMPEG_FIND(LIBSWSCALE swscale swscale.h)
|
||||
|
||||
SET(FFMPEG_FOUND "NO")
|
||||
IF (FFMPEG_LIBAVFORMAT_FOUND AND
|
||||
FFMPEG_LIBAVDEVICE_FOUND AND
|
||||
FFMPEG_LIBAVCODEC_FOUND AND
|
||||
FFMPEG_LIBAVUTIL_FOUND AND
|
||||
FFMPEG_LIBSWSCALE_FOUND
|
||||
)
|
||||
|
||||
|
||||
SET(FFMPEG_FOUND "YES")
|
||||
|
||||
SET(FFMPEG_INCLUDE_DIRS ${FFMPEG_LIBAVFORMAT_INCLUDE_DIRS})
|
||||
|
||||
SET(FFMPEG_LIBRARY_DIRS ${FFMPEG_LIBAVFORMAT_LIBRARY_DIRS})
|
||||
|
||||
SET(FFMPEG_LIBRARIES
|
||||
${FFMPEG_LIBAVFORMAT_LIBS}
|
||||
${FFMPEG_LIBAVDEVICE_LIBS}
|
||||
${FFMPEG_LIBAVCODEC_LIBS}
|
||||
${FFMPEG_LIBAVUTIL_LIBS}
|
||||
${FFMPEG_LIBSWSCALE_LIBS}
|
||||
)
|
||||
|
||||
ELSE ()
|
||||
|
||||
MESSAGE(STATUS "Could not find FFMPEG")
|
||||
|
||||
ENDIF()
|
||||
|
||||
message(STATUS ${FFMPEG_LIBRARIES} ${FFMPEG_LIBAVFORMAT_LIBRARIES})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
# handle the QUIETLY and REQUIRED arguments and set FFMPEG_FOUND to TRUE
|
||||
# if all listed variables are TRUE
|
||||
find_package_handle_standard_args(FFMPEG DEFAULT_MSG
|
||||
FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS)
|
||||
|
||||
mark_as_advanced(FFMPEG_INCLUDE_DIRS FFMPEG_LIBRARY_DIRS FFMPEG_LIBRARIES)
|
||||
@@ -1,107 +0,0 @@
|
||||
######### Linux ##############
|
||||
|
||||
Requirements:
|
||||
Ubuntu 19.10: cmake libssl-dev libavdevice-dev libboost-thread-dev libboost-filesystem-dev libboost-log-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
|
||||
|
||||
Compilation:
|
||||
* git clone <repository> --recurse-submodules
|
||||
* mkdir build && cd build
|
||||
* cmake ..
|
||||
* make
|
||||
|
||||
|
||||
Setup:
|
||||
* sunshine needs access to uinput to create mouse and gamepad events:
|
||||
* Add user to group 'input': "usermod -a -G input username
|
||||
* Create a file: "/etc/udev/rules.d/85-sunshine-input.rules"
|
||||
* The contents of the file is as follows:
|
||||
KERNEL=="uinput", GROUP="input", mode="0660"
|
||||
* assets/sunshine.conf is an example configuration file. Modify it as you see fit and use it by running: "sunshine path/to/sunshine.conf"
|
||||
* path/to/build/dir/sunshine.service is used to start sunshine in the background:
|
||||
* cp sunshine.service $HOME/.config/systemd/user/
|
||||
* Modify $HOME/.config/systemd/user/sunshine.conf to point to the sunshine executable
|
||||
* systemctl --user start sunshine
|
||||
|
||||
* assets/apps.json is an example of a list of applications that are started just before running a stream:
|
||||
* See below for a detailed explanation
|
||||
|
||||
Trouleshooting:
|
||||
* If you get "Could not create Sunshine Gamepad: Permission Denied", ensure you are part of the group "input":
|
||||
* groups
|
||||
* If Sunshine sends audio from the microphone instead of the speaker, try the following steps:
|
||||
* pacmd list-sources | grep "name:"
|
||||
* Copy the name to the configuration option "audio_sink"
|
||||
* restart sunshine
|
||||
|
||||
|
||||
|
||||
######### Windows 10 ############
|
||||
|
||||
Requirements:
|
||||
MSYS2 : mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-boost
|
||||
|
||||
Compilation:
|
||||
* git clone <repository> --recurse-submodules
|
||||
* mkdir build && cd build
|
||||
* cmake -G"Unix Makefiles" ..
|
||||
* make
|
||||
|
||||
Setup:
|
||||
* <optional> Gamepad support: Download and run 'ViGEmBus_Setup_1.16.116.exe' from [https://github.com/ViGEm/ViGEmBus/releases]
|
||||
|
||||
|
||||
|
||||
|
||||
######### Common #############
|
||||
|
||||
Usage:
|
||||
* run "sunshine path/to/sunshine.conf"
|
||||
* In Moonlight: Add PC manually
|
||||
* When Moonlight request you insert the correct pin on sunshine:
|
||||
wget xxx.xxx.xxx.xxx:47989/pin/xxxx -- where the first few x's are substituted by the ip of Sunshine and the final 4 x'es are substituted by the pin
|
||||
or
|
||||
Type in the URL bar of your browser: xxx.xxx.xxx.xxx:47989/pin/xxxx -- where the first few x's are substituted by the ip of the final 4 x'es are subsituted by the pin
|
||||
* Click on one of the Applications listed
|
||||
* Have fun :)
|
||||
|
||||
|
||||
Note:
|
||||
* The Windows key is not passed through by Moonlight, therefore Sunshine maps Right-Alt key to the Windows key
|
||||
* If you set Video Bitrate to 0.5Mb/s:
|
||||
* Sunshine will use CRF or QP to controll the quality of the stream. (See example configuration file for more details)
|
||||
* This is less CPU intensive and it has lower average bandwith requirements compared to manually setting bitrate to acceptable quality
|
||||
* However, it has higher peak bitrates, forcing Sunshine to drop entire frames when streaming 1080P due to their size.
|
||||
* When this happens, the video portion of the stream appears to be frozen.
|
||||
* This is rare enough that using this for the desktop environment is tolerable (in my opinion), however for gaming not so much.
|
||||
|
||||
|
||||
Credits:
|
||||
* Simple-Web-Server [https://gitlab.com/eidheim/Simple-Web-Server]
|
||||
* Moonlight [https://github.com/moonlight-stream]
|
||||
* Looking-Glass [https://github.com/gnif/LookingGlass] (For showing me how to properly capture frames on Windows, saving me a lot of time :)
|
||||
|
||||
|
||||
|
||||
Application List:
|
||||
* You can use Environment variables in place of values
|
||||
* $(HOME) will be replaced by the value of $HOME
|
||||
* $$ will be replaced by $ --> $$(HOME) will be replaced by $(HOME)
|
||||
* env: Adds or overwrites Environment variables for the commands/applications run by Sunshine.
|
||||
* "Variable name":"Variable value"
|
||||
* apps: The list of applications
|
||||
* name: Self explanatory
|
||||
* output <optional>: The file where the output of the command is stored
|
||||
* If it is not specified, the output is ignored
|
||||
* prep-cmd: A list of commands to be run before/after the application
|
||||
* If any of the prep-commands fail, starting the application is aborted
|
||||
* do: Run before the application
|
||||
* If it fails, all 'undo' commands of the previously succeeded 'do' commands are run
|
||||
* undo <optional>: Run after the application has terminated
|
||||
* This should not fail considering it is supposed to undo the 'do' commands.
|
||||
* If it fails, Sunshine is terminated
|
||||
* cmd <optional>: The main application
|
||||
* If not specified, a processs is started that sleeps indefinitely
|
||||
|
||||
When an application is started, if there is an application already running, it will be terminated.
|
||||
When the application has been shutdown, the stream shuts down as well.
|
||||
In addition to the apps listed, one app "Desktop" is hardcoded into Sunshine. It does not start an application, instead it simply starts a stream.
|
||||
@@ -1,48 +0,0 @@
|
||||
image:
|
||||
- Ubuntu
|
||||
- Visual Studio 2019
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- BUILD_TYPE: Debug
|
||||
- BUILD_TYPE: Release
|
||||
|
||||
install:
|
||||
- sh: sudo add-apt-repository ppa:hnakamur/icu
|
||||
- sh: sudo add-apt-repository ppa:hnakamur/boost
|
||||
|
||||
- sh: sudo add-apt-repository ppa:savoury1/build-tools
|
||||
- sh: sudo add-apt-repository ppa:savoury1/backports
|
||||
- sh: sudo add-apt-repository ppa:savoury1/graphics
|
||||
- sh: sudo add-apt-repository ppa:savoury1/multimedia
|
||||
- sh: sudo add-apt-repository ppa:savoury1/ffmpeg4
|
||||
|
||||
- sh: sudo apt update
|
||||
- sh: sudo apt install -y fakeroot cmake libssl-dev libavdevice-dev libboost-thread1.67-dev libboost-filesystem1.67-dev libboost-log1.67-dev libpulse-dev libopus-dev libxtst-dev libx11-dev libxfixes-dev libevdev-dev libxcb1-dev libxcb-shm0-dev libxcb-xfixes0-dev
|
||||
- sh: sudo update-alternatives --set gcc /usr/bin/gcc-8
|
||||
- cmd: C:\msys64\usr\bin\bash -lc "pacman --needed --noconfirm -S mingw-w64-x86_64-openssl mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain mingw-w64-x86_64-opus mingw-w64-x86_64-x265 mingw-w64-x86_64-boost git yasm nasm diffutils make"
|
||||
|
||||
before_build:
|
||||
- git submodule update --init --recursive
|
||||
- mkdir build
|
||||
- cd build
|
||||
|
||||
build_script:
|
||||
- cmd: set OLDPATH=%PATH%
|
||||
- cmd: set PATH=C:\msys64\mingw64\bin
|
||||
- sh: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSUNSHINE_EXECUTABLE_PATH=sunshine -DSUNSHINE_ASSETS_DIR=/etc/sunshine ..
|
||||
- cmd: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DOPENSSL_ROOT_DIR=C:\OpenSSL-v111-Win64 -DSUNSHINE_STANDALONE=ON -DSUNSHINE_ASSETS_DIR=assets -G "MinGW Makefiles" ..
|
||||
- sh: make -j$(nproc)
|
||||
- cmd: mingw32-make -j2
|
||||
- cmd: set PATH=%OLDPATH%
|
||||
|
||||
after_build:
|
||||
- sh: ./gen-deb
|
||||
- cmd: Del ..\assets\apps_linux.json
|
||||
- cmd: 7z a Sunshine-Windows.zip ..\assets
|
||||
- cmd: 7z a Sunshine-Windows.zip sunshine.exe
|
||||
- cmd: 7z a Sunshine-Windows.zip tools\dxgi-info.exe
|
||||
- cmd: 7z a Sunshine-Windows.zip tools\audio-info.exe
|
||||
- cmd: appveyor PushArtifact Sunshine-Windows.zip
|
||||
- sh: appveyor PushArtifact package-deb/sunshine.deb
|
||||
- sh: appveyor PushArtifact sunshine.service
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"env":{
|
||||
"DISPLAY":":0",
|
||||
"DRI_PRIME":"1",
|
||||
"XAUTHORITY":"$(HOME)/.Xauthority",
|
||||
"PATH":"$(PATH):$(HOME)/.local/bin"
|
||||
},
|
||||
"apps":[
|
||||
{
|
||||
"name":"Low Res Desktop",
|
||||
"prep-cmd":[
|
||||
{ "do":"xrandr --output HDMI-1 --mode 1920x1080", "undo":"xrandr --output HDMI-1 --mode 1920x1200" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name":"Steam BigPicture",
|
||||
|
||||
"output":"steam.txt",
|
||||
"cmd":"steam -bigpicture",
|
||||
"prep-cmd":[]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"env":{
|
||||
"PATH":"$(PATH);C:\\Program Files (x86)\\Steam"
|
||||
},
|
||||
"apps":[
|
||||
{
|
||||
"name":"Steam BigPicture",
|
||||
|
||||
"output":"steam.txt",
|
||||
"prep-cmd":[
|
||||
{"do":"steam \"steam://open/bigpicture\""}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 998 B |
@@ -0,0 +1,28 @@
|
||||
.main-content {
|
||||
padding-bottom: 3.125rem;
|
||||
}
|
||||
|
||||
footer.footer-min {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding: 0.1875rem;
|
||||
background-color: #f5f5f5;
|
||||
border-top: 1px solid #eeeeee;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none; /* no underline */
|
||||
}
|
||||
a:hover,
|
||||
a:focus {
|
||||
text-decoration: underline; /* underline when hovering */
|
||||
}
|
||||
|
||||
.navbar-custom .navbar-nav .dropdown-menu .dropdown-item:focus,
|
||||
.pagination .page-item .page-link:hover,
|
||||
.pagination .page-item .page-link:focus,
|
||||
.tag-btn:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Social Buttons for Bootstrap
|
||||
*
|
||||
* Copyright 2013-2015 Panayiotis Lipiridis
|
||||
* Licensed under the MIT License
|
||||
*
|
||||
* https://github.com/lipis/bootstrap-social
|
||||
*/
|
||||
|
||||
.btn-social{position:relative;margin-bottom:5px;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.btn-social>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)}
|
||||
.btn-social.btn-lg{padding-left:61px}.btn-social.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em}
|
||||
.btn-social.btn-sm{padding-left:38px}.btn-social.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em}
|
||||
.btn-social.btn-xs{padding-left:30px}.btn-social.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em}
|
||||
.btn-social-icon{position:relative;padding-left:44px;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;height:34px;width:34px;padding:0}.btn-social-icon>:first-child{position:absolute;left:0;top:0;bottom:0;width:32px;line-height:34px;font-size:1.6em;text-align:center;border-right:1px solid rgba(0,0,0,0.2)}
|
||||
.btn-social-icon.btn-lg{padding-left:61px}.btn-social-icon.btn-lg>:first-child{line-height:45px;width:45px;font-size:1.8em}
|
||||
.btn-social-icon.btn-sm{padding-left:38px}.btn-social-icon.btn-sm>:first-child{line-height:28px;width:28px;font-size:1.4em}
|
||||
.btn-social-icon.btn-xs{padding-left:30px}.btn-social-icon.btn-xs>:first-child{line-height:20px;width:20px;font-size:1.2em}
|
||||
.btn-social-icon>:first-child{border:none;text-align:center;width:100% !important}
|
||||
.btn-social-icon.btn-lg{height:45px;width:45px;padding-left:0;padding-right:0}
|
||||
.btn-social-icon.btn-sm{height:30px;width:30px;padding-left:0;padding-right:0}
|
||||
.btn-social-icon.btn-xs{height:22px;width:22px;padding-left:0;padding-right:0}
|
||||
.btn-adn{color:#fff;background-color:#d87a68;border-color:rgba(0,0,0,0.2)}.btn-adn:focus,.btn-adn.focus{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-adn:hover{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{color:#fff;background-color:#ce563f;border-color:rgba(0,0,0,0.2)}.btn-adn:active:hover,.btn-adn.active:hover,.open>.dropdown-toggle.btn-adn:hover,.btn-adn:active:focus,.btn-adn.active:focus,.open>.dropdown-toggle.btn-adn:focus,.btn-adn:active.focus,.btn-adn.active.focus,.open>.dropdown-toggle.btn-adn.focus{color:#fff;background-color:#b94630;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-adn:active,.btn-adn.active,.open>.dropdown-toggle.btn-adn{background-image:none}
|
||||
.btn-adn.disabled,.btn-adn[disabled],fieldset[disabled] .btn-adn,.btn-adn.disabled:hover,.btn-adn[disabled]:hover,fieldset[disabled] .btn-adn:hover,.btn-adn.disabled:focus,.btn-adn[disabled]:focus,fieldset[disabled] .btn-adn:focus,.btn-adn.disabled.focus,.btn-adn[disabled].focus,fieldset[disabled] .btn-adn.focus,.btn-adn.disabled:active,.btn-adn[disabled]:active,fieldset[disabled] .btn-adn:active,.btn-adn.disabled.active,.btn-adn[disabled].active,fieldset[disabled] .btn-adn.active{background-color:#d87a68;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-adn .badge{color:#d87a68;background-color:#fff}
|
||||
.btn-bitbucket{color:#fff;background-color:#205081;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:focus,.btn-bitbucket.focus{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-bitbucket:hover{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{color:#fff;background-color:#163758;border-color:rgba(0,0,0,0.2)}.btn-bitbucket:active:hover,.btn-bitbucket.active:hover,.open>.dropdown-toggle.btn-bitbucket:hover,.btn-bitbucket:active:focus,.btn-bitbucket.active:focus,.open>.dropdown-toggle.btn-bitbucket:focus,.btn-bitbucket:active.focus,.btn-bitbucket.active.focus,.open>.dropdown-toggle.btn-bitbucket.focus{color:#fff;background-color:#0f253c;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-bitbucket:active,.btn-bitbucket.active,.open>.dropdown-toggle.btn-bitbucket{background-image:none}
|
||||
.btn-bitbucket.disabled,.btn-bitbucket[disabled],fieldset[disabled] .btn-bitbucket,.btn-bitbucket.disabled:hover,.btn-bitbucket[disabled]:hover,fieldset[disabled] .btn-bitbucket:hover,.btn-bitbucket.disabled:focus,.btn-bitbucket[disabled]:focus,fieldset[disabled] .btn-bitbucket:focus,.btn-bitbucket.disabled.focus,.btn-bitbucket[disabled].focus,fieldset[disabled] .btn-bitbucket.focus,.btn-bitbucket.disabled:active,.btn-bitbucket[disabled]:active,fieldset[disabled] .btn-bitbucket:active,.btn-bitbucket.disabled.active,.btn-bitbucket[disabled].active,fieldset[disabled] .btn-bitbucket.active{background-color:#205081;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-bitbucket .badge{color:#205081;background-color:#fff}
|
||||
.btn-dropbox{color:#fff;background-color:#1087dd;border-color:rgba(0,0,0,0.2)}.btn-dropbox:focus,.btn-dropbox.focus{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-dropbox:hover{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{color:#fff;background-color:#0d6aad;border-color:rgba(0,0,0,0.2)}.btn-dropbox:active:hover,.btn-dropbox.active:hover,.open>.dropdown-toggle.btn-dropbox:hover,.btn-dropbox:active:focus,.btn-dropbox.active:focus,.open>.dropdown-toggle.btn-dropbox:focus,.btn-dropbox:active.focus,.btn-dropbox.active.focus,.open>.dropdown-toggle.btn-dropbox.focus{color:#fff;background-color:#0a568c;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-dropbox:active,.btn-dropbox.active,.open>.dropdown-toggle.btn-dropbox{background-image:none}
|
||||
.btn-dropbox.disabled,.btn-dropbox[disabled],fieldset[disabled] .btn-dropbox,.btn-dropbox.disabled:hover,.btn-dropbox[disabled]:hover,fieldset[disabled] .btn-dropbox:hover,.btn-dropbox.disabled:focus,.btn-dropbox[disabled]:focus,fieldset[disabled] .btn-dropbox:focus,.btn-dropbox.disabled.focus,.btn-dropbox[disabled].focus,fieldset[disabled] .btn-dropbox.focus,.btn-dropbox.disabled:active,.btn-dropbox[disabled]:active,fieldset[disabled] .btn-dropbox:active,.btn-dropbox.disabled.active,.btn-dropbox[disabled].active,fieldset[disabled] .btn-dropbox.active{background-color:#1087dd;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-dropbox .badge{color:#1087dd;background-color:#fff}
|
||||
.btn-facebook{color:#fff;background-color:#3b5998;border-color:rgba(0,0,0,0.2)}.btn-facebook:focus,.btn-facebook.focus{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-facebook:hover{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{color:#fff;background-color:#2d4373;border-color:rgba(0,0,0,0.2)}.btn-facebook:active:hover,.btn-facebook.active:hover,.open>.dropdown-toggle.btn-facebook:hover,.btn-facebook:active:focus,.btn-facebook.active:focus,.open>.dropdown-toggle.btn-facebook:focus,.btn-facebook:active.focus,.btn-facebook.active.focus,.open>.dropdown-toggle.btn-facebook.focus{color:#fff;background-color:#23345a;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-facebook:active,.btn-facebook.active,.open>.dropdown-toggle.btn-facebook{background-image:none}
|
||||
.btn-facebook.disabled,.btn-facebook[disabled],fieldset[disabled] .btn-facebook,.btn-facebook.disabled:hover,.btn-facebook[disabled]:hover,fieldset[disabled] .btn-facebook:hover,.btn-facebook.disabled:focus,.btn-facebook[disabled]:focus,fieldset[disabled] .btn-facebook:focus,.btn-facebook.disabled.focus,.btn-facebook[disabled].focus,fieldset[disabled] .btn-facebook.focus,.btn-facebook.disabled:active,.btn-facebook[disabled]:active,fieldset[disabled] .btn-facebook:active,.btn-facebook.disabled.active,.btn-facebook[disabled].active,fieldset[disabled] .btn-facebook.active{background-color:#3b5998;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-facebook .badge{color:#3b5998;background-color:#fff}
|
||||
.btn-flickr{color:#fff;background-color:#ff0084;border-color:rgba(0,0,0,0.2)}.btn-flickr:focus,.btn-flickr.focus{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-flickr:hover{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{color:#fff;background-color:#cc006a;border-color:rgba(0,0,0,0.2)}.btn-flickr:active:hover,.btn-flickr.active:hover,.open>.dropdown-toggle.btn-flickr:hover,.btn-flickr:active:focus,.btn-flickr.active:focus,.open>.dropdown-toggle.btn-flickr:focus,.btn-flickr:active.focus,.btn-flickr.active.focus,.open>.dropdown-toggle.btn-flickr.focus{color:#fff;background-color:#a80057;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-flickr:active,.btn-flickr.active,.open>.dropdown-toggle.btn-flickr{background-image:none}
|
||||
.btn-flickr.disabled,.btn-flickr[disabled],fieldset[disabled] .btn-flickr,.btn-flickr.disabled:hover,.btn-flickr[disabled]:hover,fieldset[disabled] .btn-flickr:hover,.btn-flickr.disabled:focus,.btn-flickr[disabled]:focus,fieldset[disabled] .btn-flickr:focus,.btn-flickr.disabled.focus,.btn-flickr[disabled].focus,fieldset[disabled] .btn-flickr.focus,.btn-flickr.disabled:active,.btn-flickr[disabled]:active,fieldset[disabled] .btn-flickr:active,.btn-flickr.disabled.active,.btn-flickr[disabled].active,fieldset[disabled] .btn-flickr.active{background-color:#ff0084;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-flickr .badge{color:#ff0084;background-color:#fff}
|
||||
.btn-foursquare{color:#fff;background-color:#f94877;border-color:rgba(0,0,0,0.2)}.btn-foursquare:focus,.btn-foursquare.focus{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-foursquare:hover{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{color:#fff;background-color:#f71752;border-color:rgba(0,0,0,0.2)}.btn-foursquare:active:hover,.btn-foursquare.active:hover,.open>.dropdown-toggle.btn-foursquare:hover,.btn-foursquare:active:focus,.btn-foursquare.active:focus,.open>.dropdown-toggle.btn-foursquare:focus,.btn-foursquare:active.focus,.btn-foursquare.active.focus,.open>.dropdown-toggle.btn-foursquare.focus{color:#fff;background-color:#e30742;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-foursquare:active,.btn-foursquare.active,.open>.dropdown-toggle.btn-foursquare{background-image:none}
|
||||
.btn-foursquare.disabled,.btn-foursquare[disabled],fieldset[disabled] .btn-foursquare,.btn-foursquare.disabled:hover,.btn-foursquare[disabled]:hover,fieldset[disabled] .btn-foursquare:hover,.btn-foursquare.disabled:focus,.btn-foursquare[disabled]:focus,fieldset[disabled] .btn-foursquare:focus,.btn-foursquare.disabled.focus,.btn-foursquare[disabled].focus,fieldset[disabled] .btn-foursquare.focus,.btn-foursquare.disabled:active,.btn-foursquare[disabled]:active,fieldset[disabled] .btn-foursquare:active,.btn-foursquare.disabled.active,.btn-foursquare[disabled].active,fieldset[disabled] .btn-foursquare.active{background-color:#f94877;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-foursquare .badge{color:#f94877;background-color:#fff}
|
||||
.btn-github{color:#fff;background-color:#444;border-color:rgba(0,0,0,0.2)}.btn-github:focus,.btn-github.focus{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-github:hover{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{color:#fff;background-color:#2b2b2b;border-color:rgba(0,0,0,0.2)}.btn-github:active:hover,.btn-github.active:hover,.open>.dropdown-toggle.btn-github:hover,.btn-github:active:focus,.btn-github.active:focus,.open>.dropdown-toggle.btn-github:focus,.btn-github:active.focus,.btn-github.active.focus,.open>.dropdown-toggle.btn-github.focus{color:#fff;background-color:#191919;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-github:active,.btn-github.active,.open>.dropdown-toggle.btn-github{background-image:none}
|
||||
.btn-github.disabled,.btn-github[disabled],fieldset[disabled] .btn-github,.btn-github.disabled:hover,.btn-github[disabled]:hover,fieldset[disabled] .btn-github:hover,.btn-github.disabled:focus,.btn-github[disabled]:focus,fieldset[disabled] .btn-github:focus,.btn-github.disabled.focus,.btn-github[disabled].focus,fieldset[disabled] .btn-github.focus,.btn-github.disabled:active,.btn-github[disabled]:active,fieldset[disabled] .btn-github:active,.btn-github.disabled.active,.btn-github[disabled].active,fieldset[disabled] .btn-github.active{background-color:#444;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-github .badge{color:#444;background-color:#fff}
|
||||
.btn-google{color:#fff;background-color:#dd4b39;border-color:rgba(0,0,0,0.2)}.btn-google:focus,.btn-google.focus{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-google:hover{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{color:#fff;background-color:#c23321;border-color:rgba(0,0,0,0.2)}.btn-google:active:hover,.btn-google.active:hover,.open>.dropdown-toggle.btn-google:hover,.btn-google:active:focus,.btn-google.active:focus,.open>.dropdown-toggle.btn-google:focus,.btn-google:active.focus,.btn-google.active.focus,.open>.dropdown-toggle.btn-google.focus{color:#fff;background-color:#a32b1c;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-google:active,.btn-google.active,.open>.dropdown-toggle.btn-google{background-image:none}
|
||||
.btn-google.disabled,.btn-google[disabled],fieldset[disabled] .btn-google,.btn-google.disabled:hover,.btn-google[disabled]:hover,fieldset[disabled] .btn-google:hover,.btn-google.disabled:focus,.btn-google[disabled]:focus,fieldset[disabled] .btn-google:focus,.btn-google.disabled.focus,.btn-google[disabled].focus,fieldset[disabled] .btn-google.focus,.btn-google.disabled:active,.btn-google[disabled]:active,fieldset[disabled] .btn-google:active,.btn-google.disabled.active,.btn-google[disabled].active,fieldset[disabled] .btn-google.active{background-color:#dd4b39;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-google .badge{color:#dd4b39;background-color:#fff}
|
||||
.btn-instagram{color:#fff;background-color:#3f729b;border-color:rgba(0,0,0,0.2)}.btn-instagram:focus,.btn-instagram.focus{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-instagram:hover{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{color:#fff;background-color:#305777;border-color:rgba(0,0,0,0.2)}.btn-instagram:active:hover,.btn-instagram.active:hover,.open>.dropdown-toggle.btn-instagram:hover,.btn-instagram:active:focus,.btn-instagram.active:focus,.open>.dropdown-toggle.btn-instagram:focus,.btn-instagram:active.focus,.btn-instagram.active.focus,.open>.dropdown-toggle.btn-instagram.focus{color:#fff;background-color:#26455d;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-instagram:active,.btn-instagram.active,.open>.dropdown-toggle.btn-instagram{background-image:none}
|
||||
.btn-instagram.disabled,.btn-instagram[disabled],fieldset[disabled] .btn-instagram,.btn-instagram.disabled:hover,.btn-instagram[disabled]:hover,fieldset[disabled] .btn-instagram:hover,.btn-instagram.disabled:focus,.btn-instagram[disabled]:focus,fieldset[disabled] .btn-instagram:focus,.btn-instagram.disabled.focus,.btn-instagram[disabled].focus,fieldset[disabled] .btn-instagram.focus,.btn-instagram.disabled:active,.btn-instagram[disabled]:active,fieldset[disabled] .btn-instagram:active,.btn-instagram.disabled.active,.btn-instagram[disabled].active,fieldset[disabled] .btn-instagram.active{background-color:#3f729b;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-instagram .badge{color:#3f729b;background-color:#fff}
|
||||
.btn-linkedin{color:#fff;background-color:#007bb6;border-color:rgba(0,0,0,0.2)}.btn-linkedin:focus,.btn-linkedin.focus{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-linkedin:hover{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{color:#fff;background-color:#005983;border-color:rgba(0,0,0,0.2)}.btn-linkedin:active:hover,.btn-linkedin.active:hover,.open>.dropdown-toggle.btn-linkedin:hover,.btn-linkedin:active:focus,.btn-linkedin.active:focus,.open>.dropdown-toggle.btn-linkedin:focus,.btn-linkedin:active.focus,.btn-linkedin.active.focus,.open>.dropdown-toggle.btn-linkedin.focus{color:#fff;background-color:#00405f;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-linkedin:active,.btn-linkedin.active,.open>.dropdown-toggle.btn-linkedin{background-image:none}
|
||||
.btn-linkedin.disabled,.btn-linkedin[disabled],fieldset[disabled] .btn-linkedin,.btn-linkedin.disabled:hover,.btn-linkedin[disabled]:hover,fieldset[disabled] .btn-linkedin:hover,.btn-linkedin.disabled:focus,.btn-linkedin[disabled]:focus,fieldset[disabled] .btn-linkedin:focus,.btn-linkedin.disabled.focus,.btn-linkedin[disabled].focus,fieldset[disabled] .btn-linkedin.focus,.btn-linkedin.disabled:active,.btn-linkedin[disabled]:active,fieldset[disabled] .btn-linkedin:active,.btn-linkedin.disabled.active,.btn-linkedin[disabled].active,fieldset[disabled] .btn-linkedin.active{background-color:#007bb6;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-linkedin .badge{color:#007bb6;background-color:#fff}
|
||||
.btn-microsoft{color:#fff;background-color:#2672ec;border-color:rgba(0,0,0,0.2)}.btn-microsoft:focus,.btn-microsoft.focus{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-microsoft:hover{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{color:#fff;background-color:#125acd;border-color:rgba(0,0,0,0.2)}.btn-microsoft:active:hover,.btn-microsoft.active:hover,.open>.dropdown-toggle.btn-microsoft:hover,.btn-microsoft:active:focus,.btn-microsoft.active:focus,.open>.dropdown-toggle.btn-microsoft:focus,.btn-microsoft:active.focus,.btn-microsoft.active.focus,.open>.dropdown-toggle.btn-microsoft.focus{color:#fff;background-color:#0f4bac;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-microsoft:active,.btn-microsoft.active,.open>.dropdown-toggle.btn-microsoft{background-image:none}
|
||||
.btn-microsoft.disabled,.btn-microsoft[disabled],fieldset[disabled] .btn-microsoft,.btn-microsoft.disabled:hover,.btn-microsoft[disabled]:hover,fieldset[disabled] .btn-microsoft:hover,.btn-microsoft.disabled:focus,.btn-microsoft[disabled]:focus,fieldset[disabled] .btn-microsoft:focus,.btn-microsoft.disabled.focus,.btn-microsoft[disabled].focus,fieldset[disabled] .btn-microsoft.focus,.btn-microsoft.disabled:active,.btn-microsoft[disabled]:active,fieldset[disabled] .btn-microsoft:active,.btn-microsoft.disabled.active,.btn-microsoft[disabled].active,fieldset[disabled] .btn-microsoft.active{background-color:#2672ec;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-microsoft .badge{color:#2672ec;background-color:#fff}
|
||||
.btn-odnoklassniki{color:#fff;background-color:#f4731c;border-color:rgba(0,0,0,0.2)}.btn-odnoklassniki:focus,.btn-odnoklassniki.focus{color:#fff;background-color:#d35b0a;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-odnoklassniki:hover{color:#fff;background-color:#d35b0a;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-odnoklassniki:active,.btn-odnoklassniki.active,.open>.dropdown-toggle.btn-odnoklassniki{color:#fff;background-color:#d35b0a;border-color:rgba(0,0,0,0.2)}.btn-odnoklassniki:active:hover,.btn-odnoklassniki.active:hover,.open>.dropdown-toggle.btn-odnoklassniki:hover,.btn-odnoklassniki:active:focus,.btn-odnoklassniki.active:focus,.open>.dropdown-toggle.btn-odnoklassniki:focus,.btn-odnoklassniki:active.focus,.btn-odnoklassniki.active.focus,.open>.dropdown-toggle.btn-odnoklassniki.focus{color:#fff;background-color:#b14c09;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-odnoklassniki:active,.btn-odnoklassniki.active,.open>.dropdown-toggle.btn-odnoklassniki{background-image:none}
|
||||
.btn-odnoklassniki.disabled,.btn-odnoklassniki[disabled],fieldset[disabled] .btn-odnoklassniki,.btn-odnoklassniki.disabled:hover,.btn-odnoklassniki[disabled]:hover,fieldset[disabled] .btn-odnoklassniki:hover,.btn-odnoklassniki.disabled:focus,.btn-odnoklassniki[disabled]:focus,fieldset[disabled] .btn-odnoklassniki:focus,.btn-odnoklassniki.disabled.focus,.btn-odnoklassniki[disabled].focus,fieldset[disabled] .btn-odnoklassniki.focus,.btn-odnoklassniki.disabled:active,.btn-odnoklassniki[disabled]:active,fieldset[disabled] .btn-odnoklassniki:active,.btn-odnoklassniki.disabled.active,.btn-odnoklassniki[disabled].active,fieldset[disabled] .btn-odnoklassniki.active{background-color:#f4731c;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-odnoklassniki .badge{color:#f4731c;background-color:#fff}
|
||||
.btn-openid{color:#fff;background-color:#f7931e;border-color:rgba(0,0,0,0.2)}.btn-openid:focus,.btn-openid.focus{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-openid:hover{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{color:#fff;background-color:#da7908;border-color:rgba(0,0,0,0.2)}.btn-openid:active:hover,.btn-openid.active:hover,.open>.dropdown-toggle.btn-openid:hover,.btn-openid:active:focus,.btn-openid.active:focus,.open>.dropdown-toggle.btn-openid:focus,.btn-openid:active.focus,.btn-openid.active.focus,.open>.dropdown-toggle.btn-openid.focus{color:#fff;background-color:#b86607;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-openid:active,.btn-openid.active,.open>.dropdown-toggle.btn-openid{background-image:none}
|
||||
.btn-openid.disabled,.btn-openid[disabled],fieldset[disabled] .btn-openid,.btn-openid.disabled:hover,.btn-openid[disabled]:hover,fieldset[disabled] .btn-openid:hover,.btn-openid.disabled:focus,.btn-openid[disabled]:focus,fieldset[disabled] .btn-openid:focus,.btn-openid.disabled.focus,.btn-openid[disabled].focus,fieldset[disabled] .btn-openid.focus,.btn-openid.disabled:active,.btn-openid[disabled]:active,fieldset[disabled] .btn-openid:active,.btn-openid.disabled.active,.btn-openid[disabled].active,fieldset[disabled] .btn-openid.active{background-color:#f7931e;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-openid .badge{color:#f7931e;background-color:#fff}
|
||||
.btn-pinterest{color:#fff;background-color:#cb2027;border-color:rgba(0,0,0,0.2)}.btn-pinterest:focus,.btn-pinterest.focus{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-pinterest:hover{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{color:#fff;background-color:#9f191f;border-color:rgba(0,0,0,0.2)}.btn-pinterest:active:hover,.btn-pinterest.active:hover,.open>.dropdown-toggle.btn-pinterest:hover,.btn-pinterest:active:focus,.btn-pinterest.active:focus,.open>.dropdown-toggle.btn-pinterest:focus,.btn-pinterest:active.focus,.btn-pinterest.active.focus,.open>.dropdown-toggle.btn-pinterest.focus{color:#fff;background-color:#801419;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-pinterest:active,.btn-pinterest.active,.open>.dropdown-toggle.btn-pinterest{background-image:none}
|
||||
.btn-pinterest.disabled,.btn-pinterest[disabled],fieldset[disabled] .btn-pinterest,.btn-pinterest.disabled:hover,.btn-pinterest[disabled]:hover,fieldset[disabled] .btn-pinterest:hover,.btn-pinterest.disabled:focus,.btn-pinterest[disabled]:focus,fieldset[disabled] .btn-pinterest:focus,.btn-pinterest.disabled.focus,.btn-pinterest[disabled].focus,fieldset[disabled] .btn-pinterest.focus,.btn-pinterest.disabled:active,.btn-pinterest[disabled]:active,fieldset[disabled] .btn-pinterest:active,.btn-pinterest.disabled.active,.btn-pinterest[disabled].active,fieldset[disabled] .btn-pinterest.active{background-color:#cb2027;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-pinterest .badge{color:#cb2027;background-color:#fff}
|
||||
.btn-reddit{color:#000;background-color:#eff7ff;border-color:rgba(0,0,0,0.2)}.btn-reddit:focus,.btn-reddit.focus{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-reddit:hover{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{color:#000;background-color:#bcddff;border-color:rgba(0,0,0,0.2)}.btn-reddit:active:hover,.btn-reddit.active:hover,.open>.dropdown-toggle.btn-reddit:hover,.btn-reddit:active:focus,.btn-reddit.active:focus,.open>.dropdown-toggle.btn-reddit:focus,.btn-reddit:active.focus,.btn-reddit.active.focus,.open>.dropdown-toggle.btn-reddit.focus{color:#000;background-color:#98ccff;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-reddit:active,.btn-reddit.active,.open>.dropdown-toggle.btn-reddit{background-image:none}
|
||||
.btn-reddit.disabled,.btn-reddit[disabled],fieldset[disabled] .btn-reddit,.btn-reddit.disabled:hover,.btn-reddit[disabled]:hover,fieldset[disabled] .btn-reddit:hover,.btn-reddit.disabled:focus,.btn-reddit[disabled]:focus,fieldset[disabled] .btn-reddit:focus,.btn-reddit.disabled.focus,.btn-reddit[disabled].focus,fieldset[disabled] .btn-reddit.focus,.btn-reddit.disabled:active,.btn-reddit[disabled]:active,fieldset[disabled] .btn-reddit:active,.btn-reddit.disabled.active,.btn-reddit[disabled].active,fieldset[disabled] .btn-reddit.active{background-color:#eff7ff;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-reddit .badge{color:#eff7ff;background-color:#000}
|
||||
.btn-soundcloud{color:#fff;background-color:#f50;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:focus,.btn-soundcloud.focus{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-soundcloud:hover{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{color:#fff;background-color:#c40;border-color:rgba(0,0,0,0.2)}.btn-soundcloud:active:hover,.btn-soundcloud.active:hover,.open>.dropdown-toggle.btn-soundcloud:hover,.btn-soundcloud:active:focus,.btn-soundcloud.active:focus,.open>.dropdown-toggle.btn-soundcloud:focus,.btn-soundcloud:active.focus,.btn-soundcloud.active.focus,.open>.dropdown-toggle.btn-soundcloud.focus{color:#fff;background-color:#a83800;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-soundcloud:active,.btn-soundcloud.active,.open>.dropdown-toggle.btn-soundcloud{background-image:none}
|
||||
.btn-soundcloud.disabled,.btn-soundcloud[disabled],fieldset[disabled] .btn-soundcloud,.btn-soundcloud.disabled:hover,.btn-soundcloud[disabled]:hover,fieldset[disabled] .btn-soundcloud:hover,.btn-soundcloud.disabled:focus,.btn-soundcloud[disabled]:focus,fieldset[disabled] .btn-soundcloud:focus,.btn-soundcloud.disabled.focus,.btn-soundcloud[disabled].focus,fieldset[disabled] .btn-soundcloud.focus,.btn-soundcloud.disabled:active,.btn-soundcloud[disabled]:active,fieldset[disabled] .btn-soundcloud:active,.btn-soundcloud.disabled.active,.btn-soundcloud[disabled].active,fieldset[disabled] .btn-soundcloud.active{background-color:#f50;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-soundcloud .badge{color:#f50;background-color:#fff}
|
||||
.btn-tumblr{color:#fff;background-color:#2c4762;border-color:rgba(0,0,0,0.2)}.btn-tumblr:focus,.btn-tumblr.focus{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-tumblr:hover{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{color:#fff;background-color:#1c2d3f;border-color:rgba(0,0,0,0.2)}.btn-tumblr:active:hover,.btn-tumblr.active:hover,.open>.dropdown-toggle.btn-tumblr:hover,.btn-tumblr:active:focus,.btn-tumblr.active:focus,.open>.dropdown-toggle.btn-tumblr:focus,.btn-tumblr:active.focus,.btn-tumblr.active.focus,.open>.dropdown-toggle.btn-tumblr.focus{color:#fff;background-color:#111c26;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-tumblr:active,.btn-tumblr.active,.open>.dropdown-toggle.btn-tumblr{background-image:none}
|
||||
.btn-tumblr.disabled,.btn-tumblr[disabled],fieldset[disabled] .btn-tumblr,.btn-tumblr.disabled:hover,.btn-tumblr[disabled]:hover,fieldset[disabled] .btn-tumblr:hover,.btn-tumblr.disabled:focus,.btn-tumblr[disabled]:focus,fieldset[disabled] .btn-tumblr:focus,.btn-tumblr.disabled.focus,.btn-tumblr[disabled].focus,fieldset[disabled] .btn-tumblr.focus,.btn-tumblr.disabled:active,.btn-tumblr[disabled]:active,fieldset[disabled] .btn-tumblr:active,.btn-tumblr.disabled.active,.btn-tumblr[disabled].active,fieldset[disabled] .btn-tumblr.active{background-color:#2c4762;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-tumblr .badge{color:#2c4762;background-color:#fff}
|
||||
.btn-twitter{color:#fff;background-color:#55acee;border-color:rgba(0,0,0,0.2)}.btn-twitter:focus,.btn-twitter.focus{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-twitter:hover{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{color:#fff;background-color:#2795e9;border-color:rgba(0,0,0,0.2)}.btn-twitter:active:hover,.btn-twitter.active:hover,.open>.dropdown-toggle.btn-twitter:hover,.btn-twitter:active:focus,.btn-twitter.active:focus,.open>.dropdown-toggle.btn-twitter:focus,.btn-twitter:active.focus,.btn-twitter.active.focus,.open>.dropdown-toggle.btn-twitter.focus{color:#fff;background-color:#1583d7;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-twitter:active,.btn-twitter.active,.open>.dropdown-toggle.btn-twitter{background-image:none}
|
||||
.btn-twitter.disabled,.btn-twitter[disabled],fieldset[disabled] .btn-twitter,.btn-twitter.disabled:hover,.btn-twitter[disabled]:hover,fieldset[disabled] .btn-twitter:hover,.btn-twitter.disabled:focus,.btn-twitter[disabled]:focus,fieldset[disabled] .btn-twitter:focus,.btn-twitter.disabled.focus,.btn-twitter[disabled].focus,fieldset[disabled] .btn-twitter.focus,.btn-twitter.disabled:active,.btn-twitter[disabled]:active,fieldset[disabled] .btn-twitter:active,.btn-twitter.disabled.active,.btn-twitter[disabled].active,fieldset[disabled] .btn-twitter.active{background-color:#55acee;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-twitter .badge{color:#55acee;background-color:#fff}
|
||||
.btn-vimeo{color:#fff;background-color:#1ab7ea;border-color:rgba(0,0,0,0.2)}.btn-vimeo:focus,.btn-vimeo.focus{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-vimeo:hover{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{color:#fff;background-color:#1295bf;border-color:rgba(0,0,0,0.2)}.btn-vimeo:active:hover,.btn-vimeo.active:hover,.open>.dropdown-toggle.btn-vimeo:hover,.btn-vimeo:active:focus,.btn-vimeo.active:focus,.open>.dropdown-toggle.btn-vimeo:focus,.btn-vimeo:active.focus,.btn-vimeo.active.focus,.open>.dropdown-toggle.btn-vimeo.focus{color:#fff;background-color:#0f7b9f;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-vimeo:active,.btn-vimeo.active,.open>.dropdown-toggle.btn-vimeo{background-image:none}
|
||||
.btn-vimeo.disabled,.btn-vimeo[disabled],fieldset[disabled] .btn-vimeo,.btn-vimeo.disabled:hover,.btn-vimeo[disabled]:hover,fieldset[disabled] .btn-vimeo:hover,.btn-vimeo.disabled:focus,.btn-vimeo[disabled]:focus,fieldset[disabled] .btn-vimeo:focus,.btn-vimeo.disabled.focus,.btn-vimeo[disabled].focus,fieldset[disabled] .btn-vimeo.focus,.btn-vimeo.disabled:active,.btn-vimeo[disabled]:active,fieldset[disabled] .btn-vimeo:active,.btn-vimeo.disabled.active,.btn-vimeo[disabled].active,fieldset[disabled] .btn-vimeo.active{background-color:#1ab7ea;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-vimeo .badge{color:#1ab7ea;background-color:#fff}
|
||||
.btn-vk{color:#fff;background-color:#587ea3;border-color:rgba(0,0,0,0.2)}.btn-vk:focus,.btn-vk.focus{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-vk:hover{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{color:#fff;background-color:#466482;border-color:rgba(0,0,0,0.2)}.btn-vk:active:hover,.btn-vk.active:hover,.open>.dropdown-toggle.btn-vk:hover,.btn-vk:active:focus,.btn-vk.active:focus,.open>.dropdown-toggle.btn-vk:focus,.btn-vk:active.focus,.btn-vk.active.focus,.open>.dropdown-toggle.btn-vk.focus{color:#fff;background-color:#3a526b;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-vk:active,.btn-vk.active,.open>.dropdown-toggle.btn-vk{background-image:none}
|
||||
.btn-vk.disabled,.btn-vk[disabled],fieldset[disabled] .btn-vk,.btn-vk.disabled:hover,.btn-vk[disabled]:hover,fieldset[disabled] .btn-vk:hover,.btn-vk.disabled:focus,.btn-vk[disabled]:focus,fieldset[disabled] .btn-vk:focus,.btn-vk.disabled.focus,.btn-vk[disabled].focus,fieldset[disabled] .btn-vk.focus,.btn-vk.disabled:active,.btn-vk[disabled]:active,fieldset[disabled] .btn-vk:active,.btn-vk.disabled.active,.btn-vk[disabled].active,fieldset[disabled] .btn-vk.active{background-color:#587ea3;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-vk .badge{color:#587ea3;background-color:#fff}
|
||||
.btn-yahoo{color:#fff;background-color:#720e9e;border-color:rgba(0,0,0,0.2)}.btn-yahoo:focus,.btn-yahoo.focus{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-yahoo:hover{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{color:#fff;background-color:#500a6f;border-color:rgba(0,0,0,0.2)}.btn-yahoo:active:hover,.btn-yahoo.active:hover,.open>.dropdown-toggle.btn-yahoo:hover,.btn-yahoo:active:focus,.btn-yahoo.active:focus,.open>.dropdown-toggle.btn-yahoo:focus,.btn-yahoo:active.focus,.btn-yahoo.active.focus,.open>.dropdown-toggle.btn-yahoo.focus{color:#fff;background-color:#39074e;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-yahoo:active,.btn-yahoo.active,.open>.dropdown-toggle.btn-yahoo{background-image:none}
|
||||
.btn-yahoo.disabled,.btn-yahoo[disabled],fieldset[disabled] .btn-yahoo,.btn-yahoo.disabled:hover,.btn-yahoo[disabled]:hover,fieldset[disabled] .btn-yahoo:hover,.btn-yahoo.disabled:focus,.btn-yahoo[disabled]:focus,fieldset[disabled] .btn-yahoo:focus,.btn-yahoo.disabled.focus,.btn-yahoo[disabled].focus,fieldset[disabled] .btn-yahoo.focus,.btn-yahoo.disabled:active,.btn-yahoo[disabled]:active,fieldset[disabled] .btn-yahoo:active,.btn-yahoo.disabled.active,.btn-yahoo[disabled].active,fieldset[disabled] .btn-yahoo.active{background-color:#720e9e;border-color:rgba(0,0,0,0.2)}
|
||||
.btn-yahoo .badge{color:#720e9e;background-color:#fff}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*! MIT License
|
||||
* Copyright (c) 2018 GitHub Inc.
|
||||
* https://github.com/primer/primitives/blob/main/LICENSE
|
||||
*/
|
||||
main {
|
||||
--color-prettylights-syntax-comment: #8b949e;
|
||||
--color-prettylights-syntax-constant: #79c0ff;
|
||||
--color-prettylights-syntax-entity: #d2a8ff;
|
||||
--color-prettylights-syntax-storage-modifier-import: #c9d1d9;
|
||||
--color-prettylights-syntax-entity-tag: #7ee787;
|
||||
--color-prettylights-syntax-keyword: #ff7b72;
|
||||
--color-prettylights-syntax-string: #a5d6ff;
|
||||
--color-prettylights-syntax-variable: #ffa657;
|
||||
--color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
|
||||
--color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
|
||||
--color-prettylights-syntax-invalid-illegal-bg: #8e1519;
|
||||
--color-prettylights-syntax-carriage-return-text: #f0f6fc;
|
||||
--color-prettylights-syntax-carriage-return-bg: #b62324;
|
||||
--color-prettylights-syntax-string-regexp: #7ee787;
|
||||
--color-prettylights-syntax-markup-list: #f2cc60;
|
||||
--color-prettylights-syntax-markup-heading: #1f6feb;
|
||||
--color-prettylights-syntax-markup-italic: #c9d1d9;
|
||||
--color-prettylights-syntax-markup-bold: #c9d1d9;
|
||||
--color-prettylights-syntax-markup-deleted-text: #ffdcd7;
|
||||
--color-prettylights-syntax-markup-deleted-bg: #67060c;
|
||||
--color-prettylights-syntax-markup-inserted-text: #aff5b4;
|
||||
--color-prettylights-syntax-markup-inserted-bg: #033a16;
|
||||
--color-prettylights-syntax-markup-changed-text: #ffdfb6;
|
||||
--color-prettylights-syntax-markup-changed-bg: #5a1e02;
|
||||
--color-prettylights-syntax-markup-ignored-text: #c9d1d9;
|
||||
--color-prettylights-syntax-markup-ignored-bg: #1158c7;
|
||||
--color-prettylights-syntax-meta-diff-range: #d2a8ff;
|
||||
--color-prettylights-syntax-brackethighlighter-angle: #8b949e;
|
||||
--color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;
|
||||
--color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
|
||||
--color-btn-text: #c9d1d9;
|
||||
--color-btn-bg: #21262d;
|
||||
--color-btn-border: #f0f6fc1a;
|
||||
--color-btn-shadow: 0 0 #0000;
|
||||
--color-btn-inset-shadow: 0 0 #0000;
|
||||
--color-btn-hover-bg: #30363d;
|
||||
--color-btn-hover-border: #8b949e;
|
||||
--color-btn-active-bg: #282e33;
|
||||
--color-btn-active-border: #6e7681;
|
||||
--color-btn-selected-bg: #161b22;
|
||||
--color-btn-primary-text: #fff;
|
||||
--color-btn-primary-bg: #238636;
|
||||
--color-btn-primary-border: #f0f6fc1a;
|
||||
--color-btn-primary-shadow: 0 0 #0000;
|
||||
--color-btn-primary-inset-shadow: 0 0 #0000;
|
||||
--color-btn-primary-hover-bg: #2ea043;
|
||||
--color-btn-primary-hover-border: #f0f6fc1a;
|
||||
--color-btn-primary-selected-bg: #238636;
|
||||
--color-btn-primary-selected-shadow: 0 0 #0000;
|
||||
--color-btn-primary-disabled-text: #ffffff80;
|
||||
--color-btn-primary-disabled-bg: #23863699;
|
||||
--color-btn-primary-disabled-border: #f0f6fc1a;
|
||||
--color-action-list-item-default-hover-bg: #b1bac41f;
|
||||
--color-segmented-control-bg: #6e76811a;
|
||||
--color-segmented-control-button-bg: #0d1117;
|
||||
--color-segmented-control-button-selected-border: #6e7681;
|
||||
--color-fg-default: #e6edf3;
|
||||
--color-fg-muted: #7d8590;
|
||||
--color-fg-subtle: #6e7681;
|
||||
--color-canvas-default: #242526;
|
||||
--color-canvas-overlay: #161b22;
|
||||
--color-canvas-inset: #151515;
|
||||
--color-canvas-subtle: #303436;
|
||||
--color-border-default: #30363d;
|
||||
--color-border-muted: #21262d;
|
||||
--color-neutral-muted: #6e768166;
|
||||
--color-accent-fg: #2f81f7;
|
||||
--color-accent-emphasis: #1f6feb;
|
||||
--color-accent-muted: #388bfd66;
|
||||
--color-accent-subtle: #388bfd1a;
|
||||
--color-success-fg: #3fb950;
|
||||
--color-attention-fg: #d29922;
|
||||
--color-attention-muted: #bb800966;
|
||||
--color-attention-subtle: #bb800926;
|
||||
--color-danger-fg: #f85149;
|
||||
--color-danger-muted: #f8514966;
|
||||
--color-danger-subtle: #f851491a;
|
||||
--color-primer-shadow-inset: 0 0 #0000;
|
||||
--color-scale-gray-7: #21262d;
|
||||
--color-scale-blue-8: #0c2d6b;
|
||||
|
||||
/*! Extensions from @primer/css/alerts/flash.scss */
|
||||
--color-social-reaction-bg-hover: var(--color-scale-gray-7);
|
||||
--color-social-reaction-bg-reacted-hover: var(--color-scale-blue-8)
|
||||
}
|
||||
|
||||
main .pagination-loader-container {
|
||||
background-image: url(https://github.com/images/modules/pulls/progressive-disclosure-line-dark.svg)
|
||||
}
|
||||
|
||||
main .gsc-loading-image {
|
||||
background-image: url(https://github.githubassets.com/images/mona-loading-dark.gif)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.page-heading {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/* https://github.com/richleland/pygments-css */
|
||||
.highlight .hll { background-color: #333333 }
|
||||
.highlight { background: #111111; color: #ffffff }
|
||||
.highlight .c { color: #008800; font-style: italic; background-color: #0f140f } /* Comment */
|
||||
.highlight .err { color: #ffffff } /* Error */
|
||||
.highlight .esc { color: #ffffff } /* Escape */
|
||||
.highlight .g { color: #ffffff } /* Generic */
|
||||
.highlight .k { color: #fb660a; font-weight: bold } /* Keyword */
|
||||
.highlight .l { color: #ffffff } /* Literal */
|
||||
.highlight .n { color: #ffffff } /* Name */
|
||||
.highlight .o { color: #ffffff } /* Operator */
|
||||
.highlight .x { color: #ffffff } /* Other */
|
||||
.highlight .p { color: #ffffff } /* Punctuation */
|
||||
.highlight .ch { color: #008800; font-style: italic; background-color: #0f140f } /* Comment.Hashbang */
|
||||
.highlight .cm { color: #008800; font-style: italic; background-color: #0f140f } /* Comment.Multiline */
|
||||
.highlight .cp { color: #ff0007; font-weight: bold; font-style: italic; background-color: #0f140f } /* Comment.Preproc */
|
||||
.highlight .cpf { color: #008800; font-style: italic; background-color: #0f140f } /* Comment.PreprocFile */
|
||||
.highlight .c1 { color: #008800; font-style: italic; background-color: #0f140f } /* Comment.Single */
|
||||
.highlight .cs { color: #008800; font-style: italic; background-color: #0f140f } /* Comment.Special */
|
||||
.highlight .gd { color: #ffffff } /* Generic.Deleted */
|
||||
.highlight .ge { color: #ffffff } /* Generic.Emph */
|
||||
.highlight .gr { color: #ffffff } /* Generic.Error */
|
||||
.highlight .gh { color: #ffffff; font-weight: bold } /* Generic.Heading */
|
||||
.highlight .gi { color: #ffffff } /* Generic.Inserted */
|
||||
.highlight .go { color: #444444; background-color: #888888 } /* Generic.Output */
|
||||
.highlight .gp { color: #ffffff } /* Generic.Prompt */
|
||||
.highlight .gs { color: #ffffff } /* Generic.Strong */
|
||||
.highlight .gu { color: #ffffff; font-weight: bold } /* Generic.Subheading */
|
||||
.highlight .gt { color: #ffffff } /* Generic.Traceback */
|
||||
.highlight .kc { color: #fb660a; font-weight: bold } /* Keyword.Constant */
|
||||
.highlight .kd { color: #fb660a; font-weight: bold } /* Keyword.Declaration */
|
||||
.highlight .kn { color: #fb660a; font-weight: bold } /* Keyword.Namespace */
|
||||
.highlight .kp { color: #fb660a } /* Keyword.Pseudo */
|
||||
.highlight .kr { color: #fb660a; font-weight: bold } /* Keyword.Reserved */
|
||||
.highlight .kt { color: #cdcaa9; font-weight: bold } /* Keyword.Type */
|
||||
.highlight .ld { color: #ffffff } /* Literal.Date */
|
||||
.highlight .m { color: #0086f7; font-weight: bold } /* Literal.Number */
|
||||
.highlight .s { color: #0086d2 } /* Literal.String */
|
||||
.highlight .na { color: #ff0086; font-weight: bold } /* Name.Attribute */
|
||||
.highlight .nb { color: #ffffff } /* Name.Builtin */
|
||||
.highlight .nc { color: #ffffff } /* Name.Class */
|
||||
.highlight .no { color: #0086d2 } /* Name.Constant */
|
||||
.highlight .nd { color: #ffffff } /* Name.Decorator */
|
||||
.highlight .ni { color: #ffffff } /* Name.Entity */
|
||||
.highlight .ne { color: #ffffff } /* Name.Exception */
|
||||
.highlight .nf { color: #ff0086; font-weight: bold } /* Name.Function */
|
||||
.highlight .nl { color: #ffffff } /* Name.Label */
|
||||
.highlight .nn { color: #ffffff } /* Name.Namespace */
|
||||
.highlight .nx { color: #ffffff } /* Name.Other */
|
||||
.highlight .py { color: #ffffff } /* Name.Property */
|
||||
.highlight .nt { color: #fb660a; font-weight: bold } /* Name.Tag */
|
||||
.highlight .nv { color: #fb660a } /* Name.Variable */
|
||||
.highlight .ow { color: #ffffff } /* Operator.Word */
|
||||
.highlight .w { color: #888888 } /* Text.Whitespace */
|
||||
.highlight .mb { color: #0086f7; font-weight: bold } /* Literal.Number.Bin */
|
||||
.highlight .mf { color: #0086f7; font-weight: bold } /* Literal.Number.Float */
|
||||
.highlight .mh { color: #0086f7; font-weight: bold } /* Literal.Number.Hex */
|
||||
.highlight .mi { color: #0086f7; font-weight: bold } /* Literal.Number.Integer */
|
||||
.highlight .mo { color: #0086f7; font-weight: bold } /* Literal.Number.Oct */
|
||||
.highlight .sa { color: #0086d2 } /* Literal.String.Affix */
|
||||
.highlight .sb { color: #0086d2 } /* Literal.String.Backtick */
|
||||
.highlight .sc { color: #0086d2 } /* Literal.String.Char */
|
||||
.highlight .dl { color: #0086d2 } /* Literal.String.Delimiter */
|
||||
.highlight .sd { color: #0086d2 } /* Literal.String.Doc */
|
||||
.highlight .s2 { color: #0086d2 } /* Literal.String.Double */
|
||||
.highlight .se { color: #0086d2 } /* Literal.String.Escape */
|
||||
.highlight .sh { color: #0086d2 } /* Literal.String.Heredoc */
|
||||
.highlight .si { color: #0086d2 } /* Literal.String.Interpol */
|
||||
.highlight .sx { color: #0086d2 } /* Literal.String.Other */
|
||||
.highlight .sr { color: #0086d2 } /* Literal.String.Regex */
|
||||
.highlight .s1 { color: #0086d2 } /* Literal.String.Single */
|
||||
.highlight .ss { color: #0086d2 } /* Literal.String.Symbol */
|
||||
.highlight .bp { color: #ffffff } /* Name.Builtin.Pseudo */
|
||||
.highlight .fm { color: #ff0086; font-weight: bold } /* Name.Function.Magic */
|
||||
.highlight .vc { color: #fb660a } /* Name.Variable.Class */
|
||||
.highlight .vg { color: #fb660a } /* Name.Variable.Global */
|
||||
.highlight .vi { color: #fb660a } /* Name.Variable.Instance */
|
||||
.highlight .vm { color: #fb660a } /* Name.Variable.Magic */
|
||||
.highlight .il { color: #0086f7; font-weight: bold } /* Literal.Number.Integer.Long */
|
||||
@@ -0,0 +1,3 @@
|
||||
#nav-search-link {
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/* ==========================================================================
|
||||
Forms
|
||||
========================================================================== */
|
||||
.staticman-comments form {
|
||||
margin: 0 0 5px 0;
|
||||
padding: 1em;
|
||||
background-color: #f2f3f3;
|
||||
}
|
||||
.staticman-comments form p {
|
||||
margin-bottom: 2.5px;
|
||||
}
|
||||
.staticman-comments form br {
|
||||
display: none;
|
||||
}
|
||||
.staticman-comments label, .staticman-comments input, .staticman-comments button, .staticman-comments textarea {
|
||||
vertical-align: baseline;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.staticman-comments input, .staticman-comments button, .staticman-comments textarea {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.staticman-comments label {
|
||||
display: block;
|
||||
margin-bottom: 0.25em;
|
||||
color: #494e52;
|
||||
cursor: pointer;
|
||||
}
|
||||
.staticman-comments label small {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
.staticman-comments label input, .staticman-comments label textarea {
|
||||
display: block;
|
||||
}
|
||||
.staticman-comments input, .staticman-comments textarea {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 0.25em;
|
||||
margin-bottom: 0.5em;
|
||||
color: #494e52;
|
||||
background-color: #fff;
|
||||
border: #f2f3f3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
.staticman-comments .input-mini {
|
||||
width: 60px;
|
||||
}
|
||||
.staticman-comments .input-small {
|
||||
width: 90px;
|
||||
}
|
||||
.staticman-comments input[type="button"], .staticman-comments input[type="reset"], .staticman-comments input[type="submit"] {
|
||||
width: auto;
|
||||
height: auto;
|
||||
cursor: pointer;
|
||||
overflow: visible;
|
||||
}
|
||||
.staticman-comments textarea {
|
||||
resize: vertical;
|
||||
height: auto;
|
||||
overflow: auto;
|
||||
vertical-align: top;
|
||||
}
|
||||
.staticman-comments input[type="hidden"] {
|
||||
display: none;
|
||||
}
|
||||
.staticman-comments .form {
|
||||
position: relative;
|
||||
}
|
||||
/*
|
||||
Focus & active state
|
||||
========================================================================== */
|
||||
.staticman-comments input:focus, .staticman-comments textarea:focus {
|
||||
border-color: #7a8288;
|
||||
outline: 0;
|
||||
outline: thin dotted \9;
|
||||
box-shadow: inset 0 1px 3px rgba(73, 78, 82, 0.06), 0 0 5px rgba(122, 130, 136, 0.7);
|
||||
}
|
||||
.staticman-comments input[type="file"]:focus, .staticman-comments input[type="radio"]:focus, .staticman-comments input[type="checkbox"]:focus:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
/*
|
||||
Help text
|
||||
========================================================================== */
|
||||
.staticman-comments .form-text {
|
||||
color: #898c8e;
|
||||
}
|
||||
.staticman-comments .form-text {
|
||||
display: block;
|
||||
margin-bottom: 1em;
|
||||
line-height: 1em;
|
||||
}
|
||||
/*
|
||||
.form-group
|
||||
========================================================================== */
|
||||
.staticman-comments .form-group {
|
||||
margin-bottom: 5px;
|
||||
padding: 0;
|
||||
border-width: 0;
|
||||
}
|
||||
/*
|
||||
Comments
|
||||
========================================================================== */
|
||||
.staticman-comments .page__comments {
|
||||
float: left;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
width: 100%;
|
||||
clear: both;
|
||||
}
|
||||
.staticman-comments .page__comments-title {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 10px;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid #f2f3f3;
|
||||
}
|
||||
.staticman-comments .page__comments-form {
|
||||
-webkit-transition: all 0.2s ease-in-out;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
.staticman-comments .page__comments-form.disabled input, .staticman-comments .page__comments-form.disabled button, .staticman-comments .page__comments-form.disabled textarea, .staticman-comments .page__comments-form.disabled label {
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
filter: alpha(opacity=65);
|
||||
box-shadow: none;
|
||||
opacity: 0.65;
|
||||
}
|
||||
.staticman-comments .comment {
|
||||
margin: 1em 0;
|
||||
}
|
||||
.staticman-comments .comment:not(:last-child) {
|
||||
border-bottom: 1px solid #f2f3f3;
|
||||
}
|
||||
.staticman-comments .comment__avatar-wrapper {
|
||||
float: left;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.staticman-comments .comment__avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.staticman-comments .comment__content-wrapper {
|
||||
width: calc(100% - 60px);
|
||||
}
|
||||
.staticman-comments .comment__author {
|
||||
margin: 0;
|
||||
}
|
||||
.staticman-comments .comment__author a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.staticman-comments .comment__date {
|
||||
margin: 0;
|
||||
}
|
||||
.staticman-comments .comment__date a {
|
||||
text-decoration: none;
|
||||
}
|
||||
/* ==========================================================================
|
||||
PRINT STYLES
|
||||
========================================================================== */
|
||||
.staticman-comments @media print {
|
||||
/*
|
||||
Hide the following elements on print
|
||||
========================================================================== */
|
||||
.staticman-comments [hidden] {
|
||||
display: none;
|
||||
}
|
||||
.staticman-comments .masthead, .staticman-comments .toc, .staticman-comments .page__share, .staticman-comments .page__related, .staticman-comments .pagination, .staticman-comments .ads, .staticman-comments .page__footer, .staticman-comments .page__comments-form, .staticman-comments .author__avatar, .staticman-comments .author__content, .staticman-comments .author__urls-wrapper, .staticman-comments .nav__list, .staticman-comments .sidebar, .staticman-comments .adsbygoogle {
|
||||
display: none !important;
|
||||
height: 1px !important;
|
||||
}
|
||||
}
|
||||
/*
|
||||
Post pagination navigation links
|
||||
========================================================================== */
|
||||
.staticman-comments .page__comments + .staticman-comments .pagination {
|
||||
margin-top: 2em;
|
||||
padding-top: 2em;
|
||||
border-top: 1px solid #f2f3f3;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
[
|
||||
|
||||
|
||||
{
|
||||
"title" : "Sunshine",
|
||||
"desc" : "Sunshine",
|
||||
"category" : "page",
|
||||
"url" : "/",
|
||||
"date" : "January 1, 1970"
|
||||
},
|
||||
|
||||
{
|
||||
"title" : "Blog Index",
|
||||
"desc" : "Blog Index",
|
||||
"category" : "page",
|
||||
"url" : "/tags/",
|
||||
"date" : "January 1, 1970"
|
||||
}
|
||||
|
||||
]
|
||||
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 5.5 MiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 6.2 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 5.9 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 5.0 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 662 KiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 850 KiB |
|
After Width: | Height: | Size: 4.2 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="align-left" class="svg-inline--fa fa-align-left fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M12.83 352h262.34A12.82 12.82 0 0 0 288 339.17v-38.34A12.82 12.82 0 0 0 275.17 288H12.83A12.82 12.82 0 0 0 0 300.83v38.34A12.82 12.82 0 0 0 12.83 352zm0-256h262.34A12.82 12.82 0 0 0 288 83.17V44.83A12.82 12.82 0 0 0 275.17 32H12.83A12.82 12.82 0 0 0 0 44.83v38.34A12.82 12.82 0 0 0 12.83 96zM432 160H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0 256H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z"/></svg>
|
||||
|
After Width: | Height: | Size: 713 B |
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="bolt" class="svg-inline--fa fa-bolt fa-w-10" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path fill="currentColor" d="M296 160H180.6l42.6-129.8C227.2 15 215.7 0 200 0H56C44 0 33.8 8.9 32.2 20.8l-32 240C-1.7 275.2 9.5 288 24 288h118.7L96.6 482.5c-3.6 15.2 8 29.5 23.3 29.5 8.4 0 16.4-4.4 20.8-12l176-304c9.3-15.9-2.2-36-20.7-36z"/></svg>
|
||||
|
After Width: | Height: | Size: 431 B |
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="bug" class="svg-inline--fa fa-bug fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M511.988 288.9c-.478 17.43-15.217 31.1-32.653 31.1H424v16c0 21.864-4.882 42.584-13.6 61.145l60.228 60.228c12.496 12.497 12.496 32.758 0 45.255-12.498 12.497-32.759 12.496-45.256 0l-54.736-54.736C345.886 467.965 314.351 480 280 480V236c0-6.627-5.373-12-12-12h-24c-6.627 0-12 5.373-12 12v244c-34.351 0-65.886-12.035-90.636-32.108l-54.736 54.736c-12.498 12.497-32.759 12.496-45.256 0-12.496-12.497-12.496-32.758 0-45.255l60.228-60.228C92.882 378.584 88 357.864 88 336v-16H32.666C15.23 320 .491 306.33.013 288.9-.484 270.816 14.028 256 32 256h56v-58.745l-46.628-46.628c-12.496-12.497-12.496-32.758 0-45.255 12.498-12.497 32.758-12.497 45.256 0L141.255 160h229.489l54.627-54.627c12.498-12.497 32.758-12.497 45.256 0 12.496 12.497 12.496 32.758 0 45.255L424 197.255V256h56c17.972 0 32.484 14.816 31.988 32.9zM257 0c-61.856 0-112 50.144-112 112h224C369 50.144 318.856 0 257 0z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="check-circle" class="svg-inline--fa fa-check-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"/></svg>
|
||||
|
After Width: | Height: | Size: 594 B |
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="exclamation-triangle" class="svg-inline--fa fa-exclamation-triangle fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/></svg>
|
||||
|
After Width: | Height: | Size: 702 B |
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="fire" class="svg-inline--fa fa-fire fa-w-12" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M216 23.86c0-23.8-30.65-32.77-44.15-13.04C48 191.85 224 200 224 288c0 35.63-29.11 64.46-64.85 63.99-35.17-.45-63.15-29.77-63.15-64.94v-85.51c0-21.7-26.47-32.23-41.43-16.5C27.8 213.16 0 261.33 0 320c0 105.87 86.13 192 192 192s192-86.13 192-192c0-170.29-168-193-168-296.14z"/></svg>
|
||||
|
After Width: | Height: | Size: 493 B |
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="info-circle" class="svg-inline--fa fa-info-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"/></svg>
|
||||
|
After Width: | Height: | Size: 635 B |
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="list-ol" class="svg-inline--fa fa-list-ol fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="pen" class="svg-inline--fa fa-pen fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M290.74 93.24l128.02 128.02-277.99 277.99-114.14 12.6C11.35 513.54-1.56 500.62.14 485.34l12.7-114.22 277.9-277.88zm207.2-19.06l-60.11-60.11c-18.75-18.75-49.16-18.75-67.91 0l-56.55 56.55 128.02 128.02 56.55-56.55c18.75-18.76 18.75-49.16 0-67.91z"/></svg>
|
||||
|
After Width: | Height: | Size: 464 B |
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="question-circle" class="svg-inline--fa fa-question-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zM262.655 90c-54.497 0-89.255 22.957-116.549 63.758-3.536 5.286-2.353 12.415 2.715 16.258l34.699 26.31c5.205 3.947 12.621 3.008 16.665-2.122 17.864-22.658 30.113-35.797 57.303-35.797 20.429 0 45.698 13.148 45.698 32.958 0 14.976-12.363 22.667-32.534 33.976C247.128 238.528 216 254.941 216 296v4c0 6.627 5.373 12 12 12h56c6.627 0 12-5.373 12-12v-1.333c0-28.462 83.186-29.647 83.186-106.667 0-58.002-60.165-102-116.531-102zM256 338c-25.365 0-46 20.635-46 46 0 25.364 20.635 46 46 46s46-20.636 46-46c0-25.365-20.635-46-46-46z"/></svg>
|
||||
|
After Width: | Height: | Size: 866 B |
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="quote-right" class="svg-inline--fa fa-quote-right fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M464 32H336c-26.5 0-48 21.5-48 48v128c0 26.5 21.5 48 48 48h80v64c0 35.3-28.7 64-64 64h-8c-13.3 0-24 10.7-24 24v48c0 13.3 10.7 24 24 24h8c88.4 0 160-71.6 160-160V80c0-26.5-21.5-48-48-48zm-288 0H48C21.5 32 0 53.5 0 80v128c0 26.5 21.5 48 48 48h80v64c0 35.3-28.7 64-64 64h-8c-13.3 0-24 10.7-24 24v48c0 13.3 10.7 24 24 24h8c88.4 0 160-71.6 160-160V80c0-26.5-21.5-48-48-48z"/></svg>
|
||||
|
After Width: | Height: | Size: 603 B |
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="times-circle" class="svg-inline--fa fa-times-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z"/></svg>
|
||||
|
After Width: | Height: | Size: 613 B |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 262 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 387 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
@@ -0,0 +1,153 @@
|
||||
// Dean Attali / Beautiful Jekyll 2023
|
||||
|
||||
let BeautifulJekyllJS = {
|
||||
|
||||
bigImgEl: null,
|
||||
numImgs: null,
|
||||
|
||||
init: function() {
|
||||
setTimeout(BeautifulJekyllJS.initNavbar, 10);
|
||||
|
||||
let navbar = document.querySelector(".navbar");
|
||||
|
||||
// Shorten the navbar after scrolling a little bit down
|
||||
window.addEventListener('scroll', function() {
|
||||
if (window.scrollY > 50) {
|
||||
navbar.classList.add("top-nav-short");
|
||||
} else {
|
||||
navbar.classList.remove("top-nav-short");
|
||||
}
|
||||
});
|
||||
|
||||
// On mobile, hide the avatar when expanding the navbar menu
|
||||
document.getElementById('main-navbar').addEventListener('show.bs.collapse', function() {
|
||||
navbar.classList.add("top-nav-expanded");
|
||||
});
|
||||
document.getElementById('main-navbar').addEventListener('hidden.bs.collapse', function() {
|
||||
navbar.classList.remove("top-nav-expanded");
|
||||
});
|
||||
|
||||
// show the big header image
|
||||
BeautifulJekyllJS.initImgs();
|
||||
|
||||
BeautifulJekyllJS.initSearch();
|
||||
},
|
||||
|
||||
initNavbar: function() {
|
||||
// Set the navbar-dark/light class based on its background color
|
||||
const rgb = getComputedStyle(document.querySelector('.navbar')).backgroundColor.replace(/[^\d,]/g, '').split(",");
|
||||
const brightness = Math.round(( // http://www.w3.org/TR/AERT#color-contrast
|
||||
parseInt(rgb[0]) * 299 +
|
||||
parseInt(rgb[1]) * 587 +
|
||||
parseInt(rgb[2]) * 114
|
||||
) / 1000);
|
||||
|
||||
let navbar = document.querySelector(".navbar");
|
||||
if (brightness <= 125) {
|
||||
navbar.classList.remove("navbar-light");
|
||||
navbar.classList.add("navbar-dark");
|
||||
} else {
|
||||
navbar.classList.remove("navbar-dark");
|
||||
navbar.classList.add("navbar-light");
|
||||
}
|
||||
},
|
||||
|
||||
initImgs: function() {
|
||||
// If the page has large images to randomly select from, choose an image
|
||||
if (document.getElementById("header-big-imgs")) {
|
||||
BeautifulJekyllJS.bigImgEl = document.getElementById("header-big-imgs");
|
||||
BeautifulJekyllJS.numImgs = BeautifulJekyllJS.bigImgEl.getAttribute("data-num-img");
|
||||
|
||||
// 2fc73a3a967e97599c9763d05e564189
|
||||
// set an initial image
|
||||
const imgInfo = BeautifulJekyllJS.getImgInfo();
|
||||
const src = imgInfo.src;
|
||||
const desc = imgInfo.desc;
|
||||
BeautifulJekyllJS.setImg(src, desc);
|
||||
|
||||
// For better UX, prefetch the next image so that it will already be loaded when we want to show it
|
||||
const getNextImg = function() {
|
||||
const imgInfo = BeautifulJekyllJS.getImgInfo();
|
||||
const src = imgInfo.src;
|
||||
const desc = imgInfo.desc;
|
||||
|
||||
const prefetchImg = new Image();
|
||||
prefetchImg.src = src;
|
||||
// if I want to do something once the image is ready: `prefetchImg.onload = function(){}`
|
||||
|
||||
setTimeout(function() {
|
||||
const img = document.createElement("div");
|
||||
img.className = "big-img-transition";
|
||||
img.style.backgroundImage = 'url(' + src + ')';
|
||||
document.querySelector(".intro-header.big-img").prepend(img);
|
||||
setTimeout(function() { img.style.opacity = "1"; }, 50);
|
||||
|
||||
// after the animation of fading in the new image is done, prefetch the next one
|
||||
setTimeout(function() {
|
||||
BeautifulJekyllJS.setImg(src, desc);
|
||||
img.remove();
|
||||
getNextImg();
|
||||
}, 1000);
|
||||
}, 6000);
|
||||
};
|
||||
|
||||
// If there are multiple images, cycle through them
|
||||
if (BeautifulJekyllJS.numImgs > 1) {
|
||||
getNextImg();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getImgInfo: function() {
|
||||
const randNum = Math.floor((Math.random() * BeautifulJekyllJS.numImgs) + 1);
|
||||
const src = BeautifulJekyllJS.bigImgEl.getAttribute("data-img-src-" + randNum);
|
||||
const desc = BeautifulJekyllJS.bigImgEl.getAttribute("data-img-desc-" + randNum);
|
||||
|
||||
return {
|
||||
src: src,
|
||||
desc: desc
|
||||
}
|
||||
},
|
||||
|
||||
setImg: function(src, desc) {
|
||||
document.querySelector(".intro-header.big-img").style.backgroundImage = 'url(' + src + ')';
|
||||
|
||||
let imgDesc = document.querySelector(".img-desc");
|
||||
if (typeof desc !== typeof undefined && desc !== false && desc !== null) {
|
||||
imgDesc.textContent = desc;
|
||||
imgDesc.style.display = "block";
|
||||
} else {
|
||||
imgDesc.style.display = "none";
|
||||
}
|
||||
},
|
||||
|
||||
initSearch: function() {
|
||||
if (!document.getElementById("beautifuljekyll-search-overlay")) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById("nav-search-link").addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
document.getElementById("beautifuljekyll-search-overlay").style.display = "block";
|
||||
const searchInput = document.getElementById("nav-search-input");
|
||||
searchInput.focus();
|
||||
searchInput.select();
|
||||
document.body.classList.add("overflow-hidden");
|
||||
});
|
||||
document.getElementById("nav-search-exit").addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
document.getElementById("beautifuljekyll-search-overlay").style.display = "none";
|
||||
document.body.classList.remove("overflow-hidden");
|
||||
});
|
||||
document.addEventListener('keyup', function(e) {
|
||||
if (e.key === "Escape") {
|
||||
document.getElementById("beautifuljekyll-search-overlay").style.display = "none";
|
||||
document.body.classList.remove("overflow-hidden");
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 2fc73a3a967e97599c9763d05e564189
|
||||
|
||||
document.addEventListener('DOMContentLoaded', BeautifulJekyllJS.init);
|
||||
@@ -0,0 +1 @@
|
||||
window.initCrowdIn('LizardByte', null);
|
||||
@@ -0,0 +1 @@
|
||||
window.initDiscord();
|
||||
@@ -0,0 +1,498 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const gamepadHelper = new GamepadHelper()
|
||||
const gamepadHelperVersion = window.gamepadHelperVersion;
|
||||
let gamepads = {};
|
||||
let activeGamepadIndex = null;
|
||||
let animationFrameId = null;
|
||||
let gamepadSelector = document.getElementById('gamepad-selector');
|
||||
let gamepadSelectorContainer = document.getElementById('gamepad-selector-container');
|
||||
let gamepadInfoSection = document.getElementById('gamepad-info');
|
||||
let gamepadStatus = document.getElementById('gamepad-status');
|
||||
|
||||
// Check if the Gamepad API is supported
|
||||
if (!gamepadHelper.isSupported()) {
|
||||
gamepadStatus.textContent = 'Gamepad API not supported in this browser';
|
||||
gamepadStatus.classList.remove('alert-warning');
|
||||
gamepadStatus.classList.add('alert-danger');
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup gamepad event listeners
|
||||
window.addEventListener("gamepadconnected", function(e) {
|
||||
gamepads[e.gamepad.index] = e.gamepad;
|
||||
updateGamepadSelector();
|
||||
updateStatus(`Gamepad ${e.gamepad.id} connected`);
|
||||
|
||||
// If this is the first gamepad, activate it
|
||||
if (activeGamepadIndex === null) {
|
||||
activeGamepadIndex = e.gamepad.index;
|
||||
gamepadSelector.value = activeGamepadIndex;
|
||||
startGamepadLoop();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("gamepaddisconnected", function(e) {
|
||||
delete gamepads[e.gamepad.index];
|
||||
updateGamepadSelector();
|
||||
|
||||
updateStatus(`Gamepad ${e.gamepad.id} disconnected`);
|
||||
|
||||
// If the active gamepad was disconnected
|
||||
if (activeGamepadIndex === e.gamepad.index) {
|
||||
activeGamepadIndex = null;
|
||||
|
||||
// If there are other gamepads, select the first one
|
||||
const remainingIndices = Object.keys(gamepads);
|
||||
if (remainingIndices.length > 0) {
|
||||
activeGamepadIndex = parseInt(remainingIndices[0]);
|
||||
gamepadSelector.value = activeGamepadIndex;
|
||||
} else {
|
||||
stopGamepadLoop();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Event listener for gamepad selector change
|
||||
gamepadSelector.addEventListener('change', function() {
|
||||
activeGamepadIndex = parseInt(this.value);
|
||||
initGamepadButtons();
|
||||
initGamepadAxes();
|
||||
});
|
||||
|
||||
// Event listeners for vibration controls
|
||||
document.getElementById('vibrate-btn').addEventListener('click', function() {
|
||||
vibrateGamepad();
|
||||
});
|
||||
|
||||
document.getElementById('stop-vibration-btn').addEventListener('click', function() {
|
||||
stopVibration();
|
||||
});
|
||||
|
||||
document.getElementById('vibration-weak').addEventListener('input', function() {
|
||||
document.getElementById('weak-value').textContent = this.value;
|
||||
});
|
||||
|
||||
document.getElementById('vibration-strong').addEventListener('input', function() {
|
||||
document.getElementById('strong-value').textContent = this.value;
|
||||
});
|
||||
|
||||
// Update gamepad selector dropdown
|
||||
function updateGamepadSelector() {
|
||||
gamepadSelector.innerHTML = '';
|
||||
|
||||
const gamepadIndices = Object.keys(gamepads);
|
||||
|
||||
if (gamepadIndices.length > 0) {
|
||||
gamepadSelectorContainer.style.display = 'block';
|
||||
gamepadInfoSection.style.display = 'flex';
|
||||
|
||||
gamepadIndices.forEach(index => {
|
||||
const option = document.createElement('option');
|
||||
option.value = index;
|
||||
option.textContent = `${gamepads[index].id} (Index: ${index})`;
|
||||
gamepadSelector.appendChild(option);
|
||||
});
|
||||
|
||||
// Select the active gamepad
|
||||
if (activeGamepadIndex !== null) {
|
||||
gamepadSelector.value = activeGamepadIndex;
|
||||
}
|
||||
|
||||
// Initialize buttons and axes for the selected gamepad
|
||||
initGamepadButtons();
|
||||
initGamepadAxes();
|
||||
} else {
|
||||
gamepadSelectorContainer.style.display = 'none';
|
||||
gamepadInfoSection.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize gamepad button UI elements
|
||||
function initGamepadButtons() {
|
||||
const buttonsContainer = document.getElementById('buttons-container');
|
||||
buttonsContainer.innerHTML = '';
|
||||
|
||||
if (activeGamepadIndex === null) return;
|
||||
|
||||
const gamepad = navigator.getGamepads()[activeGamepadIndex];
|
||||
if (!gamepad) return;
|
||||
|
||||
const controllerType = gamepadHelper.detectControllerType(gamepad.id);
|
||||
|
||||
for (let i = 0; i < gamepad.buttons.length; i++) {
|
||||
const buttonName = gamepadHelper.getButtonName(controllerType, i);
|
||||
const buttonImagePath = gamepadHelper.getButtonImagePath(
|
||||
controllerType,
|
||||
i,
|
||||
`https://cdn.jsdelivr.net/npm/@lizardbyte/gamepad-helper@${gamepadHelperVersion}/assets/img/gamepads/`,
|
||||
);
|
||||
|
||||
const buttonDiv = document.createElement('div');
|
||||
buttonDiv.className = 'circular-button';
|
||||
buttonDiv.id = `button-${i}`;
|
||||
|
||||
// Create the progress elements
|
||||
const progressLeft = document.createElement('span');
|
||||
progressLeft.className = 'progress-left';
|
||||
progressLeft.innerHTML = `<span class="progress-bar" id="progress-bar-left-${i}"></span>`;
|
||||
|
||||
const progressRight = document.createElement('span');
|
||||
progressRight.className = 'progress-right';
|
||||
progressRight.innerHTML = `<span class="progress-bar" id="progress-bar-right-${i}"></span>`;
|
||||
|
||||
// Create the button content
|
||||
const buttonContent = document.createElement('div');
|
||||
buttonContent.className = 'button-content';
|
||||
|
||||
// Add either image with fallback text, or just text
|
||||
if (buttonImagePath) {
|
||||
buttonContent.innerHTML = `
|
||||
<div class="button-image-container">
|
||||
<img src="${buttonImagePath}" alt="${buttonName}" class="button-image" onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
|
||||
<div class="button-name" style="display: none;">${buttonName}</div>
|
||||
</div>
|
||||
<div class="button-value" id="button-value-${i}">0.00</div>
|
||||
`;
|
||||
} else {
|
||||
buttonContent.innerHTML = `
|
||||
<div class="button-name">${buttonName}</div>
|
||||
<div class="button-value" id="button-value-${i}">0.00</div>
|
||||
`;
|
||||
}
|
||||
|
||||
buttonDiv.appendChild(progressLeft);
|
||||
buttonDiv.appendChild(progressRight);
|
||||
buttonDiv.appendChild(buttonContent);
|
||||
|
||||
buttonsContainer.appendChild(buttonDiv);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize gamepad axes UI elements
|
||||
function initGamepadAxes() {
|
||||
const axesContainer = document.getElementById('axes-container');
|
||||
axesContainer.innerHTML = '';
|
||||
|
||||
if (activeGamepadIndex === null) return;
|
||||
|
||||
const gamepad = navigator.getGamepads()[activeGamepadIndex];
|
||||
if (!gamepad) return;
|
||||
|
||||
const controllerType = gamepadHelper.detectControllerType(gamepad.id);
|
||||
|
||||
for (let i = 0; i < gamepad.axes.length; i++) {
|
||||
const colDiv = document.createElement('div');
|
||||
colDiv.className = 'col-md-6 col-lg-3 mb-3';
|
||||
|
||||
const axisName = gamepadHelper.getAxisName(controllerType, i);
|
||||
|
||||
colDiv.innerHTML = `
|
||||
<div class="mb-1">${axisName}: <span id="axis-value-${i}">0.00</span></div>
|
||||
<div class="progress" style="height: 20px">
|
||||
<div id="axis-progress-${i}" class="progress-bar bg-info" role="progressbar"
|
||||
style="width: 50%;" aria-valuenow="0" aria-valuemin="-1" aria-valuemax="1"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
axesContainer.appendChild(colDiv);
|
||||
}
|
||||
}
|
||||
|
||||
// Update gamepad status message
|
||||
function updateStatus(message) {
|
||||
gamepadStatus.textContent = message;
|
||||
|
||||
// Update classes based on if we have a gamepad
|
||||
if (Object.keys(gamepads).length > 0) {
|
||||
gamepadStatus.classList.remove('alert-warning', 'alert-danger');
|
||||
gamepadStatus.classList.add('alert-success');
|
||||
} else {
|
||||
gamepadStatus.classList.remove('alert-success', 'alert-danger');
|
||||
gamepadStatus.classList.add('alert-warning');
|
||||
}
|
||||
}
|
||||
|
||||
// Update the gamepad info function to include vibration status
|
||||
function updateGamepadInfo(gamepad) {
|
||||
const gamepadInfo = gamepadHelper.getGamepadInfo(gamepad.id);
|
||||
const vibrationCapabilities = gamepadHelper.getVibrationCapabilities(gamepad);
|
||||
|
||||
document.getElementById('gamepad-id').textContent = gamepad.id;
|
||||
document.getElementById('gamepad-index').textContent = gamepad.index;
|
||||
document.getElementById('gamepad-connected').textContent = gamepad.connected;
|
||||
document.getElementById('gamepad-mapping').textContent = gamepad.mapping || 'No mapping';
|
||||
document.getElementById('gamepad-buttons-count').textContent = gamepad.buttons.length;
|
||||
document.getElementById('gamepad-axes-count').textContent = gamepad.axes.length;
|
||||
document.getElementById('gamepad-type').textContent = gamepadInfo.type;
|
||||
document.getElementById('gamepad-name').textContent = gamepadInfo.name;
|
||||
|
||||
// Update vibration controls based on capabilities
|
||||
updateVibrationControls(vibrationCapabilities);
|
||||
}
|
||||
|
||||
// Update vibration controls based on device capabilities
|
||||
function updateVibrationControls(vibrationCapabilities) {
|
||||
const vibrationStatus = document.getElementById('vibration-status');
|
||||
const dualRumbleControls = document.getElementById('dual-rumble-controls');
|
||||
const simpleVibrationControls = document.getElementById('simple-vibration-controls');
|
||||
const vibrationDurationControls = document.getElementById('vibration-duration-controls');
|
||||
const vibrationButtons = document.getElementById('vibration-buttons');
|
||||
const vibrationUnsupported = document.getElementById('vibration-unsupported');
|
||||
|
||||
// Hide all controls first
|
||||
dualRumbleControls.classList.add('d-none');
|
||||
simpleVibrationControls.classList.add('d-none');
|
||||
vibrationDurationControls.classList.add('d-none');
|
||||
vibrationButtons.classList.add('d-none');
|
||||
vibrationUnsupported.classList.add('d-none');
|
||||
|
||||
if (!vibrationCapabilities.supported) {
|
||||
vibrationStatus.textContent = '';
|
||||
vibrationUnsupported.classList.remove('d-none');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show information about the actuator type
|
||||
vibrationStatus.innerHTML = `<span class="badge bg-success">Supported</span> Actuator type: <span class="badge bg-secondary">${vibrationCapabilities.type}</span>`;
|
||||
vibrationButtons.classList.remove('d-none');
|
||||
|
||||
// Show appropriate controls based on actuator type
|
||||
if (vibrationCapabilities.type === 'dual-rumble') {
|
||||
dualRumbleControls.classList.remove('d-none');
|
||||
} else {
|
||||
simpleVibrationControls.classList.remove('d-none');
|
||||
}
|
||||
|
||||
// Show duration controls for all types
|
||||
vibrationDurationControls.classList.remove('d-none');
|
||||
}
|
||||
|
||||
// Update button UI states
|
||||
function updateButtons(gamepad) {
|
||||
for (let i = 0; i < gamepad.buttons.length; i++) {
|
||||
const button = gamepad.buttons[i];
|
||||
const buttonElement = document.getElementById(`button-${i}`);
|
||||
const buttonValueElement = document.getElementById(`button-value-${i}`);
|
||||
const progressBarLeftElement = document.getElementById(`progress-bar-left-${i}`);
|
||||
const progressBarRightElement = document.getElementById(`progress-bar-right-${i}`);
|
||||
|
||||
if (buttonElement && progressBarLeftElement && progressBarRightElement && buttonValueElement) {
|
||||
const value = button.value;
|
||||
const isPressed = button.pressed || value > 0.1;
|
||||
|
||||
// Update the value display
|
||||
buttonValueElement.textContent = value.toFixed(2);
|
||||
|
||||
// Calculate rotation degrees based on value (0 to 1)
|
||||
// For a full circle: right part goes from 0 to 180 degrees, left part from 0 to 180 degrees
|
||||
const degrees = value * 360;
|
||||
|
||||
// Reset transforms
|
||||
progressBarRightElement.style.transform = 'rotate(0deg)';
|
||||
progressBarLeftElement.style.transform = 'rotate(0deg)';
|
||||
|
||||
if (degrees <= 180) {
|
||||
// Only the right side rotates for the first half
|
||||
progressBarRightElement.style.transform = `rotate(${degrees}deg)`;
|
||||
} else {
|
||||
// Right side is at full rotation, left side rotates for the remainder
|
||||
progressBarRightElement.style.transform = 'rotate(180deg)';
|
||||
progressBarLeftElement.style.transform = `rotate(${degrees - 180}deg)`;
|
||||
}
|
||||
|
||||
// Add/remove active class based on button state
|
||||
if (isPressed) {
|
||||
buttonElement.classList.add('active');
|
||||
} else {
|
||||
buttonElement.classList.remove('active');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update axes UI states
|
||||
function updateAxes(gamepad) {
|
||||
for (let i = 0; i < gamepad.axes.length; i++) {
|
||||
const axisValue = gamepad.axes[i];
|
||||
const axisValueElement = document.getElementById(`axis-value-${i}`);
|
||||
const axisProgressElement = document.getElementById(`axis-progress-${i}`);
|
||||
|
||||
if (axisValueElement && axisProgressElement) {
|
||||
// Display the value
|
||||
axisValueElement.textContent = axisValue.toFixed(2);
|
||||
|
||||
// Update the progress bar
|
||||
// Convert -1 to 1 to 0 to 100 for the progress bar
|
||||
const progressWidth = ((axisValue + 1) / 2) * 100;
|
||||
axisProgressElement.style.width = `${progressWidth}%`;
|
||||
|
||||
// Change color based on direction
|
||||
if (axisValue > 0.1) {
|
||||
axisProgressElement.classList.remove('bg-info', 'bg-danger');
|
||||
axisProgressElement.classList.add('bg-success');
|
||||
} else if (axisValue < -0.1) {
|
||||
axisProgressElement.classList.remove('bg-info', 'bg-success');
|
||||
axisProgressElement.classList.add('bg-danger');
|
||||
} else {
|
||||
axisProgressElement.classList.remove('bg-success', 'bg-danger');
|
||||
axisProgressElement.classList.add('bg-info');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update stick visualizations
|
||||
updateStickVisuals(gamepad);
|
||||
}
|
||||
|
||||
// Update sticks visual representation
|
||||
function updateStickVisuals(gamepad) {
|
||||
const leftStick = document.getElementById('left-stick-position');
|
||||
const rightStick = document.getElementById('right-stick-position');
|
||||
|
||||
if (gamepad.axes.length >= 2 && leftStick) {
|
||||
// Convert -1 to 1 values to pixel positions
|
||||
const x = gamepad.axes[0] * 60; // Scale factor to fit within the circle
|
||||
const y = gamepad.axes[1] * 60;
|
||||
|
||||
// Center position is 50%, then offset by the calculated amounts
|
||||
leftStick.style.left = `calc(50% + ${x}px)`;
|
||||
leftStick.style.top = `calc(50% + ${y}px)`;
|
||||
}
|
||||
|
||||
if (gamepad.axes.length >= 4 && rightStick) {
|
||||
const x = gamepad.axes[2] * 60;
|
||||
const y = gamepad.axes[3] * 60;
|
||||
|
||||
rightStick.style.left = `calc(50% + ${x}px)`;
|
||||
rightStick.style.top = `calc(50% + ${y}px)`;
|
||||
}
|
||||
}
|
||||
|
||||
// Update raw data display
|
||||
function updateRawData(gamepad) {
|
||||
const rawDataElement = document.getElementById('raw-data');
|
||||
|
||||
const buttons = Array.from(gamepad.buttons).map((button, index) => {
|
||||
return `Button ${index}: { pressed: ${button.pressed}, value: ${button.value.toFixed(2)} }`;
|
||||
});
|
||||
|
||||
const axes = Array.from(gamepad.axes).map((axis, index) => {
|
||||
return `Axis ${index}: ${axis.toFixed(2)}`;
|
||||
});
|
||||
|
||||
rawDataElement.textContent = `Gamepad: ${gamepad.id}\nType: ${gamepadHelper.detectControllerType(gamepad.id)}\n\nButtons:\n${buttons.join('\n')}\n\nAxes:\n${axes.join('\n')}`;
|
||||
}
|
||||
|
||||
// Event listeners for vibration controls
|
||||
document.getElementById('vibration-weak').addEventListener('input', function() {
|
||||
document.getElementById('weak-value').textContent = this.value;
|
||||
});
|
||||
|
||||
document.getElementById('vibration-strong').addEventListener('input', function() {
|
||||
document.getElementById('strong-value').textContent = this.value;
|
||||
});
|
||||
|
||||
document.getElementById('vibration-magnitude').addEventListener('input', function() {
|
||||
document.getElementById('magnitude-value').textContent = this.value;
|
||||
});
|
||||
|
||||
document.getElementById('vibration-duration').addEventListener('input', function() {
|
||||
document.getElementById('duration-value').textContent = this.value;
|
||||
});
|
||||
|
||||
// Vibrate the gamepad with appropriate parameters
|
||||
function vibrateGamepad() {
|
||||
if (activeGamepadIndex !== null) {
|
||||
const gamepad = navigator.getGamepads()[activeGamepadIndex];
|
||||
if (!gamepad) return;
|
||||
|
||||
const vibrationCapabilities = gamepadHelper.getVibrationCapabilities(gamepad);
|
||||
if (!vibrationCapabilities.supported) return;
|
||||
|
||||
const duration = parseInt(document.getElementById('vibration-duration').value);
|
||||
let vibrationOptions = { duration };
|
||||
|
||||
if (vibrationCapabilities.type === 'dual-rumble') {
|
||||
vibrationOptions.weakMagnitude = parseFloat(document.getElementById('vibration-weak').value);
|
||||
vibrationOptions.strongMagnitude = parseFloat(document.getElementById('vibration-strong').value);
|
||||
} else {
|
||||
const magnitude = parseFloat(document.getElementById('vibration-magnitude').value);
|
||||
vibrationOptions.weakMagnitude = magnitude;
|
||||
vibrationOptions.strongMagnitude = magnitude;
|
||||
vibrationOptions.magnitude = magnitude;
|
||||
}
|
||||
|
||||
gamepadHelper.vibrate(gamepad, vibrationOptions)
|
||||
.then(() => console.log('Vibration started'))
|
||||
.catch(e => console.error('Vibration error:', e));
|
||||
}
|
||||
}
|
||||
|
||||
// Stop vibration
|
||||
function stopVibration() {
|
||||
if (activeGamepadIndex !== null) {
|
||||
const gamepad = navigator.getGamepads()[activeGamepadIndex];
|
||||
if (gamepad) {
|
||||
gamepadHelper.stopVibration(gamepad).catch(e => {
|
||||
console.error('Stop vibration error:', e);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start the gamepad polling loop
|
||||
function startGamepadLoop() {
|
||||
if (animationFrameId) return;
|
||||
|
||||
// Make sure UI elements are initialized when starting loop
|
||||
if (activeGamepadIndex !== null) {
|
||||
initGamepadButtons();
|
||||
initGamepadAxes();
|
||||
}
|
||||
|
||||
gamepadLoop();
|
||||
}
|
||||
|
||||
// Stop the gamepad polling loop
|
||||
function stopGamepadLoop() {
|
||||
if (animationFrameId) {
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
animationFrameId = null;
|
||||
}
|
||||
}
|
||||
|
||||
// The main gamepad polling loop
|
||||
function gamepadLoop() {
|
||||
// Get the latest gamepad state
|
||||
const gamepads = navigator.getGamepads();
|
||||
|
||||
if (activeGamepadIndex !== null && gamepads[activeGamepadIndex]) {
|
||||
const gamepad = gamepads[activeGamepadIndex];
|
||||
|
||||
// Update all the UI elements
|
||||
updateGamepadInfo(gamepad);
|
||||
updateButtons(gamepad);
|
||||
updateAxes(gamepad);
|
||||
updateRawData(gamepad);
|
||||
}
|
||||
|
||||
animationFrameId = requestAnimationFrame(gamepadLoop);
|
||||
}
|
||||
|
||||
// Initial check for already connected gamepads
|
||||
const initialGamepads = navigator.getGamepads();
|
||||
for (let i = 0; i < initialGamepads.length; i++) {
|
||||
if (initialGamepads[i]) {
|
||||
gamepads[initialGamepads[i].index] = initialGamepads[i];
|
||||
}
|
||||
}
|
||||
|
||||
updateGamepadSelector();
|
||||
|
||||
// If we have gamepads already, start the loop
|
||||
if (Object.keys(gamepads).length > 0) {
|
||||
activeGamepadIndex = parseInt(Object.keys(gamepads)[0]);
|
||||
updateStatus(`Gamepad ${gamepads[activeGamepadIndex].id} connected`);
|
||||
startGamepadLoop();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,189 @@
|
||||
// projects section script
|
||||
|
||||
// get project container
|
||||
let container = document.getElementById("project-container")
|
||||
let org_name = "LizardByte"
|
||||
let base_url = `https://app.${org_name.toLowerCase()}.dev`
|
||||
let cache_repo = "dashboard"
|
||||
|
||||
|
||||
// create project cards
|
||||
$(document).ready(function(){
|
||||
// Set cache = false for all jquery ajax requests.
|
||||
$.ajaxSetup({
|
||||
cache: false,
|
||||
});
|
||||
|
||||
// get readthedocs projects
|
||||
let readthedocs = []
|
||||
$.ajax({
|
||||
url: `${base_url}/${cache_repo}/readthedocs/projects.json`,
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
for (let item in data) {
|
||||
readthedocs.push(data[item])
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: `${base_url}/${cache_repo}/github/repos.json`,
|
||||
type: "GET",
|
||||
dataType:"json",
|
||||
success: function (result) {
|
||||
let sorted = result.sort(window.rankingSorter("stargazers_count", "name"))
|
||||
|
||||
for(let repo in sorted) {
|
||||
if (sorted[repo]['archived'] === false && sorted[repo]['description'] !== null && sorted[repo]['fork'] === false) {
|
||||
let column = document.createElement("div")
|
||||
column.className = "col-lg-4 mb-5"
|
||||
container.appendChild(column)
|
||||
|
||||
let card = document.createElement("div")
|
||||
card.className = "card h-100 shadow border-0 rounded-0"
|
||||
column.appendChild(card)
|
||||
|
||||
let banner_div = document.createElement("div")
|
||||
banner_div.className = "hover-zoom"
|
||||
card.append(banner_div)
|
||||
|
||||
let banner_link = document.createElement("a")
|
||||
banner_link.className = "crowdin-ignore"
|
||||
banner_link.href = sorted[repo]['html_url']
|
||||
banner_link.target = "_blank"
|
||||
banner_div.append(banner_link)
|
||||
|
||||
let banner = document.createElement("img")
|
||||
banner.className = "card-img-top rounded-0"
|
||||
banner.src = `${base_url}/${cache_repo}/github/openGraphImages/${sorted[repo]['name']}_624x312.png`
|
||||
banner.alt = ""
|
||||
banner_link.append(banner)
|
||||
|
||||
let card_body = document.createElement("div")
|
||||
card_body.className = "card-body text-white p-4 rounded-0"
|
||||
card.appendChild(card_body)
|
||||
|
||||
let card_title_link = document.createElement("a")
|
||||
card_title_link.className = "text-decoration-none link-light crowdin-ignore"
|
||||
card_title_link.href = sorted[repo]['html_url']
|
||||
card_title_link.target = "_blank"
|
||||
card_body.appendChild(card_title_link)
|
||||
|
||||
let card_title_text = document.createElement("h5")
|
||||
card_title_text.className = "card-title mb-3 fw-bolder crowdin-ignore"
|
||||
card_title_text.textContent = result[repo]['name']
|
||||
card_title_link.appendChild(card_title_text)
|
||||
|
||||
let card_paragraph = document.createElement("p")
|
||||
card_paragraph.className = "card-text mb-0"
|
||||
card_paragraph.textContent = sorted[repo]['description']
|
||||
card_body.appendChild(card_paragraph)
|
||||
|
||||
let card_footer = document.createElement("div")
|
||||
card_footer.className = "card-footer p-2 pt-0 border-0 rounded-0"
|
||||
card.appendChild(card_footer)
|
||||
|
||||
let repo_data_row = document.createElement("div")
|
||||
repo_data_row.className = "d-flex align-items-center"
|
||||
card_footer.appendChild(repo_data_row)
|
||||
|
||||
let github_link = document.createElement("a")
|
||||
github_link.className = "nav-link text-white ms-3"
|
||||
github_link.href = sorted[repo]['html_url']
|
||||
github_link.target = "_blank"
|
||||
repo_data_row.appendChild(github_link)
|
||||
|
||||
let github_link_image = document.createElement("i")
|
||||
github_link_image.className = "fa-fw fa-brands fa-github"
|
||||
github_link.prepend(github_link_image)
|
||||
|
||||
// try to get repo subpage using base url and overwrite the links if it exists
|
||||
$.ajax({
|
||||
url: `${base_url}/${sorted[repo]['name']}/`,
|
||||
type: "GET",
|
||||
success: function () {
|
||||
banner_link.href = `${base_url}/${sorted[repo]['name']}/`
|
||||
card_title_link.href = `${base_url}/${sorted[repo]['name']}/`
|
||||
},
|
||||
})
|
||||
|
||||
let star_link = document.createElement("a")
|
||||
star_link.className = "nav-link nav-link-sm text-white ms-3 crowdin-ignore"
|
||||
star_link.href = `https://star-history.com/#${sorted[repo]['full_name']}`
|
||||
star_link.target = "_blank"
|
||||
star_link.textContent = window.formatNumber(sorted[repo]['stargazers_count'])
|
||||
repo_data_row.appendChild(star_link)
|
||||
|
||||
let star_link_image = document.createElement("i")
|
||||
star_link_image.className = "fa-fw fa-solid fa-star"
|
||||
star_link.prepend(star_link_image)
|
||||
|
||||
let fork_link = document.createElement("a")
|
||||
fork_link.className = "nav-link nav-link-sm text-white ms-3 crowdin-ignore"
|
||||
fork_link.href = `https://github.com/${sorted[repo]['full_name']}/network/members`
|
||||
fork_link.target = "_blank"
|
||||
fork_link.textContent = window.formatNumber(sorted[repo]['forks'])
|
||||
repo_data_row.appendChild(fork_link)
|
||||
|
||||
let fork_link_image = document.createElement("i")
|
||||
fork_link_image.className = "fa-fw fa-solid fa-code-fork"
|
||||
fork_link.prepend(fork_link_image)
|
||||
|
||||
for (let docs in readthedocs) {
|
||||
let docs_repo = readthedocs[docs]['repository']['url'];
|
||||
docs_repo = docs_repo.toLowerCase();
|
||||
|
||||
let project_repo = sorted[repo]['clone_url'];
|
||||
project_repo = project_repo.toLowerCase();
|
||||
|
||||
if (docs_repo === project_repo) {
|
||||
let docs_url = readthedocs[docs]['urls']['documentation']
|
||||
try {
|
||||
let parsedUrl = new URL(docs_url);
|
||||
if (parsedUrl.host !== "docs.lizardbyte.dev") {
|
||||
continue;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Invalid URL:", docs_url);
|
||||
continue;
|
||||
}
|
||||
|
||||
let docs_link = document.createElement("a")
|
||||
docs_link.className = "nav-link text-warning ms-3"
|
||||
docs_link.href = docs_url
|
||||
docs_link.target = "_blank"
|
||||
repo_data_row.appendChild(docs_link)
|
||||
|
||||
let docs_link_image = document.createElement("i")
|
||||
docs_link_image.className = "fa-fw fa-solid fa-file-lines"
|
||||
docs_link.prepend(docs_link_image)
|
||||
}
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: `${base_url}/${cache_repo}/github/languages/${sorted[repo]['name']}.json`,
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: function (languages) {
|
||||
let language_data_row = document.createElement("div")
|
||||
language_data_row.className = "card-group p-3 align-items-center"
|
||||
card_footer.appendChild(language_data_row)
|
||||
|
||||
for (let language in languages) {
|
||||
let language_file = encodeURIComponent(`${language}.svg`)
|
||||
|
||||
let language_icon = document.createElement("img")
|
||||
language_icon.className = "language-logo crowdin-ignore"
|
||||
language_icon.src = `${base_url}/uno/language-icons/${language_file}`
|
||||
language_icon.alt = language
|
||||
language_icon.title = language
|
||||
language_data_row.append(language_icon)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
$(document).ready(function(){
|
||||
// remove the widgetbot-crate
|
||||
$("widgetbot-crate").remove()
|
||||
});
|
||||
@@ -0,0 +1,218 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const issuesList = document.getElementById('issues-container');
|
||||
const issueModal = new bootstrap.Modal(document.getElementById('issueModal'));
|
||||
const issueModalBody = document.getElementById('issueModalBody');
|
||||
const issueModalLabel = document.getElementById('issueModalLabel');
|
||||
const viewOnGithubBtn = document.getElementById('viewOnGithub');
|
||||
|
||||
// GitHub API endpoint for your repository's issues
|
||||
const apiUrl = 'https://api.github.com/repos/LizardByte/roadmap/issues?state=open&labels=planned&per_page=100';
|
||||
|
||||
fetch(apiUrl)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(issues => {
|
||||
if (issues.length === 0) {
|
||||
issuesList.innerHTML = '<p>No roadmap items found.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear loading message
|
||||
issuesList.innerHTML = '';
|
||||
|
||||
issues.forEach(issue => {
|
||||
const issueCol = document.createElement('div');
|
||||
issueCol.className = 'col-lg-4 mb-5';
|
||||
|
||||
const issueEl = document.createElement('div');
|
||||
issueEl.className = 'card h-100 shadow border-0 rounded-0';
|
||||
issueEl.style.cursor = 'pointer';
|
||||
|
||||
// Store issue data for modal
|
||||
issueEl.dataset.issue = JSON.stringify(issue);
|
||||
|
||||
// Click event to show modal
|
||||
issueEl.addEventListener('click', function() {
|
||||
const issueData = JSON.parse(this.dataset.issue);
|
||||
displayIssueInModal(issueData);
|
||||
});
|
||||
|
||||
const cardBody = document.createElement('div');
|
||||
cardBody.className = 'card-body text-white p-4 rounded-0';
|
||||
|
||||
// Issue title
|
||||
const titleEl = document.createElement('h5');
|
||||
titleEl.className = 'card-title mb-3 fw-bolder crowdin-ignore';
|
||||
const titleText = document.createElement('span');
|
||||
titleText.textContent = issue.title;
|
||||
titleText.className = 'text-decoration-none';
|
||||
titleEl.appendChild(titleText);
|
||||
|
||||
// Issue metadata
|
||||
const metaEl = document.createElement('div');
|
||||
metaEl.className = 'd-flex justify-content-between mb-2 small';
|
||||
|
||||
// Issue number
|
||||
const numberEl = document.createElement('span');
|
||||
numberEl.className = 'badge bg-secondary crowdin-ignore';
|
||||
numberEl.textContent = `#${issue.number}`;
|
||||
|
||||
// Issue date
|
||||
const dateEl = document.createElement('span');
|
||||
dateEl.className = 'crowdin-ignore';
|
||||
const createdDate = new Date(issue.created_at);
|
||||
dateEl.textContent = createdDate.toLocaleDateString();
|
||||
|
||||
metaEl.appendChild(numberEl);
|
||||
metaEl.appendChild(dateEl);
|
||||
|
||||
// Labels
|
||||
const labelsEl = document.createElement('div');
|
||||
labelsEl.className = 'd-flex flex-wrap gap-1 mb-2';
|
||||
|
||||
// Sort labels alphabetically by name
|
||||
const sortedLabels = [...issue.labels].sort((a, b) =>
|
||||
a.name.toLowerCase().localeCompare(b.name.toLowerCase())
|
||||
);
|
||||
|
||||
sortedLabels.forEach(label => {
|
||||
const labelEl = document.createElement('span');
|
||||
labelEl.className = 'badge crowdin-ignore';
|
||||
labelEl.textContent = label.name;
|
||||
labelEl.style.backgroundColor = `#${label.color}`;
|
||||
|
||||
// Determine if label text should be dark or light based on background
|
||||
const r = parseInt(label.color.substring(0, 2), 16);
|
||||
const g = parseInt(label.color.substring(2, 4), 16);
|
||||
const b = parseInt(label.color.substring(4, 6), 16);
|
||||
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
||||
labelEl.style.color = brightness > 125 ? '#000' : '#fff';
|
||||
|
||||
labelsEl.appendChild(labelEl);
|
||||
});
|
||||
|
||||
// Add all elements to the card body
|
||||
cardBody.appendChild(titleEl);
|
||||
cardBody.appendChild(metaEl);
|
||||
cardBody.appendChild(labelsEl);
|
||||
|
||||
issueEl.appendChild(cardBody);
|
||||
issueCol.appendChild(issueEl);
|
||||
issuesList.appendChild(issueCol);
|
||||
});
|
||||
|
||||
})
|
||||
.catch(error => {
|
||||
issuesList.innerHTML = `<p class="text-danger">Error loading roadmap items: ${error.message}</p>`;
|
||||
});
|
||||
|
||||
// Function to display issue in modal
|
||||
function displayIssueInModal(issue) {
|
||||
// Set modal title
|
||||
issueModalLabel.textContent = `${issue.title} (#${issue.number})`;
|
||||
|
||||
// Set GitHub link
|
||||
viewOnGithubBtn.href = issue.html_url;
|
||||
|
||||
// Create modal content
|
||||
let modalContent = document.createElement('div');
|
||||
|
||||
// Issue metadata
|
||||
const metaEl = document.createElement('div');
|
||||
metaEl.className = 'd-flex justify-content-between mb-3';
|
||||
|
||||
// Issue created date
|
||||
const createdEl = document.createElement('span');
|
||||
const createdLabel = document.createElement('strong');
|
||||
createdLabel.textContent = 'Created:';
|
||||
createdEl.appendChild(createdLabel);
|
||||
createdEl.appendChild(document.createTextNode(' '));
|
||||
|
||||
const createdValue = document.createElement('span');
|
||||
createdValue.className = 'crowdin-ignore';
|
||||
const createdDate = new Date(issue.created_at);
|
||||
createdValue.textContent = createdDate.toLocaleDateString();
|
||||
createdEl.appendChild(createdValue);
|
||||
|
||||
// Issue author
|
||||
const authorEl = document.createElement('span');
|
||||
const authorLabel = document.createElement('strong');
|
||||
authorLabel.textContent = 'By:';
|
||||
authorEl.appendChild(authorLabel);
|
||||
authorEl.appendChild(document.createTextNode(' '));
|
||||
|
||||
const authorValue = document.createElement('span');
|
||||
authorValue.className = 'crowdin-ignore';
|
||||
authorValue.textContent = issue.user.login;
|
||||
authorEl.appendChild(authorValue);
|
||||
|
||||
metaEl.appendChild(createdEl);
|
||||
metaEl.appendChild(authorEl);
|
||||
modalContent.appendChild(metaEl);
|
||||
|
||||
// Labels
|
||||
if (issue.labels && issue.labels.length > 0) {
|
||||
const labelsContainer = document.createElement('div');
|
||||
labelsContainer.className = 'mb-3';
|
||||
|
||||
const labelsTitle = document.createElement('strong');
|
||||
labelsTitle.textContent = 'Labels:';
|
||||
labelsContainer.appendChild(labelsTitle);
|
||||
|
||||
const labelsEl = document.createElement('div');
|
||||
labelsEl.className = 'd-flex flex-wrap gap-1 mt-1';
|
||||
|
||||
issue.labels.forEach(label => {
|
||||
const labelEl = document.createElement('span');
|
||||
labelEl.className = 'badge crowdin-ignore';
|
||||
labelEl.textContent = label.name;
|
||||
labelEl.style.backgroundColor = `#${label.color}`;
|
||||
|
||||
// Determine if label text should be dark or light based on background
|
||||
const r = parseInt(label.color.substring(0, 2), 16);
|
||||
const g = parseInt(label.color.substring(2, 4), 16);
|
||||
const b = parseInt(label.color.substring(4, 6), 16);
|
||||
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
||||
labelEl.style.color = brightness > 125 ? '#000' : '#fff';
|
||||
|
||||
labelsEl.appendChild(labelEl);
|
||||
});
|
||||
|
||||
labelsContainer.appendChild(labelsEl);
|
||||
modalContent.appendChild(labelsContainer);
|
||||
}
|
||||
|
||||
// Issue body
|
||||
const bodyContainer = document.createElement('div');
|
||||
bodyContainer.className = 'mt-3 p-3 rounded-0';
|
||||
|
||||
if (issue.body) {
|
||||
// Create a container with crowdin-ignore class for the entire rendered content
|
||||
const markdownContainer = document.createElement('div');
|
||||
markdownContainer.className = 'crowdin-ignore';
|
||||
|
||||
// Use Marked to parse the markdown
|
||||
markdownContainer.innerHTML = marked.parse(issue.body);
|
||||
|
||||
// Add the rendered content to the body container
|
||||
bodyContainer.appendChild(markdownContainer);
|
||||
} else {
|
||||
const noContentMsg = document.createElement('div');
|
||||
noContentMsg.textContent = 'No description provided.';
|
||||
bodyContainer.appendChild(noContentMsg);
|
||||
}
|
||||
|
||||
modalContent.appendChild(bodyContainer);
|
||||
|
||||
// Set modal content
|
||||
issueModalBody.innerHTML = '';
|
||||
issueModalBody.appendChild(modalContent);
|
||||
|
||||
// Show modal
|
||||
issueModal.show();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.getElementById('new_comment').addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
const form = this;
|
||||
|
||||
form.classList.add('disabled');
|
||||
|
||||
const endpoint = '';
|
||||
const repository = '';
|
||||
const branch = '';
|
||||
const url = endpoint + repository + '/' + branch + '/comments';
|
||||
const data = new URLSearchParams(new FormData(form)).toString();
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", url);
|
||||
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
||||
xhr.onreadystatechange = function () {
|
||||
if(xhr.readyState === XMLHttpRequest.DONE) {
|
||||
const status = xhr.status;
|
||||
if (status >= 200 && status < 400) {
|
||||
formSubmitted();
|
||||
} else {
|
||||
formError();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function formSubmitted() {
|
||||
document.getElementById('comment-form-submit').classList.add('d-none');
|
||||
document.getElementById('comment-form-submitted').classList.remove('d-none');
|
||||
const notice = document.querySelector('.page__comments-form .js-notice');
|
||||
notice.classList.remove('alert-danger');
|
||||
notice.classList.add('alert-success');
|
||||
showAlert('success');
|
||||
}
|
||||
|
||||
function formError() {
|
||||
document.getElementById('comment-form-submitted').classList.add('d-none');
|
||||
document.getElementById('comment-form-submit').classList.remove('d-none');
|
||||
const notice = document.querySelector('.page__comments-form .js-notice');
|
||||
notice.classList.remove('alert-success');
|
||||
notice.classList.add('alert-danger');
|
||||
showAlert('failure');
|
||||
form.classList.remove('disabled');
|
||||
}
|
||||
|
||||
xhr.send(data);
|
||||
});
|
||||
|
||||
function showAlert(message) {
|
||||
const notice = document.querySelector('.page__comments-form .js-notice');
|
||||
notice.classList.remove('d-none');
|
||||
if (message === 'success') {
|
||||
document.querySelector('.page__comments-form .js-notice-text-success').classList.remove('d-none');
|
||||
document.querySelector('.page__comments-form .js-notice-text-failure').classList.add('d-none');
|
||||
} else {
|
||||
document.querySelector('.page__comments-form .js-notice-text-success').classList.add('d-none');
|
||||
document.querySelector('.page__comments-form .js-notice-text-failure').classList.remove('d-none');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,111 +0,0 @@
|
||||
# If no external IP address is given, the local IP address is used
|
||||
# external_ip = 123.456.789.12
|
||||
|
||||
# The private key must be 2048 bits
|
||||
# pkey = /dir/pkey.pem
|
||||
|
||||
# The certificate must be signed with a 2048 bit key
|
||||
# cert = /dir/cert.pem
|
||||
|
||||
# The name displayed by Moonlight
|
||||
# If not specified, the PC's hostname is used
|
||||
# sunshine_name = Sunshine
|
||||
|
||||
# The minimum log level printed to standard out
|
||||
#
|
||||
# none -> no logs are printed to standard out
|
||||
#
|
||||
# verbose = [0]
|
||||
# debug = [1]
|
||||
# info = [2]
|
||||
# warning = [3]
|
||||
# error = [4]
|
||||
# fatal = [5]
|
||||
# none = [6]
|
||||
#
|
||||
# min_log_level = info
|
||||
|
||||
# The origin of the remote endpoint address that is not denied for HTTP method /pin
|
||||
# Could be any of the following values:
|
||||
# pc|lan|wan
|
||||
# pc: Only localhost may access /pin
|
||||
# lan: Only those in LAN may access /pin
|
||||
# wan: Anyone may access /pin
|
||||
#
|
||||
# origin_pin_allowed = lan
|
||||
|
||||
# The file where current state of Sunshine is stored
|
||||
# file_state = sunshine_state.json
|
||||
|
||||
# How long to wait in milliseconds for data from moonlight before shutting down the stream
|
||||
# ping_timeout = 2000
|
||||
|
||||
# The file where configuration for the different applications that Sunshine can run during a stream
|
||||
# file_apps = apps.json
|
||||
|
||||
# How much error correcting packets must be send for every video
|
||||
# This is just some random number, don't know the optimal value
|
||||
# The higher fec_percentage, the lower space for the actual data to send per frame there is
|
||||
#
|
||||
# The value must be greater than 0 and lower than or equal to 100
|
||||
# fec_percentage = 10
|
||||
|
||||
|
||||
# The back/select button on the controller
|
||||
# On the Shield, the home and powerbutton are not passed to Moonlight
|
||||
# If, after the timeout, the back button is still pressed down, Home/Guide button press is emulated.
|
||||
# If back_button_timeout < 0, then the Home/Guide button will not be emulated
|
||||
# back_button_timeout = 2000
|
||||
|
||||
|
||||
# The name of the audio sink used for Audio Loopback
|
||||
# If you do not specify this variable, pulseaudio will select the default monitor device.
|
||||
#
|
||||
# You can find the name of the audio sink using the following command:
|
||||
# !! Linux only !!
|
||||
# pacmd list-sources | grep "name:"
|
||||
# audio_sink = alsa_output.pci-0000_09_00.3.analog-stereo.monitor
|
||||
#
|
||||
# !! Windows only !!
|
||||
# tools\audio-info.exe
|
||||
# audio_sink = {0.0.0.00000000}.{FD47D9CC-4218-4135-9CE2-0C195C87405B}
|
||||
|
||||
# !! Windows only !!
|
||||
# You can select the video card you want to stream:
|
||||
# The appropriate values can be found using the following command:
|
||||
# tools\dxgi-info.exe
|
||||
# adapter_name = Radeon RX 580 Series
|
||||
# output_name = \\.\DISPLAY1
|
||||
|
||||
|
||||
###############################################
|
||||
# FFmpeg software encoding parameters
|
||||
# Honestly, I have no idea what the optimal values would be.
|
||||
# Play around with this :)
|
||||
|
||||
# Constant Rate Factor. Between 1 and 52. It allows QP to go up during motion and down with still image, resulting in constant perceived quality
|
||||
# Higher value means more compression, but less quality
|
||||
# If crf == 0, then use QP directly instead
|
||||
# crf = 0
|
||||
|
||||
# Quantitization Parameter
|
||||
# Higher value means more compression, but less quality
|
||||
# If crf != 0, then this parameter is ignored
|
||||
# qp = 28
|
||||
|
||||
# Minimum number of threads used by ffmpeg to encode the video.
|
||||
# Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually
|
||||
# worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest
|
||||
# value that can reliably encode at your desired streaming settings on your hardware.
|
||||
# min_threads = 2
|
||||
|
||||
# Allows the client to request HEVC Main or HEVC Main10 video streams.
|
||||
# HEVC is more CPU-intensive to encode, so enabling this may reduce performance.
|
||||
# If set to 0 (default), Sunshine will not advertise support for HEVC
|
||||
# If set to 1, Sunshine will advertise support for HEVC Main profile
|
||||
# If set to 2, Sunshine will advertise support for HEVC Main and Main10 (HDR) profiles
|
||||
# hevc_mode = 2
|
||||
|
||||
# See x264 --fullhelp for the different presets
|
||||
# preset = superfast
|
||||
# tune = zerolatency
|
||||
|
After Width: | Height: | Size: 112 KiB |
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
<channel>
|
||||
|
||||
<title>LizardByte</title>
|
||||
|
||||
|
||||
<description>LizardByte</description>
|
||||
|
||||
<link>http://app.lizardbyte.dev/</link>
|
||||
<atom:link href="http://app.lizardbyte.dev/feed.xml" rel="self" type="application/rss+xml" />
|
||||
|
||||
|
||||
</channel>
|
||||
</rss>
|
||||
@@ -1,80 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ ! "@SUNSHINE_UNDEFINED_VARIABLE@" = "" ]; then
|
||||
echo "Please run gen-deb generated by cmake inside the build directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d package-deb ]; then
|
||||
echo "package-deb already exists: It will be replaced"
|
||||
rm -rf package-deb
|
||||
fi
|
||||
|
||||
export DEBIAN=package-deb/sunshine/DEBIAN
|
||||
export RULES=package-deb/sunshine/etc/udev/rules.d
|
||||
export BIN=package-deb/sunshine/usr/bin
|
||||
export ASSETS=package-deb/sunshine/etc/sunshine
|
||||
|
||||
mkdir -p $DEBIAN
|
||||
mkdir -p $RULES
|
||||
mkdir -p $BIN
|
||||
mkdir -p $ASSETS
|
||||
|
||||
if [ ! -f sunshine ]; then
|
||||
echo "Error: Can't find sunshine"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat << 'EOF' > $DEBIAN/conffiles
|
||||
/etc/sunshine/sunshine.conf
|
||||
/etc/sunshine/apps_linux.json
|
||||
EOF
|
||||
|
||||
cat << 'EOF' > $DEBIAN/control
|
||||
Package: sunshine
|
||||
Architecture: amd64
|
||||
Maintainer: @loki
|
||||
Priority: optional
|
||||
Version: 0.1.1
|
||||
Depends: libssl | libavdevice | libboost-thread (>= 1.67) | libboost-filesystem (>= 1.67) | libboost-log (>= 1.67) | libpulse | libopus | libxtst | libx11 | libxfixes | libevdev | libxcb1 | libxcb-shm0 | libxcb-xfixes0
|
||||
Description: Gamestream host for Moonlight
|
||||
EOF
|
||||
|
||||
cat << 'EOF' > $DEBIAN/postinst
|
||||
#!/bin/sh
|
||||
|
||||
export GROUP_INPUT=input
|
||||
|
||||
if [ -f /etc/group ]; then
|
||||
if ! grep -q $GROUP_INPUT /etc/group; then
|
||||
echo "Creating group $GROUP_INPUT"
|
||||
|
||||
groupadd $GROUP_INPUT
|
||||
fi
|
||||
else
|
||||
echo "Warning: /etc/group not found"
|
||||
fi
|
||||
|
||||
# Prevent necessity of rebooting system
|
||||
chmod 0660 /dev/uinput
|
||||
chown root:$GROUP_INPUT /dev/uinput
|
||||
EOF
|
||||
|
||||
cat << 'EOF' > $RULES/85-sunshine-rules.rules
|
||||
KERNEL=="uinput", GROUP="input", MODE="0660"
|
||||
EOF
|
||||
|
||||
cp sunshine $BIN/sunshine
|
||||
cp @CMAKE_CURRENT_SOURCE_DIR@/assets/apps_linux.json $ASSETS/apps_linux.json
|
||||
cp @CMAKE_CURRENT_SOURCE_DIR@/assets/sunshine.conf $ASSETS/sunshine.conf
|
||||
|
||||
chmod 755 $DEBIAN/postinst
|
||||
chmod 755 $BIN/sunshine
|
||||
chmod 644 $RULES/85-sunshine-rules.rules
|
||||
|
||||
cd package-deb
|
||||
if fakeroot dpkg-deb --build sunshine; then
|
||||
echo "generated debian package: @CMAKE_CURRENT_BINARY_DIR@/package-deb/sunshine.deb"
|
||||
fi
|
||||
cd ..
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Sitemap: http://app.lizardbyte.dev/sitemap.xml
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>http://app.lizardbyte.dev/</loc>
|
||||
</url>
|
||||
<url>
|
||||
<loc>http://app.lizardbyte.dev/tags/</loc>
|
||||
</url>
|
||||
</urlset>
|
||||
@@ -0,0 +1,110 @@
|
||||
# Name of the property. You can have multiple properties with completely
|
||||
# different config blocks for different sections of your site.
|
||||
# For example, you can have one property to handle comment submission and
|
||||
# another one to handle posts.
|
||||
# To encrypt strings use the following endpoint:
|
||||
# https://{STATICMAN API INSTANCE}/v3/encrypt/{TEXT TO BE ENCRYPTED}
|
||||
# {STATICMAN API INSTANCE} should match the `endpoint` in the theme config
|
||||
# file.
|
||||
|
||||
comments:
|
||||
# (*) REQUIRED
|
||||
#
|
||||
# Names of the fields the form is allowed to submit. If a field that is
|
||||
# not here is part of the request, an error will be thrown.
|
||||
allowedFields: ["name", "email", "url", "message"]
|
||||
|
||||
# (*) REQUIRED WHEN USING NOTIFICATIONS
|
||||
#
|
||||
# When allowedOrigins is defined, only requests sent from one of the domains
|
||||
# listed will be accepted. The origin is sent as part as the `options` object
|
||||
# (e.g. <input name="options[origin]" value="https://yourdomain.com/post1")
|
||||
# allowedOrigins: ["https://example.com"]
|
||||
|
||||
# (*) REQUIRED
|
||||
#
|
||||
# Name of the branch being used. Must match the `branch` in the theme config
|
||||
# file.
|
||||
branch: "master" # use "master" for user page or "gh-pages" for project pages
|
||||
|
||||
commitMessage: "New comment by {fields.name}"
|
||||
|
||||
# (*) REQUIRED
|
||||
#
|
||||
# Destination path (filename) for the data files. Accepts placeholders.
|
||||
filename: "comment-{@timestamp}"
|
||||
|
||||
# The format of the generated data files. Accepted values are "json", "yaml"
|
||||
# or "frontmatter"
|
||||
format: "yaml"
|
||||
|
||||
# List of fields to be populated automatically by Staticman and included in
|
||||
# the data file. Keys are the name of the field. The value can be an object
|
||||
# with a `type` property, which configures the generated field, or any value
|
||||
# to be used directly (e.g. a string, number or array)
|
||||
generatedFields:
|
||||
date:
|
||||
type: "date"
|
||||
options:
|
||||
format: "iso8601" # "iso8601" (default), "timestamp-seconds", "timestamp-milliseconds"
|
||||
|
||||
# Whether entries need to be approved before they are published to the main
|
||||
# branch. If set to `true`, a pull request will be created for your approval.
|
||||
# Otherwise, entries will be published to the main branch automatically.
|
||||
moderation: false
|
||||
|
||||
# Akismet spam detection.
|
||||
# akismet:
|
||||
# enabled: true
|
||||
# author: "name"
|
||||
# authorEmail: "email"
|
||||
# authorUrl: "url"
|
||||
# content: "message"
|
||||
# type: "comment"
|
||||
|
||||
# Name of the site. Used in notification emails.
|
||||
# name: "Your Site"
|
||||
|
||||
# Notification settings. When enabled, users can choose to receive notifications
|
||||
# via email when someone adds a reply or a new comment. This requires an account
|
||||
# with Mailgun, which you can get for free at https://mailgun.com.
|
||||
# notifications:
|
||||
# Enable notifications
|
||||
# enabled: true
|
||||
|
||||
# (!) ENCRYPTED
|
||||
#
|
||||
# Mailgun API key
|
||||
# apiKey: ""
|
||||
|
||||
# (!) ENCRYPTED
|
||||
#
|
||||
# Mailgun domain (encrypted)
|
||||
# domain: ""
|
||||
|
||||
# (*) REQUIRED
|
||||
#
|
||||
# Destination path (directory) for the data files. Accepts placeholders.
|
||||
path: "_data/comments/{options.slug}" # (default)
|
||||
|
||||
# Names of required files. If any of these isn't in the request or is empty,
|
||||
# an error will be thrown.
|
||||
requiredFields: ["name", "email", "message"]
|
||||
|
||||
# List of transformations to apply to any of the fields supplied. Keys are
|
||||
# the name of the field and values are possible transformation types.
|
||||
transforms:
|
||||
email: md5
|
||||
|
||||
# reCAPTCHA (OPTIONAL)
|
||||
# To enable reCAPTCHA:
|
||||
# 1. Set `enabled` to `true`
|
||||
# 2. Register your domain at https://www.google.com/recaptcha/ and choose reCAPTCHA V2
|
||||
# 3. Uncomment `siteKey` and `secret` and fill them in with your values
|
||||
reCaptcha:
|
||||
enabled: false
|
||||
#siteKey: ""
|
||||
# ENCRYPT reCaptcha secret key using Staticman /encrypt endpoint
|
||||
# i.e. https://{STATICMAN API INSTANCE}/v3/encrypt/{your-site-secret}
|
||||
# For more information, visit https://staticman.net/docs/encryption
|
||||
#secret: ""
|
||||
@@ -1,13 +0,0 @@
|
||||
[Unit]
|
||||
Description=Sunshine Gamestream Server for Moonlight
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/home/%u
|
||||
Environment="DISPLAY=:0"
|
||||
Type=simple
|
||||
# wait for Xorg
|
||||
ExecStartPre=/bin/sh -c 'while ! pgrep Xorg; do sleep 2; done'
|
||||
ExecStart=@SUNSHINE_EXECUTABLE_PATH@
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -1,132 +0,0 @@
|
||||
#include <thread>
|
||||
|
||||
#include <opus/opus_multistream.h>
|
||||
|
||||
#include "platform/common.h"
|
||||
|
||||
#include "utility.h"
|
||||
#include "thread_safe.h"
|
||||
#include "audio.h"
|
||||
#include "main.h"
|
||||
|
||||
namespace audio {
|
||||
using namespace std::literals;
|
||||
using opus_t = util::safe_ptr<OpusMSEncoder, opus_multistream_encoder_destroy>;
|
||||
using sample_queue_t = std::shared_ptr<safe::queue_t<std::vector<std::int16_t>>>;
|
||||
|
||||
struct opus_stream_config_t {
|
||||
std::int32_t sampleRate;
|
||||
int channelCount;
|
||||
int streams;
|
||||
int coupledStreams;
|
||||
const std::uint8_t *mapping;
|
||||
};
|
||||
|
||||
constexpr std::uint8_t map_stereo[] { 0, 1 };
|
||||
constexpr std::uint8_t map_surround51[] {0, 4, 1, 5, 2, 3};
|
||||
constexpr std::uint8_t map_high_surround51[] {0, 1, 2, 3, 4, 5};
|
||||
constexpr auto SAMPLE_RATE = 48000;
|
||||
static opus_stream_config_t stereo = {
|
||||
SAMPLE_RATE,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
map_stereo
|
||||
};
|
||||
|
||||
static opus_stream_config_t Surround51 = {
|
||||
SAMPLE_RATE,
|
||||
6,
|
||||
4,
|
||||
2,
|
||||
map_surround51
|
||||
};
|
||||
|
||||
static opus_stream_config_t HighSurround51 = {
|
||||
SAMPLE_RATE,
|
||||
6,
|
||||
6,
|
||||
0,
|
||||
map_high_surround51
|
||||
};
|
||||
|
||||
void encodeThread(std::shared_ptr<safe::queue_t<packet_t>> packets, sample_queue_t samples, config_t config) {
|
||||
//FIXME: Pick correct opus_stream_config_t based on config.channels
|
||||
auto stream = &stereo;
|
||||
opus_t opus { opus_multistream_encoder_create(
|
||||
stream->sampleRate,
|
||||
stream->channelCount,
|
||||
stream->streams,
|
||||
stream->coupledStreams,
|
||||
stream->mapping,
|
||||
OPUS_APPLICATION_AUDIO,
|
||||
nullptr)
|
||||
};
|
||||
|
||||
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
|
||||
while(auto sample = samples->pop()) {
|
||||
packet_t packet { 16*1024 }; // 16KB
|
||||
|
||||
int bytes = opus_multistream_encode(opus.get(), sample->data(), frame_size, std::begin(packet), packet.size());
|
||||
if(bytes < 0) {
|
||||
BOOST_LOG(error) << opus_strerror(bytes);
|
||||
packets->stop();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
packet.fake_resize(bytes);
|
||||
packets->raise(std::move(packet));
|
||||
}
|
||||
}
|
||||
|
||||
void capture(std::shared_ptr<safe::queue_t<packet_t>> packets, config_t config) {
|
||||
auto samples = std::make_shared<sample_queue_t::element_type>();
|
||||
std::thread thread { encodeThread, packets, samples, config };
|
||||
|
||||
auto fg = util::fail_guard([&]() {
|
||||
packets->stop();
|
||||
samples->stop();
|
||||
thread.join();
|
||||
});
|
||||
|
||||
//FIXME: Pick correct opus_stream_config_t based on config.channels
|
||||
auto stream = &stereo;
|
||||
|
||||
auto mic = platf::microphone(stream->sampleRate);
|
||||
if(!mic) {
|
||||
BOOST_LOG(error) << "Couldn't create audio input"sv ;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto frame_size = config.packetDuration * stream->sampleRate / 1000;
|
||||
int samples_per_frame = frame_size * stream->channelCount;
|
||||
|
||||
while(packets->running()) {
|
||||
std::vector<std::int16_t> sample_buffer;
|
||||
sample_buffer.resize(samples_per_frame);
|
||||
|
||||
auto status = mic->sample(sample_buffer);
|
||||
switch(status) {
|
||||
case platf::capture_e::ok:
|
||||
break;
|
||||
case platf::capture_e::timeout:
|
||||
continue;
|
||||
case platf::capture_e::reinit:
|
||||
mic.reset();
|
||||
mic = platf::microphone(stream->sampleRate);
|
||||
if(!mic) {
|
||||
BOOST_LOG(error) << "Couldn't re-initialize audio input"sv ;
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
samples->raise(std::move(sample_buffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#ifndef SUNSHINE_AUDIO_H
|
||||
#define SUNSHINE_AUDIO_H
|
||||
|
||||
#include "utility.h"
|
||||
#include "thread_safe.h"
|
||||
namespace audio {
|
||||
struct config_t {
|
||||
int packetDuration;
|
||||
int channels;
|
||||
int mask;
|
||||
};
|
||||
|
||||
using packet_t = util::buffer_t<std::uint8_t>;
|
||||
using packet_queue_t = std::shared_ptr<safe::queue_t<packet_t>>;
|
||||
void capture(std::shared_ptr<safe::queue_t<packet_t>> packets, config_t config);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,240 +0,0 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
#include "utility.h"
|
||||
#include "config.h"
|
||||
|
||||
#define CA_DIR "credentials"
|
||||
#define PRIVATE_KEY_FILE CA_DIR "/cakey.pem"
|
||||
#define CERTIFICATE_FILE CA_DIR "/cacert.pem"
|
||||
|
||||
#define APPS_JSON_PATH SUNSHINE_ASSETS_DIR "/" APPS_JSON
|
||||
namespace config {
|
||||
using namespace std::literals;
|
||||
video_t video {
|
||||
0, // crf
|
||||
28, // qp
|
||||
|
||||
2, // min_threads
|
||||
|
||||
0, // hevc_mode
|
||||
"superfast"s, // preset
|
||||
"zerolatency"s, // tune
|
||||
{}, // adapter_name
|
||||
{} // output_name
|
||||
};
|
||||
|
||||
audio_t audio {};
|
||||
|
||||
stream_t stream {
|
||||
2s, // ping_timeout
|
||||
|
||||
APPS_JSON_PATH,
|
||||
|
||||
10 // fecPercentage
|
||||
};
|
||||
|
||||
nvhttp_t nvhttp {
|
||||
"lan", // origin_pin
|
||||
PRIVATE_KEY_FILE,
|
||||
CERTIFICATE_FILE,
|
||||
|
||||
boost::asio::ip::host_name(), // sunshine_name,
|
||||
"sunshine_state.json"s // file_state
|
||||
};
|
||||
|
||||
input_t input {
|
||||
2s
|
||||
};
|
||||
|
||||
sunshine_t sunshine {
|
||||
2 // min_log_level
|
||||
};
|
||||
|
||||
bool whitespace(char ch) {
|
||||
return ch == ' ' || ch == '\t';
|
||||
}
|
||||
|
||||
std::string to_string(const char *begin, const char *end) {
|
||||
return { begin, (std::size_t)(end - begin) };
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> parse_line(std::string_view::const_iterator begin, std::string_view::const_iterator end) {
|
||||
begin = std::find_if(begin, end, std::not_fn(whitespace));
|
||||
end = std::find(begin, end, '#');
|
||||
end = std::find_if(std::make_reverse_iterator(end), std::make_reverse_iterator(begin), std::not_fn(whitespace)).base();
|
||||
|
||||
auto eq = std::find(begin, end, '=');
|
||||
if(eq == end || eq == begin) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto end_name = std::find_if(std::make_reverse_iterator(eq - 1), std::make_reverse_iterator(begin), std::not_fn(whitespace)).base();
|
||||
auto begin_val = std::find_if(eq + 1, end, std::not_fn(whitespace));
|
||||
|
||||
return std::pair { to_string(begin, end_name), to_string(begin_val, end) };
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string> parse_config(std::string_view file_content) {
|
||||
std::unordered_map<std::string, std::string> vars;
|
||||
|
||||
auto pos = std::begin(file_content);
|
||||
auto end = std::end(file_content);
|
||||
|
||||
while(pos < end) {
|
||||
auto newline = std::find_if(pos, end, [](auto ch) { return ch == '\n' || ch == '\r'; });
|
||||
auto var = parse_line(pos, newline);
|
||||
|
||||
pos = (*newline == '\r') ? newline + 2 : newline + 1;
|
||||
if(!var) {
|
||||
continue;
|
||||
}
|
||||
|
||||
vars.emplace(std::move(*var));
|
||||
}
|
||||
|
||||
return vars;
|
||||
}
|
||||
|
||||
void string_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input) {
|
||||
auto it = vars.find(name);
|
||||
if(it == std::end(vars)) {
|
||||
return;
|
||||
}
|
||||
|
||||
input = std::move(it->second);
|
||||
|
||||
vars.erase(it);
|
||||
}
|
||||
|
||||
void string_restricted_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, std::string &input, const std::vector<std::string_view> &allowed_vals) {
|
||||
std::string temp;
|
||||
string_f(vars, name, temp);
|
||||
|
||||
for(auto &allowed_val : allowed_vals) {
|
||||
if(temp == allowed_val) {
|
||||
input = std::move(temp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void int_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input) {
|
||||
auto it = vars.find(name);
|
||||
|
||||
if(it == std::end(vars)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &val = it->second;
|
||||
input = util::from_chars(&val[0], &val[0] + val.size());
|
||||
|
||||
vars.erase(it);
|
||||
}
|
||||
|
||||
void int_between_f(std::unordered_map<std::string, std::string> &vars, const std::string &name, int &input, const std::pair<int, int> &range) {
|
||||
int temp = input;
|
||||
|
||||
int_f(vars, name, temp);
|
||||
|
||||
TUPLE_2D_REF(lower, upper, range);
|
||||
if(temp >= lower && temp <= upper) {
|
||||
input = temp;
|
||||
}
|
||||
}
|
||||
|
||||
void parse_file(const char *file) {
|
||||
std::ifstream in(file);
|
||||
|
||||
auto vars = parse_config(std::string {
|
||||
// Quick and dirty
|
||||
std::istreambuf_iterator<char>(in),
|
||||
std::istreambuf_iterator<char>()
|
||||
});
|
||||
|
||||
for(auto &[name, val] : vars) {
|
||||
std::cout << "["sv << name << "] -- ["sv << val << ']' << std::endl;
|
||||
}
|
||||
|
||||
int_f(vars, "crf", video.crf);
|
||||
int_f(vars, "qp", video.qp);
|
||||
int_f(vars, "min_threads", video.min_threads);
|
||||
int_between_f(vars, "hevc_mode", video.hevc_mode, {
|
||||
0, 2
|
||||
});
|
||||
string_f(vars, "preset", video.preset);
|
||||
string_f(vars, "tune", video.tune);
|
||||
string_f(vars, "adapter_name", video.adapter_name);
|
||||
string_f(vars, "output_name", video.output_name);
|
||||
|
||||
string_f(vars, "pkey", nvhttp.pkey);
|
||||
string_f(vars, "cert", nvhttp.cert);
|
||||
string_f(vars, "sunshine_name", nvhttp.sunshine_name);
|
||||
string_f(vars, "file_state", nvhttp.file_state);
|
||||
string_f(vars, "external_ip", nvhttp.external_ip);
|
||||
|
||||
string_f(vars, "audio_sink", audio.sink);
|
||||
|
||||
string_restricted_f(vars, "origin_pin_allowed", nvhttp.origin_pin_allowed, {
|
||||
"pc"sv, "lan"sv, "wan"sv
|
||||
});
|
||||
|
||||
int to = -1;
|
||||
int_f(vars, "ping_timeout", to);
|
||||
if(to > 0) {
|
||||
stream.ping_timeout = std::chrono::milliseconds(to);
|
||||
}
|
||||
string_f(vars, "file_apps", stream.file_apps);
|
||||
int_between_f(vars, "fec_percentage", stream.fec_percentage, {
|
||||
1, 100
|
||||
});
|
||||
|
||||
to = std::numeric_limits<int>::min();
|
||||
int_f(vars, "back_button_timeout", to);
|
||||
|
||||
if(to > std::numeric_limits<int>::min()) {
|
||||
input.back_button_timeout = std::chrono::milliseconds {to };
|
||||
}
|
||||
|
||||
std::string log_level_string;
|
||||
string_restricted_f(vars, "min_log_level", log_level_string, {
|
||||
"verbose"sv, "debug"sv, "info"sv, "warning"sv, "error"sv, "fatal"sv, "none"sv
|
||||
});
|
||||
|
||||
if(!log_level_string.empty()) {
|
||||
if(log_level_string == "verbose"sv) {
|
||||
sunshine.min_log_level = 0;
|
||||
}
|
||||
else if(log_level_string == "debug"sv) {
|
||||
sunshine.min_log_level = 1;
|
||||
}
|
||||
else if(log_level_string == "info"sv) {
|
||||
sunshine.min_log_level = 2;
|
||||
}
|
||||
else if(log_level_string == "warning"sv) {
|
||||
sunshine.min_log_level = 3;
|
||||
}
|
||||
else if(log_level_string == "error"sv) {
|
||||
sunshine.min_log_level = 4;
|
||||
}
|
||||
else if(log_level_string == "fatal"sv) {
|
||||
sunshine.min_log_level = 5;
|
||||
}
|
||||
else if(log_level_string == "none"sv) {
|
||||
sunshine.min_log_level = 6;
|
||||
}
|
||||
}
|
||||
|
||||
if(sunshine.min_log_level <= 3) {
|
||||
for(auto &[var,_] : vars) {
|
||||
std::cout << "Warning: Unrecognized configurable option ["sv << var << ']' << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
#ifndef SUNSHINE_CONFIG_H
|
||||
#define SUNSHINE_CONFIG_H
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
namespace config {
|
||||
struct video_t {
|
||||
// ffmpeg params
|
||||
int crf; // higher == more compression and less quality
|
||||
int qp; // higher == more compression and less quality, ignored if crf != 0
|
||||
|
||||
int min_threads; // Minimum number of threads/slices for CPU encoding
|
||||
|
||||
int hevc_mode;
|
||||
std::string preset;
|
||||
std::string tune;
|
||||
|
||||
std::string adapter_name;
|
||||
std::string output_name;
|
||||
};
|
||||
|
||||
struct audio_t {
|
||||
std::string sink;
|
||||
};
|
||||
|
||||
struct stream_t {
|
||||
std::chrono::milliseconds ping_timeout;
|
||||
|
||||
std::string file_apps;
|
||||
|
||||
int fec_percentage;
|
||||
};
|
||||
|
||||
struct nvhttp_t {
|
||||
// Could be any of the following values:
|
||||
// pc|lan|wan
|
||||
std::string origin_pin_allowed;
|
||||
|
||||
std::string pkey; // must be 2048 bits
|
||||
std::string cert; // must be signed with a key of 2048 bits
|
||||
|
||||
std::string sunshine_name;
|
||||
|
||||
std::string file_state;
|
||||
|
||||
std::string external_ip;
|
||||
};
|
||||
|
||||
struct input_t {
|
||||
std::chrono::milliseconds back_button_timeout;
|
||||
};
|
||||
|
||||
struct sunshine_t {
|
||||
int min_log_level;
|
||||
};
|
||||
|
||||
extern video_t video;
|
||||
extern audio_t audio;
|
||||
extern stream_t stream;
|
||||
extern nvhttp_t nvhttp;
|
||||
extern input_t input;
|
||||
extern sunshine_t sunshine;
|
||||
|
||||
void parse_file(const char *file);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,341 +0,0 @@
|
||||
//
|
||||
// Created by loki on 5/31/19.
|
||||
//
|
||||
|
||||
#include <openssl/pem.h>
|
||||
#include "crypto.h"
|
||||
namespace crypto {
|
||||
using big_num_t = util::safe_ptr<BIGNUM, BN_free>;
|
||||
//using rsa_t = util::safe_ptr<RSA, RSA_free>;
|
||||
using asn1_string_t = util::safe_ptr<ASN1_STRING, ASN1_STRING_free>;
|
||||
|
||||
cert_chain_t::cert_chain_t() : _certs {}, _cert_ctx {X509_STORE_CTX_new() } {}
|
||||
void cert_chain_t::add(x509_t &&cert) {
|
||||
x509_store_t x509_store { X509_STORE_new() };
|
||||
|
||||
X509_STORE_add_cert(x509_store.get(), cert.get());
|
||||
_certs.emplace_back(std::make_pair(std::move(cert), std::move(x509_store)));
|
||||
}
|
||||
|
||||
/*
|
||||
* When certificates from two or more instances of Moonlight have been added to x509_store_t,
|
||||
* only one of them will be verified by X509_verify_cert, resulting in only a single instance of
|
||||
* Moonlight to be able to use Sunshine
|
||||
*
|
||||
* To circumvent this, x509_store_t instance will be created for each instance of the certificates.
|
||||
*/
|
||||
const char *cert_chain_t::verify(x509_t::element_type *cert) {
|
||||
int err_code = 0;
|
||||
for(auto &[_,x509_store] : _certs) {
|
||||
auto fg = util::fail_guard([this]() {
|
||||
X509_STORE_CTX_cleanup(_cert_ctx.get());
|
||||
});
|
||||
|
||||
X509_STORE_CTX_init(_cert_ctx.get(), x509_store.get(), nullptr, nullptr);
|
||||
X509_STORE_CTX_set_cert(_cert_ctx.get(), cert);
|
||||
|
||||
auto err = X509_verify_cert(_cert_ctx.get());
|
||||
|
||||
if (err == 1) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
err_code = X509_STORE_CTX_get_error(_cert_ctx.get());
|
||||
|
||||
//FIXME: Checking for X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY is a temporary workaround to get mmonlight-embedded to work on the raspberry pi
|
||||
if(err_code == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) {
|
||||
return nullptr;
|
||||
}
|
||||
if (err_code != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && err_code != X509_V_ERR_INVALID_CA) {
|
||||
return X509_verify_cert_error_string(err_code);
|
||||
}
|
||||
}
|
||||
|
||||
return X509_verify_cert_error_string(err_code);
|
||||
}
|
||||
|
||||
cipher_t::cipher_t(const crypto::aes_t &key) : ctx { EVP_CIPHER_CTX_new() }, key { key }, padding { true } {}
|
||||
int cipher_t::decrypt(const std::string_view &cipher, std::vector<std::uint8_t> &plaintext) {
|
||||
int len;
|
||||
|
||||
auto fg = util::fail_guard([this]() {
|
||||
EVP_CIPHER_CTX_reset(ctx.get());
|
||||
});
|
||||
|
||||
// Gen 7 servers use 128-bit AES ECB
|
||||
if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
|
||||
|
||||
plaintext.resize((cipher.size() + 15) / 16 * 16);
|
||||
auto size = (int)plaintext.size();
|
||||
// Encrypt into the caller's buffer, leaving room for the auth tag to be prepended
|
||||
if (EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t*)cipher.data(), cipher.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_DecryptFinal_ex(ctx.get(), plaintext.data(), &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
plaintext.resize(len + size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cipher_t::decrypt_gcm(aes_t &iv, const std::string_view &tagged_cipher,
|
||||
std::vector<std::uint8_t> &plaintext) {
|
||||
auto cipher = tagged_cipher.substr(16);
|
||||
auto tag = tagged_cipher.substr(0, 16);
|
||||
|
||||
auto fg = util::fail_guard([this]() {
|
||||
EVP_CIPHER_CTX_reset(ctx.get());
|
||||
});
|
||||
|
||||
if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_128_gcm(), nullptr, nullptr, nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_DecryptInit_ex(ctx.get(), nullptr, nullptr, key.data(), iv.data()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
|
||||
plaintext.resize((cipher.size() + 15) / 16 * 16);
|
||||
|
||||
int size;
|
||||
if (EVP_DecryptUpdate(ctx.get(), plaintext.data(), &size, (const std::uint8_t*)cipher.data(), cipher.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char*>(tag.data())) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int len = size;
|
||||
if (EVP_DecryptFinal_ex(ctx.get(), plaintext.data() + size, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
plaintext.resize(size + len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cipher_t::encrypt(const std::string_view &plaintext, std::vector<std::uint8_t> &cipher) {
|
||||
int len;
|
||||
|
||||
auto fg = util::fail_guard([this]() {
|
||||
EVP_CIPHER_CTX_reset(ctx.get());
|
||||
});
|
||||
|
||||
// Gen 7 servers use 128-bit AES ECB
|
||||
if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_128_ecb(), nullptr, key.data(), nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_set_padding(ctx.get(), padding);
|
||||
|
||||
cipher.resize((plaintext.size() + 15) / 16 * 16);
|
||||
auto size = (int)cipher.size();
|
||||
// Encrypt into the caller's buffer
|
||||
if (EVP_EncryptUpdate(ctx.get(), cipher.data(), &size, (const std::uint8_t*)plaintext.data(), plaintext.size()) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_EncryptFinal_ex(ctx.get(), cipher.data() + size, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cipher.resize(len + size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
aes_t gen_aes_key(const std::array<uint8_t, 16> &salt, const std::string_view &pin) {
|
||||
aes_t key;
|
||||
|
||||
std::string salt_pin;
|
||||
salt_pin.reserve(salt.size() + pin.size());
|
||||
|
||||
salt_pin.insert(std::end(salt_pin), std::begin(salt), std::end(salt));
|
||||
salt_pin.insert(std::end(salt_pin), std::begin(pin), std::end(pin));
|
||||
|
||||
auto hsh = hash(salt_pin);
|
||||
|
||||
std::copy(std::begin(hsh), std::begin(hsh) + key.size(), std::begin(key));
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
sha256_t hash(const std::string_view &plaintext) {
|
||||
sha256_t hsh;
|
||||
|
||||
SHA256_CTX sha256;
|
||||
SHA256_Init(&sha256);
|
||||
SHA256_Update(&sha256, plaintext.data(), plaintext.size());
|
||||
SHA256_Final(hsh.data(), &sha256);
|
||||
|
||||
return hsh;
|
||||
}
|
||||
|
||||
x509_t x509(const std::string_view &x) {
|
||||
bio_t io { BIO_new(BIO_s_mem()) };
|
||||
|
||||
BIO_write(io.get(), x.data(), x.size());
|
||||
|
||||
X509 *p = nullptr;
|
||||
PEM_read_bio_X509(io.get(), &p, nullptr, nullptr);
|
||||
|
||||
return x509_t { p };
|
||||
}
|
||||
|
||||
pkey_t pkey(const std::string_view &k) {
|
||||
bio_t io { BIO_new(BIO_s_mem()) };
|
||||
|
||||
BIO_write(io.get(), k.data(), k.size());
|
||||
|
||||
EVP_PKEY *p = nullptr;
|
||||
PEM_read_bio_PrivateKey(io.get(), &p, nullptr, nullptr);
|
||||
|
||||
return pkey_t { p };
|
||||
}
|
||||
|
||||
std::string pem(x509_t &x509) {
|
||||
bio_t bio { BIO_new(BIO_s_mem()) };
|
||||
|
||||
PEM_write_bio_X509(bio.get(), x509.get());
|
||||
BUF_MEM *mem_ptr;
|
||||
BIO_get_mem_ptr(bio.get(), &mem_ptr);
|
||||
|
||||
return { mem_ptr->data, mem_ptr->length };
|
||||
}
|
||||
|
||||
std::string pem(pkey_t &pkey) {
|
||||
bio_t bio { BIO_new(BIO_s_mem()) };
|
||||
|
||||
PEM_write_bio_PrivateKey(bio.get(), pkey.get(), nullptr, nullptr, 0, nullptr, nullptr);
|
||||
BUF_MEM *mem_ptr;
|
||||
BIO_get_mem_ptr(bio.get(), &mem_ptr);
|
||||
|
||||
return { mem_ptr->data, mem_ptr->length };
|
||||
}
|
||||
|
||||
std::string_view signature(const x509_t &x) {
|
||||
// X509_ALGOR *_ = nullptr;
|
||||
|
||||
const ASN1_BIT_STRING *asn1 = nullptr;
|
||||
X509_get0_signature(&asn1, nullptr, x.get());
|
||||
|
||||
return { (const char*)asn1->data, (std::size_t)asn1->length };
|
||||
}
|
||||
|
||||
std::string rand(std::size_t bytes) {
|
||||
std::string r;
|
||||
r.resize(bytes);
|
||||
|
||||
RAND_bytes((uint8_t*)r.data(), r.size());
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> sign(const pkey_t &pkey, const std::string_view &data, const EVP_MD *md) {
|
||||
md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||
|
||||
if(EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, pkey.get()) != 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if(EVP_DigestSignUpdate(ctx.get(), data.data(), data.size()) != 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::size_t slen = digest_size;
|
||||
|
||||
std::vector<uint8_t> digest;
|
||||
digest.resize(slen);
|
||||
|
||||
if(EVP_DigestSignFinal(ctx.get(), digest.data(), &slen) != 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return digest;
|
||||
}
|
||||
|
||||
creds_t gen_creds(const std::string_view &cn, std::uint32_t key_bits) {
|
||||
x509_t x509 { X509_new() };
|
||||
pkey_t pkey { EVP_PKEY_new() };
|
||||
|
||||
big_num_t big_num { BN_new() };
|
||||
BN_set_word(big_num.get(), RSA_F4);
|
||||
|
||||
auto rsa = RSA_new();
|
||||
RSA_generate_key_ex(rsa, key_bits, big_num.get(), nullptr);
|
||||
EVP_PKEY_assign_RSA(pkey.get(), rsa);
|
||||
|
||||
X509_set_version(x509.get(), 2);
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(x509.get()), 0);
|
||||
|
||||
constexpr auto year = 60 * 60 * 24 * 365;
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
X509_gmtime_adj(X509_get_notBefore(x509.get()), 0);
|
||||
X509_gmtime_adj(X509_get_notAfter(x509.get()), 20 * year);
|
||||
#else
|
||||
asn1_string_t not_before { ASN1_STRING_dup(X509_get0_notBefore(x509.get())) };
|
||||
asn1_string_t not_after { ASN1_STRING_dup(X509_get0_notAfter(x509.get())) };
|
||||
|
||||
X509_gmtime_adj(not_before.get(), 0);
|
||||
X509_gmtime_adj(not_after.get(), 20 * year);
|
||||
|
||||
X509_set1_notBefore(x509.get(), not_before.get());
|
||||
X509_set1_notAfter(x509.get(), not_after.get());
|
||||
#endif
|
||||
|
||||
X509_set_pubkey(x509.get(), pkey.get());
|
||||
|
||||
auto name = X509_get_subject_name(x509.get());
|
||||
X509_NAME_add_entry_by_txt(name,"CN", MBSTRING_ASC,
|
||||
(const std::uint8_t*)cn.data(), cn.size(),
|
||||
-1, 0);
|
||||
|
||||
X509_set_issuer_name(x509.get(), name);
|
||||
X509_sign(x509.get(), pkey.get(), EVP_sha256());
|
||||
|
||||
return { pem(x509), pem(pkey) };
|
||||
}
|
||||
|
||||
std::vector<uint8_t> sign256(const pkey_t &pkey, const std::string_view &data) {
|
||||
return sign(pkey, data, EVP_sha256());
|
||||
}
|
||||
|
||||
bool verify(const x509_t &x509, const std::string_view &data, const std::string_view &signature, const EVP_MD *md) {
|
||||
auto pkey = X509_get_pubkey(x509.get());
|
||||
|
||||
md_ctx_t ctx { EVP_MD_CTX_create() };
|
||||
|
||||
if(EVP_DigestVerifyInit(ctx.get(), nullptr, md, nullptr, pkey) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(EVP_DigestVerifyUpdate(ctx.get(), data.data(), data.size()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(EVP_DigestVerifyFinal(ctx.get(), (const uint8_t*)signature.data(), signature.size()) != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool verify256(const x509_t &x509, const std::string_view &data, const std::string_view &signature) {
|
||||
return verify(x509, data, signature, EVP_sha256());
|
||||
}
|
||||
|
||||
void md_ctx_destroy(EVP_MD_CTX *ctx) {
|
||||
EVP_MD_CTX_destroy(ctx);
|
||||
}
|
||||
}
|
||||