404
+There's nothing here.
++ +
+commit 161aa20155361268f6785497ca50bd6efa594c85 Author: LizardByte-bot <108553330+LizardByte-bot@users.noreply.github.com> Date: Sat Aug 29 23:40:12 2026 +0000 Deploy site from 2234c0079a6a7b0e2f25990d3c705be85bcd37fd diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..872a08f --- /dev/null +++ b/404.html @@ -0,0 +1,521 @@ + + + +
+ + + + + + + + + + + + + + +There's nothing here.
+
+No roadmap items found.
'; + 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 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; + applyLabelStyle(labelEl, label.color); + + 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 = `Error loading roadmap items: ${error.message}
`; + }); + + // 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; + applyLabelStyle(labelEl, label.color); + + 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(); + } +}); diff --git a/assets/js/staticman.js b/assets/js/staticman.js new file mode 100644 index 0000000..9b8b226 --- /dev/null +++ b/assets/js/staticman.js @@ -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'); + } + } +}); diff --git a/assets/js/store.js b/assets/js/store.js new file mode 100644 index 0000000..bdaff77 --- /dev/null +++ b/assets/js/store.js @@ -0,0 +1,26 @@ +(() => { + 'use strict'; + + document.querySelectorAll('.store-gallery-modal').forEach((modalElement) => { + const carouselElement = modalElement.querySelector('.store-gallery-carousel'); + if (!carouselElement) { + return; + } + + modalElement.addEventListener('show.bs.modal', (event) => { + const requestedSlide = Number.parseInt(event.relatedTarget?.dataset.storeSlide ?? '0', 10); + const carousel = bootstrap.Carousel.getOrCreateInstance(carouselElement, { + interval: false, + keyboard: true, + touch: true, + wrap: true, + }); + + carousel.to(Number.isNaN(requestedSlide) ? 0 : requestedSlide); + }); + + modalElement.addEventListener('shown.bs.modal', () => { + carouselElement.focus(); + }); + }); +})(); diff --git a/assets/js/tabs.js b/assets/js/tabs.js new file mode 100644 index 0000000..411e285 --- /dev/null +++ b/assets/js/tabs.js @@ -0,0 +1,57 @@ +/** + * Keep matching groups of tabs in sync. + */ +(function() { + 'use strict'; + + let synchronizing = false; + + function getTabButtons(group) { + const tabList = Array.from(group.children).find((child) => child.getAttribute('role') === 'tablist'); + + if (!tabList) { + return []; + } + + return Array.from(tabList.querySelectorAll('[role="tab"][data-tab-name]')); + } + + function haveMatchingNames(firstButtons, secondButtons) { + return firstButtons.length === secondButtons.length && firstButtons.every( + (button, index) => button.dataset.tabName === secondButtons[index].dataset.tabName + ); + } + + function synchronizeTabs(event) { + const selectedTab = event.target; + + if (synchronizing || !selectedTab.matches('.tabs [role="tab"][data-tab-name]')) { + return; + } + + const selectedGroup = selectedTab.closest('.tabs'); + const selectedButtons = getTabButtons(selectedGroup); + const selectedIndex = selectedButtons.indexOf(selectedTab); + + synchronizing = true; + + try { + document.querySelectorAll('.tabs').forEach((group) => { + const groupButtons = getTabButtons(group); + const matchingTab = groupButtons[selectedIndex]; + + if ( + group !== selectedGroup && + haveMatchingNames(selectedButtons, groupButtons) && + !matchingTab.classList.contains('active') + ) { + bootstrap.Tab.getOrCreateInstance(matchingTab).show(); + } + }); + } finally { + synchronizing = false; + } + } + + document.addEventListener('shown.bs.tab', synchronizeTabs); +})(); diff --git a/assets/js/theme-init.js b/assets/js/theme-init.js new file mode 100644 index 0000000..9346345 --- /dev/null +++ b/assets/js/theme-init.js @@ -0,0 +1,26 @@ +// Beautiful Jekyll Next - Theme Initialization +// This script must run before page renders to prevent flash of wrong theme (FOUC) + +(function() { + 'use strict'; + + const getStoredTheme = () => localStorage.getItem('theme'); + + const getPreferredTheme = () => { + const storedTheme = getStoredTheme(); + if (storedTheme) { + return storedTheme; + } + return 'auto'; + }; + + const getThemeToApply = (theme) => { + if (theme === 'auto') { + return globalThis.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + } + return theme; + }; + + const theme = getPreferredTheme(); + document.documentElement.dataset.bsTheme = getThemeToApply(theme); +})(); diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..79620bf Binary files /dev/null and b/favicon.ico differ diff --git a/feed.xml b/feed.xml new file mode 100644 index 0000000..f1de0b8 --- /dev/null +++ b/feed.xml @@ -0,0 +1,15 @@ + ++ libvirtualhid is a cross-platform C++ library for creating virtual input devices. It focuses on + gamepad support for remote streaming hosts, while keeping the public API portable and hiding + platform-specific virtual HID details behind backend implementations. +
++ A license is required for virtual gamepads created through the Windows Virtual HID + Driver. Non-Windows backends do not currently require a license. Choose a yearly or + lifetime license at checkout. +
+Build against portable runtime, profile, device, and report concepts while backend-specific HID details stay behind the library implementation. +
+Create virtual gamepads that model generic HID, Xbox, DualShock, DualSense, and Nintendo Switch Pro-style controller layouts. +
+Designed for low-latency streaming hosts that need predictable lifecycle, normalized input state, and output feedback callbacks. +
+Windows gamepads are exposed through a user-mode UMDF2 control driver backed by Virtual HID Framework, without requiring a custom kernel-mode driver. +
+Linux gamepads use descriptor-driven uhid devices, with uinput available for keyboard, mouse, touchscreen, trackpad, and pen tablet devices. +
+Consume libvirtualhid through installed packages, vendored source, add_subdirectory, or FetchContent in CMake-based projects. +
++ Explore the separate Windows driver package for compatible applications that create + virtual HID gamepads. +
++ Read the documentation to learn how to build, use, and integrate libvirtualhid. +
++ Browse the library source, public headers, and streaming-host-oriented examples. +
++ Virtual HID Driver is the Windows gamepad backend for libvirtualhid. It installs the user-mode driver + component that compatible applications use to create virtual HID gamepads discoverable by Windows apps. + The driver package is separate from the portable C++ library and is currently packaged for AMD64 systems. +
++ A license is required to create virtual gamepads with this Windows driver. Non-Windows backends + do not currently require a license. Choose a yearly or lifetime license at checkout. +
+ +Uses a UMDF2 control driver backed by Windows Virtual HID Framework, with no custom kernel-mode driver added by libvirtualhid. +
+Publishes virtual HID devices for compatible DirectInput, SDL/HIDAPI, Windows Gaming Input, GameInput, and browser Gamepad API consumers. +
+Associates each virtual gamepad with the client that created it so Windows can clean up the device if that process exits or crashes. +
+Includes a native tool for creating, controlling, and monitoring gamepads, device nodes, battery state, and output feedback. +
+Supports Generic, Xbox One, Xbox Series, DualShock 4, DualSense, and Nintendo Switch Pro-style virtual controller profiles. +
+Normalizes supported rumble, RGB LED, adaptive trigger, and raw output events for the application that owns the virtual gamepad. +
++ Applications create and update gamepads through libvirtualhid's platform-neutral C++ + API. The Windows backend communicates with the installed driver, while platform-specific + HID details stay out of consumer code. The library and driver must use matching control + protocol versions. +
+
+ The installed package also includes the virtualhid_control diagnostic UI
+ and the gamepad_adapter example for local validation.
+
+ The Windows UMDF driver source and generated driver package artifacts use the + LizardByte Source-Available License 1.0. Packaged helper components may use the MIT + License as documented in the repository license map. +
+