Merge remote-tracking branch 'gzdoom/master' into gzdoom_merge

This commit is contained in:
Mari the Deer 2025-09-24 19:39:17 +02:00
commit b2a9dbf809
203 changed files with 11531 additions and 4298 deletions

View file

@ -20,13 +20,13 @@ jobs:
- name: macOS
os: macos-14
deps_cmdline: brew install libvpx
deps_cmdline: brew install libvpx molten-vk vulkan-volk
build_type: Release
- name: macOS
os: macos-14
extra_options: -G Xcode -DDYN_OPENAL=OFF
deps_cmdline: brew install libvpx
deps_cmdline: brew install libvpx molten-vk vulkan-volk
build_type: Debug
- name: Linux GCC 9 # oldest version available
@ -46,6 +46,7 @@ jobs:
extra_options: -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++
deps_cmdline: sudo apt-get update && sudo apt-get install libsdl2-dev libvpx-dev libwebp-dev
build_type: Release
export_appimage: true
- name: Linux Clang 11 # oldest version available
os: ubuntu-22.04
@ -58,12 +59,14 @@ jobs:
extra_options: -DCMAKE_C_COMPILER=clang-15 -DCMAKE_CXX_COMPILER=clang++-15
deps_cmdline: sudo apt-get update && sudo apt-get install clang-15 libsdl2-dev libvpx-dev libwebp-dev
build_type: Release
export_appimage: true
- name: Linux Clang Latest # rolling default, not actually latest
os: ubuntu-latest
extra_options: -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
deps_cmdline: sudo apt-get update && sudo apt-get install libsdl2-dev libvpx-dev libwebp-dev
build_type: Release
export_appimage: true
steps:
- uses: actions/checkout@v4
@ -113,7 +116,6 @@ jobs:
fi
- name: Upload Package
if: runner.os == 'Windows' || matrix.config.name == 'Linux GCC 12'
uses: actions/upload-artifact@v4
with:
path: build/package

3
.gitignore vendored
View file

@ -1,7 +1,6 @@
# build artifacts
/build*
/wadsrc/*.pk3
/appimage-build
.flatpak-builder/
*.AppImage
*.flatpak
@ -21,6 +20,7 @@ fmodapi*linux/
.vscode
.cache
*.kate-swp
*.geany
# private dev files
*.user
@ -40,5 +40,4 @@ fmodapi*linux/
/llvm
/src/gl/unused
/mapfiles_release/*.map
/AppDir
.cache/

View file

@ -316,7 +316,9 @@ if (HAVE_VULKAN)
add_subdirectory( libraries/ZVulkan )
endif()
set(ZWIDGET_BUILD_EXAMPLE OFF CACHE INTERNAL "" FORCE)
add_subdirectory( libraries/ZWidget )
add_subdirectory( libraries/webp )
add_subdirectory( libraries/discordrpc EXCLUDE_FROM_ALL )

View file

@ -20,7 +20,6 @@
# this module will try to find on your behalf.) Also for OS X, this
# module will automatically add the -framework Cocoa on your behalf.
#
#
# Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration
# and no SDL2_LIBRARY, it means CMake did not find your SDL2 library
# (SDL2.dll, libsdl2.so, SDL2.framework, etc).
@ -29,7 +28,6 @@
# as appropriate. These values are used to generate the final SDL2_LIBRARY
# variable, but when these values are unset, SDL2_LIBRARY does not get created.
#
#
# $SDL2DIR is an environment variable that would
# correspond to the ./configure --prefix=$SDL2DIR
# used in building SDL2.
@ -50,30 +48,26 @@
# SDL2_LIBRARY to override this selection or set the CMake environment
# CMAKE_INCLUDE_PATH to modify the search paths.
#
# Note that the header path has changed from SDL2/SDL.h to just SDL.h
# This needed to change because "proper" SDL2 convention
# is #include "SDL.h", not <SDL2/SDL.h>. This is done for portability
# reasons because not all systems place things in SDL2/ (see FreeBSD).
#
# Ported by Johnny Patterson. This is a literal port for SDL2 of the FindSDL.cmake
# module with the minor edit of changing "SDL" to "SDL2" where necessary. This
# was not created for redistribution, and exists temporarily pending official
# SDL2 CMake modules.
FIND_PATH(SDL2_INCLUDE_DIR SDL.h
FIND_PATH(SDL2_INCLUDE_DIR SDL2/SDL.h
HINTS
$ENV{SDL2DIR}
PATH_SUFFIXES include/SDL2 include
PATH_SUFFIXES include
PATHS
~/Library/Frameworks
/Library/Frameworks
/usr/local/include/SDL2
/usr/include/SDL2
/usr/local
/usr
/sw # Fink
/opt/local # DarwinPorts
/opt/csw # Blastwave
/opt/homebrew
/opt
/boot/system/develop/headers/SDL2 #Hiaku OS
/boot/system/develop/headers #Haiku OS
)
#MESSAGE("SDL2_INCLUDE_DIR is ${SDL2_INCLUDE_DIR}")
@ -86,10 +80,10 @@ FIND_LIBRARY(SDL2_LIBRARY_TEMP
/sw
/opt/local
/opt/csw
/opt/homebrew
/opt
/system/lib #Hiaku OS
/system/lib #Haiku OS
)
#MESSAGE("SDL2_LIBRARY_TEMP is ${SDL2_LIBRARY_TEMP}")
IF(NOT SDL2_BUILDING_LIBRARY)
@ -107,6 +101,7 @@ IF(NOT SDL2_BUILDING_LIBRARY)
/sw
/opt/local
/opt/csw
/opt/homebrew
/opt
)
ENDIF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework")

View file

@ -1,4 +1,5 @@
cmake_minimum_required(VERSION 3.15)
cmake_minimum_required(VERSION 3.24)
project(zvulkan)
option( VULKAN_USE_XLIB "Use Vulkan xlib (X11) WSI integration" ON )
@ -198,15 +199,19 @@ source_group("include\\volk" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/inc
include_directories(include include/zvulkan src)
if(WIN32)
set(ZVULKAN_SOURCES ${ZVULKAN_SOURCES} ${ZVULKAN_WIN32_SOURCES})
list(APPEND ZVULKAN_SOURCES ${ZVULKAN_WIN32_SOURCES})
add_definitions(-DUNICODE -D_UNICODE)
else()
set(ZVULKAN_SOURCES ${ZVULKAN_SOURCES} ${ZVULKAN_UNIX_SOURCES})
if(NOT HAIKU)
set(ZVULKAN_LIBS ${CMAKE_DL_LIBS} -ldl)
if(APPLE)
find_package(Vulkan REQUIRED COMPONENTS MoltenVK)
list(APPEND ZVULKAN_LIBS Vulkan::Vulkan)
elseif(NOT HAIKU AND NOT CMAKE_SYSTEM_NAME MATCHES "FreeBSD|NetBSD|OpenBSD|DragonFly")
list(APPEND ZVULKAN_LIBS ${CMAKE_DL_LIBS} -ldl)
else()
set(ZVULKAN_LIBS ${CMAKE_DL_LIBS})
list(APPEND ZVULKAN_LIBS ${CMAKE_DL_LIBS})
endif()
list(APPEND ZVULKAN_SOURCES ${ZVULKAN_UNIX_SOURCES})
add_definitions(-DUNIX -D_UNIX)
add_link_options(-pthread)
endif()
@ -220,7 +225,7 @@ if(MSVC)
endif()
add_library(zvulkan STATIC ${ZVULKAN_SOURCES} ${ZVULKAN_INCLUDES} ${VULKAN_INCLUDES})
target_link_libraries(zvulkan ${ZVULKAN_LIBS})
target_link_libraries(zvulkan PUBLIC ${ZVULKAN_LIBS})
set_target_properties(zvulkan PROPERTIES CXX_STANDARD 17)
if(MSVC)

View file

@ -569,7 +569,7 @@ inline RenderPassBegin& RenderPassBegin::Framebuffer(VulkanFramebuffer* framebuf
inline RenderPassBegin& RenderPassBegin::AddClearColor(float r, float g, float b, float a)
{
VkClearValue clearValue = { };
clearValue.color = { r, g, b, a };
clearValue.color = {{ r, g, b, a }};
clearValues.push_back(clearValue);
renderPassInfo.clearValueCount = (uint32_t)clearValues.size();

View file

@ -64,30 +64,30 @@ jobs:
shell: bash
run: cmake --build . --config $BUILD_TYPE
# macos-build:
# runs-on: macos-10.15
# name: 🍎 macOS 10.15
# steps:
macos-build:
runs-on: macos-latest
name: 🍎 macOS arm64
steps:
# - name: 🧰 Checkout
# uses: actions/checkout@v2
# with:
# fetch-depth: 0
# submodules: true
- name: 🧰 Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: true
# - name: ⬇️ Install dependencies
# run: brew install sdl2 sdl2_mixer
- name: ⬇️ Install dependencies
run: brew install sdl2 sdl2_mixer
# - name: Create Build Environment
# run: cmake -E make_directory ${{runner.workspace}}/build
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
# - name: Configure CMake
# shell: bash
# working-directory: ${{runner.workspace}}/build
# run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE
- name: Configure CMake
shell: bash
working-directory: ${{runner.workspace}}/build
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE
# - name: Build
# working-directory: ${{runner.workspace}}/build
# shell: bash
# run: |
# cmake --build . --config $BUILD_TYPE
- name: Build
working-directory: ${{runner.workspace}}/build
shell: bash
run: |
cmake --build . --config $BUILD_TYPE

View file

@ -17,6 +17,14 @@ if (UNIX AND NOT APPLE)
)
endif()
# SDL2 finding stuff
# Optional on all platforms
find_package(SDL2 QUIET)
if(NOT ${SDL2_FOUND})
include(FindPkgConfig)
pkg_search_module(SDL2 sdl2)
endif()
set(ZWIDGET_SOURCES
src/core/canvas.cpp
src/core/font.cpp
@ -138,6 +146,8 @@ set(ZWIDGET_X11_SOURCES
src/window/x11/x11_display_backend.h
src/window/x11/x11_display_window.cpp
src/window/x11/x11_display_window.h
src/window/x11/x11_connection.cpp
src/window/x11/x11_connection.h
)
set(ZWIDGET_WAYLAND_SOURCES
@ -147,6 +157,10 @@ set(ZWIDGET_WAYLAND_SOURCES
src/window/wayland/wayland_display_window.h
src/window/wayland/wl_fractional_scaling_protocol.cpp
src/window/wayland/wl_fractional_scaling_protocol.hpp
src/window/wayland/wl_cursor_shape.cpp
src/window/wayland/wl_cursor_shape.hpp
src/window/wayland/wl_xdg_toplevel_icon.cpp
src/window/wayland/wl_xdg_toplevel_icon.hpp
)
source_group("src" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/.+")
@ -226,11 +240,12 @@ elseif(APPLE)
set(ZWIDGET_DEFINES -DUNIX -D_UNIX)
set(ZWIDGET_LINK_OPTIONS -pthread)
else()
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_SDL2_SOURCES} ${ZWIDGET_X11_SOURCES})
set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -ldl -lX11)
set(ZWIDGET_DEFINES -DUNIX -D_UNIX -DUSE_SDL2 -DUSE_X11)
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_X11_SOURCES})
set(ZWIDGET_LIBS ${CMAKE_DL_LIBS} -lX11 -lXi)
set(ZWIDGET_DEFINES -DUNIX -D_UNIX -DUSE_X11)
set(ZWIDGET_LINK_OPTIONS -pthread)
if (DBUS_FOUND)
include_directories("/usr/lib64/dbus-1.0/include") # Bazzite (and probably Fedora 42) keeps the platform-specific dbus headers in this folder
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_DBUS_SOURCES})
set(ZWIDGET_INCLUDE_DIRS ${ZWIDGET_INCLUDE_DIRS} ${DBUS_INCLUDE_DIRS})
set(ZWIDGET_LIBS ${ZWIDGET_LIBS} ${DBUS_LDFLAGS})
@ -244,14 +259,77 @@ else()
endif()
endif()
if(SDL2_FOUND AND NOT WIN32)
set(ZWIDGET_SOURCES ${ZWIDGET_SOURCES} ${ZWIDGET_SDL2_SOURCES})
set(ZWIDGET_DEFINES ${ZWIDGET_DEFINES} -DUSE_SDL2)
endif()
if(MSVC)
set(CXX_WARNING_FLAGS /W3)
else()
set(CXX_WARNING_FLAGS -Wall -Wpedantic)
endif()
add_library(zwidget STATIC ${ZWIDGET_SOURCES} ${ZWIDGET_INCLUDES})
target_compile_options(zwidget PRIVATE ${ZWIDGET_COMPILE_OPTIONS})
target_compile_definitions(zwidget PRIVATE ${ZWIDGET_DEFINES})
target_include_directories(zwidget PRIVATE ${ZWIDGET_INCLUDE_DIRS})
target_link_options(zwidget PRIVATE ${ZWIDGET_LINK_OPTIONS})
target_link_libraries(zwidget ${ZWIDGET_LIBS})
set_target_properties(zwidget PROPERTIES CXX_STANDARD 17)
target_link_libraries(zwidget PRIVATE ${ZWIDGET_LIBS})
if(SDL2_FOUND)
if(TARGET SDL2::SDL2)
target_link_libraries(zwidget PRIVATE SDL2::SDL2)
else() # needed for gzdoom compat for now
target_include_directories(zwidget PRIVATE ${SDL2_INCLUDE_DIR} ${SDL2_INCLUDE_DIRS})
target_link_libraries(zwidget PRIVATE ${SDL2_LIBRARY})
endif()
endif()
set_target_properties(zwidget PROPERTIES CXX_STANDARD 20)
target_compile_options(zwidget PRIVATE ${CXX_WARNING_FLAGS})
if(MSVC)
set_property(TARGET zwidget PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
option(ZWIDGET_BUILD_EXAMPLE "Build the zwidget example application" ON)
if(ZWIDGET_BUILD_EXAMPLE)
add_executable(zwidget_example WIN32
example/example.cpp
example/picopng.cpp
example/picopng.h
)
target_compile_options(zwidget_example PRIVATE ${CXX_WARNING_FLAGS})
add_custom_command(
TARGET zwidget_example POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${CMAKE_CURRENT_SOURCE_DIR}/example/banner.png"
"${CMAKE_CURRENT_BINARY_DIR}/banner.png"
COMMAND ${CMAKE_COMMAND} -E copy
"${CMAKE_CURRENT_SOURCE_DIR}/example/OpenSans.ttf"
"${CMAKE_CURRENT_BINARY_DIR}/OpenSans.ttf"
)
target_include_directories(zwidget_example PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/example)
target_link_libraries(zwidget_example PRIVATE zwidget)
if(WIN32)
target_compile_definitions(zwidget_example PRIVATE UNICODE _UNICODE)
target_link_libraries(zwidget_example PRIVATE gdi32 user32 shell32 comdlg32)
elseif(APPLE)
target_link_libraries(zwidget_example PRIVATE "-framework Cocoa -framework OpenGL")
else()
target_link_libraries(zwidget_example PRIVATE ${ZWIDGET_LIBS})
endif()
if(SDL2_FOUND)
target_link_libraries(zwidget_example PRIVATE SDL2::SDL2)
endif()
set_target_properties(zwidget_example PROPERTIES CXX_STANDARD 20)
if(MSVC)
set_property(TARGET zwidget_example PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
endif()

View file

@ -13,136 +13,29 @@
#include <zwidget/widgets/pushbutton/pushbutton.h>
#include <zwidget/widgets/checkboxlabel/checkboxlabel.h>
#include "picopng.h"
#include <zwidget/widgets/tabwidget/tabwidget.h>
// ************************************************************
// Prototypes
// ************************************************************
static std::vector<uint8_t> ReadAllBytes(const std::string& filename);
class LauncherWindow : public Widget
class LauncherWindowTab1 : public Widget
{
public:
LauncherWindow() : Widget(nullptr, WidgetType::Window)
{
Logo = new ImageBox(this);
WelcomeLabel = new TextLabel(this);
VersionLabel = new TextLabel(this);
SelectLabel = new TextLabel(this);
GeneralLabel = new TextLabel(this);
ExtrasLabel = new TextLabel(this);
FullscreenCheckbox = new CheckboxLabel(this);
DisableAutoloadCheckbox = new CheckboxLabel(this);
DontAskAgainCheckbox = new CheckboxLabel(this);
LightsCheckbox = new CheckboxLabel(this);
BrightmapsCheckbox = new CheckboxLabel(this);
WidescreenCheckbox = new CheckboxLabel(this);
PlayButton = new PushButton(this);
ExitButton = new PushButton(this);
GamesList = new ListView(this);
Choices = new Dropdown(this);
LauncherWindowTab1(Widget parent);
void OnGeometryChanged() override;
private:
TextEdit* Text = nullptr;
};
SetWindowBackground(Colorf::fromRgba8(51, 51, 51));
SetWindowBorderColor(Colorf::fromRgba8(51, 51, 51));
SetWindowCaptionColor(Colorf::fromRgba8(33, 33, 33));
SetWindowCaptionTextColor(Colorf::fromRgba8(226, 223, 219));
SetWindowTitle("VKDoom Launcher");
WelcomeLabel->SetText("Welcome to VKDoom");
VersionLabel->SetText("Version 0xdeadbabe.");
SelectLabel->SetText("Select which game file (IWAD) to run.");
PlayButton->SetText("Play Game");
ExitButton->SetText("Exit");
ExitButton->OnClick = []{
DisplayWindow::ExitLoop();
};
GeneralLabel->SetText("General");
ExtrasLabel->SetText("Extra Graphics");
FullscreenCheckbox->SetText("Fullscreen");
DisableAutoloadCheckbox->SetText("Disable autoload");
DontAskAgainCheckbox->SetText("Don't ask me again");
LightsCheckbox->SetText("Lights");
BrightmapsCheckbox->SetText("Brightmaps");
WidescreenCheckbox->SetText("Widescreen");
Choices->SetMaxDisplayItems(2);
Choices->AddItem("First");
Choices->AddItem("Second");
Choices->AddItem("Third");
Choices->AddItem("Fourth");
Choices->AddItem("Fifth");
Choices->AddItem("Sixth");
Choices->OnChanged = [this](int index) {
std::cout << "Selected " << index << ":" << Choices->GetItem(index) << std::endl;
};
try
{
auto filedata = ReadAllBytes("banner.png");
std::vector<unsigned char> pixels;
unsigned long width = 0, height = 0;
int result = decodePNG(pixels, width, height, (const unsigned char*)filedata.data(), filedata.size(), true);
if (result == 0)
{
Logo->SetImage(Image::Create(width, height, ImageFormat::R8G8B8A8, pixels.data()));
}
}
catch (...)
{
}
}
void OnGeometryChanged() override
{
double y = 0.0;
Logo->SetFrameGeometry(0.0, y, GetWidth(), Logo->GetPreferredHeight());
y += Logo->GetPreferredHeight();
y += 10.0;
WelcomeLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0, WelcomeLabel->GetPreferredHeight());
y += WelcomeLabel->GetPreferredHeight();
VersionLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0, VersionLabel->GetPreferredHeight());
y += VersionLabel->GetPreferredHeight();
y += 10.0;
SelectLabel->SetFrameGeometry(20.0, y, GetWidth() - 40.0 - Choices->GetPreferredWidth(), SelectLabel->GetPreferredHeight());
y += SelectLabel->GetPreferredHeight();
Choices->SetFrameGeometry(GetWidth() - 20.0 - Choices->GetPreferredWidth(), y-Choices->GetPreferredHeight(), Choices->GetPreferredWidth(), Choices->GetPreferredHeight());
double listViewTop = y + 10.0;
y = GetHeight() - 15.0 - PlayButton->GetPreferredHeight();
PlayButton->SetFrameGeometry(20.0, y, 120.0, PlayButton->GetPreferredHeight());
ExitButton->SetFrameGeometry(GetWidth() - 20.0 - 120.0, y, 120.0, PlayButton->GetPreferredHeight());
y -= 20.0;
double panelWidth = 150.0;
y -= DontAskAgainCheckbox->GetPreferredHeight();
DontAskAgainCheckbox->SetFrameGeometry(20.0, y, 190.0, DontAskAgainCheckbox->GetPreferredHeight());
WidescreenCheckbox->SetFrameGeometry(GetWidth() - 20.0 - panelWidth, y, panelWidth, WidescreenCheckbox->GetPreferredHeight());
y -= DisableAutoloadCheckbox->GetPreferredHeight();
DisableAutoloadCheckbox->SetFrameGeometry(20.0, y, 190.0, DisableAutoloadCheckbox->GetPreferredHeight());
BrightmapsCheckbox->SetFrameGeometry(GetWidth() - 20.0 - panelWidth, y, panelWidth, BrightmapsCheckbox->GetPreferredHeight());
y -= FullscreenCheckbox->GetPreferredHeight();
FullscreenCheckbox->SetFrameGeometry(20.0, y, 190.0, FullscreenCheckbox->GetPreferredHeight());
LightsCheckbox->SetFrameGeometry(GetWidth() - 20.0 - panelWidth, y, panelWidth, LightsCheckbox->GetPreferredHeight());
y -= GeneralLabel->GetPreferredHeight();
GeneralLabel->SetFrameGeometry(20.0, y, 190.0, GeneralLabel->GetPreferredHeight());
ExtrasLabel->SetFrameGeometry(GetWidth() - 20.0 - panelWidth, y, panelWidth, ExtrasLabel->GetPreferredHeight());
double listViewBottom = y - 10.0;
GamesList->SetFrameGeometry(20.0, listViewTop, GetWidth() - 40.0, std::max(listViewBottom - listViewTop, 0.0));
}
ImageBox* Logo = nullptr;
class LauncherWindowTab2 : public Widget
{
public:
LauncherWindowTab2(Widget parent);
void OnGeometryChanged() override;
private:
TextLabel* WelcomeLabel = nullptr;
TextLabel* VersionLabel = nullptr;
TextLabel* SelectLabel = nullptr;
@ -154,13 +47,261 @@ public:
CheckboxLabel* LightsCheckbox = nullptr;
CheckboxLabel* BrightmapsCheckbox = nullptr;
CheckboxLabel* WidescreenCheckbox = nullptr;
PushButton* PlayButton = nullptr;
PushButton* ExitButton = nullptr;
ListView* GamesList = nullptr;
Dropdown* Choices = nullptr;
};
static std::vector<uint8_t> ReadAllBytes(const std::string& filename);
class LauncherWindowTab3 : public Widget
{
public:
LauncherWindowTab3(Widget parent);
void OnGeometryChanged() override;
private:
TextLabel* Label = nullptr;
Dropdown* Choices = nullptr;
PushButton* Popup = nullptr;
};
class LauncherWindow : public Widget
{
public:
LauncherWindow();
private:
void OnClose() override;
void OnGeometryChanged() override;
ImageBox* Logo = nullptr;
TabWidget* Pages = nullptr;
PushButton* ExitButton = nullptr;
LauncherWindowTab1* Tab1 = nullptr;
LauncherWindowTab2* Tab2 = nullptr;
LauncherWindowTab3* Tab3 = nullptr;
};
// ************************************************************
// UI implementation
// ************************************************************
LauncherWindow::LauncherWindow(): Widget(nullptr, WidgetType::Window)
{
SetWindowTitle("ZWidget Demo");
try
{
SetWindowIcon({
Image::LoadResource("surreal-engine-icon-16.png"),
Image::LoadResource("surreal-engine-icon-24.png"),
Image::LoadResource("surreal-engine-icon-32.png"),
Image::LoadResource("surreal-engine-icon-48.png"),
Image::LoadResource("surreal-engine-icon-64.png"),
Image::LoadResource("surreal-engine-icon-128.png"),
Image::LoadResource("surreal-engine-icon-256.png")
});
}
catch (...)
{
}
Logo = new ImageBox(this);
ExitButton = new PushButton(this);
Pages = new TabWidget(this);
Tab1 = new LauncherWindowTab1(this);
Tab2 = new LauncherWindowTab2(this);
Tab3 = new LauncherWindowTab3(this);
Pages->AddTab(Tab1, "Welcome");
Pages->AddTab(Tab2, "VKDoom");
Pages->AddTab(Tab3, "ZWidgets");
ExitButton->SetText("Exit");
ExitButton->OnClick = []{
DisplayWindow::ExitLoop();
};
try
{
auto filedata = ReadAllBytes("banner.png");
std::vector<unsigned char> pixels;
unsigned long width = 0, height = 0;
int result = decodePNG(pixels, width, height, (const unsigned char*)filedata.data(), filedata.size(), true);
if (result == 0)
{
Logo->SetImage(Image::Create(width, height, ImageFormat::R8G8B8A8, pixels.data()));
}
}
catch (...)
{
}
}
void LauncherWindow::OnGeometryChanged()
{
double y = 0, h;
h = Logo->GetPreferredHeight();
Logo->SetFrameGeometry(0, y, GetWidth(), h);
y += h;
h = GetHeight() - y - ExitButton->GetPreferredHeight() - 40;
Pages->SetFrameGeometry(0, y, GetWidth(), h);
y += h + 20;
ExitButton->SetFrameGeometry(GetWidth() - 20 - 120, y, 120, ExitButton->GetPreferredHeight());
}
void LauncherWindow::OnClose()
{
DisplayWindow::ExitLoop();
}
LauncherWindowTab1::LauncherWindowTab1(Widget parent): Widget(nullptr)
{
Text = new TextEdit(this);
Text->SetText(
"Welcome to VKDoom\n\n"
"Click the tabs to look at other widgets\n\n"
"Also, this text is editable\n"
);
}
void LauncherWindowTab1::OnGeometryChanged()
{
Text->SetFrameGeometry(0, 10, GetWidth(), GetHeight());
}
LauncherWindowTab2::LauncherWindowTab2(Widget parent): Widget(nullptr)
{
WelcomeLabel = new TextLabel(this);
VersionLabel = new TextLabel(this);
SelectLabel = new TextLabel(this);
GeneralLabel = new TextLabel(this);
ExtrasLabel = new TextLabel(this);
FullscreenCheckbox = new CheckboxLabel(this);
DisableAutoloadCheckbox = new CheckboxLabel(this);
DontAskAgainCheckbox = new CheckboxLabel(this);
LightsCheckbox = new CheckboxLabel(this);
BrightmapsCheckbox = new CheckboxLabel(this);
WidescreenCheckbox = new CheckboxLabel(this);
GamesList = new ListView(this);
WelcomeLabel->SetText("Welcome to VKDoom");
VersionLabel->SetText("Version 0xdeadbabe.");
SelectLabel->SetText("Select which game file (IWAD) to run.");
GamesList->AddItem("Doom");
GamesList->AddItem("Doom 2: Electric Boogaloo");
GamesList->AddItem("Doom 3D");
GamesList->AddItem("Doom 4: The Quest for Peace");
GamesList->AddItem("Doom on Ice");
GamesList->AddItem("The Doom");
GamesList->AddItem("Doom 2");
GeneralLabel->SetText("General");
ExtrasLabel->SetText("Extra Graphics");
FullscreenCheckbox->SetText("Fullscreen");
DisableAutoloadCheckbox->SetText("Disable autoload");
DontAskAgainCheckbox->SetText("Don't ask me again");
LightsCheckbox->SetText("Lights");
BrightmapsCheckbox->SetText("Brightmaps");
WidescreenCheckbox->SetText("Widescreen");
}
void LauncherWindowTab2::OnGeometryChanged()
{
double y = 0, h;
h = WelcomeLabel->GetPreferredHeight();
WelcomeLabel->SetFrameGeometry(20, y, GetWidth() - 40, h);
y += h;
h = VersionLabel->GetPreferredHeight();
VersionLabel->SetFrameGeometry(20, y, GetWidth() - 40, h);
y += h + 10;
h = SelectLabel->GetPreferredHeight();
SelectLabel->SetFrameGeometry(20, y, GetWidth() - 40, h);
y += h;
double listViewTop = y + 10, listViewBottom;
y = GetHeight();
h = DontAskAgainCheckbox->GetPreferredHeight();
y -= h;
DontAskAgainCheckbox->SetFrameGeometry(20, y, 190, h);
WidescreenCheckbox->SetFrameGeometry(GetWidth() - 170, y, 150, WidescreenCheckbox->GetPreferredHeight());
h = DisableAutoloadCheckbox->GetPreferredHeight();
y -= h;
DisableAutoloadCheckbox->SetFrameGeometry(20, y, 190, h);
BrightmapsCheckbox->SetFrameGeometry(GetWidth() - 170, y, 150, BrightmapsCheckbox->GetPreferredHeight());
h = FullscreenCheckbox->GetPreferredHeight();
y -= h;
FullscreenCheckbox->SetFrameGeometry(20, y, 190, h);
LightsCheckbox->SetFrameGeometry(GetWidth() - 170, y, 150, LightsCheckbox->GetPreferredHeight());
h = GeneralLabel->GetPreferredHeight();
y -= h;
GeneralLabel->SetFrameGeometry(20, y, 190, GeneralLabel->GetPreferredHeight());
ExtrasLabel->SetFrameGeometry(GetWidth() - 170, y, 150, ExtrasLabel->GetPreferredHeight());
listViewBottom = y - 10;
GamesList->SetFrameGeometry(20, listViewTop, GetWidth() - 40, std::max<double>(listViewBottom - listViewTop, 0));
}
LauncherWindowTab3::LauncherWindowTab3(Widget parent): Widget(nullptr)
{
Label = new TextLabel(this);
Choices = new Dropdown(this);
Popup = new PushButton(this);
Label->SetText("Oh my, even more widgets");
Popup->SetText("Click me.");
Choices->SetMaxDisplayItems(2);
Choices->AddItem("First");
Choices->AddItem("Second");
Choices->AddItem("Third");
Choices->AddItem("Fourth");
Choices->AddItem("Fifth");
Choices->AddItem("Sixth");
Choices->OnChanged = [this](int index) {
std::cout << "Selected " << index << ":" << Choices->GetItem(index) << std::endl;
};
Popup->OnClick = []{
std::cout << "TODO: open popup" << std::endl;
};
}
void LauncherWindowTab3::OnGeometryChanged()
{
double y = 0, h;
y += 10;
h = Label->GetPreferredHeight();
Label->SetFrameGeometry(20, y, GetWidth() - 40, h);
y += h + 10;
h = Choices->GetPreferredHeight();
Choices->SetFrameGeometry(20, y, Choices->GetPreferredWidth(), h);
y += h + 10;
h = Popup->GetPreferredHeight();
Popup->SetFrameGeometry(20, y, 120, h);
y += h;
}
// ************************************************************
// Shared code
// ************************************************************
std::vector<SingleFontData> LoadWidgetFontData(const std::string& name)
{
@ -174,13 +315,24 @@ std::vector<uint8_t> LoadWidgetData(const std::string& name)
return ReadAllBytes(name);
}
enum class Backend {
{
Default, Win32, SDL2, X11, Wayland
};
int example(Backend backend = Backend::Default)
enum class Theme
{
WidgetTheme::SetTheme(std::make_unique<DarkWidgetTheme>());
Default, Light, Dark
};
int example(Backend backend = Backend::Default, Theme theme = Theme::Default)
{
// just for testing themes
switch (theme)
{
case Theme::Default: WidgetTheme::SetTheme(std::make_unique<DarkWidgetTheme>()); break;
case Theme::Dark: WidgetTheme::SetTheme(std::make_unique<DarkWidgetTheme>()); break;
case Theme::Light: WidgetTheme::SetTheme(std::make_unique<LightWidgetTheme>()); break;
}
// just for testing backends
switch (backend)
@ -192,40 +344,23 @@ int example(Backend backend = Backend::Default)
case Backend::Wayland: DisplayBackend::Set(DisplayBackend::TryCreateWayland()); break;
}
#if 1
auto launcher = new LauncherWindow();
launcher->SetFrameGeometry(100.0, 100.0, 615.0, 668.0);
launcher->Show();
#else
auto mainwindow = new MainWindow();
auto textedit = new TextEdit(mainwindow);
textedit->SetText(R"(
#version 460
in vec4 AttrPos;
in vec4 AttrColor;
out vec4 Color;
void main()
{
gl_Position = AttrPos;
Color = AttrColor;
}
)");
mainwindow->SetWindowTitle("ZWidget Example");
mainwindow->SetFrameGeometry(100.0, 100.0, 1700.0, 900.0);
mainwindow->SetCentralWidget(textedit);
textedit->SetFocus();
mainwindow->Show();
#endif
DisplayWindow::RunLoop();
return 0;
}
// ************************************************************
// Platform-specific code
// ************************************************************
#ifdef WIN32
#define WIN32_MEAN_AND_LEAN
#define NOMINMAX
#include <Windows.h>
#include <stdexcept>
@ -277,6 +412,7 @@ static std::vector<uint8_t> ReadAllBytes(const std::string& filename)
int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, PSTR cmdline, int cmdshow)
{
SetProcessDPIAware();
example();
}
@ -305,18 +441,25 @@ static std::vector<uint8_t> ReadAllBytes(const std::string& filename)
int main(int argc, const char** argv)
{
std::string backendStr = argc > 1? argv[1]: "";
std::transform(backendStr.begin(), backendStr.end(), backendStr.begin(),
[](unsigned char c){ return std::tolower(c); });
Backend backend = Backend::Default;
Theme theme = Theme::Default;
if (backendStr == "sdl2") backend = Backend::SDL2;
if (backendStr == "x11") backend = Backend::X11;
if (backendStr == "wayland") backend = Backend::Wayland;
if (backendStr == "win32") backend = Backend::Win32; // lol
for (auto i = 1; i < argc; i++)
{
std::string s = argv[i];
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c){ return std::tolower(c); });
example(backend);
if (s == "light") { theme = Theme::Light; continue; }
if (s == "dark") { theme = Theme::Dark; continue; }
if (s == "sdl2") { backend = Backend::SDL2; continue; }
if (s == "x11") { backend = Backend::X11; continue; }
if (s == "wayland") { backend = Backend::Wayland; continue; }
if (s == "win32") { backend = Backend::Win32; continue; } // lol
}
example(backend, theme);
}
#endif

View file

@ -260,7 +260,7 @@ int decodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width,
readPngHeader(&in[0], size); if(error) return;
size_t pos = 33; //first byte of the first chunk after the header
std::vector<unsigned char> idat; //the data from idat chunks
bool IEND = false, known_type = true;
bool IEND = false/*, known_type = true*/;
info.key_defined = false;
while(!IEND) //loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. IDAT data is put at the start of the in buffer
{
@ -312,7 +312,7 @@ int decodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width,
{
if(!(in[pos + 0] & 32)) { error = 69; return; } //error: unknown critical chunk (5th bit of first byte of chunk type is 0)
pos += (chunkLength + 4); //skip 4 letters and uninterpreted data of unimplemented chunk
known_type = false;
//known_type = false;
}
pos += 4; //step over CRC (which is ignored)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View file

@ -17,6 +17,21 @@ public:
return { r * s, g * s, b * s, a * s };
}
static Colorf fromRgba(uint32_t rgba)
{
return fromRgba8(
0xff & rgba>>24,
0xff & rgba>>16,
0xff & rgba>>8,
0xff & rgba
);
}
static Colorf fromRgb(uint32_t rgb)
{
return fromRgba(rgb<<8 | 0xff);
}
uint32_t toBgra8() const
{
uint32_t cr = (int)(std::max(std::min(r * 255.0f, 255.0f), 0.0f));

View file

@ -212,7 +212,7 @@ private:
Colorf cursor_color;
std::string::size_type sel_start = 0, sel_end = 0;
Colorf sel_foreground, sel_background = Colorf::fromRgba8(153, 201, 239);
Colorf sel_foreground, sel_background;
std::string text;
std::vector<SpanObject> objects;

View file

@ -55,7 +55,23 @@ public:
class WidgetTheme
{
struct SimpleTheme {
const Colorf bgMain; // background
const Colorf fgMain; //
const Colorf bgLight; // headers / inputs
const Colorf fgLight; //
const Colorf bgAction; // interactive elements
const Colorf fgAction; //
const Colorf bgHover; // hover / highlight
const Colorf fgHover; //
const Colorf bgActive; // click
const Colorf fgActive; //
const Colorf border; // around elements
const Colorf divider; // between elements
};
public:
WidgetTheme() {}
WidgetTheme(const struct SimpleTheme &theme);
virtual ~WidgetTheme() = default;
WidgetStyle* RegisterStyle(std::unique_ptr<WidgetStyle> widgetStyle, const std::string& widgetClass);

View file

@ -40,8 +40,8 @@ public:
std::string GetWindowTitle() const;
void SetWindowTitle(const std::string& text);
// Icon GetWindowIcon() const;
// void SetWindowIcon(const Icon& icon);
std::vector<std::shared_ptr<Image>> GetWindowIcon() const;
void SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images);
// Widget content box
Size GetSize() const;
@ -233,6 +233,7 @@ private:
Colorf WindowBackground = Colorf::fromRgba8(240, 240, 240);
std::string WindowTitle;
std::vector<std::shared_ptr<Image>> WindowIcon;
std::unique_ptr<DisplayWindow> DispWindow;
std::unique_ptr<Canvas> DispCanvas;
Widget* FocusWidget = nullptr;

View file

@ -83,6 +83,11 @@ private:
SpanLayout layout;
Rect box;
bool invalidated = true;
Line(const TextEdit *self)
{
layout.SetSelectionColors(self->selectionFG, self->selectionBG);
}
};
struct ivec2
@ -96,9 +101,10 @@ private:
bool operator!=(const ivec2& b) const { return x != b.x || y != b.y; }
};
Colorf selectionBG, selectionFG;
Scrollbar* vert_scrollbar;
Timer* timer = nullptr;
std::vector<Line> lines = { Line() };
std::vector<Line> lines = { Line{this} };
ivec2 cursor_pos = { 0, 0 };
int max_length = -1;
bool mouse_selecting = false;

View file

@ -26,6 +26,7 @@ class Widget;
class OpenFileDialog;
class SaveFileDialog;
class OpenFolderDialog;
class Image;
enum class StandardCursor
{
@ -163,6 +164,7 @@ public:
virtual ~DisplayWindow() = default;
virtual void SetWindowTitle(const std::string& text) = 0;
virtual void SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images) = 0;
virtual void SetWindowFrame(const Rect& box) = 0;
virtual void SetClientFrame(const Rect& box) = 0;
virtual void Show() = 0;

View file

@ -549,9 +549,7 @@ void BitmapCanvas::plot(float x, float y, float alpha, const Colorf& color)
int xx = (int)x;
int yy = (int)y;
int dwidth = width;
int dheight = height;
uint32_t* dest = pixels.data() + xx + yy * dwidth;
uint32_t* dest = pixels.data() + xx + yy * width;
uint32_t cred = (int32_t)clamp(color.r * 256.0f, 0.0f, 256.0f);
uint32_t cgreen = (int32_t)clamp(color.g * 256.0f, 0.0f, 256.0f);
@ -665,7 +663,6 @@ void BitmapCanvas::fillTile(float left, float top, float width, float height, Co
return;
int dwidth = this->width;
int dheight = this->height;
uint32_t* dest = this->pixels.data();
int x0 = (int)left;
@ -771,11 +768,9 @@ void BitmapCanvas::drawTile(CanvasTexture* tex, float left, float top, float wid
auto texture = static_cast<BitmapTexture*>(tex);
int swidth = texture->Width;
int sheight = texture->Height;
const uint32_t* src = texture->Data.data();
int dwidth = this->width;
int dheight = this->height;
uint32_t* dest = this->pixels.data();
int x0 = (int)left;
@ -880,11 +875,9 @@ void BitmapCanvas::drawGlyph(CanvasTexture* tex, float left, float top, float wi
auto texture = static_cast<BitmapTexture*>(tex);
int swidth = texture->Width;
int sheight = texture->Height;
const uint32_t* src = texture->Data.data();
int dwidth = this->width;
int dheight = this->height;
uint32_t* dest = this->pixels.data();
int x0 = (int)left;

View file

@ -255,7 +255,7 @@ PathFillRasterizer::Extent PathFillRasterizer::FindExtent(const PathScanline* sc
void PathFillRasterizer::Clear()
{
for (size_t y = first_scanline; y < last_scanline; y++)
for (int y = first_scanline; y < last_scanline; y++)
{
auto& scanline = scanlines[y];
if (!scanline.edges.empty())

View file

@ -262,7 +262,7 @@ int decodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width,
readPngHeader(&in[0], size); if(error) return;
size_t pos = 33; //first byte of the first chunk after the header
std::vector<unsigned char> idat; //the data from idat chunks
bool IEND = false, known_type = true;
bool IEND = false/*, known_type = true*/;
info.key_defined = false;
while(!IEND) //loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. IDAT data is put at the start of the in buffer
{
@ -314,7 +314,7 @@ int decodePNG(std::vector<unsigned char>& out_image, unsigned long& image_width,
{
if(!(in[pos + 0] & 32)) { error = 69; return; } //error: unknown critical chunk (5th bit of first byte of chunk type is 0)
pos += (chunkLength + 4); //skip 4 letters and uninterpreted data of unimplemented chunk
known_type = false;
/*known_type = false;*/
}
pos += 4; //step over CRC (which is ignored)
}

View file

@ -72,16 +72,12 @@ void SpanLayout::DrawLayout(Canvas* canvas)
LineSegment& segment = line.segments.back();
if (cursor_visible && segment.end <= cursor_pos)
{
switch (segment.type)
{
case object_text:
if (segment.type == object_text)
{
double cursor_x = x + segment.x_position + canvas->measureText(segment.font, text.substr(segment.start, segment.end - segment.start)).width;
double cursor_width = 1;
canvas->fillRect(Rect::ltrb(cursor_x, y + line.ascender - segment.ascender, cursor_width, y + line.ascender + segment.descender), cursor_color);
}
break;
}
}
}

View file

@ -141,12 +141,26 @@ WidgetTheme* WidgetTheme::GetTheme()
return CurrentTheme.get();
}
/////////////////////////////////////////////////////////////////////////////
DarkWidgetTheme::DarkWidgetTheme()
WidgetTheme::WidgetTheme(const struct SimpleTheme &theme)
{
auto bgMain = theme.bgMain; // background
auto fgMain = theme.fgMain; //
auto bgLight = theme.bgLight; // headers / inputs
auto fgLight = theme.fgLight; //
auto bgAction = theme.bgAction; // interactive elements
auto fgAction = theme.fgAction; //
auto bgHover = theme.bgHover; // hover / highlight
auto fgHover = theme.fgHover; //
auto bgActive = theme.bgActive; // click
auto fgActive = theme.fgActive; //
auto border = theme.border; // around elements
auto divider = theme.divider; // between elements
auto none = Colorf::transparent();
auto widget = RegisterStyle(std::make_unique<BasicWidgetStyle>(), "widget");
auto textlabel = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textlabel");
/*auto textlabel =*/ RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textlabel");
auto pushbutton = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "pushbutton");
auto lineedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "lineedit");
auto textedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textedit");
@ -167,272 +181,177 @@ DarkWidgetTheme::DarkWidgetTheme()
auto statusbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "statusbar");
widget->SetString("font-family", "NotoSans");
widget->SetColor("color", Colorf::fromRgba8(226, 223, 219));
widget->SetColor("window-background", Colorf::fromRgba8(51, 51, 51));
widget->SetColor("window-border", Colorf::fromRgba8(51, 51, 51));
widget->SetColor("window-caption-color", Colorf::fromRgba8(33, 33, 33));
widget->SetColor("window-caption-text-color", Colorf::fromRgba8(226, 223, 219));
widget->SetColor("color", fgMain);
widget->SetColor("window-background", bgMain);
widget->SetColor("window-border", bgMain);
widget->SetColor("window-caption-color", bgLight);
widget->SetColor("window-caption-text-color", fgLight);
pushbutton->SetDouble("noncontent-left", 10.0);
pushbutton->SetDouble("noncontent-top", 5.0);
pushbutton->SetDouble("noncontent-right", 10.0);
pushbutton->SetDouble("noncontent-bottom", 5.0);
pushbutton->SetColor("background-color", Colorf::fromRgba8(68, 68, 68));
pushbutton->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100));
pushbutton->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100));
pushbutton->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100));
pushbutton->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
pushbutton->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78));
pushbutton->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88));
pushbutton->SetColor("color", fgAction);
pushbutton->SetColor("background-color", bgAction);
pushbutton->SetColor("border-left-color", border);
pushbutton->SetColor("border-top-color", border);
pushbutton->SetColor("border-right-color", border);
pushbutton->SetColor("border-bottom-color", border);
pushbutton->SetColor("hover", "color", fgHover);
pushbutton->SetColor("hover", "background-color", bgHover);
pushbutton->SetColor("down", "color", fgActive);
pushbutton->SetColor("down", "background-color", bgActive);
lineedit->SetDouble("noncontent-left", 5.0);
lineedit->SetDouble("noncontent-top", 3.0);
lineedit->SetDouble("noncontent-right", 5.0);
lineedit->SetDouble("noncontent-bottom", 3.0);
lineedit->SetColor("background-color", Colorf::fromRgba8(38, 38, 38));
lineedit->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100));
lineedit->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100));
lineedit->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100));
lineedit->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
lineedit->SetColor("selection-color", Colorf::fromRgba8(100, 100, 100));
lineedit->SetColor("no-focus-selection-color", Colorf::fromRgba8(68, 68, 68));
lineedit->SetColor("color", fgLight);
lineedit->SetColor("background-color", bgLight);
lineedit->SetColor("border-left-color", border);
lineedit->SetColor("border-top-color", border);
lineedit->SetColor("border-right-color", border);
lineedit->SetColor("border-bottom-color", border);
lineedit->SetColor("selection-color", bgHover);
lineedit->SetColor("no-focus-selection-color", bgHover);
textedit->SetDouble("noncontent-left", 8.0);
textedit->SetDouble("noncontent-top", 8.0);
textedit->SetDouble("noncontent-right", 8.0);
textedit->SetDouble("noncontent-bottom", 8.0);
textedit->SetColor("background-color", Colorf::fromRgba8(38, 38, 38));
textedit->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100));
textedit->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100));
textedit->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100));
textedit->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
textedit->SetColor("color", fgLight);
textedit->SetColor("background-color", bgLight);
textedit->SetColor("border-left-color", border);
textedit->SetColor("border-top-color", border);
textedit->SetColor("border-right-color", border);
textedit->SetColor("border-bottom-color", border);
textedit->SetColor("selection-color", bgHover);
listview->SetDouble("noncontent-left", 10.0);
listview->SetDouble("noncontent-top", 10.0);
listview->SetDouble("noncontent-right", 3.0);
listview->SetDouble("noncontent-bottom", 10.0);
listview->SetColor("background-color", Colorf::fromRgba8(38, 38, 38));
listview->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100));
listview->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100));
listview->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100));
listview->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
listview->SetColor("selection-color", Colorf::fromRgba8(100, 100, 100));
listview->SetColor("color", fgLight);
listview->SetColor("background-color", bgLight);
listview->SetColor("border-left-color", border);
listview->SetColor("border-top-color", border);
listview->SetColor("border-right-color", border);
listview->SetColor("border-bottom-color", border);
listview->SetColor("selection-color", bgHover);
dropdown->SetDouble("noncontent-left", 5.0);
dropdown->SetDouble("noncontent-top", 5.0);
dropdown->SetDouble("noncontent-right", 5.0);
dropdown->SetDouble("noncontent-bottom", 5.0);
dropdown->SetColor("background-color", Colorf::fromRgba8(38, 38, 38));
dropdown->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100));
dropdown->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100));
dropdown->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100));
dropdown->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
dropdown->SetColor("selection-color", Colorf::fromRgba8(100, 100, 100));
dropdown->SetColor("arrow-color", Colorf::fromRgba8(100, 100, 100));
dropdown->SetColor("color", fgLight);
dropdown->SetColor("background-color", bgLight);
dropdown->SetColor("border-left-color", border);
dropdown->SetColor("border-top-color", border);
dropdown->SetColor("border-right-color", border);
dropdown->SetColor("border-bottom-color", border);
dropdown->SetColor("arrow-color", border);
scrollbar->SetColor("track-color", Colorf::fromRgba8(33, 33, 33));
scrollbar->SetColor("thumb-color", Colorf::fromRgba8(58, 58, 58));
scrollbar->SetColor("track-color", divider);
scrollbar->SetColor("thumb-color", border);
tabbar->SetDouble("spacer-left", 20.0);
tabbar->SetDouble("spacer-right", 20.0);
tabbar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33));
tabbar->SetColor("background-color", bgLight);
tabbar_tab->SetDouble("noncontent-left", 15.0);
tabbar_tab->SetDouble("noncontent-right", 15.0);
tabbar_tab->SetDouble("noncontent-top", 1.0);
tabbar_tab->SetDouble("noncontent-bottom", 1.0);
tabbar_tab->SetColor("background-color", Colorf::fromRgba8(38, 38, 38));
tabbar_tab->SetColor("border-left-color", Colorf::fromRgba8(68, 68, 68));
tabbar_tab->SetColor("border-top-color", Colorf::fromRgba8(68, 68, 68));
tabbar_tab->SetColor("border-right-color", Colorf::fromRgba8(68, 68, 68));
tabbar_tab->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
tabbar_tab->SetColor("hover", "background-color", Colorf::fromRgba8(45, 45, 45));
tabbar_tab->SetColor("active", "background-color", Colorf::fromRgba8(51, 51, 51));
tabbar_tab->SetColor("active", "border-left-color", Colorf::fromRgba8(100, 100, 100));
tabbar_tab->SetColor("active", "border-top-color", Colorf::fromRgba8(100, 100, 100));
tabbar_tab->SetColor("active", "border-right-color", Colorf::fromRgba8(100, 100, 100));
tabbar_tab->SetColor("active", "border-bottom-color", Colorf::transparent());
tabbar_tab->SetColor("color", fgMain);
tabbar_tab->SetColor("background-color", bgMain);
tabbar_tab->SetColor("border-left-color", divider);
tabbar_tab->SetColor("border-top-color", divider);
tabbar_tab->SetColor("border-right-color", divider);
tabbar_tab->SetColor("border-bottom-color", border);
tabbar_tab->SetColor("hover", "color", fgAction);
tabbar_tab->SetColor("hover", "background-color", bgAction);
tabbar_tab->SetColor("active", "background-color", bgMain);
tabbar_tab->SetColor("active", "border-left-color", border);
tabbar_tab->SetColor("active", "border-top-color", border);
tabbar_tab->SetColor("active", "border-right-color", border);
tabbar_tab->SetColor("active", "border-bottom-color", none);
tabbar_spacer->SetDouble("noncontent-bottom", 1.0);
tabbar_spacer->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
tabbar_spacer->SetColor("border-bottom-color", border);
tabwidget_stack->SetDouble("noncontent-left", 20.0);
tabwidget_stack->SetDouble("noncontent-top", 5.0);
tabwidget_stack->SetDouble("noncontent-right", 20.0);
tabwidget_stack->SetDouble("noncontent-bottom", 5.0);
checkbox_label->SetColor("checked-outer-border-color", Colorf::fromRgba8(100, 100, 100));
checkbox_label->SetColor("checked-inner-border-color", Colorf::fromRgba8(51, 51, 51));
checkbox_label->SetColor("checked-color", Colorf::fromRgba8(226, 223, 219));
checkbox_label->SetColor("unchecked-outer-border-color", Colorf::fromRgba8(99, 99, 99));
checkbox_label->SetColor("unchecked-inner-border-color", Colorf::fromRgba8(51, 51, 51));
checkbox_label->SetColor("checked-outer-border-color", border);
checkbox_label->SetColor("checked-inner-border-color", bgMain);
checkbox_label->SetColor("checked-color", fgMain);
checkbox_label->SetColor("unchecked-outer-border-color", border);
checkbox_label->SetColor("unchecked-inner-border-color", bgMain);
menubar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33));
toolbar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33));
statusbar->SetColor("background-color", Colorf::fromRgba8(33, 33, 33));
menubar->SetColor("background-color", bgLight);
toolbar->SetColor("background-color", bgLight);
statusbar->SetColor("background-color", bgLight);
toolbarbutton->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78));
toolbarbutton->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88));
toolbarbutton->SetColor("hover", "color", fgHover);
toolbarbutton->SetColor("hover", "background-color", bgHover);
toolbarbutton->SetColor("down", "color", fgActive);
toolbarbutton->SetColor("down", "background-color", bgActive);
menubaritem->SetColor("color", Colorf::fromRgba8(226, 223, 219));
menubaritem->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78));
menubaritem->SetColor("hover", "color", Colorf::fromRgba8(0, 0, 0));
menubaritem->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88));
menubaritem->SetColor("down", "color", Colorf::fromRgba8(0, 0, 0));
menubaritem->SetColor("color", fgMain);
menubaritem->SetColor("hover", "color", fgHover);
menubaritem->SetColor("hover", "background-color", bgHover);
menubaritem->SetColor("down", "color", fgActive);
menubaritem->SetColor("down", "background-color", bgActive);
menu->SetDouble("noncontent-left", 5.0);
menu->SetDouble("noncontent-top", 5.0);
menu->SetDouble("noncontent-right", 5.0);
menu->SetDouble("noncontent-bottom", 5.0);
menu->SetColor("border-left-color", Colorf::fromRgba8(100, 100, 100));
menu->SetColor("border-top-color", Colorf::fromRgba8(100, 100, 100));
menu->SetColor("border-right-color", Colorf::fromRgba8(100, 100, 100));
menu->SetColor("border-bottom-color", Colorf::fromRgba8(100, 100, 100));
menu->SetColor("color", fgMain);
menu->SetColor("background-color", bgMain);
menu->SetColor("border-left-color", border);
menu->SetColor("border-top-color", border);
menu->SetColor("border-right-color", border);
menu->SetColor("border-bottom-color", border);
menuitem->SetColor("hover", "background-color", Colorf::fromRgba8(78, 78, 78));
menuitem->SetColor("down", "background-color", Colorf::fromRgba8(88, 88, 88));
menuitem->SetColor("hover", "color", fgHover);
menuitem->SetColor("hover", "background-color", bgHover);
menuitem->SetColor("down", "color", fgActive);
menuitem->SetColor("down", "background-color", bgActive);
}
/////////////////////////////////////////////////////////////////////////////
LightWidgetTheme::LightWidgetTheme()
{
auto widget = RegisterStyle(std::make_unique<BasicWidgetStyle>(), "widget");
auto textlabel = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textlabel");
auto pushbutton = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "pushbutton");
auto lineedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "lineedit");
auto textedit = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "textedit");
auto listview = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "listview");
auto dropdown = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "dropdown");
auto scrollbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "scrollbar");
auto tabbar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar");
auto tabbar_tab = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar-tab");
auto tabbar_spacer = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabbar-spacer");
auto tabwidget_stack = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "tabwidget-stack");
auto checkbox_label = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "checkbox-label");
auto menubar = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menubar");
auto menubaritem = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menubaritem");
auto menu = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menu");
auto menuitem = RegisterStyle(std::make_unique<BasicWidgetStyle>(widget), "menuitem");
DarkWidgetTheme::DarkWidgetTheme(): WidgetTheme({
Colorf::fromRgb(0x2A2A2A), // background
Colorf::fromRgb(0xE2DFDB), //
Colorf::fromRgb(0x212121), // headers / inputs
Colorf::fromRgb(0xE2DFDB), //
Colorf::fromRgb(0x444444), // interactive elements
Colorf::fromRgb(0xFFFFFF), //
Colorf::fromRgb(0xC83C00), // hover / highlight
Colorf::fromRgb(0xFFFFFF), //
Colorf::fromRgb(0xBBBBBB), // click
Colorf::fromRgb(0x000000), //
Colorf::fromRgb(0x646464), // around elements
Colorf::fromRgb(0x555555) // between elements
}) {};
widget->SetString("font-family", "NotoSans");
widget->SetColor("color", Colorf::fromRgba8(0, 0, 0));
widget->SetColor("window-background", Colorf::fromRgba8(240, 240, 240));
widget->SetColor("window-border", Colorf::fromRgba8(100, 100, 100));
widget->SetColor("window-caption-color", Colorf::fromRgba8(70, 70, 70));
widget->SetColor("window-caption-text-color", Colorf::fromRgba8(226, 223, 219));
/////////////////////////////////////////////////////////////////////////////
pushbutton->SetDouble("noncontent-left", 10.0);
pushbutton->SetDouble("noncontent-top", 5.0);
pushbutton->SetDouble("noncontent-right", 10.0);
pushbutton->SetDouble("noncontent-bottom", 5.0);
pushbutton->SetColor("background-color", Colorf::fromRgba8(210, 210, 210));
pushbutton->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155));
pushbutton->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155));
pushbutton->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155));
pushbutton->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155));
pushbutton->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200));
pushbutton->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190));
lineedit->SetDouble("noncontent-left", 5.0);
lineedit->SetDouble("noncontent-top", 3.0);
lineedit->SetDouble("noncontent-right", 5.0);
lineedit->SetDouble("noncontent-bottom", 3.0);
lineedit->SetColor("background-color", Colorf::fromRgba8(255, 255, 255));
lineedit->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155));
lineedit->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155));
lineedit->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155));
lineedit->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155));
lineedit->SetColor("selection-color", Colorf::fromRgba8(210, 210, 255));
lineedit->SetColor("no-focus-selection-color", Colorf::fromRgba8(240, 240, 255));
textedit->SetDouble("noncontent-left", 8.0);
textedit->SetDouble("noncontent-top", 8.0);
textedit->SetDouble("noncontent-right", 8.0);
textedit->SetDouble("noncontent-bottom", 8.0);
textedit->SetColor("background-color", Colorf::fromRgba8(255, 255, 255));
textedit->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155));
textedit->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155));
textedit->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155));
textedit->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155));
listview->SetDouble("noncontent-left", 10.0);
listview->SetDouble("noncontent-top", 10.0);
listview->SetDouble("noncontent-right", 3.0);
listview->SetDouble("noncontent-bottom", 10.0);
listview->SetColor("background-color", Colorf::fromRgba8(230, 230, 230));
listview->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155));
listview->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155));
listview->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155));
listview->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155));
listview->SetColor("selection-color", Colorf::fromRgba8(200, 200, 200));
dropdown->SetDouble("noncontent-left", 5.0);
dropdown->SetDouble("noncontent-top", 5.0);
dropdown->SetDouble("noncontent-right", 5.0);
dropdown->SetDouble("noncontent-bottom", 5.0);
dropdown->SetColor("background-color", Colorf::fromRgba8(230, 230, 230));
dropdown->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155));
dropdown->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155));
dropdown->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155));
dropdown->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155));
dropdown->SetColor("selection-color", Colorf::fromRgba8(200, 200, 200));
dropdown->SetColor("arrow-color", Colorf::fromRgba8(200, 200, 200));
scrollbar->SetColor("track-color", Colorf::fromRgba8(210, 210, 220));
scrollbar->SetColor("thumb-color", Colorf::fromRgba8(180, 180, 180));
tabbar->SetDouble("spacer-left", 20.0);
tabbar->SetDouble("spacer-right", 20.0);
tabbar->SetColor("background-color", Colorf::fromRgba8(210, 210, 210));
tabbar_tab->SetDouble("noncontent-left", 15.0);
tabbar_tab->SetDouble("noncontent-right", 15.0);
tabbar_tab->SetDouble("noncontent-top", 1.0);
tabbar_tab->SetDouble("noncontent-bottom", 1.0);
tabbar_tab->SetColor("background-color", Colorf::fromRgba8(220, 220, 220));
tabbar_tab->SetColor("border-left-color", Colorf::fromRgba8(200, 200, 200));
tabbar_tab->SetColor("border-top-color", Colorf::fromRgba8(200, 200, 200));
tabbar_tab->SetColor("border-right-color", Colorf::fromRgba8(200, 200, 200));
tabbar_tab->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155));
tabbar_tab->SetColor("hover", "background-color", Colorf::fromRgba8(210, 210, 210));
tabbar_tab->SetColor("active", "background-color", Colorf::fromRgba8(240, 240, 240));
tabbar_tab->SetColor("active", "border-left-color", Colorf::fromRgba8(155, 155, 155));
tabbar_tab->SetColor("active", "border-top-color", Colorf::fromRgba8(155, 155, 155));
tabbar_tab->SetColor("active", "border-right-color", Colorf::fromRgba8(155, 155, 155));
tabbar_tab->SetColor("active", "border-bottom-color", Colorf::transparent());
tabbar_spacer->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155));
tabwidget_stack->SetDouble("noncontent-left", 20.0);
tabwidget_stack->SetDouble("noncontent-top", 5.0);
tabwidget_stack->SetDouble("noncontent-right", 20.0);
tabwidget_stack->SetDouble("noncontent-bottom", 5.0);
checkbox_label->SetColor("checked-outer-border-color", Colorf::fromRgba8(155, 155, 155));
checkbox_label->SetColor("checked-inner-border-color", Colorf::fromRgba8(200, 200, 200));
checkbox_label->SetColor("checked-color", Colorf::fromRgba8(50, 50, 50));
checkbox_label->SetColor("unchecked-outer-border-color", Colorf::fromRgba8(156, 156, 156));
checkbox_label->SetColor("unchecked-inner-border-color", Colorf::fromRgba8(200, 200, 200));
menubar->SetColor("background-color", Colorf::fromRgba8(70, 70, 70));
menubaritem->SetColor("color", Colorf::fromRgba8(226, 223, 219));
menubaritem->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200));
menubaritem->SetColor("hover", "color", Colorf::fromRgba8(0, 0, 0));
menubaritem->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190));
menubaritem->SetColor("down", "color", Colorf::fromRgba8(0, 0, 0));
menu->SetDouble("noncontent-left", 5.0);
menu->SetDouble("noncontent-top", 5.0);
menu->SetDouble("noncontent-right", 5.0);
menu->SetDouble("noncontent-bottom", 5.0);
menu->SetColor("background-color", Colorf::fromRgba8(255, 255, 255));
menu->SetColor("border-left-color", Colorf::fromRgba8(155, 155, 155));
menu->SetColor("border-top-color", Colorf::fromRgba8(155, 155, 155));
menu->SetColor("border-right-color", Colorf::fromRgba8(155, 155, 155));
menu->SetColor("border-bottom-color", Colorf::fromRgba8(155, 155, 155));
menuitem->SetColor("hover", "background-color", Colorf::fromRgba8(200, 200, 200));
menuitem->SetColor("down", "background-color", Colorf::fromRgba8(190, 190, 190));
}
LightWidgetTheme::LightWidgetTheme(): WidgetTheme({
Colorf::fromRgb(0xF0F0F0), // background
Colorf::fromRgb(0x191919), //
Colorf::fromRgb(0xFAFAFA), // headers / inputs
Colorf::fromRgb(0x191919), //
Colorf::fromRgb(0xC8C8C8), // interactive elements
Colorf::fromRgb(0x000000), //
Colorf::fromRgb(0xD2D2FF), // hover / highlight
Colorf::fromRgb(0x000000), //
Colorf::fromRgb(0xC7B4FF), // click
Colorf::fromRgb(0x000000), //
Colorf::fromRgb(0xA0A0A0), // around elements
Colorf::fromRgb(0xB9B9B9) // between elements
}) {};

View file

@ -5,9 +5,9 @@
Timer::Timer(Widget* owner) : OwnerObj(owner)
{
PrevTimerObj = owner->FirstTimerObj;
if (PrevTimerObj)
PrevTimerObj->PrevTimerObj = this;
NextTimerObj = owner->FirstTimerObj;
if (NextTimerObj)
NextTimerObj->PrevTimerObj = this;
owner->FirstTimerObj = this;
}
@ -27,7 +27,7 @@ void Timer::Start(int timeoutMilliseconds, bool repeat)
{
Stop();
TimerId = DisplayWindow::StartTimer(timeoutMilliseconds, [=]() {
TimerId = DisplayWindow::StartTimer(timeoutMilliseconds, [this,repeat]() {
if (!repeat)
Stop();
if (FuncExpired)

View file

@ -303,10 +303,10 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos
reader.Seek(loca.offsets[glyphIndex]);
ttf_int16 numberOfContours = reader.ReadInt16();
ttf_int16 xMin = reader.ReadInt16();
ttf_int16 yMin = reader.ReadInt16();
ttf_int16 xMax = reader.ReadInt16();
ttf_int16 yMax = reader.ReadInt16();
/*ttf_int16 xMin =*/ reader.ReadInt16();
/*ttf_int16 yMin =*/ reader.ReadInt16();
/*ttf_int16 xMax =*/ reader.ReadInt16();
/*ttf_int16 yMax =*/ reader.ReadInt16();
if (numberOfContours > 0) // Simple glyph
{
@ -450,7 +450,7 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos
if (transform)
{
for (int i = childPointsOffset; i < g.points.size(); i++)
for (int i = childPointsOffset; i < (int)g.points.size(); i++)
{
float x = g.points[i].x * mat2x2[0] + g.points[i].y * mat2x2[1];
float y = g.points[i].x * mat2x2[2] + g.points[i].y * mat2x2[3];
@ -492,7 +492,7 @@ void TrueTypeFont::LoadGlyph(TTF_SimpleGlyph& g, uint32_t glyphIndex, int compos
dy = g.points[parentPointIndex].y - g.points[childPointIndex].y;
}
for (int i = childPointsOffset; i < g.points.size(); i++)
for (int i = childPointsOffset; i < (int)g.points.size(); i++)
{
g.points[i].x += dx;
g.points[i].y += dy;
@ -589,9 +589,9 @@ void TrueTypeFont::LoadCharacterMapEncoding(TrueTypeFileReader& reader)
for (ttf_uint16 c = startCode; c <= endCode; c++)
{
int offset = idRangeOffset / 2 + (c - startCode) - ((int)subformat.segCount - i);
if (offset >= 0 && offset < subformat.glyphIdArray.size())
if (offset >= 0 && offset < (int)subformat.glyphIdArray.size())
{
int glyphId = subformat.glyphIdArray[offset];
ttf_uint32 glyphId = subformat.glyphIdArray[offset];
if (firstGlyph)
{
range.startGlyphID = glyphId;
@ -689,9 +689,9 @@ void TTF_CMapSubtable4::Load(TrueTypeFileReader& reader)
language = reader.ReadUInt16();
segCount = reader.ReadUInt16() / 2;
ttf_uint16 searchRange = reader.ReadUInt16();
ttf_uint16 entrySelector = reader.ReadUInt16();
ttf_uint16 rangeShift = reader.ReadUInt16();
/*ttf_uint16 searchRange =*/ reader.ReadUInt16();
/*ttf_uint16 entrySelector =*/ reader.ReadUInt16();
/*ttf_uint16 rangeShift =*/ reader.ReadUInt16();
endCode.reserve(segCount);
startCode.reserve(segCount);
@ -746,9 +746,9 @@ void TTF_TableDirectory::Load(TrueTypeFileReader& reader)
numTables = reader.ReadUInt16();
// opentype spec says we can't use these for security reasons, so we pretend they never was part of the header
ttf_uint16 searchRange = reader.ReadUInt16();
ttf_uint16 entrySelector = reader.ReadUInt16();
ttf_uint16 rangeShift = reader.ReadUInt16();
/*ttf_uint16 searchRange =*/ reader.ReadUInt16();
/*ttf_uint16 entrySelector =*/ reader.ReadUInt16();
/*ttf_uint16 rangeShift =*/ reader.ReadUInt16();
for (ttf_uint16 i = 0; i < numTables; i++)
{

View file

@ -71,7 +71,7 @@ unsigned int UTF8Reader::character()
else
{
unsigned int ucs4 = (data[current_position] & bitmask_leadbyte_for_utf8[trailing_bytes]);
for (std::string::size_type i = 0; i < trailing_bytes; i++)
for (std::string::size_type i = 0; i < (size_t)trailing_bytes; i++)
{
if ((data[current_position + 1 + i] & 0xC0) == 0x80)
ucs4 = (ucs4 << 6) + (data[current_position + 1 + i] & 0x3f);
@ -94,7 +94,7 @@ std::string::size_type UTF8Reader::char_length()
if (current_position + 1 + trailing_bytes > length)
return 1;
for (std::string::size_type i = 0; i < trailing_bytes; i++)
for (std::string::size_type i = 0; i < (size_t)trailing_bytes; i++)
{
if ((data[current_position + 1 + i] & 0xC0) != 0x80)
return 1;
@ -136,7 +136,7 @@ void UTF8Reader::move_to_leadbyte()
lead_position--;
int trailing_bytes = trailing_bytes_for_utf8[data[lead_position]];
if (lead_position + trailing_bytes >= current_position)
if (lead_position + trailing_bytes >= (int)current_position)
current_position = lead_position;
}

View file

@ -173,6 +173,18 @@ void Widget::SetWindowTitle(const std::string& text)
}
}
std::vector<std::shared_ptr<Image>> Widget::GetWindowIcon() const
{
return WindowIcon;
}
void Widget::SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images)
{
WindowIcon = images;
if (DispWindow)
DispWindow->SetWindowIcon(WindowIcon);
}
Size Widget::GetSize() const
{
return ContentGeometry.size();
@ -365,7 +377,7 @@ void Widget::Repaint()
Widget* w = Window();
if (w->DispCanvas)
{
w->DispCanvas->begin(WindowBackground);
w->DispCanvas->begin(w->WindowBackground);
w->Paint(w->DispCanvas.get());
w->DispCanvas->end();
}

View file

@ -124,7 +124,7 @@ void Dropdown::ClearItems()
void Dropdown::SetSelectedItem(int index)
{
if (index < 0) index = 0;
if (index >= (int)items.size()) index = items.size() - 1;
if (index >= (int)items.size()) index = (int)items.size() - 1;
if (selectedItem == index) return;
@ -270,7 +270,7 @@ void Dropdown::OnGeometryChanged()
Point pos = MapTo(Window(), Point(0,0));
double width = GetWidth() + GetNoncontentLeft() + GetNoncontentRight();
double innerH = GetDisplayItems() * 25.0 + 10.0;
double innerH = GetDisplayItems() * 20.0 + 20;
double outerH = GetHeight();
pos.x -= GetNoncontentLeft();
@ -311,14 +311,14 @@ bool Dropdown::OpenDropdown()
listView->SetSelectedItem(selectedItem);
listView->OnActivated = [=]() { OnDropdownActivated(); };
listView->OnChanged = [=](int index) { OnDropdownActivated(); };
listView->OnActivated = [this]() { OnDropdownActivated(); };
listView->OnChanged = [this](int index) { OnDropdownActivated(); };
listView->SetFrameGeometry(
0,
0,
GetWidth() + GetNoncontentLeft() + GetNoncontentRight(),
GetDisplayItems() * 25.0 + 10.0
GetDisplayItems() * 20.0 + 20
);
OnGeometryChanged();

View file

@ -9,10 +9,10 @@ LineEdit::LineEdit(Widget* parent) : Widget(parent)
SetStyleClass("lineedit");
timer = new Timer(this);
timer->FuncExpired = [=]() { OnTimerExpired(); };
timer->FuncExpired = [this]() { OnTimerExpired(); };
scroll_timer = new Timer(this);
scroll_timer->FuncExpired = [=]() { OnScrollTimerExpired(); };
scroll_timer->FuncExpired = [this]() { OnScrollTimerExpired(); };
SetCursor(StandardCursor::ibeam);
}
@ -90,12 +90,16 @@ int LineEdit::GetCursorPos() const
Size LineEdit::GetTextSize()
{
Canvas* canvas = GetCanvas();
if (!canvas)
return Size(0.0, 0.0);
return GetVisualTextSize(canvas);
}
Size LineEdit::GetTextSize(const std::string& str)
{
Canvas* canvas = GetCanvas();
if (!canvas)
return Size(0.0, 0.0);
return canvas->measureText(str).size();
}
@ -674,7 +678,7 @@ bool LineEdit::InsertText(int pos, const std::string& str)
}
// checking if insert exceeds max length
if (UTF8Reader::utf8_length(text) + UTF8Reader::utf8_length(str) > max_length)
if (UTF8Reader::utf8_length(text) + UTF8Reader::utf8_length(str) > (size_t)max_length)
{
return false;
}
@ -757,6 +761,9 @@ int LineEdit::GetCharacterIndex(double mouse_x)
}
Canvas* canvas = GetCanvas();
if (!canvas)
return 0;
UTF8Reader utf8_reader(text.data(), text.length());
int seek_start = clip_start_offset;
@ -808,7 +815,7 @@ void LineEdit::UpdateTextClipping()
if (!canvas)
return;
Size text_size = GetVisualTextSize(canvas, clip_start_offset, (int)text.size() - clip_start_offset);
// Size text_size = GetVisualTextSize(canvas, clip_start_offset, (int)text.size() - clip_start_offset);
if (cursor_pos < clip_start_offset)
clip_start_offset = cursor_pos;
@ -822,7 +829,7 @@ void LineEdit::UpdateTextClipping()
utf8_reader.set_position(clip_start_offset);
utf8_reader.next();
clip_start_offset = (int)utf8_reader.position();
if (clip_start_offset == text.size())
if (clip_start_offset == (int)text.size())
break;
cursor_rect = GetCursorRect();
}
@ -837,7 +844,7 @@ void LineEdit::UpdateTextClipping()
utf8_reader.set_position(midpoint);
utf8_reader.move_to_leadbyte();
if (midpoint != utf8_reader.position())
if (midpoint != (int)utf8_reader.position())
utf8_reader.next();
midpoint = (int)utf8_reader.position();
@ -862,6 +869,8 @@ void LineEdit::UpdateTextClipping()
Rect LineEdit::GetCursorRect()
{
Canvas* canvas = GetCanvas();
if (!canvas)
return Rect::xywh(0.0, 0.0, 0.0, 0.0);
int substr_end = cursor_pos - clip_start_offset;
if (substr_end < 0)
@ -890,6 +899,8 @@ Rect LineEdit::GetCursorRect()
Rect LineEdit::GetSelectionRect()
{
Canvas* canvas = GetCanvas();
if (!canvas)
return Rect::xywh(0.0, 0.0, 0.0, 0.0);
// text before selection:
@ -949,6 +960,8 @@ void LineEdit::OnTimerExpired()
void LineEdit::OnGeometryChanged()
{
Canvas* canvas = GetCanvas();
if (!canvas)
return;
vertical_text_align = canvas->verticalTextAlign();

View file

@ -6,7 +6,7 @@ ListView::ListView(Widget* parent) : Widget(parent)
SetStyleClass("listview");
scrollbar = new Scrollbar(this);
scrollbar->FuncScroll = [=]() { OnScrollbarScroll(); };
scrollbar->FuncScroll = [this]() { OnScrollbarScroll(); };
SetColumnWidths({ 0.0 });
}
@ -37,7 +37,7 @@ void ListView::SetColumnWidths(const std::vector<double>& widths)
void ListView::AddItem(const std::string& text, int index, int column)
{
if (column < 0 || column >= columnwidths.size())
if (column < 0 || column >= (int)columnwidths.size())
return;
std::vector<std::string> newEntry;
@ -47,10 +47,10 @@ void ListView::AddItem(const std::string& text, int index, int column)
newEntry[column] = text;
if (index >= 0)
{
if (index >= items.size())
if (index >= (int)items.size())
{
newEntry[column] = "";
while (items.size() < index)
while ((int)items.size() < index)
items.push_back(newEntry);
newEntry[column] = text;
@ -71,7 +71,7 @@ void ListView::AddItem(const std::string& text, int index, int column)
void ListView::UpdateItem(const std::string& text, int index, int column)
{
if (index < 0 || index >= items.size() || column < 0 || column >= columnwidths.size())
if (index < 0 || index >= (int)items.size() || column < 0 || column >= (int)columnwidths.size())
return;
items[index][column] = text;
@ -80,7 +80,7 @@ void ListView::UpdateItem(const std::string& text, int index, int column)
void ListView::RemoveItem(int index)
{
if (!items.size() || index >= items.size())
if (!items.size() || index >= (int)items.size())
return;
if (index < 0)
@ -102,7 +102,7 @@ void ListView::Activate()
void ListView::SetSelectedItem(int index)
{
if (selectedItem != index && index >= 0 && index < items.size())
if (selectedItem != index && index >= 0 && index < (int)items.size())
{
selectedItem = index;
if (OnChanged) OnChanged(selectedItem);

View file

@ -36,7 +36,7 @@ void Menubar::ShowMenu(MenubarItem* item)
if (item->GetOpenCallback())
{
openMenu = new Menu(this);
openMenu->onCloseMenu = [=]() { CloseMenu(); };
openMenu->onCloseMenu = [this]() { CloseMenu(); };
item->GetOpenCallback()(openMenu);
if (item->AlignRight)
openMenu->SetRightPosition(item->MapToGlobal(Point(item->GetWidth(), item->GetHeight())));
@ -215,7 +215,7 @@ void Menubar::OnKeyDown(InputKey key)
/////////////////////////////////////////////////////////////////////////////
MenubarItem::MenubarItem(Menubar* menubar, std::string text, bool alignRight) : Widget(menubar), menubar(menubar), text(text), AlignRight(alignRight)
MenubarItem::MenubarItem(Menubar* menubar, std::string text, bool alignRight) : Widget(menubar), AlignRight(alignRight), menubar(menubar), text(text)
{
SetStyleClass("menubaritem");
}

View file

@ -3,13 +3,16 @@
#include "core/colorf.h"
#include <stdexcept>
#define HIDE_SMALL_BAR false
#define HIDE_SMALL_THUMB true
Scrollbar::Scrollbar(Widget* parent) : Widget(parent)
{
SetStyleClass("scrollbar");
UpdatePartPositions();
mouse_down_timer = new Timer(this);
mouse_down_timer->FuncExpired = [=]() { OnTimerExpired(); };
mouse_down_timer->FuncExpired = [this]() { OnTimerExpired(); };
}
Scrollbar::~Scrollbar()
@ -295,7 +298,15 @@ void Scrollbar::OnPaint(Canvas* canvas)
part_button_increment.render_box(canvas, rect_button_increment);
*/
canvas->fillRect(Rect::shrink(Rect::xywh(0.0, 0.0, GetWidth(), GetHeight()), 4.0, 0.0, 4.0, 0.0), GetStyleColor("track-color"));
auto height = GetHeight();
auto paint = rect_thumb.height < height;
if (HIDE_SMALL_BAR && !paint) return;
canvas->fillRect(Rect::shrink(Rect::xywh(0.0, 0.0, GetWidth(), height), 4.0, 0.0, 4.0, 0.0), GetStyleColor("track-color"));
if (HIDE_SMALL_THUMB && !paint) return;
canvas->fillRect(Rect::shrink(rect_thumb, 4.0, 0.0, 4.0, 0.0), GetStyleColor("thumb-color"));
}

View file

@ -9,7 +9,7 @@ TabWidget::TabWidget(Widget* parent) : Widget(parent)
Bar = new TabBar(this);
PageStack = new TabWidgetStack(this);
Bar->OnCurrentChanged = [=]() { OnBarCurrentChanged(); };
Bar->OnCurrentChanged = [this]() { OnBarCurrentChanged(); };
}
int TabWidget::AddTab(Widget* page, const std::string& label)
@ -127,7 +127,7 @@ int TabBar::AddTab(const std::shared_ptr<Image>& icon, const std::string& label)
TabBarTab* tab = new TabBarTab(this);
tab->SetIcon(icon);
tab->SetText(label);
tab->OnClick = [=]() { OnTabClicked(tab); };
tab->OnClick = [this,tab]() { OnTabClicked(tab); };
int pageIndex = (int)Tabs.size();
Tabs.push_back(tab);
if (CurrentIndex == -1)

View file

@ -13,11 +13,14 @@ TextEdit::TextEdit(Widget* parent) : Widget(parent)
{
SetStyleClass("textedit");
selectionBG = GetStyleColor("selection-color");
selectionFG = GetStyleColor("color");
timer = new Timer(this);
timer->FuncExpired = [=]() { OnTimerExpired(); };
timer->FuncExpired = [this]() { OnTimerExpired(); };
scroll_timer = new Timer(this);
scroll_timer->FuncExpired = [=]() { OnScrollTimerExpired(); };
scroll_timer->FuncExpired = [this]() { OnScrollTimerExpired(); };
SetCursor(StandardCursor::ibeam);
@ -175,13 +178,13 @@ void TextEdit::SetText(const std::string& text)
std::string::size_type end = text.find('\n');
while (end != std::string::npos)
{
TextEdit::Line line;
TextEdit::Line line{this};
line.text = text.substr(start, end - start);
lines.push_back(line);
start = end + 1;
end = text.find('\n', start);
}
TextEdit::Line line;
TextEdit::Line line{this};
line.text = text.substr(start);
lines.push_back(line);
@ -197,13 +200,13 @@ void TextEdit::AddText(const std::string& text)
std::string::size_type end = text.find('\n');
while (end != std::string::npos)
{
TextEdit::Line line;
TextEdit::Line line{this};
line.text = text.substr(start, end - start);
lines.push_back(line);
start = end + 1;
end = text.find('\n', start);
}
TextEdit::Line line;
TextEdit::Line line{this};
line.text = text.substr(start);
lines.push_back(line);
@ -434,7 +437,7 @@ void TextEdit::OnKeyDown(InputKey key)
if (GetKeyState(InputKey::Shift) && selection_length == 0)
selection_start = cursor_pos;
if (cursor_pos.y < lines.size() - 1)
if (cursor_pos.y < (int)lines.size() - 1)
{
cursor_pos.y++;
cursor_pos.x = std::min(lines[cursor_pos.y].text.size(), (size_t)cursor_pos.x);
@ -578,7 +581,7 @@ void TextEdit::OnLostFocus()
void TextEdit::CreateComponents()
{
vert_scrollbar = new Scrollbar(this);
vert_scrollbar->FuncScroll = [=]() { OnVerticalScroll(); };
vert_scrollbar->FuncScroll = [this]() { OnVerticalScroll(); };
vert_scrollbar->SetVisible(false);
vert_scrollbar->SetVertical();
}
@ -714,10 +717,10 @@ TextEdit::ivec2 TextEdit::FindNextBreakCharacter(ivec2 search_start)
if (search_start.x >= int(lines[search_start.y].text.size()) - 1)
return ivec2(lines[search_start.y].text.size(), search_start.y);
int pos = lines[search_start.y].text.find_first_of(break_characters, search_start.x);
size_t pos = lines[search_start.y].text.find_first_of(break_characters, search_start.x);
if (pos == std::string::npos)
return ivec2(lines[search_start.y].text.size(), search_start.y);
return ivec2(pos, search_start.y);
return ivec2((int)pos, search_start.y);
}
TextEdit::ivec2 TextEdit::FindPreviousBreakCharacter(ivec2 search_start)
@ -725,10 +728,10 @@ TextEdit::ivec2 TextEdit::FindPreviousBreakCharacter(ivec2 search_start)
search_start.x--;
if (search_start.x <= 0)
return ivec2(0, search_start.y);
int pos = lines[search_start.y].text.find_last_of(break_characters, search_start.x);
size_t pos = lines[search_start.y].text.find_last_of(break_characters, search_start.x);
if (pos == std::string::npos)
return ivec2(0, search_start.y);
return ivec2(pos, search_start.y);
return ivec2((int)pos, search_start.y);
}
void TextEdit::InsertText(ivec2 pos, const std::string& str)
@ -741,7 +744,7 @@ void TextEdit::InsertText(ivec2 pos, const std::string& str)
}
// checking if insert exceeds max length
if (ToOffset(ivec2(lines[lines.size() - 1].text.size(), lines.size() - 1)) + str.length() > max_length)
if (ToOffset(ivec2((int)lines[lines.size() - 1].text.size(), (int)lines.size() - 1)) + str.length() > (size_t)max_length)
{
return;
}
@ -759,7 +762,7 @@ void TextEdit::InsertText(ivec2 pos, const std::string& str)
pos.x += next_newline - start;
Line line;
Line line{this};
line.text = lines[pos.y].text.substr(pos.x);
lines.insert(lines.begin() + pos.y + 1, line);
lines[pos.y].text = lines[pos.y].text.substr(0, pos.x);
@ -836,9 +839,9 @@ void TextEdit::Del()
lines[cursor_pos.y].invalidated = true;
Update();
}
else if (cursor_pos.y + 1 < lines.size())
else if (cursor_pos.y + 1 < (int)lines.size())
{
selection_start = ivec2(lines[cursor_pos.y].text.length(), cursor_pos.y);
selection_start = ivec2((int)lines[cursor_pos.y].text.length(), cursor_pos.y);
selection_length = 1;
DeleteSelectedText();
}
@ -904,7 +907,7 @@ bool TextEdit::InputMaskAcceptsInput(ivec2 cursor_pos, const std::string& str)
std::string::size_type TextEdit::ToOffset(ivec2 pos) const
{
if (pos.y < lines.size())
if (pos.y < (int)lines.size())
{
std::string::size_type offset = 0;
for (int line = 0; line < pos.y; line++)
@ -927,7 +930,7 @@ std::string::size_type TextEdit::ToOffset(ivec2 pos) const
TextEdit::ivec2 TextEdit::FromOffset(std::string::size_type offset) const
{
int line_offset = 0;
for (int line = 0; line < lines.size(); line++)
for (int line = 0; line < (int)lines.size(); line++)
{
if (offset <= line_offset + lines[line].text.size())
{
@ -984,9 +987,9 @@ void TextEdit::LayoutLines(Canvas* canvas)
line.invalidated = false;
}
if (sel_start != sel_end && sel_start.y <= i && sel_end.y >= i)
if (sel_start != sel_end && sel_start.y <= (int)i && sel_end.y >= (int)i)
{
line.layout.SetSelectionRange(sel_start.y < i ? 0 : sel_start.x, sel_end.y > i ? line.text.size() : sel_end.x);
line.layout.SetSelectionRange(sel_start.y < (int)i ? 0 : sel_start.x, sel_end.y > (int)i ? line.text.size() : sel_end.x);
}
else
{
@ -996,7 +999,7 @@ void TextEdit::LayoutLines(Canvas* canvas)
line.layout.HideCursor();
if (HasFocus())
{
if (cursor_blink_visible && cursor_pos.y == i)
if (cursor_blink_visible && cursor_pos.y == (int)i)
{
line.layout.SetCursorPos(cursor_pos.x);
line.layout.SetCursorColor(textColor);
@ -1035,6 +1038,7 @@ TextEdit::ivec2 TextEdit::GetCharacterIndex(Point mouse_wincoords)
return ivec2(clamp(result.offset, (size_t)0, line.text.size()), i);
case SpanLayout::HitTestResult::outside_left:
return ivec2(0, i);
default:
case SpanLayout::HitTestResult::outside_right:
return ivec2(line.text.size(), i);
}

View file

@ -281,8 +281,30 @@ bool DBusOpenFileDialog::Show()
for (const std::string& uri : uris)
{
if (uri.size() > 7 && uri.substr(0, 7) == "file://")
outputFilenames.push_back(uri.substr(7));
outputFilenames.push_back(unescapeUri(uri.substr(7)));
}
return !uris.empty();
}
std::string DBusOpenFileDialog::unescapeUri(const std::string& s)
{
// This needs to be much more advanced to support any kind of uri. Why did they have to pass it as an uri!? idiots.
std::string result;
result.reserve(s.size());
size_t i = 0;
while (i < s.length())
{
if (s[i] == '%' && i + 2 < s.length() && s[i + 1] == '2' && s[i + 2] == '0')
{
result += ' ';
i += 3;
}
else
{
result += s[i];
i++;
}
}
return result;
}

View file

@ -20,6 +20,8 @@ public:
bool Show() override;
private:
static std::string unescapeUri(const std::string& s);
std::string ownerHandle;
std::string title;
std::string initialDirectory;

View file

@ -1,5 +1,8 @@
#include "dbus_open_folder_dialog.h"
#include <dbus/dbus.h>
#include <vector>
#include <algorithm>
DBusOpenFolderDialog::DBusOpenFolderDialog(std::string ownerHandle) : ownerHandle(ownerHandle)
{
@ -7,20 +10,271 @@ DBusOpenFolderDialog::DBusOpenFolderDialog(std::string ownerHandle) : ownerHandl
bool DBusOpenFolderDialog::Show()
{
return false;
// https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.FileChooser.html
dbus_bool_t bresult = {};
DBusError error = {};
dbus_error_init(&error);
DBusConnection* connection = dbus_bus_get(DBUS_BUS_SESSION, &error);
if (!connection)
{
dbus_error_free(&error);
return false;
}
std::string busname = dbus_bus_get_unique_name(connection);
DBusMessage* request = dbus_message_new_method_call("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.FileChooser", "OpenFile");
if (!request)
{
dbus_connection_unref(connection);
dbus_error_free(&error);
return false;
}
const char* parentWindow = ownerHandle.c_str();
const char* title = this->title.c_str();
DBusMessageIter requestArgs = {};
dbus_message_iter_init_append(request, &requestArgs);
bresult = dbus_message_iter_append_basic(&requestArgs, DBUS_TYPE_STRING, &parentWindow);
bresult = dbus_message_iter_append_basic(&requestArgs, DBUS_TYPE_STRING, &title);
DBusMessageIter requestOptions = {};
bresult = dbus_message_iter_open_container(&requestArgs, DBUS_TYPE_ARRAY, "{sv}", &requestOptions);
// handle_token - s race condition prevention for signal (/org/freedesktop/portal/desktop/request/SENDER/TOKEN)
// accept_label - s text label for the OK button
// modal - b makes dialog modal. Defaults to true. Not really sure what it means since nothing stayed modal on my computer
// directory - b open folder mode, added in version 3
// filters - a(sa(us)) [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])]
// current_filter - sa(us) filter from filters that should be current filter
// choices - a(ssa(ss)s) list of serialized combo boxes to add to the file chooser
// current_name - s suggested name
// current_folder - ay
if (!initialDirectory.empty())
{
// Note: the docs unfortunately says "The portal implementation is free to ignore this option"
const char* key = "current_folder";
const char* value = initialDirectory.c_str();
int valueCount = (int)initialDirectory.size() + 1;
DBusMessageIter entry = {};
bresult = dbus_message_iter_open_container(&requestOptions, DBUS_TYPE_DICT_ENTRY, nullptr, &entry);
bresult = dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
DBusMessageIter variant = {};
DBusMessageIter array = {};
bresult = dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "ay", &variant);
bresult = dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &array);
bresult = dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &value, valueCount);
bresult = dbus_message_iter_close_container(&variant, &array);
bresult = dbus_message_iter_close_container(&entry, &variant);
bresult = dbus_message_iter_close_container(&requestOptions, &entry);
}
// directory - b
{
const char* key = "directory";
dbus_bool_t value = true;
DBusMessageIter entry = {};
bresult = dbus_message_iter_open_container(&requestOptions, DBUS_TYPE_DICT_ENTRY, nullptr, &entry);
bresult = dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
DBusMessageIter variant = {};
bresult = dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &variant);
bresult = dbus_message_iter_append_basic(&variant, DBUS_TYPE_BOOLEAN, &value);
bresult = dbus_message_iter_close_container(&entry, &variant);
bresult = dbus_message_iter_close_container(&requestOptions, &entry);
}
bresult = dbus_message_iter_close_container(&requestArgs, &requestOptions);
DBusMessage* response = dbus_connection_send_with_reply_and_block(connection, request, DBUS_TIMEOUT_USE_DEFAULT, &error);
if (!response)
{
dbus_message_unref(request);
dbus_connection_unref(connection);
dbus_error_free(&error);
return false;
}
const char* handle = nullptr;
bresult = dbus_message_get_args(response, &error, DBUS_TYPE_OBJECT_PATH, &handle, DBUS_TYPE_INVALID);
if (!bresult)
{
dbus_message_unref(response);
dbus_message_unref(request);
dbus_connection_unref(connection);
dbus_error_free(&error);
return false;
}
std::string signalObjectPath = handle;
dbus_message_unref(response);
dbus_message_unref(request);
std::string rule = "type='signal',interface='org.freedesktop.portal.Request',member='Response',path='" + signalObjectPath + "'";
dbus_bus_add_match(connection, rule.c_str(), &error);
// Wait for the response signal
//
// To do: process the run loop while we wait
//
DBusMessage* signalmsg = nullptr;
while (!signalmsg && dbus_connection_read_write(connection, -1))
{
while (true)
{
DBusMessage* message = dbus_connection_pop_message(connection);
if (!message)
break;
if (dbus_message_is_signal(message, "org.freedesktop.portal.Request", "Response") && dbus_message_get_path(message) == signalObjectPath)
{
signalmsg = message;
break;
}
else
{
dbus_message_unref(message);
}
}
}
dbus_bus_remove_match(connection, rule.c_str(), &error);
// Read the response
dbus_uint32_t responseCode = 0;
std::vector<std::string> uris;
DBusMessageIter signalArgs;
bresult = dbus_message_iter_init(signalmsg, &signalArgs);
// response code - u
if (dbus_message_iter_get_arg_type(&signalArgs) == DBUS_TYPE_UINT32)
{
dbus_message_iter_get_basic(&signalArgs, &responseCode);
}
dbus_message_iter_next(&signalArgs);
// results - a{sv}
if (dbus_message_iter_get_arg_type(&signalArgs) == DBUS_TYPE_ARRAY)
{
DBusMessageIter resultsArray = {};
dbus_message_iter_recurse(&signalArgs, &resultsArray);
while (true)
{
int type = dbus_message_iter_get_arg_type(&resultsArray);
if (type != DBUS_TYPE_DICT_ENTRY)
break;
DBusMessageIter entry = {};
dbus_message_iter_recurse(&resultsArray, &entry);
const char* key = nullptr;
dbus_message_iter_get_basic(&entry, &key);
dbus_message_iter_next(&entry);
DBusMessageIter value = {};
dbus_message_iter_recurse(&entry, &value);
std::string k = key;
if (k == "uris" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_ARRAY) // as
{
DBusMessageIter uriArray = {};
dbus_message_iter_recurse(&value, &uriArray);
while (true)
{
int type = dbus_message_iter_get_arg_type(&uriArray);
if (type != DBUS_TYPE_STRING)
break;
const char* uri = nullptr;
dbus_message_iter_get_basic(&uriArray, &uri);
uris.push_back(uri);
dbus_message_iter_next(&uriArray);
}
}
else if (k == "choices" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_ARRAY) // a(ss)
{
// An array of pairs of strings,
// the first string being the ID of a combobox that was passed into this call,
// the second string being the selected option.
}
else if (k == "current_filter" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_STRUCT) // sa(us)
{
// The filter that was selected.
// This may match a filter in the filter list or another filter that was applied unconditionally.
}
dbus_message_iter_next(&entry);
dbus_message_iter_next(&resultsArray);
}
}
dbus_message_iter_next(&signalArgs);
dbus_message_unref(signalmsg);
dbus_connection_unref(connection);
dbus_error_free(&error);
if (responseCode != 0) // User cancelled
return false;
for (const std::string& uri : uris)
{
if (uri.size() > 7 && uri.substr(0, 7) == "file://")
{
selectedPath = unescapeUri(uri.substr(7));
break;
}
}
return !selectedPath.empty();
}
std::string DBusOpenFolderDialog::SelectedPath() const
{
return selected_path;
return selectedPath;
}
void DBusOpenFolderDialog::SetInitialDirectory(const std::string& path)
{
initial_directory = path;
initialDirectory = path;
}
void DBusOpenFolderDialog::SetTitle(const std::string& newtitle)
{
title = newtitle;
}
std::string DBusOpenFolderDialog::unescapeUri(const std::string& s)
{
// This needs to be much more advanced to support any kind of uri. Why did they have to pass it as an uri!? idiots.
std::string result;
result.reserve(s.size());
size_t i = 0;
while (i < s.length())
{
if (s[i] == '%' && i + 2 < s.length() && s[i + 1] == '2' && s[i + 2] == '0')
{
result += ' ';
i += 3;
}
else
{
result += s[i];
i++;
}
}
return result;
}

View file

@ -13,9 +13,10 @@ public:
void SetTitle(const std::string& newtitle) override;
private:
std::string ownerHandle;
static std::string unescapeUri(const std::string& s);
std::string selected_path;
std::string initial_directory;
std::string ownerHandle;
std::string title;
std::string initialDirectory;
std::string selectedPath;
};

View file

@ -1,4 +1,3 @@
#include "sdl2_display_window.h"
#include <stdexcept>
#include <SDL2/SDL_vulkan.h>
@ -33,7 +32,7 @@ SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWi
if (result != 0)
throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError());
}
WindowList[SDL_GetWindowID(Handle.window)] = this;
}
@ -73,8 +72,7 @@ std::vector<std::string> SDL2DisplayWindow::GetVulkanInstanceExtensions()
VkSurfaceKHR SDL2DisplayWindow::CreateVulkanSurface(VkInstance instance)
{
VkSurfaceKHR surfaceHandle = {};
SDL_Vulkan_CreateSurface(Handle.window, instance, &surfaceHandle);
if (surfaceHandle)
if (SDL_Vulkan_CreateSurface(Handle.window, instance, &surfaceHandle) != SDL_TRUE)
throw std::runtime_error("Could not create vulkan surface");
return surfaceHandle;
}
@ -84,6 +82,12 @@ void SDL2DisplayWindow::SetWindowTitle(const std::string& text)
SDL_SetWindowTitle(Handle.window, text.c_str());
}
void SDL2DisplayWindow::SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images)
{
// To do: call SDL_SetWindowIcon for the highest resolution image (or maybe the first 32x32 or higher? not sure what is best here)
// To do: when this is upgraded to SDL3, call SDL_AddSurfaceAlternateImage for all the images
}
void SDL2DisplayWindow::SetWindowFrame(const Rect& box)
{
// SDL2 doesn't really seem to have an API for this.
@ -423,14 +427,14 @@ SDL2DisplayWindow* SDL2DisplayWindow::FindEventWindow(const SDL_Event& event)
int windowID;
switch (event.type)
{
case SDL_WINDOWEVENT: windowID = event.window.windowID; break;
case SDL_TEXTINPUT: windowID = event.text.windowID; break;
case SDL_KEYUP: windowID = event.key.windowID; break;
case SDL_KEYDOWN: windowID = event.key.windowID; break;
case SDL_MOUSEBUTTONUP: windowID = event.button.windowID; break;
case SDL_WINDOWEVENT: windowID = event.window.windowID; break;
case SDL_TEXTINPUT: windowID = event.text.windowID; break;
case SDL_KEYUP: windowID = event.key.windowID; break;
case SDL_KEYDOWN: windowID = event.key.windowID; break;
case SDL_MOUSEBUTTONUP: windowID = event.button.windowID; break;
case SDL_MOUSEBUTTONDOWN: windowID = event.button.windowID; break;
case SDL_MOUSEWHEEL: windowID = event.wheel.windowID; break;
case SDL_MOUSEMOTION: windowID = event.motion.windowID; break;
case SDL_MOUSEWHEEL: windowID = event.wheel.windowID; break;
case SDL_MOUSEMOTION: windowID = event.motion.windowID; break;
default:
if (event.type == PaintEventNumber) windowID = event.user.windowID;
else return nullptr;
@ -451,14 +455,14 @@ void SDL2DisplayWindow::DispatchEvent(const SDL_Event& event)
switch (event.type)
{
case SDL_WINDOWEVENT: window->OnWindowEvent(event.window); break;
case SDL_TEXTINPUT: window->OnTextInput(event.text); break;
case SDL_KEYUP: window->OnKeyUp(event.key); break;
case SDL_KEYDOWN: window->OnKeyDown(event.key); break;
case SDL_MOUSEBUTTONUP: window->OnMouseButtonUp(event.button); break;
case SDL_WINDOWEVENT: window->OnWindowEvent (event.window); break;
case SDL_TEXTINPUT: window->OnTextInput (event.text); break;
case SDL_KEYUP: window->OnKeyUp (event.key); break;
case SDL_KEYDOWN: window->OnKeyDown (event.key); break;
case SDL_MOUSEBUTTONUP: window->OnMouseButtonUp (event.button); break;
case SDL_MOUSEBUTTONDOWN: window->OnMouseButtonDown(event.button); break;
case SDL_MOUSEWHEEL: window->OnMouseWheel(event.wheel); break;
case SDL_MOUSEMOTION: window->OnMouseMotion(event.motion); break;
case SDL_MOUSEWHEEL: window->OnMouseWheel (event.wheel); break;
case SDL_MOUSEMOTION: window->OnMouseMotion (event.motion); break;
default:
if (event.type == PaintEventNumber) window->OnPaintEvent();
}
@ -468,13 +472,39 @@ void SDL2DisplayWindow::OnWindowEvent(const SDL_WindowEvent& event)
{
switch (event.event)
{
case SDL_WINDOWEVENT_CLOSE: WindowHost->OnWindowClose(); break;
case SDL_WINDOWEVENT_MOVED: WindowHost->OnWindowGeometryChanged(); break;
case SDL_WINDOWEVENT_RESIZED: WindowHost->OnWindowGeometryChanged(); break;
case SDL_WINDOWEVENT_SHOWN: WindowHost->OnWindowPaint(); break;
case SDL_WINDOWEVENT_EXPOSED: WindowHost->OnWindowPaint(); break;
case SDL_WINDOWEVENT_FOCUS_GAINED: WindowHost->OnWindowActivated(); break;
case SDL_WINDOWEVENT_FOCUS_LOST: WindowHost->OnWindowDeactivated(); break;
case SDL_WINDOWEVENT_CLOSE:
WindowHost->OnWindowClose();
break;
case SDL_WINDOWEVENT_MOVED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
case SDL_WINDOWEVENT_RESIZED:
WindowHost->OnWindowGeometryChanged();
break;
case SDL_WINDOWEVENT_SHOWN:
case SDL_WINDOWEVENT_EXPOSED:
WindowHost->OnWindowPaint();
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
case SDL_WINDOWEVENT_FOCUS_LOST:
WindowHost->OnWindowDeactivated();
break;
case SDL_WINDOWEVENT_NONE:
case SDL_WINDOWEVENT_HIDDEN:
case SDL_WINDOWEVENT_MINIMIZED:
case SDL_WINDOWEVENT_MAXIMIZED:
case SDL_WINDOWEVENT_RESTORED:
case SDL_WINDOWEVENT_ENTER:
case SDL_WINDOWEVENT_LEAVE:
case SDL_WINDOWEVENT_TAKE_FOCUS:
case SDL_WINDOWEVENT_HIT_TEST:
case SDL_WINDOWEVENT_ICCPROF_CHANGED:
case SDL_WINDOWEVENT_DISPLAY_CHANGED:
// nope
break;
}
}
@ -788,5 +818,5 @@ SDL_Scancode SDL2DisplayWindow::InputKeyToScancode(InputKey inputkey)
case InputKey::RControl: return SDL_SCANCODE_RCTRL;
case InputKey::Tilde: return SDL_SCANCODE_GRAVE;
default: return (SDL_Scancode)0;
}
}
}

View file

@ -13,6 +13,7 @@ public:
~SDL2DisplayWindow();
void SetWindowTitle(const std::string& text) override;
void SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images) override;
void SetWindowFrame(const Rect& box) override;
void SetClientFrame(const Rect& box) override;
void Show() override;

View file

@ -9,49 +9,69 @@
#include <wayland-client-protocol-unstable.hpp>
#include <wayland-cursor.hpp>
#include "wl_fractional_scaling_protocol.hpp"
#include "wl_xdg_toplevel_icon.hpp"
#include <linux/input.h>
#include <poll.h>
#include <map>
#include <set>
#include <xkbcommon/xkbcommon.h>
static short poll_single(int fd, short events, int timeout) {
pollfd pfd { .fd = fd, .events = events, .revents = 0 };
if (0 > poll(&pfd, 1, timeout)) {
throw std::runtime_error("poll() failed");
}
#include "wl_cursor_shape.hpp"
return pfd.revents;
inline auto hash_proxy = [](wayland::proxy_t const& proxy) { return reinterpret_cast<size_t>(proxy.c_ptr()); };
using hash_proxy_t = decltype(hash_proxy);
static short poll_single(int fd, short events, int timeout)
{
pollfd pfd { .fd = fd, .events = events, .revents = 0 };
if (0 > poll(&pfd, 1, timeout))
{
throw std::runtime_error("poll() failed");
}
return pfd.revents;
}
enum pointer_event_mask {
POINTER_EVENT_ENTER = 1 << 0,
POINTER_EVENT_LEAVE = 1 << 1,
POINTER_EVENT_MOTION = 1 << 2,
POINTER_EVENT_BUTTON = 1 << 3,
POINTER_EVENT_AXIS = 1 << 4,
POINTER_EVENT_AXIS_SOURCE = 1 << 5,
POINTER_EVENT_AXIS_STOP = 1 << 6,
POINTER_EVENT_AXIS_DISCRETE = 1 << 7,
POINTER_EVENT_AXIS_120 = 1 << 8,
POINTER_EVENT_RELATIVE_MOTION = 1 << 9,
enum pointer_event_mask
{
POINTER_EVENT_ENTER = 1 << 0,
POINTER_EVENT_LEAVE = 1 << 1,
POINTER_EVENT_MOTION = 1 << 2,
POINTER_EVENT_BUTTON = 1 << 3,
POINTER_EVENT_AXIS = 1 << 4,
POINTER_EVENT_AXIS_SOURCE = 1 << 5,
POINTER_EVENT_AXIS_STOP = 1 << 6,
POINTER_EVENT_AXIS_DISCRETE = 1 << 7,
POINTER_EVENT_AXIS_120 = 1 << 8,
POINTER_EVENT_RELATIVE_MOTION = 1 << 9,
};
struct WaylandPointerEvent
{
uint32_t event_mask;
double surfaceX, surfaceY;
double dx, dy;
uint32_t button;
wayland::pointer_button_state state;
uint32_t time;
uint32_t serial;
struct {
bool valid;
double value;
int32_t discrete;
int32_t value120;
} axes[2];
wayland::pointer_axis_source axis_source;
uint32_t event_mask;
double surfaceX, surfaceY;
double dx, dy, dx_unaccel, dy_unaccel;
uint32_t button;
wayland::pointer_button_state state;
uint32_t time;
uint32_t serial;
struct {
bool valid;
double value;
int32_t discrete;
int32_t value120;
} axes[2];
wayland::pointer_axis_source axis_source;
};
class WaylandTimer
{
public:
WaylandTimer(int timeoutMilliseconds, std::function<void()> onTimer, int64_t nextTime) : timeoutMilliseconds(timeoutMilliseconds), onTimer(onTimer), nextTime(nextTime) {}
int timeoutMilliseconds = 0;
std::function<void()> onTimer;
int64_t nextTime = 0;
};
class WaylandDisplayWindow;
@ -60,7 +80,7 @@ class WaylandDisplayBackend : public DisplayBackend
{
public:
WaylandDisplayBackend();
~WaylandDisplayBackend();
~WaylandDisplayBackend();
std::unique_ptr<DisplayWindow> Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) override;
void ProcessEvents() override;
@ -78,9 +98,13 @@ public:
void OnWindowDestroyed(WaylandDisplayWindow* window);
void SetCursor(StandardCursor cursor);
void ShowCursor(bool enable);
void ShowCursor(bool enable);
bool GetKeyState(InputKey key);
std::string GetClipboardText() { return m_ClipboardContents; }
wayland::data_device_t& GetDataDevice() { return m_DataDevice; }
uint32_t GetKeyboardSerial() const { return m_KeyboardSerial; }
#ifdef USE_DBUS
std::unique_ptr<OpenFileDialog> CreateOpenFileDialog(DisplayWindow* owner) override;
std::unique_ptr<SaveFileDialog> CreateSaveFileDialog(DisplayWindow* owner) override;
@ -93,76 +117,92 @@ public:
wayland::registry_t s_waylandRegistry;
std::vector<WaylandDisplayWindow*> s_Windows;
WaylandDisplayWindow* m_FocusWindow = nullptr;
WaylandDisplayWindow* m_MouseFocusWindow = nullptr; // Mouse focus should be tracked separately.
WaylandDisplayWindow* m_MouseFocusWindow = nullptr; // Mouse focus should be tracked separately.
wayland::compositor_t m_waylandCompositor;
wayland::shm_t m_waylandSHM;
wayland::seat_t m_waylandSeat;
wayland::output_t m_waylandOutput;
wayland::data_device_manager_t m_DataDeviceManager;
wayland::xdg_wm_base_t m_XDGWMBase;
wayland::zwp_pointer_constraints_v1_t m_PointerConstraints;
wayland::xdg_activation_v1_t m_XDGActivation;
wayland::zxdg_decoration_manager_v1_t m_XDGDecorationManager;
wayland::fractional_scale_manager_v1_t m_FractionalScaleManager;
wayland::zxdg_output_manager_v1_t m_XDGOutputManager;
wayland::zxdg_output_v1_t m_XDGOutput;
wayland::zxdg_exporter_v2_t m_XDGExporter;
wayland::compositor_t m_waylandCompositor;
wayland::shm_t m_waylandSHM;
wayland::seat_t m_waylandSeat;
wayland::output_t m_waylandOutput;
wayland::data_device_manager_t m_DataDeviceManager;
wayland::xdg_wm_base_t m_XDGWMBase;
wayland::zwp_pointer_constraints_v1_t m_PointerConstraints;
wayland::xdg_activation_v1_t m_XDGActivation;
wayland::zxdg_decoration_manager_v1_t m_XDGDecorationManager;
wayland::fractional_scale_manager_v1_t m_FractionalScaleManager;
wayland::zxdg_output_manager_v1_t m_XDGOutputManager;
wayland::zxdg_output_v1_t m_XDGOutput;
wayland::zxdg_exporter_v2_t m_XDGExporter;
wayland::keyboard_t m_waylandKeyboard;
wayland::pointer_t m_waylandPointer;
wayland::keyboard_t m_waylandKeyboard;
wayland::pointer_t m_waylandPointer;
wayland::zwp_relative_pointer_manager_v1_t m_RelativePointerManager;
wayland::zwp_relative_pointer_v1_t m_RelativePointer;
wayland::zwp_relative_pointer_manager_v1_t m_RelativePointerManager;
wayland::zwp_relative_pointer_v1_t m_RelativePointer;
wayland::cursor_image_t m_cursorImage;
wayland::surface_t m_cursorSurface;
wayland::buffer_t m_cursorBuffer;
wayland::xdg_toplevel_icon_manager_v1_t m_XDGToplevelIconManager;
std::map<InputKey, bool> inputKeyStates; // True when the key is pressed, false when isn't
wayland::cursor_shape_manager_v1_t m_CursorShapeManager;
wayland::cursor_shape_device_v1_t m_CursorShapeDevice;
bool IsMouseLocked() { return hasMouseLock; }
void SetMouseLocked(bool val) { hasMouseLock = val; }
wayland::cursor_image_t m_cursorImage;
wayland::surface_t m_cursorSurface;
wayland::buffer_t m_cursorBuffer;
std::map<InputKey, bool> inputKeyStates; // True when the key is pressed, false when isn't
bool IsMouseLocked() { return hasMouseLock; }
void SetMouseLocked(bool val) { hasMouseLock = val; }
private:
void CheckNeedsUpdate();
void CheckNeedsUpdate();
void UpdateTimers();
void ConnectDeviceEvents();
void OnKeyboardKeyEvent(xkb_keysym_t xkbKeySym, wayland::keyboard_key_state state);
void OnKeyboardCharEvent(const char* ch, wayland::keyboard_key_state state);
void OnKeyboardDelayEnd();
void OnKeyboardRepeat();
void OnMouseEnterEvent(uint32_t serial);
void OnMouseLeaveEvent();
void OnMousePressEvent(InputKey button);
void OnMouseReleaseEvent(InputKey button);
void OnMouseMoveEvent(Point surfacePos);
void OnMouseMoveRawEvent(int surfaceX, int surfaceY);
void OnMouseWheelEvent(InputKey button);
void WaitForEvents(int timeout);
int GetTimerTimeout();
void ConnectDeviceEvents();
void OnKeyboardKeyEvent(xkb_keysym_t xkbKeySym, wayland::keyboard_key_state state);
void OnKeyboardCharEvent(const char* ch, wayland::keyboard_key_state state);
void OnKeyboardDelayEnd();
void OnKeyboardRepeat();
void OnMouseEnterEvent(uint32_t serial);
void OnMouseLeaveEvent();
void OnMousePressEvent(InputKey button);
void OnMouseReleaseEvent(InputKey button);
void OnMouseMoveEvent(Point surfacePos);
void OnMouseMoveRawEvent(int surfaceX, int surfaceY);
void OnMouseWheelEvent(InputKey button);
InputKey XKBKeySymToInputKey(xkb_keysym_t keySym);
InputKey LinuxInputEventCodeToInputKey(uint32_t inputCode);
InputKey XKBKeySymToInputKey(xkb_keysym_t keySym);
InputKey LinuxInputEventCodeToInputKey(uint32_t inputCode);
std::string GetWaylandCursorName(StandardCursor cursor);
wayland::cursor_shape_device_v1_shape GetWaylandCursorShape(StandardCursor cursor);
std::string GetWaylandCursorName(StandardCursor cursor);
bool hasKeyboard = false;
bool hasPointer = false;
bool hasMouseLock = false;
bool hasKeyboard = false;
bool hasPointer = false;
bool hasMouseLock = false;
ZTimer::TimePoint m_previousTime;
ZTimer::TimePoint m_currentTime;
ZTimer::TimePoint m_previousTime;
ZTimer::TimePoint m_currentTime;
ZTimer m_keyboardDelayTimer;
ZTimer m_keyboardRepeatTimer;
ZTimer m_keyboardDelayTimer;
ZTimer m_keyboardRepeatTimer;
InputKey previousKey = {};
std::string previousChars;
InputKey previousKey = {};
std::string previousChars;
uint32_t m_KeyboardSerial = 0;
uint32_t m_KeyboardSerial = 0;
uint32_t m_MouseSerial = 0;
xkb_context* m_KeymapContext = nullptr;
xkb_keymap* m_Keymap = nullptr;
xkb_state* m_KeyboardState = nullptr;
xkb_context* m_KeymapContext = nullptr;
xkb_keymap* m_Keymap = nullptr;
xkb_state* m_KeyboardState = nullptr;
WaylandPointerEvent currentPointerEvent = {0};
WaylandPointerEvent currentPointerEvent = {0};
wayland::data_device_t m_DataDevice;
std::unordered_map<wayland::data_offer_t, std::set<std::string>, hash_proxy_t> m_DataOfferMimeTypes;
std::string m_ClipboardContents;
std::vector<std::shared_ptr<WaylandTimer>> m_timers;
};

View file

@ -3,288 +3,313 @@
#include <cstring>
#include <dlfcn.h>
#include "core/image.h"
WaylandDisplayWindow::WaylandDisplayWindow(WaylandDisplayBackend* backend, DisplayWindowHost* windowHost, bool popupWindow, WaylandDisplayWindow* owner, RenderAPI renderAPI)
: backend(backend), m_owner(owner), windowHost(windowHost), m_PopupWindow(popupWindow), m_renderAPI(renderAPI)
: backend(backend), m_owner(owner), windowHost(windowHost), m_PopupWindow(popupWindow), m_renderAPI(renderAPI)
{
m_AppSurface = backend->m_waylandCompositor.create_surface();
m_AppSurface = backend->m_waylandCompositor.create_surface();
m_NativeHandle.display = backend->s_waylandDisplay;
m_NativeHandle.surface = m_AppSurface;
m_NativeHandle.display = backend->s_waylandDisplay;
m_NativeHandle.surface = m_AppSurface;
if (backend->m_FractionalScaleManager)
{
m_FractionalScale = backend->m_FractionalScaleManager.get_fractional_scale(m_AppSurface);
if (backend->m_FractionalScaleManager)
{
m_FractionalScale = backend->m_FractionalScaleManager.get_fractional_scale(m_AppSurface);
m_FractionalScale.on_preferred_scale() = [&](uint32_t scale_numerator) {
// parameter is the numerator of a fraction with the denominator of 120
m_ScaleFactor = scale_numerator / 120.0;
m_FractionalScale.on_preferred_scale() = [&](uint32_t scale_numerator) {
// parameter is the numerator of a fraction with the denominator of 120
m_ScaleFactor = scale_numerator / 120.0;
m_NeedsUpdate = true;
windowHost->OnWindowDpiScaleChanged();
};
}
m_NeedsUpdate = true;
windowHost->OnWindowDpiScaleChanged();
};
}
m_XDGSurface = backend->m_XDGWMBase.get_xdg_surface(m_AppSurface);
m_XDGSurface.on_configure() = [&] (uint32_t serial) {
m_XDGSurface.ack_configure(serial);
};
m_XDGSurface = backend->m_XDGWMBase.get_xdg_surface(m_AppSurface);
m_XDGSurface.on_configure() = [&] (uint32_t serial) {
m_XDGSurface.ack_configure(serial);
};
if (popupWindow)
{
InitializePopup();
}
else
{
InitializeToplevel();
}
if (popupWindow)
{
InitializePopup();
}
else
{
InitializeToplevel();
}
backend->OnWindowCreated(this);
backend->OnWindowCreated(this);
this->DrawSurface();
this->DrawSurface();
}
WaylandDisplayWindow::~WaylandDisplayWindow()
{
backend->OnWindowDestroyed(this);
backend->OnWindowDestroyed(this);
}
void WaylandDisplayWindow::InitializeToplevel()
{
m_XDGToplevel = m_XDGSurface.get_toplevel();
m_XDGToplevel.set_title("ZWidget Window");
m_XDGToplevel = m_XDGSurface.get_toplevel();
m_XDGToplevel.set_title("ZWidget Window");
if (m_owner)
m_XDGToplevel.set_parent(m_owner->m_XDGToplevel);
if (m_owner)
m_XDGToplevel.set_parent(m_owner->m_XDGToplevel);
if (backend->m_XDGDecorationManager)
{
// Force server side decorations if possible
m_XDGToplevelDecoration = backend->m_XDGDecorationManager.get_toplevel_decoration(m_XDGToplevel);
m_XDGToplevelDecoration.set_mode(wayland::zxdg_toplevel_decoration_v1_mode::server_side);
}
if (backend->m_XDGDecorationManager)
{
// Force server side decorations if possible
m_XDGToplevelDecoration = backend->m_XDGDecorationManager.get_toplevel_decoration(m_XDGToplevel);
m_XDGToplevelDecoration.set_mode(wayland::zxdg_toplevel_decoration_v1_mode::server_side);
}
m_AppSurface.commit();
m_AppSurface.commit();
backend->s_waylandDisplay.roundtrip();
backend->s_waylandDisplay.roundtrip();
// These have to be added after the roundtrip
m_XDGToplevel.on_configure() = [&] (int32_t width, int32_t height, wayland::array_t states) {
OnXDGToplevelConfigureEvent(width, height);
};
// These have to be added after the roundtrip
m_XDGToplevel.on_configure() = [&] (int32_t width, int32_t height, wayland::array_t states) {
OnXDGToplevelConfigureEvent(width, height);
};
m_XDGToplevel.on_close() = [&] () {
OnExitEvent();
};
m_XDGToplevel.on_close() = [&] () {
OnExitEvent();
};
m_XDGToplevel.on_configure_bounds() = [this] (int32_t width, int32_t height)
{
m_XDGToplevel.on_configure_bounds() = [this] (int32_t width, int32_t height)
{
};
};
m_XDGExported = backend->m_XDGExporter.export_toplevel(m_AppSurface);
m_XDGExported = backend->m_XDGExporter.export_toplevel(m_AppSurface);
m_XDGExported.on_handle() = [&] (std::string handleStr) {
OnExportHandleEvent(handleStr);
};
m_XDGExported.on_handle() = [&] (std::string handleStr) {
OnExportHandleEvent(handleStr);
};
}
void WaylandDisplayWindow::InitializePopup()
{
if (!m_owner)
throw std::runtime_error("Popup window must have an owner!");
if (!m_owner)
throw std::runtime_error("Popup window must have an owner!");
wayland::xdg_positioner_t popupPositioner = backend->m_XDGWMBase.create_positioner();
wayland::xdg_positioner_t popupPositioner = backend->m_XDGWMBase.create_positioner();
popupPositioner.set_anchor(wayland::xdg_positioner_anchor::bottom);
popupPositioner.set_anchor_rect(0, 0, 1, 30);
popupPositioner.set_size(1, 1);
popupPositioner.set_anchor(wayland::xdg_positioner_anchor::bottom);
popupPositioner.set_anchor_rect(0, 0, 1, 30);
popupPositioner.set_size(1, 1);
m_XDGPopup = m_XDGSurface.get_popup(m_owner->m_XDGSurface, popupPositioner);
m_XDGPopup = m_XDGSurface.get_popup(m_owner->m_XDGSurface, popupPositioner);
m_XDGPopup.on_configure() = [&] (int32_t x, int32_t y, int32_t width, int32_t height) {
SetClientFrame(Rect::xywh(x, y, width, height));
};
m_XDGPopup.on_configure() = [&] (int32_t x, int32_t y, int32_t width, int32_t height) {
SetClientFrame(Rect::xywh(x, y, width, height));
};
//m_XDGPopup.on_repositioned()
//m_XDGPopup.on_repositioned()
m_XDGPopup.on_popup_done() = [&] () {
OnExitEvent();
};
m_XDGPopup.on_popup_done() = [&] () {
OnExitEvent();
};
m_AppSurface.commit();
m_AppSurface.commit();
backend->s_waylandDisplay.roundtrip();
backend->s_waylandDisplay.roundtrip();
}
void WaylandDisplayWindow::SetWindowTitle(const std::string& text)
{
if (m_XDGToplevel)
m_XDGToplevel.set_title(text);
if (m_XDGToplevel)
m_XDGToplevel.set_title(text);
}
void WaylandDisplayWindow::SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images)
{
if (backend->m_XDGToplevelIconManager)
{
if (images.empty())
{
backend->m_XDGToplevelIconManager.set_icon(m_XDGToplevel, nullptr);
return;
}
m_XDGToplevelIcon = backend->m_XDGToplevelIconManager.create_icon();
CreateAppIconBuffers(images);
for (auto& icon_buffer : appIconBuffers)
{
m_XDGToplevelIcon.add_buffer(icon_buffer, 1);
}
backend->m_XDGToplevelIconManager.set_icon(m_XDGToplevel, m_XDGToplevelIcon);
}
}
void WaylandDisplayWindow::SetWindowFrame(const Rect& box)
{
// Resizing will be shown on the next commit
CreateBuffers(box.width, box.height);
windowHost->OnWindowGeometryChanged();
m_NeedsUpdate = true;
m_AppSurface.commit();
// Resizing will be shown on the next commit
CreateBuffers(box.width, box.height);
windowHost->OnWindowGeometryChanged();
m_NeedsUpdate = true;
m_AppSurface.commit();
}
void WaylandDisplayWindow::SetClientFrame(const Rect& box)
{
SetWindowFrame(box);
SetWindowFrame(box);
}
void WaylandDisplayWindow::Show()
{
m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0);
m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height);
m_AppSurface.commit();
m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0);
m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height);
m_AppSurface.commit();
}
void WaylandDisplayWindow::ShowFullscreen()
{
if (m_XDGToplevel)
{
m_XDGToplevel.set_fullscreen(backend->m_waylandOutput);
isFullscreen = true;
}
if (m_XDGToplevel)
{
m_XDGToplevel.set_fullscreen(backend->m_waylandOutput);
isFullscreen = true;
}
}
void WaylandDisplayWindow::ShowMaximized()
{
if (m_XDGToplevel)
m_XDGToplevel.set_maximized();
if (m_XDGToplevel)
m_XDGToplevel.set_maximized();
}
void WaylandDisplayWindow::ShowMinimized()
{
if (m_XDGToplevel)
m_XDGToplevel.set_minimized();
if (m_XDGToplevel)
m_XDGToplevel.set_minimized();
}
void WaylandDisplayWindow::ShowNormal()
{
if (m_XDGToplevel)
m_XDGToplevel.unset_fullscreen();
if (m_XDGToplevel)
m_XDGToplevel.unset_fullscreen();
}
bool WaylandDisplayWindow::IsWindowFullscreen()
{
return isFullscreen;
return isFullscreen;
}
void WaylandDisplayWindow::Hide()
{
// Apparently this is how hiding a window works
// By attaching a null buffer to the surface
// See: https://lists.freedesktop.org/archives/wayland-devel/2017-November/035963.html
m_AppSurface.attach(nullptr, 0, 0);
m_AppSurface.commit();
// Apparently this is how hiding a window works
// By attaching a null buffer to the surface
// See: https://lists.freedesktop.org/archives/wayland-devel/2017-November/035963.html
m_AppSurface.attach(nullptr, 0, 0);
m_AppSurface.commit();
}
void WaylandDisplayWindow::Activate()
{
// To do: this needs to be in the backend instance if all windows share the activation token
wayland::xdg_activation_token_v1_t xdgActivationToken = backend->m_XDGActivation.get_activation_token();
// To do: this needs to be in the backend instance if all windows share the activation token
wayland::xdg_activation_token_v1_t xdgActivationToken = backend->m_XDGActivation.get_activation_token();
std::string tokenString;
std::string tokenString;
xdgActivationToken.on_done() = [&tokenString] (std::string obtainedString) {
tokenString = obtainedString;
};
xdgActivationToken.on_done() = [&tokenString] (std::string obtainedString) {
tokenString = obtainedString;
};
xdgActivationToken.set_surface(m_AppSurface);
xdgActivationToken.commit(); // This will set our token string
xdgActivationToken.set_surface(m_AppSurface);
xdgActivationToken.commit(); // This will set our token string
backend->m_XDGActivation.activate(tokenString, m_AppSurface);
backend->m_FocusWindow = this;
backend->m_MouseFocusWindow = this;
windowHost->OnWindowActivated();
backend->m_XDGActivation.activate(tokenString, m_AppSurface);
backend->m_FocusWindow = this;
backend->m_MouseFocusWindow = this;
windowHost->OnWindowActivated();
}
void WaylandDisplayWindow::ShowCursor(bool enable)
{
backend->ShowCursor(enable);
backend->ShowCursor(enable);
}
void WaylandDisplayWindow::LockCursor()
{
m_LockedPointer = backend->m_PointerConstraints.lock_pointer(m_AppSurface, backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent);
backend->SetMouseLocked(true);
ShowCursor(false);
m_LockedPointer = backend->m_PointerConstraints.lock_pointer(m_AppSurface, backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent);
backend->SetMouseLocked(true);
ShowCursor(false);
}
void WaylandDisplayWindow::UnlockCursor()
{
if (m_LockedPointer)
m_LockedPointer.proxy_release();
backend->SetMouseLocked(false);
ShowCursor(true);
if (m_LockedPointer)
m_LockedPointer.proxy_release();
backend->SetMouseLocked(false);
ShowCursor(true);
}
void WaylandDisplayWindow::CaptureMouse()
{
m_ConfinedPointer = backend->m_PointerConstraints.confine_pointer(GetWindowSurface(), backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent);
ShowCursor(false);
m_ConfinedPointer = backend->m_PointerConstraints.confine_pointer(GetWindowSurface(), backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent);
ShowCursor(false);
}
void WaylandDisplayWindow::ReleaseMouseCapture()
{
if (m_ConfinedPointer)
m_ConfinedPointer.proxy_release();
ShowCursor(true);
if (m_ConfinedPointer)
m_ConfinedPointer.proxy_release();
ShowCursor(true);
}
void WaylandDisplayWindow::Update()
{
m_NeedsUpdate = true;
m_NeedsUpdate = true;
}
bool WaylandDisplayWindow::GetKeyState(InputKey key)
{
return backend->GetKeyState(key);
return backend->GetKeyState(key);
}
void WaylandDisplayWindow::SetCursor(StandardCursor cursor)
{
backend->SetCursor(cursor);
backend->SetCursor(cursor);
}
Rect WaylandDisplayWindow::GetWindowFrame() const
{
return Rect(m_WindowGlobalPos.x, m_WindowGlobalPos.y, m_WindowSize.width, m_WindowSize.height);
return Rect(m_WindowGlobalPos.x, m_WindowGlobalPos.y, m_WindowSize.width, m_WindowSize.height);
}
Size WaylandDisplayWindow::GetClientSize() const
{
return m_WindowSize / m_ScaleFactor;
return m_WindowSize / m_ScaleFactor;
}
int WaylandDisplayWindow::GetPixelWidth() const
{
return m_WindowSize.width;
return m_WindowSize.width;
}
int WaylandDisplayWindow::GetPixelHeight() const
{
return m_WindowSize.height;
return m_WindowSize.height;
}
double WaylandDisplayWindow::GetDpiScale() const
{
return m_ScaleFactor;
return m_ScaleFactor;
}
void WaylandDisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels)
{
// Make new buffers if the sizes don't match
if (width != m_WindowSize.width || height != m_WindowSize.height)
CreateBuffers(width, height);
// Make new buffers if the sizes don't match
if (width != m_WindowSize.width || height != m_WindowSize.height)
CreateBuffers(width, height);
std::memcpy(shared_mem->get_mem(), (void*)pixels, width * height * 4);
std::memcpy(shared_mem->get_mem(), (void*)pixels, width * height * 4);
}
void WaylandDisplayWindow::SetBorderColor(uint32_t bgra8)
@ -304,79 +329,115 @@ void WaylandDisplayWindow::SetCaptionTextColor(uint32_t bgra8)
std::string WaylandDisplayWindow::GetClipboardText()
{
return m_ClipboardContents;
return backend->GetClipboardText();
}
void WaylandDisplayWindow::SetClipboardText(const std::string& text)
{
m_ClipboardContents = text;
m_DataSource = backend->m_DataDeviceManager.create_data_source();
m_DataSource.on_send() = [text] (const std::string& mime_type, const int fd)
{
write(fd, text.c_str(), text.size());
close(fd);
};
m_DataSource.offer("text/plain");
backend->GetDataDevice().set_selection(m_DataSource, backend->GetKeyboardSerial());
}
Point WaylandDisplayWindow::MapFromGlobal(const Point& pos) const
{
return (pos - m_WindowGlobalPos) / m_ScaleFactor;
return (pos - m_WindowGlobalPos) / m_ScaleFactor;
}
Point WaylandDisplayWindow::MapToGlobal(const Point& pos) const
{
return (m_WindowGlobalPos + pos) / m_ScaleFactor;
return (m_WindowGlobalPos + pos) / m_ScaleFactor;
}
void WaylandDisplayWindow::OnXDGToplevelConfigureEvent(int32_t width, int32_t height)
{
Rect rect = GetWindowFrame();
rect.width = width / m_ScaleFactor;
rect.height = height / m_ScaleFactor;
SetWindowFrame(rect);
windowHost->OnWindowGeometryChanged();
Rect rect = GetWindowFrame();
rect.width = width / m_ScaleFactor;
rect.height = height / m_ScaleFactor;
SetWindowFrame(rect);
windowHost->OnWindowGeometryChanged();
}
void WaylandDisplayWindow::OnExportHandleEvent(std::string exportedHandle)
{
m_windowID = exportedHandle;
m_windowID = exportedHandle;
}
void WaylandDisplayWindow::OnExitEvent()
{
windowHost->OnWindowClose();
windowHost->OnWindowClose();
}
void WaylandDisplayWindow::DrawSurface(uint32_t serial)
{
m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0);
m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height);
m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0);
m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height);
if (m_renderAPI == RenderAPI::Unspecified || m_renderAPI == RenderAPI::Bitmap)
{
m_FrameCallback = m_AppSurface.frame();
if (m_renderAPI == RenderAPI::Unspecified || m_renderAPI == RenderAPI::Bitmap)
{
m_FrameCallback = m_AppSurface.frame();
m_FrameCallback.on_done() = bind_mem_fn(&WaylandDisplayWindow::DrawSurface, this);
}
m_AppSurface.commit();
m_FrameCallback.on_done() = bind_mem_fn(&WaylandDisplayWindow::DrawSurface, this);
}
m_AppSurface.commit();
}
void WaylandDisplayWindow::CreateBuffers(int32_t width, int32_t height)
{
if (width == 0 || height == 0)
return;
if (width == 0 || height == 0)
return;
if (shared_mem)
shared_mem.reset();
if (shared_mem)
shared_mem.reset();
int scaled_width = width * m_ScaleFactor;
int scaled_height = height * m_ScaleFactor;
int scaled_width = width * m_ScaleFactor;
int scaled_height = height * m_ScaleFactor;
shared_mem = std::make_shared<SharedMemHelper>(scaled_width * scaled_height * 4);
auto pool = backend->m_waylandSHM.create_pool(shared_mem->get_fd(), scaled_width * scaled_height * 4);
shared_mem = std::make_shared<SharedMemHelper>(scaled_width * scaled_height * 4);
auto pool = backend->m_waylandSHM.create_pool(shared_mem->get_fd(), scaled_width * scaled_height * 4);
m_AppSurfaceBuffer = pool.create_buffer(0, scaled_width, scaled_height, scaled_width * 4, wayland::shm_format::xrgb8888);
m_AppSurfaceBuffer = pool.create_buffer(0, scaled_width, scaled_height, scaled_width * 4, wayland::shm_format::xrgb8888);
m_WindowSize = Size(scaled_width, scaled_height);
m_WindowSize = Size(scaled_width, scaled_height);
}
void WaylandDisplayWindow::CreateAppIconBuffers(const std::vector<std::shared_ptr<Image>>& images)
{
if (images.empty())
return;
appIconBuffers.clear();
appIconSharedMems.clear();
for (auto& image: images)
{
const int image_size = image->GetWidth() * image->GetHeight() * 4;
const int stride = image->GetWidth() * 4;
auto new_shared_mem = std::make_shared<SharedMemHelper>(image_size);
auto pool = backend->m_waylandSHM.create_pool(new_shared_mem->get_fd(), image_size);
auto new_buffer = pool.create_buffer(0, image->GetWidth(), image->GetHeight(), stride, wayland::shm_format::argb8888);
// Fill the buffer with the icon data
std::memcpy(new_shared_mem->get_mem(), image->GetData(), image_size);
appIconSharedMems.push_back(new_shared_mem);
appIconBuffers.push_back(new_buffer);
}
}
std::string WaylandDisplayWindow::GetWaylandWindowID()
{
return m_windowID;
return m_windowID;
}
// This is to avoid needing all the Vulkan headers and the volk binding library just for this:
@ -398,11 +459,11 @@ typedef PFN_vkVoidFunction(VKAPI_PTR* PFN_vkGetInstanceProcAddr)(VkInstance inst
typedef VkFlags VkWaylandSurfaceCreateFlagsKHR;
typedef struct VkWaylandSurfaceCreateInfoKHR
{
VkStructureType sType;
const void* pNext;
VkWaylandSurfaceCreateFlagsKHR flags;
struct wl_display* display;
struct wl_surface* surface;
VkStructureType sType;
const void* pNext;
VkWaylandSurfaceCreateFlagsKHR flags;
struct wl_display* display;
struct wl_surface* surface;
} VkWaylandSurfaceCreateInfoKHR;
typedef VkResult(VKAPI_PTR* PFN_vkCreateWaylandSurfaceKHR)(VkInstance instance, const VkWaylandSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface);

View file

@ -25,70 +25,71 @@ template <typename R, typename T, typename... Args>
std::function<R(Args...)> bind_mem_fn(R(T::* func)(Args...), T *t)
{
return [func, t] (Args... args)
{
return (t->*func)(args...);
};
{
return (t->*func)(args...);
};
}
class SharedMemHelper
{
public:
SharedMemHelper(size_t size)
: len(size)
{
std::stringstream ss;
std::random_device device;
std::default_random_engine engine(device());
std::uniform_int_distribution<unsigned int> distribution(0, std::numeric_limits<unsigned int>::max());
ss << distribution(engine);
name = ss.str();
SharedMemHelper(size_t size)
: len(size)
{
std::stringstream ss;
std::random_device device;
std::default_random_engine engine(device());
std::uniform_int_distribution<unsigned int> distribution(0, std::numeric_limits<unsigned int>::max());
ss << distribution(engine);
name = ss.str();
fd = memfd_create(name.c_str(), 0);
if(fd < 0)
throw std::runtime_error("shm_open failed.");
fd = memfd_create(name.c_str(), 0);
if(fd < 0)
throw std::runtime_error("shm_open failed.");
if(ftruncate(fd, size) < 0)
throw std::runtime_error("ftruncate failed.");
if(ftruncate(fd, size) < 0)
throw std::runtime_error("ftruncate failed.");
mem = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(mem == MAP_FAILED) // NOLINT
throw std::runtime_error("mmap failed with len " + std::to_string(len) + ".");
}
mem = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(mem == MAP_FAILED) // NOLINT
throw std::runtime_error("mmap failed with len " + std::to_string(len) + ".");
}
~SharedMemHelper() noexcept
{
if(fd)
{
munmap(mem, len);
close(fd);
shm_unlink(name.c_str());
}
}
~SharedMemHelper() noexcept
{
if(fd)
{
munmap(mem, len);
close(fd);
shm_unlink(name.c_str());
}
}
int get_fd() const
{
return fd;
}
int get_fd() const
{
return fd;
}
void *get_mem()
{
return mem;
}
void *get_mem()
{
return mem;
}
private:
std::string name;
int fd = 0;
size_t len = 0;
void *mem = nullptr;
std::string name;
int fd = 0;
size_t len = 0;
void *mem = nullptr;
};
class WaylandDisplayWindow : public DisplayWindow
{
public:
WaylandDisplayWindow(WaylandDisplayBackend* backend, DisplayWindowHost* windowHost, bool popupWindow, WaylandDisplayWindow* owner, RenderAPI renderAPI);
~WaylandDisplayWindow();
WaylandDisplayWindow(WaylandDisplayBackend* backend, DisplayWindowHost* windowHost, bool popupWindow, WaylandDisplayWindow* owner, RenderAPI renderAPI);
~WaylandDisplayWindow();
void SetWindowTitle(const std::string& text) override;
void SetWindowTitle(const std::string& text) override;
void SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images) override;
void SetWindowFrame(const Rect& box) override;
void SetClientFrame(const Rect& box) override;
void Show() override;
@ -135,64 +136,69 @@ public:
bool IsWindowFullscreen() override;
private:
// Event handlers as otherwise linking DisplayWindowHost On...() functions with Wayland events directly crashes the app
// Alternatively to avoid crashes one can capture by value ([=]) instead of reference ([&])
void OnXDGToplevelConfigureEvent(int32_t width, int32_t height);
void OnExportHandleEvent(std::string exportedHandle);
void OnExitEvent();
// Event handlers as otherwise linking DisplayWindowHost On...() functions with Wayland events directly crashes the app
// Alternatively to avoid crashes one can capture by value ([=]) instead of reference ([&])
void OnXDGToplevelConfigureEvent(int32_t width, int32_t height);
void OnExportHandleEvent(std::string exportedHandle);
void OnExitEvent();
void DrawSurface(uint32_t serial = 0);
void DrawSurface(uint32_t serial = 0);
void InitializeToplevel();
void InitializePopup();
void InitializeToplevel();
void InitializePopup();
WaylandDisplayBackend* backend = nullptr;
WaylandDisplayWindow* m_owner = nullptr;
DisplayWindowHost* windowHost = nullptr;
bool m_PopupWindow = false;
WaylandDisplayBackend* backend = nullptr;
WaylandDisplayWindow* m_owner = nullptr;
DisplayWindowHost* windowHost = nullptr;
bool m_PopupWindow = false;
bool m_NeedsUpdate = true;
bool m_NeedsUpdate = true;
Point m_WindowGlobalPos = Point(0, 0);
Size m_WindowSize = Size(0, 0);
double m_ScaleFactor = 1.0;
Point m_WindowGlobalPos = Point(0, 0);
Size m_WindowSize = Size(0, 0);
double m_ScaleFactor = 1.0;
Point m_SurfaceMousePos = Point(0, 0);
Point m_SurfaceMousePos = Point(0, 0);
WaylandNativeHandle m_NativeHandle;
RenderAPI m_renderAPI;
WaylandNativeHandle m_NativeHandle;
RenderAPI m_renderAPI;
wayland::data_device_t m_DataDevice;
wayland::data_source_t m_DataSource;
wayland::data_source_t m_DataSource;
wayland::zxdg_toplevel_decoration_v1_t m_XDGToplevelDecoration;
wayland::zxdg_toplevel_decoration_v1_t m_XDGToplevelDecoration;
wayland::fractional_scale_v1_t m_FractionalScale;
wayland::fractional_scale_v1_t m_FractionalScale;
wayland::surface_t m_AppSurface;
wayland::buffer_t m_AppSurfaceBuffer;
wayland::surface_t m_AppSurface;
wayland::buffer_t m_AppSurfaceBuffer;
wayland::xdg_surface_t m_XDGSurface;
wayland::xdg_toplevel_t m_XDGToplevel;
wayland::xdg_popup_t m_XDGPopup;
wayland::xdg_surface_t m_XDGSurface;
wayland::xdg_toplevel_t m_XDGToplevel;
wayland::xdg_popup_t m_XDGPopup;
wayland::zxdg_exported_v2_t m_XDGExported;
wayland::zxdg_exported_v2_t m_XDGExported;
wayland::zwp_locked_pointer_v1_t m_LockedPointer;
wayland::zwp_confined_pointer_v1_t m_ConfinedPointer;
wayland::zwp_locked_pointer_v1_t m_LockedPointer;
wayland::zwp_confined_pointer_v1_t m_ConfinedPointer;
wayland::callback_t m_FrameCallback;
wayland::xdg_toplevel_icon_v1_t m_XDGToplevelIcon;
std::string m_windowID;
std::string m_ClipboardContents;
wayland::callback_t m_FrameCallback;
std::shared_ptr<SharedMemHelper> shared_mem;
std::string m_windowID;
bool isFullscreen = false;
std::shared_ptr<SharedMemHelper> shared_mem;
// Helper functions
void CreateBuffers(int32_t width, int32_t height);
std::string GetWaylandWindowID();
std::vector<std::shared_ptr<SharedMemHelper>> appIconSharedMems;
std::vector<wayland::buffer_t> appIconBuffers;
friend WaylandDisplayBackend;
bool isFullscreen = false;
// Helper functions
void CreateBuffers(int32_t width, int32_t height);
void CreateAppIconBuffers(const std::vector<std::shared_ptr<Image>>& images);
std::string GetWaylandWindowID();
friend WaylandDisplayBackend;
};

View file

@ -0,0 +1,218 @@
#include "wl_cursor_shape.hpp"
using namespace wayland;
using namespace wayland::detail;
const wl_interface* cursor_shape_manager_v1_interface_destroy_request[0] = {
};
const wl_interface* cursor_shape_manager_v1_interface_get_pointer_request[2] = {
&cursor_shape_device_v1_interface,
&pointer_interface,
};
const wl_interface* cursor_shape_manager_v1_interface_get_tablet_tool_v2_request[2] = {
&cursor_shape_device_v1_interface,
&zwp_tablet_tool_v2_interface,
};
const wl_message cursor_shape_manager_v1_interface_requests[3] = {
{
"destroy",
"",
cursor_shape_manager_v1_interface_destroy_request,
},
{
"get_pointer",
"no",
cursor_shape_manager_v1_interface_get_pointer_request,
},
{
"get_tablet_tool_v2",
"no",
cursor_shape_manager_v1_interface_get_tablet_tool_v2_request,
},
};
const wl_message cursor_shape_manager_v1_interface_events[0] = {
};
const wl_interface wayland::detail::cursor_shape_manager_v1_interface =
{
"wp_cursor_shape_manager_v1",
2,
3,
cursor_shape_manager_v1_interface_requests,
0,
cursor_shape_manager_v1_interface_events,
};
const wl_interface* cursor_shape_device_v1_interface_destroy_request[0] = {
};
const wl_interface* cursor_shape_device_v1_interface_set_shape_request[2] = {
nullptr,
nullptr,
};
const wl_message cursor_shape_device_v1_interface_requests[2] = {
{
"destroy",
"",
cursor_shape_device_v1_interface_destroy_request,
},
{
"set_shape",
"uu",
cursor_shape_device_v1_interface_set_shape_request,
},
};
const wl_message cursor_shape_device_v1_interface_events[0] = {
};
const wl_interface wayland::detail::cursor_shape_device_v1_interface =
{
"wp_cursor_shape_device_v1",
2,
2,
cursor_shape_device_v1_interface_requests,
0,
cursor_shape_device_v1_interface_events,
};
cursor_shape_manager_v1_t::cursor_shape_manager_v1_t(const proxy_t &p)
: proxy_t(p)
{
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
{
set_events(std::shared_ptr<detail::events_base_t>(new events_t), dispatcher);
set_destroy_opcode(0U);
}
set_interface(&cursor_shape_manager_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return cursor_shape_manager_v1_t(p); });
}
cursor_shape_manager_v1_t::cursor_shape_manager_v1_t()
{
set_interface(&cursor_shape_manager_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return cursor_shape_manager_v1_t(p); });
}
cursor_shape_manager_v1_t::cursor_shape_manager_v1_t(wp_cursor_shape_manager_v1 *p, wrapper_type t)
: proxy_t(reinterpret_cast<wl_proxy*> (p), t){
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
{
set_events(std::shared_ptr<detail::events_base_t>(new events_t), dispatcher);
set_destroy_opcode(0U);
}
set_interface(&cursor_shape_manager_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return cursor_shape_manager_v1_t(p); });
}
cursor_shape_manager_v1_t::cursor_shape_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/)
: proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){
set_interface(&cursor_shape_manager_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return cursor_shape_manager_v1_t(p); });
}
cursor_shape_manager_v1_t cursor_shape_manager_v1_t::proxy_create_wrapper()
{
return {*this, construct_proxy_wrapper_tag()};
}
const std::string cursor_shape_manager_v1_t::interface_name = "wp_cursor_shape_manager_v1";
cursor_shape_manager_v1_t::operator wp_cursor_shape_manager_v1*() const
{
return reinterpret_cast<wp_cursor_shape_manager_v1*> (c_ptr());
}
cursor_shape_device_v1_t cursor_shape_manager_v1_t::get_pointer(pointer_t const& pointer)
{
proxy_t p = marshal_constructor(1U, &cursor_shape_device_v1_interface, nullptr, pointer.proxy_has_object() ? reinterpret_cast<wl_object*>(pointer.c_ptr()) : nullptr);
return cursor_shape_device_v1_t(p);
}
cursor_shape_device_v1_t cursor_shape_manager_v1_t::get_tablet_tool_v2(zwp_tablet_tool_v2_t const& tablet_tool)
{
proxy_t p = marshal_constructor(2U, &cursor_shape_device_v1_interface, nullptr, tablet_tool.proxy_has_object() ? reinterpret_cast<wl_object*>(tablet_tool.c_ptr()) : nullptr);
return cursor_shape_device_v1_t(p);
}
int cursor_shape_manager_v1_t::dispatcher(uint32_t opcode, const std::vector<any>& args, const std::shared_ptr<detail::events_base_t>& e)
{
return 0;
}
cursor_shape_device_v1_t::cursor_shape_device_v1_t(const proxy_t &p)
: proxy_t(p)
{
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
{
set_events(std::shared_ptr<detail::events_base_t>(new events_t), dispatcher);
set_destroy_opcode(0U);
}
set_interface(&cursor_shape_device_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return cursor_shape_device_v1_t(p); });
}
cursor_shape_device_v1_t::cursor_shape_device_v1_t()
{
set_interface(&cursor_shape_device_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return cursor_shape_device_v1_t(p); });
}
cursor_shape_device_v1_t::cursor_shape_device_v1_t(wp_cursor_shape_device_v1 *p, wrapper_type t)
: proxy_t(reinterpret_cast<wl_proxy*> (p), t){
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
{
set_events(std::shared_ptr<detail::events_base_t>(new events_t), dispatcher);
set_destroy_opcode(0U);
}
set_interface(&cursor_shape_device_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return cursor_shape_device_v1_t(p); });
}
cursor_shape_device_v1_t::cursor_shape_device_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/)
: proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){
set_interface(&cursor_shape_device_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return cursor_shape_device_v1_t(p); });
}
cursor_shape_device_v1_t cursor_shape_device_v1_t::proxy_create_wrapper()
{
return {*this, construct_proxy_wrapper_tag()};
}
const std::string cursor_shape_device_v1_t::interface_name = "wp_cursor_shape_device_v1";
cursor_shape_device_v1_t::operator wp_cursor_shape_device_v1*() const
{
return reinterpret_cast<wp_cursor_shape_device_v1*> (c_ptr());
}
void cursor_shape_device_v1_t::set_shape(uint32_t serial, cursor_shape_device_v1_shape const& shape)
{
marshal(1U, serial, static_cast<uint32_t>(shape));
}
int cursor_shape_device_v1_t::dispatcher(uint32_t opcode, const std::vector<any>& args, const std::shared_ptr<detail::events_base_t>& e)
{
return 0;
}

View file

@ -0,0 +1,258 @@
#pragma once
#include <array>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include <wayland-client.hpp>
#include <wayland-client-protocol-unstable.hpp>
struct wp_cursor_shape_manager_v1;
struct wp_cursor_shape_device_v1;
namespace wayland
{
class cursor_shape_manager_v1_t;
class cursor_shape_device_v1_t;
enum class cursor_shape_device_v1_shape : uint32_t;
enum class cursor_shape_device_v1_error : uint32_t;
namespace detail
{
extern const wl_interface cursor_shape_manager_v1_interface;
extern const wl_interface cursor_shape_device_v1_interface;
}
/** \brief cursor shape manager
This global offers an alternative, optional way to set cursor images. This
new way uses enumerated cursors instead of a wl_surface like
wl_pointer.set_cursor does.
Warning! The protocol described in this file is currently in the testing
phase. Backward compatible changes may be added together with the
corresponding interface version bump. Backward incompatible changes can
only be done by creating a new major version of the extension.
*/
class cursor_shape_manager_v1_t : public proxy_t
{
private:
struct events_t : public detail::events_base_t
{
};
static int dispatcher(uint32_t opcode, const std::vector<detail::any>& args, const std::shared_ptr<detail::events_base_t>& e);
cursor_shape_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/);
public:
cursor_shape_manager_v1_t();
explicit cursor_shape_manager_v1_t(const proxy_t &proxy);
cursor_shape_manager_v1_t(wp_cursor_shape_manager_v1 *p, wrapper_type t = wrapper_type::standard);
cursor_shape_manager_v1_t proxy_create_wrapper();
static const std::string interface_name;
operator wp_cursor_shape_manager_v1*() const;
/** \brief manage the cursor shape of a pointer device
\param pointer
Obtain a wp_cursor_shape_device_v1 for a wl_pointer object.
When the pointer capability is removed from the wl_seat, the
wp_cursor_shape_device_v1 object becomes inert.
*/
cursor_shape_device_v1_t get_pointer(pointer_t const& pointer);
/** \brief Minimum protocol version required for the \ref get_pointer function
*/
static constexpr std::uint32_t get_pointer_since_version = 1;
/** \brief manage the cursor shape of a tablet tool device
\param tablet_tool
Obtain a wp_cursor_shape_device_v1 for a zwp_tablet_tool_v2 object.
When the zwp_tablet_tool_v2 is removed, the wp_cursor_shape_device_v1
object becomes inert.
*/
cursor_shape_device_v1_t get_tablet_tool_v2(zwp_tablet_tool_v2_t const& tablet_tool);
/** \brief Minimum protocol version required for the \ref get_tablet_tool_v2 function
*/
static constexpr std::uint32_t get_tablet_tool_v2_since_version = 1;
};
/** \brief cursor shape for a device
This interface allows clients to set the cursor shape.
*/
class cursor_shape_device_v1_t : public proxy_t
{
private:
struct events_t : public detail::events_base_t
{
};
static int dispatcher(uint32_t opcode, const std::vector<detail::any>& args, const std::shared_ptr<detail::events_base_t>& e);
cursor_shape_device_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/);
public:
cursor_shape_device_v1_t();
explicit cursor_shape_device_v1_t(const proxy_t &proxy);
cursor_shape_device_v1_t(wp_cursor_shape_device_v1 *p, wrapper_type t = wrapper_type::standard);
cursor_shape_device_v1_t proxy_create_wrapper();
static const std::string interface_name;
operator wp_cursor_shape_device_v1*() const;
/** \brief set device cursor to the shape
\param serial serial number of the enter event
\param shape
Sets the device cursor to the specified shape. The compositor will
change the cursor image based on the specified shape.
The cursor actually changes only if the input device focus is one of
the requesting client's surfaces. If any, the previous cursor image
(surface or shape) is replaced.
The "shape" argument must be a valid enum entry, otherwise the
invalid_shape protocol error is raised.
This is similar to the wl_pointer.set_cursor and
zwp_tablet_tool_v2.set_cursor requests, but this request accepts a
shape instead of contents in the form of a surface. Clients can mix
set_cursor and set_shape requests.
The serial parameter must match the latest wl_pointer.enter or
zwp_tablet_tool_v2.proximity_in serial number sent to the client.
Otherwise the request will be ignored.
*/
void set_shape(uint32_t serial, cursor_shape_device_v1_shape const& shape);
/** \brief Minimum protocol version required for the \ref set_shape function
*/
static constexpr std::uint32_t set_shape_since_version = 1;
};
/** \brief cursor shapes
This enum describes cursor shapes.
The names are taken from the CSS W3C specification:
https://w3c.github.io/csswg-drafts/css-ui/#cursor
with a few additions.
Note that there are some groups of cursor shapes that are related:
The first group is drag-and-drop cursors which are used to indicate
the selected action during dnd operations. The second group is resize
cursors which are used to indicate resizing and moving possibilities
on window borders. It is recommended that the shapes in these groups
should use visually compatible images and metaphors.
*/
enum class cursor_shape_device_v1_shape : uint32_t
{
/** \brief default cursor */
_default = 1,
/** \brief a context menu is available for the object under the cursor */
context_menu = 2,
/** \brief help is available for the object under the cursor */
help = 3,
/** \brief pointer that indicates a link or another interactive element */
pointer = 4,
/** \brief progress indicator */
progress = 5,
/** \brief program is busy, user should wait */
wait = 6,
/** \brief a cell or set of cells may be selected */
cell = 7,
/** \brief simple crosshair */
crosshair = 8,
/** \brief text may be selected */
text = 9,
/** \brief vertical text may be selected */
vertical_text = 10,
/** \brief drag-and-drop: alias of/shortcut to something is to be created */
alias = 11,
/** \brief drag-and-drop: something is to be copied */
copy = 12,
/** \brief drag-and-drop: something is to be moved */
move = 13,
/** \brief drag-and-drop: the dragged item cannot be dropped at the current cursor location */
no_drop = 14,
/** \brief drag-and-drop: the requested action will not be carried out */
not_allowed = 15,
/** \brief drag-and-drop: something can be grabbed */
grab = 16,
/** \brief drag-and-drop: something is being grabbed */
grabbing = 17,
/** \brief resizing: the east border is to be moved */
e_resize = 18,
/** \brief resizing: the north border is to be moved */
n_resize = 19,
/** \brief resizing: the north-east corner is to be moved */
ne_resize = 20,
/** \brief resizing: the north-west corner is to be moved */
nw_resize = 21,
/** \brief resizing: the south border is to be moved */
s_resize = 22,
/** \brief resizing: the south-east corner is to be moved */
se_resize = 23,
/** \brief resizing: the south-west corner is to be moved */
sw_resize = 24,
/** \brief resizing: the west border is to be moved */
w_resize = 25,
/** \brief resizing: the east and west borders are to be moved */
ew_resize = 26,
/** \brief resizing: the north and south borders are to be moved */
ns_resize = 27,
/** \brief resizing: the north-east and south-west corners are to be moved */
nesw_resize = 28,
/** \brief resizing: the north-west and south-east corners are to be moved */
nwse_resize = 29,
/** \brief resizing: that the item/column can be resized horizontally */
col_resize = 30,
/** \brief resizing: that the item/row can be resized vertically */
row_resize = 31,
/** \brief something can be scrolled in any direction */
all_scroll = 32,
/** \brief something can be zoomed in */
zoom_in = 33,
/** \brief something can be zoomed out */
zoom_out = 34,
/** \brief drag-and-drop: the user will select which action will be carried out (non-css value) */
dnd_ask = 35,
/** \brief resizing: something can be moved or resized in any direction (non-css value) */
all_resize = 36
};
/** \brief
*/
enum class cursor_shape_device_v1_error : uint32_t
{
/** \brief the specified shape value is invalid */
invalid_shape = 1
};
}

View file

@ -0,0 +1,267 @@
#include "wl_xdg_toplevel_icon.hpp"
using namespace wayland;
using namespace wayland::detail;
const wl_interface* xdg_toplevel_icon_manager_v1_interface_destroy_request[0] = {
};
const wl_interface* xdg_toplevel_icon_manager_v1_interface_create_icon_request[1] = {
&xdg_toplevel_icon_v1_interface,
};
const wl_interface* xdg_toplevel_icon_manager_v1_interface_set_icon_request[2] = {
&xdg_toplevel_interface,
&xdg_toplevel_icon_v1_interface,
};
const wl_interface* xdg_toplevel_icon_manager_v1_interface_icon_size_event[1] = {
nullptr,
};
const wl_interface* xdg_toplevel_icon_manager_v1_interface_done_event[0] = {
};
const wl_message xdg_toplevel_icon_manager_v1_interface_requests[3] = {
{
"destroy",
"",
xdg_toplevel_icon_manager_v1_interface_destroy_request,
},
{
"create_icon",
"n",
xdg_toplevel_icon_manager_v1_interface_create_icon_request,
},
{
"set_icon",
"o?o",
xdg_toplevel_icon_manager_v1_interface_set_icon_request,
},
};
const wl_message xdg_toplevel_icon_manager_v1_interface_events[2] = {
{
"icon_size",
"i",
xdg_toplevel_icon_manager_v1_interface_icon_size_event,
},
{
"done",
"",
xdg_toplevel_icon_manager_v1_interface_done_event,
},
};
const wl_interface wayland::detail::xdg_toplevel_icon_manager_v1_interface =
{
"xdg_toplevel_icon_manager_v1",
1,
3,
xdg_toplevel_icon_manager_v1_interface_requests,
2,
xdg_toplevel_icon_manager_v1_interface_events,
};
const wl_interface* xdg_toplevel_icon_v1_interface_destroy_request[0] = {
};
const wl_interface* xdg_toplevel_icon_v1_interface_set_name_request[1] = {
nullptr,
};
const wl_interface* xdg_toplevel_icon_v1_interface_add_buffer_request[2] = {
&buffer_interface,
nullptr,
};
const wl_message xdg_toplevel_icon_v1_interface_requests[3] = {
{
"destroy",
"",
xdg_toplevel_icon_v1_interface_destroy_request,
},
{
"set_name",
"s",
xdg_toplevel_icon_v1_interface_set_name_request,
},
{
"add_buffer",
"oi",
xdg_toplevel_icon_v1_interface_add_buffer_request,
},
};
const wl_message xdg_toplevel_icon_v1_interface_events[0] = {
};
const wl_interface wayland::detail::xdg_toplevel_icon_v1_interface =
{
"xdg_toplevel_icon_v1",
1,
3,
xdg_toplevel_icon_v1_interface_requests,
0,
xdg_toplevel_icon_v1_interface_events,
};
xdg_toplevel_icon_manager_v1_t::xdg_toplevel_icon_manager_v1_t(const proxy_t &p)
: proxy_t(p)
{
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
{
set_events(std::shared_ptr<detail::events_base_t>(new events_t), dispatcher);
set_destroy_opcode(0U);
}
set_interface(&xdg_toplevel_icon_manager_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return xdg_toplevel_icon_manager_v1_t(p); });
}
xdg_toplevel_icon_manager_v1_t::xdg_toplevel_icon_manager_v1_t()
{
set_interface(&xdg_toplevel_icon_manager_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return xdg_toplevel_icon_manager_v1_t(p); });
}
xdg_toplevel_icon_manager_v1_t::xdg_toplevel_icon_manager_v1_t(xdg_toplevel_icon_manager_v1 *p, wrapper_type t)
: proxy_t(reinterpret_cast<wl_proxy*> (p), t){
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
{
set_events(std::shared_ptr<detail::events_base_t>(new events_t), dispatcher);
set_destroy_opcode(0U);
}
set_interface(&xdg_toplevel_icon_manager_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return xdg_toplevel_icon_manager_v1_t(p); });
}
xdg_toplevel_icon_manager_v1_t::xdg_toplevel_icon_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/)
: proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){
set_interface(&xdg_toplevel_icon_manager_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return xdg_toplevel_icon_manager_v1_t(p); });
}
xdg_toplevel_icon_manager_v1_t xdg_toplevel_icon_manager_v1_t::proxy_create_wrapper()
{
return {*this, construct_proxy_wrapper_tag()};
}
const std::string xdg_toplevel_icon_manager_v1_t::interface_name = "xdg_toplevel_icon_manager_v1";
xdg_toplevel_icon_manager_v1_t::operator xdg_toplevel_icon_manager_v1*() const
{
return reinterpret_cast<xdg_toplevel_icon_manager_v1*> (c_ptr());
}
xdg_toplevel_icon_v1_t xdg_toplevel_icon_manager_v1_t::create_icon()
{
proxy_t p = marshal_constructor(1U, &xdg_toplevel_icon_v1_interface, nullptr);
return xdg_toplevel_icon_v1_t(p);
}
void xdg_toplevel_icon_manager_v1_t::set_icon(xdg_toplevel_t const& toplevel, xdg_toplevel_icon_v1_t const& icon)
{
marshal(2U, toplevel.proxy_has_object() ? reinterpret_cast<wl_object*>(toplevel.c_ptr()) : nullptr, icon.proxy_has_object() ? reinterpret_cast<wl_object*>(icon.c_ptr()) : nullptr);
}
std::function<void(int32_t)> &xdg_toplevel_icon_manager_v1_t::on_icon_size()
{
return std::static_pointer_cast<events_t>(get_events())->icon_size;
}
std::function<void()> &xdg_toplevel_icon_manager_v1_t::on_done()
{
return std::static_pointer_cast<events_t>(get_events())->done;
}
int xdg_toplevel_icon_manager_v1_t::dispatcher(uint32_t opcode, const std::vector<any>& args, const std::shared_ptr<detail::events_base_t>& e)
{
std::shared_ptr<events_t> events = std::static_pointer_cast<events_t>(e);
switch(opcode)
{
case 0:
if(events->icon_size) events->icon_size(args[0].get<int32_t>());
break;
case 1:
if(events->done) events->done();
break;
}
return 0;
}
xdg_toplevel_icon_v1_t::xdg_toplevel_icon_v1_t(const proxy_t &p)
: proxy_t(p)
{
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
{
set_events(std::shared_ptr<detail::events_base_t>(new events_t), dispatcher);
set_destroy_opcode(0U);
}
set_interface(&xdg_toplevel_icon_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return xdg_toplevel_icon_v1_t(p); });
}
xdg_toplevel_icon_v1_t::xdg_toplevel_icon_v1_t()
{
set_interface(&xdg_toplevel_icon_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return xdg_toplevel_icon_v1_t(p); });
}
xdg_toplevel_icon_v1_t::xdg_toplevel_icon_v1_t(xdg_toplevel_icon_v1 *p, wrapper_type t)
: proxy_t(reinterpret_cast<wl_proxy*> (p), t){
if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard)
{
set_events(std::shared_ptr<detail::events_base_t>(new events_t), dispatcher);
set_destroy_opcode(0U);
}
set_interface(&xdg_toplevel_icon_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return xdg_toplevel_icon_v1_t(p); });
}
xdg_toplevel_icon_v1_t::xdg_toplevel_icon_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/)
: proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){
set_interface(&xdg_toplevel_icon_v1_interface);
set_copy_constructor([] (const proxy_t &p) -> proxy_t
{ return xdg_toplevel_icon_v1_t(p); });
}
xdg_toplevel_icon_v1_t xdg_toplevel_icon_v1_t::proxy_create_wrapper()
{
return {*this, construct_proxy_wrapper_tag()};
}
const std::string xdg_toplevel_icon_v1_t::interface_name = "xdg_toplevel_icon_v1";
xdg_toplevel_icon_v1_t::operator xdg_toplevel_icon_v1*() const
{
return reinterpret_cast<xdg_toplevel_icon_v1*> (c_ptr());
}
void xdg_toplevel_icon_v1_t::set_name(std::string const& icon_name)
{
marshal(1U, icon_name);
}
void xdg_toplevel_icon_v1_t::add_buffer(buffer_t const& buffer, int32_t scale)
{
marshal(2U, buffer.proxy_has_object() ? reinterpret_cast<wl_object*>(buffer.c_ptr()) : nullptr, scale);
}
int xdg_toplevel_icon_v1_t::dispatcher(uint32_t opcode, const std::vector<any>& args, const std::shared_ptr<detail::events_base_t>& e)
{
return 0;
}

View file

@ -0,0 +1,244 @@
#pragma once
#include <array>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include <wayland-client.hpp>
#include <wayland-client-protocol-extra.hpp>
struct xdg_toplevel_icon_manager_v1;
struct xdg_toplevel_icon_v1;
namespace wayland
{
class xdg_toplevel_icon_manager_v1_t;
class xdg_toplevel_icon_v1_t;
enum class xdg_toplevel_icon_v1_error : uint32_t;
namespace detail
{
extern const wl_interface xdg_toplevel_icon_manager_v1_interface;
extern const wl_interface xdg_toplevel_icon_v1_interface;
}
/** \brief interface to manage toplevel icons
This interface allows clients to create toplevel window icons and set
them on toplevel windows to be displayed to the user.
*/
class xdg_toplevel_icon_manager_v1_t : public proxy_t
{
private:
struct events_t : public detail::events_base_t
{
std::function<void(int32_t)> icon_size;
std::function<void()> done;
};
static int dispatcher(uint32_t opcode, const std::vector<detail::any>& args, const std::shared_ptr<detail::events_base_t>& e);
xdg_toplevel_icon_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/);
public:
xdg_toplevel_icon_manager_v1_t();
explicit xdg_toplevel_icon_manager_v1_t(const proxy_t &proxy);
xdg_toplevel_icon_manager_v1_t(xdg_toplevel_icon_manager_v1 *p, wrapper_type t = wrapper_type::standard);
xdg_toplevel_icon_manager_v1_t proxy_create_wrapper();
static const std::string interface_name;
operator xdg_toplevel_icon_manager_v1*() const;
/** \brief create a new icon instance
Creates a new icon object. This icon can then be attached to a
xdg_toplevel via the 'set_icon' request.
*/
xdg_toplevel_icon_v1_t create_icon();
/** \brief Minimum protocol version required for the \ref create_icon function
*/
static constexpr std::uint32_t create_icon_since_version = 1;
/** \brief set an icon on a toplevel window
\param toplevel the toplevel to act on
\param icon
This request assigns the icon 'icon' to 'toplevel', or clears the
toplevel icon if 'icon' was null.
This state is double-buffered and is applied on the next
wl_surface.commit of the toplevel.
After making this call, the xdg_toplevel_icon_v1 provided as 'icon'
can be destroyed by the client without 'toplevel' losing its icon.
The xdg_toplevel_icon_v1 is immutable from this point, and any
future attempts to change it must raise the
'xdg_toplevel_icon_v1.immutable' protocol error.
The compositor must set the toplevel icon from either the pixel data
the icon provides, or by loading a stock icon using the icon name.
See the description of 'xdg_toplevel_icon_v1' for details.
If 'icon' is set to null, the icon of the respective toplevel is reset
to its default icon (usually the icon of the application, derived from
its desktop-entry file, or a placeholder icon).
If this request is passed an icon with no pixel buffers or icon name
assigned, the icon must be reset just like if 'icon' was null.
*/
void set_icon(xdg_toplevel_t const& toplevel, xdg_toplevel_icon_v1_t const& icon);
/** \brief Minimum protocol version required for the \ref set_icon function
*/
static constexpr std::uint32_t set_icon_since_version = 1;
/** \brief describes a supported & preferred icon size
\param size the edge size of the square icon in surface-local coordinates, e.g. 64
This event indicates an icon size the compositor prefers to be
available if the client has scalable icons and can render to any size.
When the 'xdg_toplevel_icon_manager_v1' object is created, the
compositor may send one or more 'icon_size' events to describe the list
of preferred icon sizes. If the compositor has no size preference, it
may not send any 'icon_size' event, and it is up to the client to
decide a suitable icon size.
A sequence of 'icon_size' events must be finished with a 'done' event.
If the compositor has no size preferences, it must still send the
'done' event, without any preceding 'icon_size' events.
*/
std::function<void(int32_t)> &on_icon_size();
/** \brief all information has been sent
This event is sent after all 'icon_size' events have been sent.
*/
std::function<void()> &on_done();
};
/** \brief a toplevel window icon
This interface defines a toplevel icon.
An icon can have a name, and multiple buffers.
In order to be applied, the icon must have either a name, or at least
one buffer assigned. Applying an empty icon (with no buffer or name) to
a toplevel should reset its icon to the default icon.
It is up to compositor policy whether to prefer using a buffer or loading
an icon via its name. See 'set_name' and 'add_buffer' for details.
*/
class xdg_toplevel_icon_v1_t : public proxy_t
{
private:
struct events_t : public detail::events_base_t
{
};
static int dispatcher(uint32_t opcode, const std::vector<detail::any>& args, const std::shared_ptr<detail::events_base_t>& e);
xdg_toplevel_icon_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/);
public:
xdg_toplevel_icon_v1_t();
explicit xdg_toplevel_icon_v1_t(const proxy_t &proxy);
xdg_toplevel_icon_v1_t(xdg_toplevel_icon_v1 *p, wrapper_type t = wrapper_type::standard);
xdg_toplevel_icon_v1_t proxy_create_wrapper();
static const std::string interface_name;
operator xdg_toplevel_icon_v1*() const;
/** \brief set an icon name
\param icon_name
This request assigns an icon name to this icon.
Any previously set name is overridden.
The compositor must resolve 'icon_name' according to the lookup rules
described in the XDG icon theme specification[1] using the
environment's current icon theme.
If the compositor does not support icon names or cannot resolve
'icon_name' according to the XDG icon theme specification it must
fall back to using pixel buffer data instead.
If this request is made after the icon has been assigned to a toplevel
via 'set_icon', a 'immutable' error must be raised.
[1]: https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
*/
void set_name(std::string const& icon_name);
/** \brief Minimum protocol version required for the \ref set_name function
*/
static constexpr std::uint32_t set_name_since_version = 1;
/** \brief add icon data from a pixel buffer
\param buffer
\param scale the scaling factor of the icon, e.g. 1
This request adds pixel data supplied as wl_buffer to the icon.
The client should add pixel data for all icon sizes and scales that
it can provide, or which are explicitly requested by the compositor
via 'icon_size' events on xdg_toplevel_icon_manager_v1.
The wl_buffer supplying pixel data as 'buffer' must be backed by wl_shm
and must be a square (width and height being equal).
If any of these buffer requirements are not fulfilled, a 'invalid_buffer'
error must be raised.
If this icon instance already has a buffer of the same size and scale
from a previous 'add_buffer' request, data from the last request
overrides the preexisting pixel data.
The wl_buffer must be kept alive for as long as the xdg_toplevel_icon
it is associated with is not destroyed, otherwise a 'no_buffer' error
is raised. The buffer contents must not be modified after it was
assigned to the icon. As a result, the region of the wl_shm_pool's
backing storage used for the wl_buffer must not be modified after this
request is sent. The wl_buffer.release event is unused.
If this request is made after the icon has been assigned to a toplevel
via 'set_icon', a 'immutable' error must be raised.
*/
void add_buffer(buffer_t const& buffer, int32_t scale);
/** \brief Minimum protocol version required for the \ref add_buffer function
*/
static constexpr std::uint32_t add_buffer_since_version = 1;
};
/** \brief
*/
enum class xdg_toplevel_icon_v1_error : uint32_t
{
/** \brief the provided buffer does not satisfy requirements */
invalid_buffer = 1,
/** \brief the icon has already been assigned to a toplevel and must not be changed */
immutable = 2,
/** \brief the provided buffer has been destroyed before the toplevel icon */
no_buffer = 3
};
}

View file

@ -1,5 +1,6 @@
#include "win32_display_window.h"
#include <zwidget/core/image.h>
#include <windowsx.h>
#include <stdexcept>
#include <cmath>
@ -30,11 +31,11 @@
// Code for delay loading DPI related functions, needed for continued Windows 7 compatibility.
typedef BOOL (WINAPI *PFN_AdjustWindowRectExForDpi)(
LPRECT lpRect,
DWORD dwStyle,
BOOL bMenu,
DWORD dwExStyle,
UINT dpi
LPRECT lpRect,
DWORD dwStyle,
BOOL bMenu,
DWORD dwExStyle,
UINT dpi
);
typedef UINT (WINAPI *PFN_GetDpiForWindow)(HWND hwnd);
@ -44,39 +45,36 @@ static PFN_GetDpiForWindow pGetDpiForWindow = nullptr;
static void DPIDelayLoad()
{
HMODULE hUser32 = GetModuleHandleW(L"user32.dll");
if (hUser32) {
pAdjustWindowRectExForDpi = (PFN_AdjustWindowRectExForDpi)
GetProcAddress(hUser32, "AdjustWindowRectExForDpi");
pGetDpiForWindow = (PFN_GetDpiForWindow)
GetProcAddress(hUser32, "GetDpiForWindow");
}
HMODULE hUser32 = GetModuleHandleW(L"user32.dll");
if (hUser32)
{
pAdjustWindowRectExForDpi = (PFN_AdjustWindowRectExForDpi)GetProcAddress(hUser32, "AdjustWindowRectExForDpi");
pGetDpiForWindow = (PFN_GetDpiForWindow)GetProcAddress(hUser32, "GetDpiForWindow");
}
}
static BOOL DelayLoadAdjustWindowRectExForDpi(
LPRECT lpRect,
DWORD dwStyle,
BOOL bMenu,
DWORD dwExStyle,
HWND hwnd
) {
static BOOL DelayLoadAdjustWindowRectExForDpi(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, HWND hwnd)
{
DPIDelayLoad();
if (pAdjustWindowRectExForDpi) {
return pAdjustWindowRectExForDpi(lpRect, dwStyle, bMenu, dwExStyle,
pGetDpiForWindow(hwnd));
} else {
return AdjustWindowRectEx(lpRect, dwStyle, bMenu, dwExStyle);
}
if (pAdjustWindowRectExForDpi)
{
return pAdjustWindowRectExForDpi(lpRect, dwStyle, bMenu, dwExStyle,
pGetDpiForWindow(hwnd));
}
else
{
return AdjustWindowRectEx(lpRect, dwStyle, bMenu, dwExStyle);
}
}
static double DelayLoadGetDpiScale(HWND hwnd)
{
DPIDelayLoad();
if (pGetDpiForWindow) {
return pGetDpiForWindow(hwnd) / 96.0;
} else {
return 1.0;
}
if (pGetDpiForWindow) {
return pGetDpiForWindow(hwnd) / 96.0;
} else {
return 1.0;
}
}
Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner, RenderAPI renderAPI) : WindowHost(windowHost), PopupWindow(popupWindow)
@ -120,6 +118,11 @@ Win32DisplayWindow::~Win32DisplayWindow()
WindowHandle.hwnd = 0;
}
if (SmallIcon)
DestroyCursor(SmallIcon);
if (LargeIcon)
DestroyCursor(LargeIcon);
Windows.erase(WindowsIterator);
}
@ -128,6 +131,95 @@ void Win32DisplayWindow::SetWindowTitle(const std::string& text)
SetWindowText(WindowHandle.hwnd, to_utf16(text).c_str());
}
static HICON CreateIconFromImageList(HDC hdc, const std::vector<std::shared_ptr<Image>>& images, int desiredSize)
{
if (images.empty())
return 0;
Image* image = images.front().get();
for (const auto& i : images)
{
int curdist = std::abs(image->GetWidth() - desiredSize);
int dist = std::abs(i->GetWidth() - desiredSize);
if (dist < curdist)
image = i.get();
}
int width = image->GetWidth();
int height = image->GetHeight();
std::vector<uint32_t> pixels(width * height * 4);
if (image->GetFormat() == ImageFormat::R8G8B8A8)
{
int count = width * height;
const uint32_t* src = (const uint32_t*)image->GetData();
uint32_t* dest = pixels.data();
for (int i = 0; i < count; i++)
{
uint32_t r = src[i] & 0xff;
uint32_t g = (src[i] >> 8) & 0xff;
uint32_t b = (src[i] >> 16) & 0xff;
uint32_t a = (src[i] >> 24) & 0xff;
dest[i] = (a << 24) | (r << 16) | (g << 8) | b;
}
}
else if (image->GetFormat() == ImageFormat::B8G8R8A8)
{
memcpy(pixels.data(), image->GetData(), width * height * 4);
}
else
{
return 0;
}
BITMAPV5HEADER bmp_header = {};
bmp_header.bV5Size = sizeof(BITMAPV5HEADER);
bmp_header.bV5Width = width;
bmp_header.bV5Height = height;
bmp_header.bV5Planes = 1;
bmp_header.bV5BitCount = 32;
bmp_header.bV5Compression = BI_RGB;
HBITMAP bitmap = CreateDIBitmap(hdc, (BITMAPINFOHEADER*)&bmp_header, CBM_INIT, pixels.data(), (BITMAPINFO*)&bmp_header, DIB_RGB_COLORS);
if (!bitmap)
return 0;
ICONINFO iconinfo = {};
iconinfo.fIcon = TRUE;
iconinfo.hbmColor = bitmap;
iconinfo.hbmMask = bitmap;
HICON icon = CreateIconIndirect(&iconinfo);
DeleteObject(bitmap);
return icon;
}
void Win32DisplayWindow::SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images)
{
double dpiScale = GetDpiScale();
if (SmallIcon)
{
DestroyCursor(SmallIcon);
SmallIcon = {};
}
if (LargeIcon)
{
DestroyCursor(LargeIcon);
LargeIcon = {};
}
HDC hdc = GetDC(WindowHandle.hwnd);
if (hdc)
{
SmallIcon = CreateIconFromImageList(hdc, images, (int)std::round(16 * dpiScale));
LargeIcon = CreateIconFromImageList(hdc, images, (int)std::round(32 * dpiScale));
ReleaseDC(WindowHandle.hwnd, hdc);
}
SendMessage(WindowHandle.hwnd, WM_SETICON, ICON_SMALL, (LPARAM)SmallIcon);
SendMessage(WindowHandle.hwnd, WM_SETICON, ICON_BIG, (LPARAM)LargeIcon);
}
void Win32DisplayWindow::SetBorderColor(uint32_t bgra8)
{
bgra8 = bgra8 & 0x00ffffff;

View file

@ -14,6 +14,7 @@ public:
~Win32DisplayWindow();
void SetWindowTitle(const std::string& text) override;
void SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images) override;
void SetWindowFrame(const Rect& box) override;
void SetClientFrame(const Rect& box) override;
void Show() override;
@ -90,5 +91,8 @@ public:
HDC PaintDC = 0;
HICON SmallIcon = {};
HICON LargeIcon = {};
StandardCursor CurrentCursor = StandardCursor::arrow;
};

View file

@ -3,6 +3,7 @@
#include "win32_display_window.h"
#include "core/widget.h"
#include <stdexcept>
#include <exception>
#include <thread>
#include <mutex>
#include <condition_variable>
@ -14,6 +15,65 @@ Win32OpenFileDialog::Win32OpenFileDialog(Win32DisplayWindow* owner) : owner(owne
bool Win32OpenFileDialog::Show()
{
// For some reason this can hang deep inside Win32 if we do it on the calling thread!
bool done = false;
std::mutex mutex;
std::condition_variable condvar;
bool showResult = false;
std::exception_ptr exception;
std::thread thread([&]() {
try
{
showResult = ShowWorkerThread();
}
catch (...)
{
exception = std::current_exception();
}
std::unique_lock lock(mutex);
done = true;
condvar.notify_all();
});
std::unique_lock lock(mutex);
while (!done)
{
DisplayBackend::Get()->ProcessEvents();
using namespace std::chrono_literals;
condvar.wait_for(lock, 50ms, [&]() { return done; });
}
lock.unlock();
thread.join();
if (exception)
std::rethrow_exception(exception);
return showResult;
}
bool Win32OpenFileDialog::ShowWorkerThread()
{
class InitCOM
{
public:
InitCOM()
{
HRESULT result = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
if (FAILED(result))
throw std::runtime_error("CoInitializeEx(COINIT_APARTMENTTHREADED) failed");
}
~InitCOM()
{
CoUninitialize();
}
};
InitCOM initCOM;
std::wstring title16 = to_utf16(title);
std::wstring initial_directory16 = to_utf16(initial_directory);
@ -92,35 +152,10 @@ bool Win32OpenFileDialog::Show()
}
}
// For some reason this can hang deep inside Win32 if we do it on the calling thread!
{
bool done = false;
std::mutex mutex;
std::condition_variable condvar;
std::thread thread([&]() {
if (owner)
result = open_dialog->Show(owner->WindowHandle.hwnd);
else
result = open_dialog->Show(0);
std::unique_lock lock(mutex);
done = true;
condvar.notify_all();
});
std::unique_lock lock(mutex);
while (!done)
{
DisplayBackend::Get()->ProcessEvents();
using namespace std::chrono_literals;
condvar.wait_for(lock, 50ms, [&]() { return done; });
}
lock.unlock();
thread.join();
}
if (owner)
result = open_dialog->Show(owner->WindowHandle.hwnd);
else
result = open_dialog->Show(0);
if (SUCCEEDED(result))
{

View file

@ -23,6 +23,7 @@ public:
void SetDefaultExtension(const std::string& extension) override;
private:
bool ShowWorkerThread();
void throw_if_failed(HRESULT result, const std::string& error);
Win32DisplayWindow* owner = nullptr;

View file

@ -0,0 +1,261 @@
#include "x11_connection.h"
#include "x11_display_window.h"
#include <stdexcept>
#include <chrono>
X11Connection* GetX11Connection()
{
static X11Connection connection;
return &connection;
}
X11Connection::X11Connection()
{
// This is required by vulkan
XInitThreads();
display = XOpenDisplay(nullptr);
if (!display)
throw std::runtime_error("Could not open X11 display");
// Make auto-repeat keys detectable
Bool supports_detectable_autorepeat = {};
XkbSetDetectableAutoRepeat(display, True, &supports_detectable_autorepeat);
// Loads the XMODIFIERS environment variable to see what IME to use
XSetLocaleModifiers("");
xim = XOpenIM(display, 0, 0, 0);
if (!xim)
{
// fallback to internal input method
XSetLocaleModifiers("@im=none");
xim = XOpenIM(display, 0, 0, 0);
}
// Look for XInput support
int event = 0, error = 0;
if (XQueryExtension(display, "XInputExtension", &XInputOpcode, &event, &error))
{
// We need XInput 2.0 support
int major = 2, minor = 0;
if (XIQueryVersion(display, &major, &minor) != BadRequest)
{
// And we need a master pointer ID
int ndevices = 0;
XIDeviceInfo *devices = XIQueryDevice(display, XIAllDevices, &ndevices);
if (devices)
{
for (int i = 0; i < ndevices; i++)
{
if (devices[i].use == XIMasterPointer)
{
MasterPointerID = devices[i].deviceid;
XInput2Supported = true;
break;
}
}
XIFreeDeviceInfo(devices);
}
}
}
// This XInput API is the gift that just keeps on giving. Instead of routing events to the
// focus window it sends it to the root window. Thanks!
if (XInput2Supported)
{
unsigned char mask[3] = { 0 };
XISetMask(mask, XI_RawMotion);
XIEventMask eventmask;
eventmask.deviceid = MasterPointerID;
eventmask.mask_len = sizeof(mask);
eventmask.mask = mask;
Window root = XRootWindow(display, XDefaultScreen(display));
XISelectEvents(display, root, &eventmask, 1);
}
}
X11Connection::~X11Connection()
{
for (auto& it : standardCursors)
XFreeCursor(display, it.second);
if (xim)
XCloseIM(xim);
XCloseDisplay(display);
}
Atom X11Connection::GetAtom(const std::string& name)
{
auto it = atoms.find(name);
if (it != atoms.end())
return it->second;
Atom atom = XInternAtom(display, name.c_str(), True);
atoms[name] = atom;
return atom;
}
void X11Connection::ProcessEvents()
{
CheckNeedsUpdate();
while (XPending(display) > 0)
{
XEvent event = {};
XNextEvent(display, &event);
DispatchEvent(&event);
}
CheckTimers();
}
void X11Connection::RunLoop()
{
X11Connection* connection = GetX11Connection();
connection->ExitRunLoop = false;
while (!connection->ExitRunLoop && !connection->windows.empty())
{
ProcessEvents();
WaitForEvents(GetTimerTimeout());
}
}
void X11Connection::ExitLoop()
{
X11Connection* connection = GetX11Connection();
connection->ExitRunLoop = true;
}
bool X11Connection::WaitForEvents(int timeout)
{
int fd = XConnectionNumber(display);
struct timeval tv;
if (timeout > 0)
{
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) / 1000;
}
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
int result = select(fd + 1, &rfds, nullptr, nullptr, timeout >= 0 ? &tv : nullptr);
return result > 0 && FD_ISSET(fd, &rfds);
}
void X11Connection::CheckNeedsUpdate()
{
for (auto& it : windows)
{
if (it.second->needsUpdate)
{
it.second->needsUpdate = false;
it.second->windowHost->OnWindowPaint();
}
}
}
void X11Connection::DispatchEvent(XEvent* event)
{
if (XInput2Supported)
{
// XInput sends all raw input to the root window. We want it routed to the focused window
if (event->xcookie.type == GenericEvent &&
event->xcookie.extension == XInputOpcode &&
XGetEventData(display, &event->xcookie))
{
for (auto& it : windows)
{
X11DisplayWindow* window = it.second;
if (window->OnXInputEvent(event))
break;
}
XFreeEventData(display, &event->xcookie);
}
}
auto it = windows.find(event->xany.window);
if (it != windows.end())
{
X11DisplayWindow* window = it->second;
window->OnEvent(event);
}
}
Size X11Connection::GetScreenSize()
{
int screen = XDefaultScreen(display);
int disp_width_px = XDisplayWidth(display, screen);
int disp_height_px = XDisplayHeight(display, screen);
int disp_width_mm = XDisplayWidthMM(display, screen);
double ppi = (disp_width_mm < 24) ? 96.0 : (25.4 * static_cast<double>(disp_width_px) / static_cast<double>(disp_width_mm));
double dpiScale = ppi / 96.0;
return Size(disp_width_px / dpiScale, disp_height_px / dpiScale);
}
static int64_t GetTimePoint()
{
using namespace std::chrono;
return (int64_t)(duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count());
}
void* X11Connection::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)
{
timeoutMilliseconds = std::max(timeoutMilliseconds, 1);
timers.push_back(std::make_shared<X11Timer>(timeoutMilliseconds, onTimer, GetTimePoint() + timeoutMilliseconds));
return timers.back().get();
}
void X11Connection::StopTimer(void* timerID)
{
for (auto it = timers.begin(); it != timers.end(); ++it)
{
if (it->get() == timerID)
{
timers.erase(it);
return;
}
}
}
void X11Connection::CheckTimers()
{
int64_t now = GetTimePoint();
// The callback may stop timers. Iterators might invalidate.
while (true)
{
std::shared_ptr<X11Timer> foundTimer;
for (auto& timer : timers)
{
if (timer->nextTime < now)
{
foundTimer = timer;
break;
}
}
if (!foundTimer)
break;
// Not very precise, but these aren't high precision timers
foundTimer->nextTime = now + foundTimer->timeoutMilliseconds;
foundTimer->onTimer();
}
}
int X11Connection::GetTimerTimeout()
{
if (timers.empty())
return 0;
int64_t nextTime = timers.front()->nextTime;
for (auto& timer : timers)
{
nextTime = std::min(nextTime, timer->nextTime);
}
int64_t now = GetTimePoint();
return (int)std::max(nextTime - now, (int64_t)1);
}

View file

@ -0,0 +1,65 @@
#pragma once
#include "window/window.h"
#include <string>
#include <map>
#include <memory>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/cursorfont.h>
#include <X11/keysymdef.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XInput2.h>
class X11DisplayWindow;
class X11Timer
{
public:
X11Timer(int timeoutMilliseconds, std::function<void()> onTimer, int64_t nextTime) : timeoutMilliseconds(timeoutMilliseconds), onTimer(onTimer), nextTime(nextTime) {}
int timeoutMilliseconds = 0;
std::function<void()> onTimer;
int64_t nextTime = 0;
};
class X11Connection
{
public:
X11Connection();
~X11Connection();
void ProcessEvents();
void RunLoop();
void ExitLoop();
bool WaitForEvents(int timeout);
void* StartTimer(int timeoutMilliseconds, std::function<void()> onTimer);
void StopTimer(void* timerID);
Size GetScreenSize();
Atom GetAtom(const std::string& name);
Display* display = nullptr;
std::map<std::string, Atom> atoms;
std::map<Window, X11DisplayWindow*> windows;
std::map<StandardCursor, Cursor> standardCursors;
bool ExitRunLoop = false;
XIM xim = nullptr;
bool XInput2Supported = false;
int XInputOpcode = 0;
int MasterPointerID = 0;
private:
void DispatchEvent(XEvent* event);
void CheckNeedsUpdate();
void CheckTimers();
int GetTimerTimeout();
std::vector<std::shared_ptr<X11Timer>> timers;
};
X11Connection* GetX11Connection();

View file

@ -1,6 +1,8 @@
#include "x11_display_backend.h"
#include "x11_display_window.h"
#include "x11_connection.h"
#include <stdexcept>
#ifdef USE_DBUS
#include "window/dbus/dbus_open_file_dialog.h"
@ -15,32 +17,32 @@ std::unique_ptr<DisplayWindow> X11DisplayBackend::Create(DisplayWindowHost* wind
void X11DisplayBackend::ProcessEvents()
{
X11DisplayWindow::ProcessEvents();
GetX11Connection()->ProcessEvents();
}
void X11DisplayBackend::RunLoop()
{
X11DisplayWindow::RunLoop();
GetX11Connection()->RunLoop();
}
void X11DisplayBackend::ExitLoop()
{
X11DisplayWindow::ExitLoop();
GetX11Connection()->ExitLoop();
}
Size X11DisplayBackend::GetScreenSize()
{
return X11DisplayWindow::GetScreenSize();
return GetX11Connection()->GetScreenSize();
}
void* X11DisplayBackend::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)
{
return X11DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer));
return GetX11Connection()->StartTimer(timeoutMilliseconds, onTimer);
}
void X11DisplayBackend::StopTimer(void* timerID)
{
X11DisplayWindow::StopTimer(timerID);
GetX11Connection()->StopTimer(timerID);
}
#ifdef USE_DBUS

View file

@ -1,5 +1,7 @@
#include "x11_display_window.h"
#include "x11_connection.h"
#include <zwidget/core/image.h>
#include <stdexcept>
#include <vector>
#include <cmath>
@ -10,83 +12,24 @@
#include <unistd.h>
#include <iostream>
class X11Connection
{
public:
X11Connection()
{
// If we ever want to support windows on multiple threads:
// XInitThreads();
display = XOpenDisplay(nullptr);
if (!display)
throw std::runtime_error("Could not open X11 display");
// Make auto-repeat keys detectable
Bool supports_detectable_autorepeat = {};
XkbSetDetectableAutoRepeat(display, True, &supports_detectable_autorepeat);
// Loads the XMODIFIERS environment variable to see what IME to use
XSetLocaleModifiers("");
xim = XOpenIM(display, 0, 0, 0);
if (!xim)
{
// fallback to internal input method
XSetLocaleModifiers("@im=none");
xim = XOpenIM(display, 0, 0, 0);
}
}
~X11Connection()
{
for (auto& it : standardCursors)
XFreeCursor(display, it.second);
if (xim)
XCloseIM(xim);
XCloseDisplay(display);
}
Display* display = nullptr;
std::map<std::string, Atom> atoms;
std::map<Window, X11DisplayWindow*> windows;
std::map<StandardCursor, Cursor> standardCursors;
bool ExitRunLoop = false;
XIM xim = nullptr;
};
static X11Connection* GetX11Connection()
{
static X11Connection connection;
return &connection;
}
static Atom GetAtom(const std::string& name)
{
auto connection = GetX11Connection();
auto it = connection->atoms.find(name);
if (it != connection->atoms.end())
return it->second;
Atom atom = XInternAtom(connection->display, name.c_str(), True);
connection->atoms[name] = atom;
return atom;
}
X11DisplayWindow::X11DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, X11DisplayWindow* owner, RenderAPI renderAPI) : windowHost(windowHost), owner(owner)
{
display = GetX11Connection()->display;
auto connection = GetX11Connection();
display = connection->display;
screen = XDefaultScreen(display);
depth = XDefaultDepth(display, screen);
visual = XDefaultVisual(display, screen);
colormap = XDefaultColormap(display, screen);
int disp_width_px = XDisplayWidth(display, screen);
int disp_height_px = XDisplayHeight(display, screen);
int disp_width_mm = XDisplayWidthMM(display, screen);
double ppi = (disp_width_mm < 24) ? 96.0 : (25.4 * static_cast<double>(disp_width_px) / static_cast<double>(disp_width_mm));
dpiScale = std::round(ppi / 96.0 * 4.0) / 4.0; // 100%, 125%, 150%, 175%, 200%, etc.
if (char* value = XGetDefault(display, "Xft", "dpi"))
{
int dpi = std::atoi(value);
if (dpi != 0)
{
dpiScale = dpi / 96.0;
}
}
XSetWindowAttributes attributes = {};
attributes.backing_store = Always;
@ -94,14 +37,32 @@ X11DisplayWindow::X11DisplayWindow(DisplayWindowHost* windowHost, bool popupWind
attributes.save_under = popupWindow ? True : False;
attributes.colormap = colormap;
attributes.event_mask =
KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
EnterWindowMask | LeaveWindowMask | PointerMotionMask | KeymapStateMask |
KeyPressMask | KeyReleaseMask |
EnterWindowMask | LeaveWindowMask | KeymapStateMask |
ExposureMask | StructureNotifyMask | FocusChangeMask | PropertyChangeMask;
if (!connection->XInput2Supported)
{
attributes.event_mask |= ButtonPressMask | ButtonReleaseMask | PointerMotionMask;
}
unsigned long mask = CWBackingStore | CWSaveUnder | CWEventMask | CWOverrideRedirect;
window = XCreateWindow(display, XRootWindow(display, screen), 0, 0, 100, 100, 0, depth, InputOutput, visual, mask, &attributes);
GetX11Connection()->windows[window] = this;
connection->windows[window] = this;
if (connection->XInput2Supported)
{
unsigned char mask[1] = { 0 };
XISetMask(mask, XI_ButtonPress);
XISetMask(mask, XI_ButtonRelease);
XISetMask(mask, XI_Motion);
XIEventMask eventmask;
eventmask.deviceid = connection->MasterPointerID;
eventmask.mask_len = sizeof(mask);
eventmask.mask = mask;
XISelectEvents(display, window, &eventmask, 1);
}
if (owner)
{
@ -109,59 +70,59 @@ X11DisplayWindow::X11DisplayWindow(DisplayWindowHost* windowHost, bool popupWind
}
// Tell window manager which process this window came from
if (GetAtom("_NET_WM_PID") != None)
if (connection->GetAtom("_NET_WM_PID") != None)
{
int32_t pid = getpid();
if (pid != 0)
{
XChangeProperty(display, window, GetAtom("_NET_WM_PID"), XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);
XChangeProperty(display, window, connection->GetAtom("_NET_WM_PID"), XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1);
}
}
// Tell window manager which machine this window came from
if (GetAtom("WM_CLIENT_MACHINE") != None)
if (connection->GetAtom("WM_CLIENT_MACHINE") != None)
{
std::vector<char> hostname(256);
if (gethostname(hostname.data(), hostname.size()) >= 0)
{
hostname.push_back(0);
XChangeProperty(display, window, GetAtom("WM_CLIENT_MACHINE"), XA_STRING, 8, PropModeReplace, (unsigned char *)hostname.data(), strlen(hostname.data()));
XChangeProperty(display, window, connection->GetAtom("WM_CLIENT_MACHINE"), XA_STRING, 8, PropModeReplace, (unsigned char *)hostname.data(), strlen(hostname.data()));
}
}
// Tell window manager we want to listen to close events
if (GetAtom("WM_DELETE_WINDOW") != None)
if (connection->GetAtom("WM_DELETE_WINDOW") != None)
{
Atom protocol = GetAtom("WM_DELETE_WINDOW");
Atom protocol = connection->GetAtom("WM_DELETE_WINDOW");
XSetWMProtocols(display, window, &protocol, 1);
}
// Tell window manager what type of window we are
if (GetAtom("_NET_WM_WINDOW_TYPE") != None)
if (connection->GetAtom("_NET_WM_WINDOW_TYPE") != None)
{
Atom type = None;
if (popupWindow)
{
type = GetAtom("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU");
type = connection->GetAtom("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU");
if (type == None)
type = GetAtom("_NET_WM_WINDOW_TYPE_POPUP_MENU");
type = connection->GetAtom("_NET_WM_WINDOW_TYPE_POPUP_MENU");
if (type == None)
type = GetAtom("_NET_WM_WINDOW_TYPE_COMBO");
type = connection->GetAtom("_NET_WM_WINDOW_TYPE_COMBO");
}
if (type == None)
type = GetAtom("_NET_WM_WINDOW_TYPE_NORMAL");
type = connection->GetAtom("_NET_WM_WINDOW_TYPE_NORMAL");
if (type != None)
{
XChangeProperty(display, window, GetAtom("_NET_WM_WINDOW_TYPE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&type, 1);
XChangeProperty(display, window, connection->GetAtom("_NET_WM_WINDOW_TYPE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&type, 1);
}
}
// Create input context
if (GetX11Connection()->xim)
if (connection->xim)
{
xic = XCreateIC(
GetX11Connection()->xim,
connection->xim,
XNInputStyle,
XIMPreeditNothing | XIMStatusNothing,
XNClientWindow, window,
@ -188,6 +149,87 @@ void X11DisplayWindow::SetWindowTitle(const std::string& text)
XSetStandardProperties(display, window, text.c_str(), text.c_str(), None, nullptr, 0, nullptr);
}
void X11DisplayWindow::SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images)
{
if (images.empty())
return;
double dpiscale = GetDpiScale();
int desiredSize = (int)std::round(32 * dpiscale);
Image* image = images.front().get();
for (const auto& i : images)
{
int curdist = std::abs(image->GetWidth() - desiredSize);
int dist = std::abs(i->GetWidth() - desiredSize);
if (dist < curdist)
image = i.get();
}
int width = image->GetWidth();
int height = image->GetHeight();
const uint32_t* s = (const uint32_t*)image->GetData();
unsigned int size = (width * height) + 2; // header is 2 ints
unsigned long* data = (unsigned long*)malloc(size * sizeof(unsigned long));
// set header
data[0] = width;
data[1] = height;
// on 64bit systems, the destination buffer is 64 bit per pixel
// thus, we have to copy each pixel individually (no memcpy)
// icon data is expected as ARGB
if (image->GetFormat() == ImageFormat::R8G8B8A8)
{
for (int y = 0; y < height; ++y)
{
const uint32_t* src = s + y * width;
unsigned long* dst = &data[2 + (y * width)];
for (int x = 0; x < width; ++x)
{
uint32_t r = src[x] & 0xff;
uint32_t g = (src[x] >> 8) & 0xff;
uint32_t b = (src[x] >> 16) & 0xff;
uint32_t a = (src[x] >> 24) & 0xff;
dst[x] = (a << 24) | (r << 16) | (g << 8) | b;
}
}
}
else if (image->GetFormat() == ImageFormat::B8G8R8A8)
{
for (int y = 0; y < height; ++y)
{
const uint32_t* src = s + y * width;
unsigned long* dst = &data[2 + (y * width)];
for (int x = 0; x < width; ++x)
{
dst[x] = src[x];
}
}
}
else
{
free(data);
return;
}
// set icon geometry
unsigned long* geom = (unsigned long*)malloc(4 * sizeof(unsigned long));
geom[0] = geom[1] = 0; // x, y
geom[2] = width;
geom[3] = height;
Atom propertyGeom = XInternAtom(display, "_NET_WM_ICON_GEOMETRY", 0);
XChangeProperty(display, window, propertyGeom, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)geom, 4);
// set icon data
Atom property = XInternAtom(display, "_NET_WM_ICON", 0);
XChangeProperty(display, window, property, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)data, size);
}
void X11DisplayWindow::SetWindowFrame(const Rect& box)
{
// To do: this requires cooperation with the window manager
@ -226,10 +268,11 @@ void X11DisplayWindow::ShowFullscreen()
{
Show();
if (GetAtom("_NET_WM_STATE") != None && GetAtom("_NET_WM_STATE_FULLSCREEN") != None)
auto connection = GetX11Connection();
if (connection->GetAtom("_NET_WM_STATE") != None && connection->GetAtom("_NET_WM_STATE_FULLSCREEN") != None)
{
Atom state = GetAtom("_NET_WM_STATE_FULLSCREEN");
XChangeProperty(display, window, GetAtom("_NET_WM_STATE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&state, 1);
Atom state = connection->GetAtom("_NET_WM_STATE_FULLSCREEN");
XChangeProperty(display, window, connection->GetAtom("_NET_WM_STATE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&state, 1);
isFullscreen = true;
}
}
@ -378,7 +421,7 @@ Rect X11DisplayWindow::GetWindowFrame() const
unsigned int height = 0;
unsigned int borderwidth = 0;
unsigned int depth = 0;
Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth);
/*Status status =*/ XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth);
return Rect::xywh(x / dpiscale, y / dpiscale, width / dpiscale, height / dpiscale);
}
@ -394,7 +437,7 @@ Size X11DisplayWindow::GetClientSize() const
unsigned int height = 0;
unsigned int borderwidth = 0;
unsigned int depth = 0;
Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth);
/*Status status =*/ XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth);
return Size(width / dpiscale, height / dpiscale);
}
@ -408,7 +451,7 @@ int X11DisplayWindow::GetPixelWidth() const
unsigned int height = 0;
unsigned int borderwidth = 0;
unsigned int depth = 0;
Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth);
/*Status status =*/ XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth);
return width;
}
@ -421,7 +464,7 @@ int X11DisplayWindow::GetPixelHeight() const
unsigned int height = 0;
unsigned int borderwidth = 0;
unsigned int depth = 0;
Status status = XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth);
/*Status status =*/ XGetGeometry(display, window, &root, &x, &y, &width, &height, &borderwidth, &depth);
return height;
}
@ -486,8 +529,8 @@ void X11DisplayWindow::SetCaptionTextColor(uint32_t bgra8)
std::vector<uint8_t> X11DisplayWindow::GetWindowProperty(Atom property, Atom &actual_type, int &actual_format, unsigned long &item_count)
{
long read_bytes = 0;
Atom _actual_type = actual_type;
int _actual_format = actual_format;
//Atom _actual_type = actual_type;
//int _actual_format = actual_format;
unsigned long _item_count = item_count;
unsigned long bytes_remaining = 0;
unsigned char *read_data = nullptr;
@ -516,7 +559,8 @@ std::vector<uint8_t> X11DisplayWindow::GetWindowProperty(Atom property, Atom &ac
std::string X11DisplayWindow::GetClipboardText()
{
Atom clipboard = GetAtom("CLIPBOARD");
auto connection = GetX11Connection();
Atom clipboard = connection->GetAtom("CLIPBOARD");
if (clipboard == None)
return {};
@ -529,7 +573,7 @@ std::string X11DisplayWindow::GetClipboardText()
{
if (XCheckTypedWindowEvent(display, window, SelectionNotify, &event))
break;
if (!WaitForEvents(500))
if (!connection->WaitForEvents(500))
return {};
}
@ -548,7 +592,8 @@ void X11DisplayWindow::SetClipboardText(const std::string& text)
{
clipboardText = text;
Atom clipboard = GetAtom("CLIPBOARD");
auto connection = GetX11Connection();
Atom clipboard = connection->GetAtom("CLIPBOARD");
if (clipboard == None)
return;
@ -565,7 +610,7 @@ Point X11DisplayWindow::MapFromGlobal(const Point& pos) const
int srcy = (int)std::round(pos.y * dpiscale);
int destx = 0;
int desty = 0;
Bool result = XTranslateCoordinates(display, root, window, srcx, srcy, &destx, &desty, &child);
/*Bool result =*/ XTranslateCoordinates(display, root, window, srcx, srcy, &destx, &desty, &child);
return Point(destx / dpiscale, desty / dpiscale);
}
@ -578,7 +623,7 @@ Point X11DisplayWindow::MapToGlobal(const Point& pos) const
int srcy = (int)std::round(pos.y * dpiscale);
int destx = 0;
int desty = 0;
Bool result = XTranslateCoordinates(display, window, root, srcx, srcy, &destx, &desty, &child);
/*Bool result =*/ XTranslateCoordinates(display, window, root, srcx, srcy, &destx, &desty, &child);
return Point(destx / dpiscale, desty / dpiscale);
}
@ -587,79 +632,6 @@ void* X11DisplayWindow::GetNativeHandle()
return reinterpret_cast<void*>(window);
}
bool X11DisplayWindow::WaitForEvents(int timeout)
{
Display* display = GetX11Connection()->display;
int fd = XConnectionNumber(display);
struct timeval tv;
if (timeout > 0)
{
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) / 1000;
}
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
int result = select(fd + 1, &rfds, nullptr, nullptr, timeout >= 0 ? &tv : nullptr);
return result > 0 && FD_ISSET(fd, &rfds);
}
void X11DisplayWindow::CheckNeedsUpdate()
{
for (auto& it : GetX11Connection()->windows)
{
if (it.second->needsUpdate)
{
it.second->needsUpdate = false;
it.second->windowHost->OnWindowPaint();
}
}
}
void X11DisplayWindow::ProcessEvents()
{
CheckNeedsUpdate();
Display* display = GetX11Connection()->display;
while (XPending(display) > 0)
{
XEvent event = {};
XNextEvent(display, &event);
DispatchEvent(&event);
}
}
void X11DisplayWindow::RunLoop()
{
X11Connection* connection = GetX11Connection();
connection->ExitRunLoop = false;
while (!connection->ExitRunLoop && !connection->windows.empty())
{
CheckNeedsUpdate();
XEvent event = {};
XNextEvent(connection->display, &event);
DispatchEvent(&event);
}
}
void X11DisplayWindow::ExitLoop()
{
X11Connection* connection = GetX11Connection();
connection->ExitRunLoop = true;
}
void X11DisplayWindow::DispatchEvent(XEvent* event)
{
X11Connection* connection = GetX11Connection();
auto it = connection->windows.find(event->xany.window);
if (it != connection->windows.end())
{
X11DisplayWindow* window = it->second;
window->OnEvent(event);
}
}
void X11DisplayWindow::OnEvent(XEvent* event)
{
if (event->type == ConfigureNotify)
@ -703,11 +675,12 @@ void X11DisplayWindow::OnConfigureNotify(XEvent* event)
void X11DisplayWindow::OnClientMessage(XEvent* event)
{
Atom protocolsAtom = GetAtom("WM_PROTOCOLS");
auto connection = GetX11Connection();
Atom protocolsAtom = connection->GetAtom("WM_PROTOCOLS");
if (protocolsAtom != None && event->xclient.message_type == protocolsAtom)
{
Atom deleteAtom = GetAtom("WM_DELETE_WINDOW");
Atom pingAtom = GetAtom("_NET_WM_PING");
Atom deleteAtom = connection->GetAtom("WM_DELETE_WINDOW");
Atom pingAtom = connection->GetAtom("_NET_WM_PING");
Atom protocol = event->xclient.data.l[0];
if (deleteAtom != None && protocol == deleteAtom)
@ -731,11 +704,13 @@ void X11DisplayWindow::OnFocusIn(XEvent* event)
if (xic)
XSetICFocus(xic);
RawInput.Focused = true;
windowHost->OnWindowActivated();
}
void X11DisplayWindow::OnFocusOut(XEvent* event)
{
RawInput.Focused = false;
windowHost->OnWindowDeactivated();
}
@ -949,6 +924,8 @@ void X11DisplayWindow::OnKeyRelease(XEvent* event)
void X11DisplayWindow::OnButtonPress(XEvent* event)
{
// Note: this only gets called if XInput is not available
InputKey key = GetInputKey(event);
keyState[key] = true;
windowHost->OnWindowMouseDown(GetMousePos(event), key);
@ -958,6 +935,8 @@ void X11DisplayWindow::OnButtonPress(XEvent* event)
void X11DisplayWindow::OnButtonRelease(XEvent* event)
{
// Note: this only gets called if XInput is not available
InputKey key = GetInputKey(event);
keyState[key] = false;
windowHost->OnWindowMouseUp(GetMousePos(event), key);
@ -965,6 +944,8 @@ void X11DisplayWindow::OnButtonRelease(XEvent* event)
void X11DisplayWindow::OnMotionNotify(XEvent* event)
{
// Note: this only gets called if XInput is not available
double dpiScale = GetDpiScale();
int x = event->xmotion.x;
int y = event->xmotion.y;
@ -974,18 +955,134 @@ void X11DisplayWindow::OnMotionNotify(XEvent* event)
}
else
{
MouseX = ClientSizeX / 2;
MouseY = ClientSizeY / 2;
if (MouseX != -1 && MouseY != -1)
int dx = x - RawInput.LastX;
int dy = y - RawInput.LastY;
RawInput.LastX = x;
RawInput.LastY = y;
int centerX = ClientSizeX / 2;
int centerY = ClientSizeY / 2;
if (x != centerX || y != centerY)
{
windowHost->OnWindowRawMouseMove(x - MouseX, y - MouseY);
// We still have to contain the mouse even if we are using XInput.
// Maybe we can do that in a simpler way if XInput is available?
XWarpPointer(display, window, window, 0, 0, ClientSizeX, ClientSizeY, centerX, centerY);
// Use the accelerated mouse cursor if there's no XInput 2.0 support
if (!GetX11Connection()->XInput2Supported)
{
if (dx != 0 || dy != 0)
windowHost->OnWindowRawMouseMove(dx, dy);
}
}
}
}
bool X11DisplayWindow::OnXInputEvent(XEvent* event)
{
// This API is so horrible it makes Win32 look attractive!
if (event->xcookie.evtype == XI_ButtonPress || event->xcookie.evtype == XI_ButtonRelease)
{
auto deviceEvent = (XIDeviceEvent*)event->xcookie.data;
if (deviceEvent->event != window)
return false;
InputKey key = {};
switch (deviceEvent->detail)
{
case 1: key = InputKey::LeftMouse; break;
case 2: key = InputKey::MiddleMouse; break;
case 3: key = InputKey::RightMouse; break;
case 4: key = InputKey::MouseWheelUp; break;
case 5: key = InputKey::MouseWheelDown; break;
// case 6: key = InputKey::XButton1; break;
// case 7: key = InputKey::XButton2; break;
default: return false;
}
// Warp pointer to the center of the window
XWarpPointer(display, window, window, 0, 0, ClientSizeX, ClientSizeY, ClientSizeX / 2, ClientSizeY / 2);
}
double dpiScale = GetDpiScale();
int x = (int)std::round(deviceEvent->event_x);
int y = (int)std::round(deviceEvent->event_y);
Point mousePos(x / dpiScale, y / dpiScale);
if (!isCursorEnabled)
{
// Raw input gets blocked until we ungrab the pointer. Worst design EVER.
XUngrabPointer(display, deviceEvent->time);
}
if (event->xcookie.evtype == XI_ButtonPress)
{
keyState[key] = true;
windowHost->OnWindowMouseDown(mousePos, key);
// if (lastClickWithin400ms)
// windowHost->OnWindowMouseDoubleclick(GetMousePos(event), InputKey::LeftMouse);
}
else
{
keyState[key] = false;
windowHost->OnWindowMouseUp(mousePos, key);
}
return true;
}
else if (event->xcookie.evtype == XI_Motion)
{
auto deviceEvent = (XIDeviceEvent*)event->xcookie.data;
if (deviceEvent->event != window)
return false;
if (isCursorEnabled)
{
double dpiScale = GetDpiScale();
int x = (int)std::round(deviceEvent->event_x);
int y = (int)std::round(deviceEvent->event_y);
Point mousePos(x / dpiScale, y / dpiScale);
windowHost->OnWindowMouseMove(Point(x / dpiScale, y / dpiScale));
}
else if (RawInput.Focused)
{
int x = (int)std::round(deviceEvent->event_x);
int y = (int)std::round(deviceEvent->event_y);
int centerX = ClientSizeX / 2;
int centerY = ClientSizeY / 2;
if (x != centerX || y != centerY)
{
// We still have to contain the mouse even if we are using XInput.
// Maybe we can do that in a simpler way?
XWarpPointer(display, window, window, 0, 0, ClientSizeX, ClientSizeY, centerX, centerY);
}
}
return true;
}
else if (!isCursorEnabled && RawInput.Focused && event->xcookie.evtype == XI_RawMotion)
{
std::vector<double> values;
values.reserve(2);
auto rawEvent = (XIRawEvent*)event->xcookie.data;
double *rawValuator = rawEvent->raw_values;
for (int i = 0; i < rawEvent->valuators.mask_len * 8; i++)
{
if (XIMaskIsSet(rawEvent->valuators.mask, i))
{
values.push_back(*rawValuator);
rawValuator++;
}
}
if (values.size() >= 2)
{
// Values seems to be integers for my mouse. Is that always the case?
int dx = (int)std::round(values[0]);
int dy = (int)std::round(values[1]);
if (dx != 0 || dy != 0)
windowHost->OnWindowRawMouseMove(dx, dy);
}
return true;
}
return false;
}
void X11DisplayWindow::OnLeaveNotify(XEvent* event)
@ -1009,8 +1106,9 @@ void X11DisplayWindow::OnSelectionRequest(XEvent* event)
if (requestor == window)
return;
Atom targetsAtom = GetAtom("TARGETS");
Atom multipleAtom = GetAtom("MULTIPLE");
X11Connection* connection = GetX11Connection();
Atom targetsAtom = connection->GetAtom("TARGETS");
Atom multipleAtom = connection->GetAtom("MULTIPLE");
struct Request { Window target; Atom property; };
std::vector<Request> requests;
@ -1067,31 +1165,6 @@ void X11DisplayWindow::OnSelectionRequest(XEvent* event)
}
}
Size X11DisplayWindow::GetScreenSize()
{
X11Connection* connection = GetX11Connection();
Display* display = connection->display;
int screen = XDefaultScreen(display);
int disp_width_px = XDisplayWidth(display, screen);
int disp_height_px = XDisplayHeight(display, screen);
int disp_width_mm = XDisplayWidthMM(display, screen);
double ppi = (disp_width_mm < 24) ? 96.0 : (25.4 * static_cast<double>(disp_width_px) / static_cast<double>(disp_width_mm));
double dpiScale = ppi / 96.0;
return Size(disp_width_px / dpiScale, disp_height_px / dpiScale);
}
void* X11DisplayWindow::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)
{
throw std::logic_error("unimplemented: X11DisplayWindow::StartTimer");
}
void X11DisplayWindow::StopTimer(void* timerID)
{
throw std::logic_error("unimplemented: X11DisplayWindow::StopTimer");
}
// This is to avoid needing all the Vulkan headers and the volk binding library just for this:
#ifndef VK_VERSION_1_0

View file

@ -7,6 +7,7 @@
#include <X11/cursorfont.h>
#include <X11/keysymdef.h>
#include <X11/XKBlib.h>
#include <X11/extensions/XInput2.h>
#include <map>
class X11DisplayWindow : public DisplayWindow
@ -16,6 +17,7 @@ public:
~X11DisplayWindow();
void SetWindowTitle(const std::string& text) override;
void SetWindowIcon(const std::vector<std::shared_ptr<Image>>& images) override;
void SetWindowFrame(const Rect& box) override;
void SetClientFrame(const Rect& box) override;
void Show() override;
@ -59,13 +61,6 @@ public:
std::vector<std::string> GetVulkanInstanceExtensions() override;
VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override;
static void ProcessEvents();
static void RunLoop();
static void ExitLoop();
static Size GetScreenSize();
static void* StartTimer(int timeoutMilliseconds, std::function<void()> onTimer);
static void StopTimer(void* timerID);
private:
void UpdateCursor();
@ -85,6 +80,7 @@ private:
void OnSelectionClear(XEvent* event);
void OnSelectionNotify(XEvent* event);
void OnSelectionRequest(XEvent* event);
bool OnXInputEvent(XEvent* event);
void CreateBackbuffer(int width, int height);
void DestroyBackbuffer();
@ -92,11 +88,6 @@ private:
InputKey GetInputKey(XEvent* event);
Point GetMousePos(XEvent* event);
static bool WaitForEvents(int timeout);
static void DispatchEvent(XEvent* event);
static void CheckNeedsUpdate();
std::vector<uint8_t> GetWindowProperty(Atom property, Atom &actual_type, int &actual_format, unsigned long &item_count);
DisplayWindowHost* windowHost = nullptr;
@ -117,8 +108,13 @@ private:
int ClientSizeX = 0;
int ClientSizeY = 0;
int MouseX = -1;
int MouseY = -1;
struct
{
int LastX = -1;
int LastY = -1;
bool Focused = false;
} RawInput;
Pixmap cursor_bitmap = None;
Cursor hidden_cursor = None;
@ -138,5 +134,6 @@ private:
bool needsUpdate = false;
friend class X11Connection;
friend class X11DisplayBackend;
};

View file

@ -37,15 +37,17 @@ public:
// [Construction / Destruction]
// --------------------------------------------------------------------------
ASMJIT_INLINE CpuFeatures() noexcept { reset(); }
ASMJIT_INLINE CpuFeatures(const CpuFeatures& other) noexcept { init(other); }
ASMJIT_INLINE CpuFeatures() noexcept : _bits{} {}
ASMJIT_INLINE CpuFeatures(const CpuFeatures& other) noexcept = default;
ASMJIT_INLINE CpuFeatures& operator=(const CpuFeatures& other) noexcept = default;
// --------------------------------------------------------------------------
// [Init / Reset]
// --------------------------------------------------------------------------
ASMJIT_INLINE void init(const CpuFeatures& other) noexcept { ::memcpy(this, &other, sizeof(*this)); }
ASMJIT_INLINE void reset() noexcept { ::memset(this, 0, sizeof(*this)); }
ASMJIT_INLINE void init(const CpuFeatures& other) noexcept { *this = other; }
ASMJIT_INLINE void reset() noexcept { *this = CpuFeatures{}; }
// --------------------------------------------------------------------------
// [Ops]
@ -248,8 +250,21 @@ public:
// [Construction / Destruction]
// --------------------------------------------------------------------------
ASMJIT_INLINE CpuInfo() noexcept { reset(); }
ASMJIT_INLINE CpuInfo(const CpuInfo& other) noexcept { init(other); }
ASMJIT_INLINE CpuInfo() noexcept:
_archInfo{},
_vendorId{},
_family{},
_model{},
_stepping{},
_hwThreadsCount{},
_features{},
_vendorString{},
_brandString{},
_x86Data{}
{}
ASMJIT_INLINE CpuInfo(const CpuInfo& other) noexcept = default;
ASMJIT_INLINE CpuInfo& operator=(const CpuInfo& other) noexcept = default;
// --------------------------------------------------------------------------
// [Init / Reset]
@ -260,8 +275,8 @@ public:
_archInfo.init(archType, archMode);
}
ASMJIT_INLINE void init(const CpuInfo& other) noexcept { ::memcpy(this, &other, sizeof(*this)); }
ASMJIT_INLINE void reset() noexcept { ::memset(this, 0, sizeof(*this)); }
ASMJIT_INLINE void init(const CpuInfo& other) noexcept { *this = other; }
ASMJIT_INLINE void reset() noexcept { *this = CpuInfo{}; }
// --------------------------------------------------------------------------
// [Detect]

View file

@ -734,10 +734,18 @@ public:
// [Construction / Destruction]
// --------------------------------------------------------------------------
ASMJIT_INLINE FuncDetail() noexcept { reset(); }
ASMJIT_INLINE FuncDetail(const FuncDetail& other) noexcept {
::memcpy(this, &other, sizeof(*this));
}
ASMJIT_INLINE FuncDetail() noexcept:
_callConv{},
_argCount{},
_retCount{},
_usedRegs{},
_argStackSize{},
_rets{},
_args{}
{}
ASMJIT_INLINE FuncDetail(const FuncDetail& other) noexcept = default;
ASMJIT_INLINE FuncDetail& operator=(const FuncDetail& other) noexcept = default;
// --------------------------------------------------------------------------
// [Init / Reset]
@ -745,7 +753,7 @@ public:
//! Initialize this `FuncDetail` to the given signature.
ASMJIT_API Error init(const FuncSignature& sign);
ASMJIT_INLINE void reset() noexcept { ::memset(this, 0, sizeof(*this)); }
ASMJIT_INLINE void reset() noexcept { *this = FuncDetail{}; }
// --------------------------------------------------------------------------
// [Accessors - Calling Convention]
@ -878,20 +886,23 @@ struct FuncFrameInfo {
// [Construction / Destruction]
// --------------------------------------------------------------------------
ASMJIT_INLINE FuncFrameInfo() noexcept { reset(); }
ASMJIT_INLINE FuncFrameInfo() noexcept:
_attributes{},
_dirtyRegs{},
_stackFrameAlignment{},
_callFrameAlignment{},
_stackArgsRegId{Globals::kInvalidRegId},
_stackFrameSize{},
_callFrameSize{}
{}
ASMJIT_INLINE FuncFrameInfo(const FuncFrameInfo& other) noexcept {
::memcpy(this, &other, sizeof(*this));
}
ASMJIT_INLINE FuncFrameInfo(const FuncFrameInfo& other) noexcept = default;
// --------------------------------------------------------------------------
// [Init / Reset]
// --------------------------------------------------------------------------
ASMJIT_INLINE void reset() noexcept {
::memset(this, 0, sizeof(*this));
_stackArgsRegId = Globals::kInvalidRegId;
}
ASMJIT_INLINE void reset() noexcept { *this = FuncFrameInfo{}; }
// --------------------------------------------------------------------------
// [Accessors]
@ -1180,19 +1191,18 @@ public:
// [Construction / Destruction]
// --------------------------------------------------------------------------
explicit ASMJIT_INLINE FuncArgsMapper(const FuncDetail* fd) noexcept { reset(fd); }
ASMJIT_INLINE FuncArgsMapper(const FuncArgsMapper& other) noexcept {
::memcpy(this, &other, sizeof(*this));
}
explicit ASMJIT_INLINE FuncArgsMapper(const FuncDetail* fd = nullptr) noexcept:
_funcDetail(fd),
_args{}
{}
ASMJIT_INLINE FuncArgsMapper(const FuncArgsMapper& other) noexcept = default;
// --------------------------------------------------------------------------
// [Reset]
// --------------------------------------------------------------------------
ASMJIT_INLINE void reset(const FuncDetail* fd = nullptr) noexcept {
_funcDetail = fd;
::memset(_args, 0, sizeof(_args));
}
ASMJIT_INLINE void reset(const FuncDetail* fd = nullptr) noexcept { *this = FuncArgsMapper(fd); }
// --------------------------------------------------------------------------
// [Accessors]

View file

@ -101,7 +101,7 @@ struct TiedReg {
// --------------------------------------------------------------------------
ASMJIT_INLINE TiedReg& operator=(const TiedReg& other) {
::memcpy(this, &other, sizeof(TiedReg));
::memcpy((void*)this, &other, sizeof(TiedReg));
return *this;
}

View file

@ -221,7 +221,8 @@ void ZoneHeap::reset(Zone* zone) noexcept {
}
// Zero the entire class and initialize to the given `zone`.
::memset(this, 0, sizeof(*this));
_dynamicBlocks = nullptr;
::memset(_slots, 0, sizeof(_slots));
_zone = zone;
}

View file

@ -297,14 +297,19 @@ class ZoneHeap {
//! Create a new `ZoneHeap`.
//!
//! NOTE: To use it, you must first `init()` it.
ASMJIT_INLINE ZoneHeap() noexcept {
::memset(this, 0, sizeof(*this));
}
ASMJIT_INLINE ZoneHeap() noexcept:
_zone{},
_slots{},
_dynamicBlocks{}
{}
//! Create a new `ZoneHeap` initialized to use `zone`.
explicit ASMJIT_INLINE ZoneHeap(Zone* zone) noexcept {
::memset(this, 0, sizeof(*this));
_zone = zone;
}
explicit ASMJIT_INLINE ZoneHeap(Zone* zone) noexcept:
_zone(zone),
_slots{},
_dynamicBlocks{}
{}
//! Destroy the `ZoneHeap`.
ASMJIT_INLINE ~ZoneHeap() noexcept { reset(); }

View file

@ -60,6 +60,7 @@ if( WIN32 )
wsock32
winmm
dinput8
Xinput
ole32
user32
gdi32
@ -133,6 +134,7 @@ else()
if( NOT APPLE OR NOT OSX_COCOA_BACKEND )
find_package( SDL2 REQUIRED )
include_directories( SYSTEM "${SDL2_INCLUDE_DIR}" )
link_directories(${SDL2_LINK_DIR})
set( PROJECT_LIBRARIES ${PROJECT_LIBRARIES} "${SDL2_LIBRARY}" )
endif()
@ -1079,6 +1081,7 @@ set (PCH_SOURCES
common/engine/renderstyle.cpp
common/engine/v_colortables.cpp
common/engine/serializer.cpp
common/engine/m_haptics.cpp
common/engine/m_joy.cpp
common/engine/m_random.cpp
common/objects/autosegs.cpp
@ -1445,11 +1448,18 @@ add_custom_command(TARGET zdoom POST_BUILD
${CMAKE_SOURCE_DIR}/fm_banks/gs-by-papiezak-and-sneakernets.wopn $<TARGET_FILE_DIR:zdoom>/fm_banks/gs-by-papiezak-and-sneakernets.wopn
)
if ( NOT APPLE AND NOT ZMUSIC_SYSTEM_INSTALL)
if ( NOT ZMUSIC_SYSTEM_INSTALL AND (NOT DEFINED BUILD_SHARED_LIBS OR BUILD_SHARED_LIBS))
# bring zmusic in
add_custom_command(TARGET zdoom POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:zmusic> $<TARGET_FILE_DIR:zdoom>
DEPENDS zmusic )
if ( NOT WIN32 )
add_custom_command(TARGET zdoom POST_BUILD
COMMAND ${CMAKE_COMMAND} -E create_symlink $<TARGET_FILE_NAME:zmusic> $<TARGET_SONAME_FILE_NAME:zmusic>
COMMAND ${CMAKE_COMMAND} -E create_symlink $<TARGET_SONAME_FILE_NAME:zmusic> $<TARGET_LINKER_FILE_NAME:zmusic>
WORKING_DIRECTORY $<TARGET_FILE_DIR:zdoom>
DEPENDS zmusic )
endif()
endif()
if( WIN32 )
@ -1516,6 +1526,17 @@ elseif(UNIX)
set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -rdynamic" )
endif()
if ( NOT ZMUSIC_SYSTEM_INSTALL )
if ( WIN32 OR APPLE )
# nothing to do
else()
install(TARGETS zmusic
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT "Game executable"
)
endif()
endif()
if( WIN32 )
set( INSTALL_PATH . CACHE STRING "Directory where the executable will be placed during install." )
else()

View file

@ -1,11 +1,11 @@
#ifndef __SNDINT_H
#define __SNDINT_H
#include <stdio.h>
#include <stdint.h>
#include <stdio.h>
#include "vectors.h"
#include "tflags.h"
#include "vectors.h"
enum EChanFlag
{
@ -32,6 +32,8 @@ enum EChanFlag
CHANF_TRANSIENT = 32768, // Do not record in savegames - used for sounds that get restarted outside the sound system (e.g. ambients in SW and Blood)
CHANF_FORCE = 65536, // Start, even if sound is paused.
CHANF_SINGULAR = 0x20000, // Only start if no sound of this name is already playing.
CHANF_RUMBLE = 0x40000, // Hint to rumble trigger rumble from sound
CHANF_NORUMBLE = 0x80000, // Disable rumble even if it would normally happen
};
typedef TFlags<EChanFlag> EChanFlags;
@ -146,5 +148,4 @@ struct FISoundChannel
void S_SetSoundPaused(int state);
#endif

View file

@ -5,6 +5,7 @@
**---------------------------------------------------------------------------
** Copyright 1998-2016 Randy Heit
** Copyright 2002-2019 Christoph Oelckers
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -36,15 +37,15 @@
#include <stdio.h>
#include <stdlib.h>
#include "s_soundinternal.h"
#include "m_swap.h"
#include "superfasthash.h"
#include "s_music.h"
#include "m_random.h"
#include "printf.h"
#include "c_cvars.h"
#include "gamestate.h"
#include "i_soundinternal.h"
#include "m_haptics.h"
#include "m_random.h"
#include "m_swap.h"
#include "printf.h"
#include "s_music.h"
#include "s_soundinternal.h"
CVARD(Bool, snd_enabled, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "enables/disables sound effects")
CVAR(Bool, i_soundinbackground, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
@ -475,6 +476,11 @@ FSoundChan *SoundEngine::StartSound(int type, const void *source,
// Attenuate the attenuation based on the sound.
attenuation *= sfx->Attenuation;
#if 0
// sound debug printout
Printf("sound: '%s' -> '%s' %g\n", soundEngine->GetSoundName(org_id), soundEngine->GetSoundName(sound_id), attenuation);
#endif
// The passed rolloff overrides any sound-specific rolloff.
if (forcedrolloff != NULL && forcedrolloff->MinDistance != 0)
{
@ -608,6 +614,14 @@ FSoundChan *SoundEngine::StartSound(int type, const void *source,
{
chanflags |= CHANF_LISTENERZ | CHANF_JUSTSTARTED;
}
if (chanflags & CHANF_RUMBLE && !(chanflags & CHANF_NORUMBLE))
{
// I would love to use attenuation here, but it seems weird.
// Pistol's attenuation is always 1, but it's not silent.
// I'm not sure why that is.
// Joy_Rumble(soundEngine->GetSoundName(org_id), (chanflags&CHANF_IS3D)? attenuation: 0);
Joy_Rumble(soundEngine->GetSoundName(org_id));
}
if (chan != NULL)
{
chan->SoundID = sound_id;
@ -1009,7 +1023,6 @@ void SoundEngine::RelinkSound (int sourcetype, const void *from, const void *to,
}
}
//==========================================================================
//
// S_ChangeSoundVolume
@ -1450,7 +1463,6 @@ void SoundEngine::Reset()
RestoreEvictedChannels();
}
//==========================================================================
//
// S_FindSound
@ -1560,7 +1572,6 @@ FSoundID SoundEngine::AddSoundLump(const char* logicalname, int lump, int Curren
return id;
}
//==========================================================================
//
// S_FindSoundTentative
@ -1581,7 +1592,6 @@ FSoundID SoundEngine::FindSoundTentative(const char* name, int nearlimit)
return id;
}
//==========================================================================
//
// S_CacheRandomSound
@ -1694,7 +1704,6 @@ void SoundEngine::HashSounds()
}
S_rnd.ShrinkToFit();
}
void SoundEngine::AddRandomSound(FSoundID Owner, TArray<FSoundID> list)
@ -1728,7 +1737,6 @@ void S_SoundReset()
#include "i_net.h"
#include "i_interface.h"
CCMD(cachesound)
{
if (argv.argc() < 2)
@ -1746,7 +1754,6 @@ CCMD(cachesound)
}
}
CCMD(listsoundchannels)
{
Printf("%s", soundEngine->ListSoundChannels().GetChars());
@ -1844,8 +1851,6 @@ void S_SetSoundPaused(int state)
}
}
CCMD(snd_status)
{
GSnd->PrintStatus();
@ -1876,4 +1881,3 @@ ADD_STAT(sound)
return GSnd->GatherStats();
}

View file

@ -334,11 +334,23 @@ void FKeyBindings::DoBind (const char *key, const char *bind)
//
//=============================================================================
void FKeyBindings::UnbindAll ()
void FKeyBindings::UnbindAll (const TArray<int> *filter_ptr)
{
for (int i = 0; i < NUM_KEYS; ++i)
if (filter_ptr != nullptr)
{
Binds[i] = "";
const TArray<int> &filter = *filter_ptr;
for (auto key : filter)
{
assert(key >= 0 && key < NUM_KEYS);
Binds[key] = "";
}
}
else
{
for (int i = 0; i < NUM_KEYS; ++i)
{
Binds[i] = "";
}
}
}
@ -555,11 +567,11 @@ void FKeyBindings::DefaultBind(const char *keyname, const char *cmd)
//
//=============================================================================
void C_UnbindAll ()
void C_UnbindAll (const TArray<int> *filter)
{
Bindings.UnbindAll();
DoubleBindings.UnbindAll();
AutomapBindings.UnbindAll();
Bindings.UnbindAll(filter);
DoubleBindings.UnbindAll(filter);
AutomapBindings.UnbindAll(filter);
}
UNSAFE_CCMD (unbindall)
@ -677,7 +689,7 @@ CCMD(rebind)
//
//=============================================================================
void ReadBindings(int lump, bool override)
void ReadBindings(int lump, bool override, const TArray<int> *filter = nullptr)
{
FScanner sc(lump);
@ -692,6 +704,15 @@ void ReadBindings(int lump, bool override)
if (override)
{
// This is only for games to clear unsuitable base defaults, not for mods.
if (filter != nullptr)
{
key = GetKeyFromName(sc.String);
if (!filter->Contains(key))
{
continue;
}
}
dest->UnbindKey(sc.String);
}
continue;
@ -712,13 +733,17 @@ void ReadBindings(int lump, bool override)
dest = &AutomapBindings;
sc.MustGetString();
}
else if (sc.Compare("unbind"))
{
sc.MustGetString();
dest->UnbindKey(sc.String);
continue;
}
key = GetConfigKeyFromName(sc.String);
if (filter != nullptr)
{
if (!filter->Contains(key))
{
continue;
}
}
sc.MustGetString();
dest->SetBind(key, sc.String, override);
}
@ -730,7 +755,7 @@ void ReadBindings(int lump, bool override)
//
//=============================================================================
void C_SetDefaultKeys(const char* baseconfig)
void C_SetDefaultKeys(const char* baseconfig, const TArray<int> *filter = nullptr)
{
auto lump = fileSystem.CheckNumForFullName("engine/commonbinds.txt");
if (lump >= 0)
@ -743,7 +768,7 @@ void C_SetDefaultKeys(const char* baseconfig)
fileSystem.GetResourceFileFullName(fileno2), "engine/commonbinds.txt");
}
ReadBindings(lump, true);
ReadBindings(lump, true, filter);
}
int lastlump = 0;
@ -751,7 +776,7 @@ void C_SetDefaultKeys(const char* baseconfig)
{
// Read this only from the main game resources.
if (fileSystem.GetFileContainer(lump) <= fileSystem.GetMaxIwadNum())
ReadBindings(lump, true);
ReadBindings(lump, true, filter);
}
lastlump = 0;
@ -761,9 +786,9 @@ void C_SetDefaultKeys(const char* baseconfig)
// Comes from an IWAD/IPK3/IPK7 allow it to override the users settings...
// If it comes from a user mod however, don't.
if (fileSystem.GetFileContainer(lump) > fileSystem.GetMaxIwadNum())
ReadBindings(lump, false);
ReadBindings(lump, false, filter);
else
ReadBindings(lump, true);
ReadBindings(lump, true, filter);
}
}
@ -775,15 +800,18 @@ void C_SetDefaultKeys(const char* baseconfig)
CVAR(Int, cl_defaultconfiguration, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
void C_BindDefaults()
void C_BindDefaults(const TArray<int> *filter = nullptr)
{
C_SetDefaultKeys(cl_defaultconfiguration == 1 ? "engine/origbinds.txt" : cl_defaultconfiguration == 2 ? "engine/leftbinds.txt" : "engine/defbinds.txt");
C_SetDefaultKeys(
cl_defaultconfiguration == 1 ? "engine/origbinds.txt" : cl_defaultconfiguration == 2 ? "engine/leftbinds.txt" : "engine/defbinds.txt",
filter
);
}
void C_SetDefaultBindings()
void C_SetDefaultBindings(const TArray<int> *filter)
{
C_UnbindAll();
C_BindDefaults();
C_UnbindAll(filter);
C_BindDefaults(filter);
}

View file

@ -56,7 +56,7 @@ public:
int GetKeysForCommand (const char *cmd, int *first, int *second);
TArray<int> GetKeysForCommand (const char *cmd);
void UnbindACommand (const char *str);
void UnbindAll ();
void UnbindAll (const TArray<int> *filter = nullptr);
void UnbindKey(const char *key);
void DoBind (const char *key, const char *bind);
void DefaultBind(const char *keyname, const char *cmd);
@ -92,8 +92,8 @@ extern FKeyBindings AutomapBindings;
bool C_DoKey (event_t *ev, FKeyBindings *binds, FKeyBindings *doublebinds);
// Stuff used by the customize controls menu
void C_SetDefaultBindings ();
void C_UnbindAll ();
void C_SetDefaultBindings (const TArray<int> *filter = nullptr);
void C_UnbindAll (const TArray<int> *filter = nullptr);
extern const char *KeyNames[];

View file

@ -39,6 +39,8 @@
#include "printf.h"
#include "cmdlib.h"
#include "c_console.h"
#include "m_joy.h"
#include "c_bind.h"
ButtonMap buttonMap;
@ -150,6 +152,26 @@ void ButtonMap::ResetButtonStates ()
//
//=============================================================================
void ButtonMap::GetAxes ()
{
float joyaxes[NUM_AXIS_CODES];
I_GetAxes(joyaxes);
for (int i = 0; i < Buttons.Size(); i++)
{
FButtonStatus &btn = Buttons[i];
FString &btn_name = NumToName[i];
btn.AddAxes(btn_name, joyaxes);
}
}
//=============================================================================
//
//
//
//=============================================================================
bool FButtonStatus::PressKey (int keynum)
{
int i, open;
@ -246,6 +268,53 @@ bool FButtonStatus::ReleaseKey (int keynum)
//
//=============================================================================
void FButtonStatus::AddAxes (FString &btn_name, float joyaxes[NUM_AXIS_CODES])
{
int i;
bIsAxis = false;
Axis = 0.0f;
char cmd_name[16];
strcpy(&cmd_name[1], btn_name.GetChars());
cmd_name[0] = '+';
TArray<int> positive_keys = Bindings.GetKeysForCommand(cmd_name);
cmd_name[0] = '-';
TArray<int> negative_keys = Bindings.GetKeysForCommand(cmd_name);
for (i = 0; i < NUM_AXIS_CODES; i++)
{
float axis_value = joyaxes[i];
if (axis_value > 0.0)
{
int key_code = KeyAxisMapping[i];
if (positive_keys.Contains(key_code))
{
Axis += axis_value;
bIsAxis = true;
}
if (negative_keys.Contains(key_code))
{
Axis -= axis_value;
bIsAxis = true;
}
}
}
Axis = clamp<float>(Axis, 0.0f, 1.0f);
}
//=============================================================================
//
//
//
//=============================================================================
void ButtonMap::AddButtonTabCommands()
{
// Add all the action commands for tab completion

View file

@ -3,6 +3,7 @@
#include <stdint.h>
#include "tarray.h"
#include "name.h"
#include "keydef.h"
// Actions
struct FButtonStatus
@ -14,13 +15,18 @@ struct FButtonStatus
bool bWentDown; // Button went down this tic
bool bWentUp; // Button went up this tic
bool bReleaseLock; // Lock ReleaseKey call in ResetButtonStates
bool bIsAxis; // Whenever or not this button is being controlled by any axis.
float Axis; // How far the button has been pressed. Updated by I_GetAxes.
void (*PressHandler)(); // for optional game-side customization
void (*ReleaseHandler)();
bool PressKey (int keynum); // Returns true if this key caused the button to be pressed.
bool ReleaseKey (int keynum); // Returns true if this key is no longer pressed.
void AddAxes (FString &btn_name, float joyaxes[NUM_AXIS_CODES]); // Update joystick axis information.
void ResetTriggers () { bWentDown = bWentUp = false; }
void Reset () { bDown = bWentDown = bWentUp = false; }
void Reset () { bDown = bWentDown = bWentUp = bIsAxis = false; Axis = 0.0f; }
};
class ButtonMap
@ -53,6 +59,7 @@ public:
void ResetButtonTriggers(); // Call ResetTriggers for all buttons
void ResetButtonStates(); // Same as above, but also clear bDown
void GetAxes(); // Call AddAxes for all buttons
int ListActionCommands(const char* pattern);
void AddButtonTabCommands();
@ -62,6 +69,28 @@ public:
return Buttons[x].bDown;
}
bool ButtonDownDigital(int x) const
{
// Like ButtonDown, but only for digital buttons.
// This is necessary, because of how the game runs different
// code for both digital and analog, and they can't be rolled
// together without introducing some subtle gameplay differences.
return (Buttons[x].bIsAxis == false && Buttons[x].bDown == true);
}
float ButtonAnalog(int x) const
{
// Get the analog value of a button.
// This is 0.0 (not 1.0) for digital presses, because again,
// of how the game logic is split between the two.
if (Buttons[x].bIsAxis == true)
{
return Buttons[x].Axis;
}
return 0.0f;
}
bool ButtonPressed(int x) const
{
return Buttons[x].bWentDown;

View file

@ -4,6 +4,7 @@
**---------------------------------------------------------------------------
** Copyright 1998-2006 Randy Heit
** Copyright 2010-2020 Christoph Oelckers
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -31,11 +32,12 @@
**---------------------------------------------------------------------------
**
*/
#include "c_commandbuffer.h"
#include "v_draw.h"
#include "v_2ddrawer.h"
#include "v_font.h"
#include "utf8.h"
#include "v_2ddrawer.h"
#include "v_draw.h"
#include "v_font.h"
FCommandBuffer CmdLine;
@ -155,18 +157,23 @@ void FCommandBuffer::MakeStartPosGood()
}
}
void FCommandBuffer::CursorStart()
bool FCommandBuffer::CursorStart()
{
bool moved = CursorPos != 0;
CursorPos = 0;
StartPos = 0;
CursorPosCells = 0;
StartPosCells = 0;
return moved;
}
void FCommandBuffer::CursorEnd()
bool FCommandBuffer::CursorEnd()
{
CursorPos = (unsigned)Text.length();
unsigned len = Text.length();
bool moved = CursorPos != len;
CursorPos = len;
MakeStartPosGood();
return moved;
}
void FCommandBuffer::CursorLeft()

View file

@ -1,5 +1,7 @@
#pragma once
#include <string>
#include "zstring.h"
struct FCommandBuffer
@ -31,8 +33,8 @@ public:
unsigned CalcCellSize(unsigned length);
unsigned CharsForCells(unsigned cellin, bool *overflow);
void MakeStartPosGood();
void CursorStart();
void CursorEnd();
bool CursorStart();
bool CursorEnd();
private:
void MoveCursorLeft()

View file

@ -4,6 +4,7 @@
**
**---------------------------------------------------------------------------
** Copyright 1998-2006 Randy Heit
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -32,38 +33,33 @@
**
*/
#include <string>
#include "version.h"
#include "c_bind.h"
#include "c_commandbuffer.h"
#include "c_console.h"
#include "c_consolebuffer.h"
#include "c_cvars.h"
#include "c_dispatch.h"
#include "gamestate.h"
#include "v_text.h"
#include "filesystem.h"
#include "d_gui.h"
#include "c_notifybufferbase.h"
#include "cmdlib.h"
#include "common/scripting/dap/GameEventEmit.h"
#include "d_eventbase.h"
#include "c_consolebuffer.h"
#include "d_gui.h"
#include "g_input.h"
#include "gamestate.h"
#include "i_interface.h"
#include "i_system.h"
#include "i_time.h"
#include "menu.h"
#include "menustate.h"
#include "printf.h"
#include "texturemanager.h"
#include "utf8.h"
#include "v_2ddrawer.h"
#include "v_draw.h"
#include "v_font.h"
#include "printf.h"
#include "i_time.h"
#include "texturemanager.h"
#include "v_draw.h"
#include "i_interface.h"
#include "v_text.h"
#include "v_video.h"
#include "i_system.h"
#include "menu.h"
#include "menustate.h"
#include "v_2ddrawer.h"
#include "c_notifybufferbase.h"
#include "g_input.h"
#include "c_commandbuffer.h"
#include "version.h"
#include "vm.h"
#include "common/widgets/errorwindow.h"
#include "common/scripting/dap/GameEventEmit.h"
@ -142,6 +138,9 @@ CUSTOM_CVAR(Float, con_alpha, 0.75f, CVAR_ARCHIVE)
if (self > 1.f) self = 1.f;
}
CUSTOM_CVARD(Bool, con_quick_home_end, true, CVAR_ARCHIVE, "Use HOME/END keys to scroll when cursor is at start/end of line already")
{}
// Show developer messages if true.
CUSTOM_CVAR(Int, developer, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
@ -702,12 +701,12 @@ void C_DrawConsole ()
if (textScale == 1)
DrawText(twod, CurrentConsoleFont, CR_ORANGE, twod->GetWidth() - 8 -
CurrentConsoleFont->StringWidth (GetVersionString()),
ConBottom / textScale - CurrentConsoleFont->GetHeight() - 4,
round((float)ConBottom / textScale) - CurrentConsoleFont->GetHeight() - 4,
GetVersionString(), TAG_DONE);
else
DrawText(twod, CurrentConsoleFont, CR_ORANGE, twod->GetWidth() / textScale - 8 -
DrawText(twod, CurrentConsoleFont, CR_ORANGE, (float)twod->GetWidth() / textScale - 8 -
CurrentConsoleFont->StringWidth(GetVersionString()),
ConBottom / textScale - CurrentConsoleFont->GetHeight() - 4,
round((float)ConBottom / textScale) - CurrentConsoleFont->GetHeight() - 4,
GetVersionString(),
DTA_VirtualWidth, twod->GetWidth() / textScale,
DTA_VirtualHeight, twod->GetHeight() / textScale,
@ -834,6 +833,11 @@ static bool C_HandleKey (event_t *ev, FCommandBuffer &buffer)
int data1 = ev->data1;
bool keepappending = false;
int page_height = (twod->GetHeight()-4)/active_con_scale(twod) /
((gamestate == GS_FULLCONSOLE || gamestate == GS_STARTUP) ? CurrentConsoleFont->GetHeight() : CurrentConsoleFont->GetHeight()*2) - 3;
int total_lines = conbuffer->GetFormattedLineCount();
int top_row = total_lines - page_height - 1;
switch (ev->subtype)
{
default:
@ -883,75 +887,48 @@ static bool C_HandleKey (event_t *ev, FCommandBuffer &buffer)
break;
case GK_PGUP:
if (ev->data3 & (GKM_SHIFT|GKM_CTRL))
{ // Scroll console buffer up one page
RowAdjust += (twod->GetHeight()-4)/active_con_scale(twod) /
((gamestate == GS_FULLCONSOLE || gamestate == GS_STARTUP) ? CurrentConsoleFont->GetHeight() : CurrentConsoleFont->GetHeight()*2) - 3;
}
else if (RowAdjust < conbuffer->GetFormattedLineCount())
if (ev->subtype == EV_GUI_WheelUp)
{ // Scroll console buffer up
if (ev->subtype == EV_GUI_WheelUp)
{
RowAdjust += 3;
}
else
{
RowAdjust++;
}
if (RowAdjust > conbuffer->GetFormattedLineCount())
{
RowAdjust = conbuffer->GetFormattedLineCount();
}
if (ev->data3 & (GKM_SHIFT|GKM_CTRL)) RowAdjust += 1;
else RowAdjust += 3;
if (RowAdjust > total_lines) RowAdjust = total_lines;
}
else
{ // Scroll console buffer up one page
RowAdjust += page_height;
if (RowAdjust > top_row) RowAdjust = top_row; // intentionally different than scroll behavior
}
break;
case GK_PGDN:
if (ev->data3 & (GKM_SHIFT|GKM_CTRL))
{ // Scroll console buffer down one page
const int scrollamt = (twod->GetHeight()-4)/active_con_scale(twod) /
((gamestate == GS_FULLCONSOLE || gamestate == GS_STARTUP) ? CurrentConsoleFont->GetHeight() : CurrentConsoleFont->GetHeight()*2) - 3;
if (RowAdjust < scrollamt)
{
RowAdjust = 0;
}
else
{
RowAdjust -= scrollamt;
}
}
else if (RowAdjust > 0)
if (ev->subtype == EV_GUI_WheelDown)
{ // Scroll console buffer down
if (ev->subtype == EV_GUI_WheelDown)
{
RowAdjust = max (0, RowAdjust - 3);
}
else
{
RowAdjust--;
}
if (ev->data3 & (GKM_SHIFT|GKM_CTRL)) RowAdjust -= 1;
else RowAdjust -= 3;
}
else
{ // Scroll console buffer down one page
RowAdjust -= page_height;
}
if (RowAdjust < 0) RowAdjust = 0;
break;
case GK_HOME:
if (ev->data3 & GKM_CTRL)
if ((ev->data3 & (GKM_CTRL|GKM_SHIFT))
|| (!buffer.CursorStart() && con_quick_home_end)) // Try to move cursor to start of line
{ // Move to top of console buffer
RowAdjust = conbuffer->GetFormattedLineCount();
}
else
{ // Move cursor to start of line
buffer.CursorStart();
RowAdjust = top_row;
}
break;
case GK_END:
if (ev->data3 & GKM_CTRL)
if ((ev->data3 & (GKM_CTRL|GKM_SHIFT))
|| (!buffer.CursorEnd() && con_quick_home_end)) // Try to move cursor to end of line
{ // Move to bottom of console buffer
RowAdjust = 0;
}
else
{ // Move cursor to end of line
buffer.CursorEnd();
}
break;
case GK_LEFT:
@ -979,39 +956,53 @@ static bool C_HandleKey (event_t *ev, FCommandBuffer &buffer)
break;
case GK_UP:
// Move to previous entry in the command history
if (HistPos == NULL)
if (ev->data3 & GKM_SHIFT)
{
HistPos = HistHead;
}
else if (HistPos->Older)
{
HistPos = HistPos->Older;
}
if (HistPos)
{
buffer.SetString(HistPos->String);
}
TabbedLast = false;
TabbedList = false;
break;
case GK_DOWN:
// Move to next entry in the command history
if (HistPos && HistPos->Newer)
{
HistPos = HistPos->Newer;
buffer.SetString(HistPos->String);
RowAdjust = min (total_lines, RowAdjust + 1); // match mousewheel
}
else
{
HistPos = NULL;
buffer.SetString("");
// Move to previous entry in the command history
if (HistPos == NULL)
{
HistPos = HistHead;
}
else if (HistPos->Older)
{
HistPos = HistPos->Older;
}
if (HistPos)
{
buffer.SetString(HistPos->String);
}
TabbedLast = false;
TabbedList = false;
}
break;
case GK_DOWN:
if (ev->data3 & GKM_SHIFT)
{
RowAdjust = max (0, RowAdjust - 1);
}
else
{
// Move to next entry in the command history
if (HistPos && HistPos->Newer)
{
HistPos = HistPos->Newer;
buffer.SetString(HistPos->String);
}
else
{
HistPos = NULL;
buffer.SetString("");
}
TabbedLast = false;
TabbedList = false;
}
TabbedLast = false;
TabbedList = false;
break;
case 'X':
@ -1261,4 +1252,3 @@ CCMD(toggleconsole)
{
C_ToggleConsole();
}

View file

@ -147,3 +147,77 @@ enum EKeyCodes
NUM_JOYAXISBUTTONS = 8,
};
// Axes are provided per-key now, but passing around
// an entire NUM_KEYS-sized array is overkill. These
// codes are used for translating between axes and keys.
enum EAxisCodes
{
AXIS_CODE_NULL = -1,
AXIS_CODE_JOY1_PLUS = 0,
AXIS_CODE_JOY1_MINUS,
AXIS_CODE_JOY2_PLUS,
AXIS_CODE_JOY2_MINUS,
AXIS_CODE_JOY3_PLUS,
AXIS_CODE_JOY3_MINUS,
AXIS_CODE_JOY4_PLUS,
AXIS_CODE_JOY4_MINUS,
AXIS_CODE_JOY5_PLUS,
AXIS_CODE_JOY5_MINUS,
AXIS_CODE_JOY6_PLUS,
AXIS_CODE_JOY6_MINUS,
AXIS_CODE_JOY7_PLUS,
AXIS_CODE_JOY7_MINUS,
AXIS_CODE_JOY8_PLUS,
AXIS_CODE_JOY8_MINUS,
AXIS_CODE_PAD_LTHUMB_RIGHT,
AXIS_CODE_PAD_LTHUMB_LEFT,
AXIS_CODE_PAD_LTHUMB_DOWN,
AXIS_CODE_PAD_LTHUMB_UP,
AXIS_CODE_PAD_RTHUMB_RIGHT,
AXIS_CODE_PAD_RTHUMB_LEFT,
AXIS_CODE_PAD_RTHUMB_DOWN,
AXIS_CODE_PAD_RTHUMB_UP,
AXIS_CODE_PAD_LTRIGGER,
AXIS_CODE_PAD_RTRIGGER,
NUM_AXIS_CODES,
};
static const int KeyAxisMapping[NUM_AXIS_CODES] = {
KEY_JOYAXIS1PLUS,
KEY_JOYAXIS1MINUS,
KEY_JOYAXIS2PLUS,
KEY_JOYAXIS2MINUS,
KEY_JOYAXIS3PLUS,
KEY_JOYAXIS3MINUS,
KEY_JOYAXIS4PLUS,
KEY_JOYAXIS4MINUS,
KEY_JOYAXIS5PLUS,
KEY_JOYAXIS5MINUS,
KEY_JOYAXIS6PLUS,
KEY_JOYAXIS6MINUS,
KEY_JOYAXIS7PLUS,
KEY_JOYAXIS7MINUS,
KEY_JOYAXIS8PLUS,
KEY_JOYAXIS8MINUS,
KEY_PAD_LTHUMB_RIGHT,
KEY_PAD_LTHUMB_LEFT,
KEY_PAD_LTHUMB_DOWN,
KEY_PAD_LTHUMB_UP,
KEY_PAD_RTHUMB_RIGHT,
KEY_PAD_RTHUMB_LEFT,
KEY_PAD_RTHUMB_DOWN,
KEY_PAD_RTHUMB_UP,
KEY_PAD_LTRIGGER,
KEY_PAD_RTRIGGER,
};

View file

@ -34,15 +34,19 @@
*/
#include "c_bind.h"
#include "d_eventbase.h"
#include "c_console.h"
#include "c_cvars.h"
#include "d_eventbase.h"
#include "d_gui.h"
#include "menu.h"
#include "utf8.h"
#include "m_joy.h"
#include "vm.h"
#include "gamestate.h"
#include "i_interface.h"
#include "keydef.h"
#include "m_joy.h"
#include "menu.h"
#include "utf8.h"
#include "vm.h"
extern bool ToggleFullscreen;
int eventhead;
int eventtail;
@ -73,6 +77,7 @@ void D_ProcessEvents (void)
while (eventtail != eventhead)
{
event_t *ev = &events[eventtail];
eventtail = (eventtail + 1) & (MAXEVENTS - 1);
if (ev->type == EV_KeyUp && keywasdown[ev->data1])
@ -86,6 +91,16 @@ void D_ProcessEvents (void)
if (ev->type == EV_DeviceChange)
UpdateJoystickMenu(I_UpdateDeviceList());
#if defined(__linux__) || defined(_WIN32)
// I cannot test on macos, so it is disabled for now
if ((ev->type == EV_KeyDown && ev->data1 == KEY_ENTER && (ev->data3 & GKM_ALT))
|| (ev->type == EV_GUI_Event && ev->subtype == EV_GUI_KeyDown && ev->data1 == GK_RETURN && (ev->data3 & GKM_ALT)))
{
ToggleFullscreen = !ToggleFullscreen;
continue;
}
#endif
// allow the game to intercept Escape before dispatching it.
if (ev->type != EV_KeyDown || ev->data1 != KEY_ESCAPE || !sysCallbacks.WantEscape || !sysCallbacks.WantEscape())
{

View file

@ -43,7 +43,7 @@ CVAR(Int, defaultnetplayers, 8, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Int, defaultnethostport, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Int, defaultnetticdup, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Int, defaultnetmode, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Int, defaultnetgamemode, -1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Int, defaultnetgamemode, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Bool, defaultnetaltdm, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(String, defaultnetaddress, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Int, defaultnetjoinport, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)

View file

@ -93,8 +93,8 @@ struct FStartupSelectionInfo
int DefaultNetHostPort = 0;
int DefaultNetTicDup = 0;
bool DefaultNetExtraTic = false;
int DefaultNetMode = -1;
int DefaultNetGameMode = -1;
int DefaultNetMode = 0;
int DefaultNetGameMode = 0;
bool DefaultNetAltDM = false;
FString DefaultNetAddress = {};

View file

@ -0,0 +1,832 @@
/*
** m_haptics.cpp
**
** Haptic feedback implementation
**
**---------------------------------------------------------------------------
**
** Copyright 2025 Marcus Minhorst
** Copyright 2025 GZDoom Maintainers and Contributors
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see http://www.gnu.org/licenses/
**
**---------------------------------------------------------------------------
**
*/
// HEADER FILES ------------------------------------------------------------
#include <math.h>
#include "c_cvars.h"
#include "c_dispatch.h"
#include "doomdef.h"
#include "doomstat.h"
#include "m_haptics.h"
#include "name.h"
#include "printf.h"
#include "s_soundinternal.h"
#include "tarray.h"
#include "vm.h"
#include "zstring.h"
// MACROS ------------------------------------------------------------------
#ifndef MAX_TRY_DEPTH
#define MAX_TRY_DEPTH 8
#endif
// TYPES -------------------------------------------------------------------
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
inline void _Rumble(const int);
inline void _RumbleDirect(int, int, double, double, double, double);
const FName * Joy_GetMapping(const FName);
const FName * Joy_GuessMapping(const FName);
const struct Haptics * Joy_GetRumble(FName);
void Joy_Rumble(const FName, const struct Haptics, double);
void RumbleDump();
void RumblePrint(const FName, const FName *, const struct Haptics *, double);
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
extern bool AppActive;
// PRIVATE DATA DEFINITIONS ------------------------------------------------
struct {
int tic = gametic; // last tic processed
bool dirty = false; // do we need to do something next tick ?
bool enabled = true; // do we need to do anything ever ?
bool active = true; // is the game currently not paused ?
struct {
double base = 1.0; // [0,1] -> number <1 turns down rumble strength
double high_frequency = 1.0; // [0,inf)
double low_frequency = 1.0; // [0,inf)
double left_trigger = 1.0; // [0,inf)
double right_trigger = 1.0; // [0,inf)
} strength;
struct Haptics current = {0,0,0,0,0}; // current state of the controller
TMap<FName, struct Haptics> channels; // active rumbles (that will be mixed)
} Haptics;
TMap<FName, struct Haptics> RumbleDefinition = {};
TMap<FName, FName> RumbleMapping = {};
TMap<FName, FName> RumbleAlias = {};
TArray<FName> RumbleMissed = {};
// fallback names. these exist in base sndinfo
const FName HapticIntense = "INTENSE",
HapticHeavy = "HEAVY",
HapticMedium = "MEDIUM",
HapticLight = "LIGHT",
HapticSubtle = "SUBTLE",
HapticNone = "NONE",
HapticDirect = "DIRECT"; // except for this one
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// for fine-control, if user wants/needs
// added because trigger haptics are much stronger on xbone controller than expected
CUSTOM_CVARD(Float, haptics_strength_lf, 1.0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "low frequency motor fine-control")
{
if (self < 0) self = 0;
Haptics.strength.low_frequency = self * Haptics.strength.base;
};
CUSTOM_CVARD(Float, haptics_strength_hf, 1.0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "high frequency motor fine-control")
{
if (self < 0) self = 0;
Haptics.strength.high_frequency = self * Haptics.strength.base;
};
CUSTOM_CVARD(Float, haptics_strength_lt, 1.0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "left trigger motor fine-control")
{
if (self < 0) self = 0;
Haptics.strength.left_trigger = self * Haptics.strength.base;
};
CUSTOM_CVARD(Float, haptics_strength_rt, 1.0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "right trigger motor fine-control")
{
if (self < 0) self = 0;
Haptics.strength.right_trigger = self * Haptics.strength.base;
};
CUSTOM_CVARD(Int, haptics_strength, 10, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "Translate linear haptics to audio taper")
{
double l1 = self / 10.0;
double l2 = l1 * l1;
double l3 = l2 * l1;
double m3 = 2; // cubed portion
double m2 = -2; // squared portion
double m1 = 1 - m2 - m3; // linear portion
Haptics.enabled = self > 0;
Haptics.strength.base = l1*m1 + l2*m2 + l3*m3;
Haptics.strength.high_frequency = haptics_strength_hf * Haptics.strength.base;
Haptics.strength.low_frequency = haptics_strength_lf * Haptics.strength.base;
Haptics.strength.left_trigger = haptics_strength_lt * Haptics.strength.base;
Haptics.strength.right_trigger = haptics_strength_rt * Haptics.strength.base;
if (!Haptics.enabled) I_Rumble(0, 0, 0, 0);
}
CVARD(Bool, haptics_debug, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "print diagnostics for haptic feedback");
CUSTOM_CVARD(Int, haptics_compat, HAPTCOMPAT_MATCH, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "haptic feedback compatibility level")
{
if (self < 0) self = 0;
if (self >= NUM_HAPTCOMPAT) self = NUM_HAPTCOMPAT-1;
}
CVARD(Bool, haptics_do_menus, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "allow haptic feedback for menus");
CVARD(Bool, haptics_do_world, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "allow haptic feedback for things acting on player");
CVARD(Bool, haptics_do_damage, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "allow haptic feedback for things hurting player");
CVARD(Bool, haptics_do_action, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "allow haptic feedback for player doing things");
// CODE --------------------------------------------------------------------
//==========================================================================
//
// Joy_GuessMapping
//
// Takes a sound name, and tried to figure out a similar mapping.
// These are cached, so performance is probably not a huge deal
// This can absolutely be expanded/improved, and probably should.
//
//==========================================================================
const FName * Joy_GuessMapping(const FName identifier)
{
FString text = identifier.GetChars();
// I would like to slugify here
// Maybe one day
text.ToLower();
struct MapSet { const FName * mapping; const TArray<FString> tokens; };
static struct MapSet mappings[] = {
{ &HapticNone, { "ricochet", "casing" } },
{ &HapticIntense, { "quake", "death", "gibbed" } },
{ &HapticHeavy, { "teleport", "activate", "secret" } },
{ &HapticMedium, { "success", "grunt", "land", "pain", "pkup", "pickup", "fist", "weapon",
"fire", "shoot", "blast", "attack", "launch", "punch" } },
{ &HapticLight, { "push", "menu", "use", "fail", "open", "close", "eject", "reload",
"charge", "try" } },
{ &HapticNone, { "step", "floor" } },
};
for (auto mapping: mappings)
for (auto token: mapping.tokens)
if (text.IndexOf(token) != -1)
return mapping.mapping;
return nullptr;
}
//==========================================================================
//
// Joy_GetMapping
//
// Takes a sound name, and find its corresponding rumble identifier.
//
//==========================================================================
const FName * Joy_GetMapping(const FName identifier)
{
const FName * mapping = RumbleMapping.CheckKey(identifier);
FName actual = identifier;
// try to grab an aliased sound
// todo: mapping candidate for aliases
if (!mapping)
{
auto id = soundEngine->FindSoundTentative(identifier.GetChars());
// loop a couple layers deep, trying to find a candidate
for (auto i = 0; i < MAX_TRY_DEPTH; i++)
{
if (!id.isvalid()) break;
actual = soundEngine->GetSoundName(id);
mapping = RumbleMapping.CheckKey(actual);
if (mapping) break;
// writable to be able to get random sound
auto sfx = soundEngine->GetWritableSfx(id);
// is there a way to get the actual random sound? Selecting the first is probably fine
id = sfx->bRandomHeader
? soundEngine->ResolveRandomSound(sfx)->Choices[0]
: sfx->link;
}
}
// convert unknown skinned sound to sound
// is there a better way to do this?
if (!mapping)
{
FString idString = identifier.GetChars();
auto skindex = idString.IndexOf("*");
if (skindex >= 0)
{
idString.Remove(0, skindex);
}
actual = FName(idString);
mapping = RumbleMapping.CheckKey(actual);
}
bool replaced = actual != identifier && actual.IsValidName();
auto warn = [&identifier]()
{
if (RumbleMissed.Contains(identifier)) return;
RumbleMissed.Push(identifier);
Printf(DMSG_WARNING|PRINT_NONOTIFY, "Unknown rumble mapping '%s'\n", identifier.GetChars());
};
if (!mapping && identifier != "")
{
switch (haptics_compat)
{
case HAPTCOMPAT_NONE: // sometimes warn for devs
if (!RumbleMissed.Contains(identifier)) warn();
break;
case HAPTCOMPAT_WARN: // always warn for authors
replaced = nullptr != (mapping = Joy_GuessMapping(identifier));
if (replaced) Printf("Rumble compat mapped '%s' to '%s'\n", identifier.GetChars(), mapping->GetChars());
else Printf(TEXTCOLOR_ORANGE "Unknown rumble mapping '%s'\n", identifier.GetChars());
break;
case HAPTCOMPAT_MATCH: // try our best to choose correct (default)
replaced = nullptr != (mapping = Joy_GuessMapping(identifier));
if (!replaced) warn();
break;
case HAPTCOMPAT_ALL: // simple fallback
mapping = &HapticMedium;
replaced = true;
break;
}
}
if (mapping && replaced && haptics_compat != HAPTCOMPAT_WARN)
{
// cache replacement
RumbleMapping.Insert(identifier, *mapping);
}
return mapping;
}
//==========================================================================
//
// Joy_GetRumble
//
// Takes a rumble identifier, and returns its haptics definition.
//
//==========================================================================
const struct Haptics * Joy_GetRumble(FName identifier)
{
struct Haptics * rumble = RumbleDefinition.CheckKey(identifier);
if (!rumble && !RumbleMissed.Contains(identifier))
{
RumbleMissed.Push(identifier);
Printf(DMSG_ERROR, TEXTCOLOR_RED "Rumble mapping not found! '%s'\n", identifier.GetChars());
return nullptr;
}
return rumble;
}
//==========================================================================
//
// Joy_AddRumbleType
//
// Ties a rumble identifier to a haptics definition.
//
//==========================================================================
void Joy_AddRumbleType(const FName identifier, const struct Haptics data)
{
if (haptics_debug)
{
if (data.ticks == 0) Printf("rumble disabled %s\n", identifier.GetChars());
else Printf("rumble add %s T%d H%.1g L%.1g\n", identifier.GetChars(), data.ticks, data.high_frequency, data.low_frequency);
}
RumbleDefinition.Insert(identifier, data);
}
//==========================================================================
//
// Joy_AddRumbleAlias
//
// Adds an alternative rumble identifier for a haptics definition.
// Joy_ReadyRumbleMapping must be called after adding any aliases.
//
//==========================================================================
void Joy_AddRumbleAlias(const FName alias, const FName actual)
{
if (haptics_debug) Printf("rumble alias %s -> %s\n", alias.GetChars(), actual.GetChars());
RumbleAlias.Insert(alias, actual);
}
//==========================================================================
//
// Joy_MapRumbleType
//
// Ties a sound to a rumble identifier.
//
//==========================================================================
void Joy_MapRumbleType(const FName sound, const FName identifier)
{
if (haptics_debug) Printf("rumble map %s -> %s\n", sound.GetChars(), identifier.GetChars());
RumbleMapping.Insert(sound, identifier);
}
//==========================================================================
//
// Joy_ResetRumbleMapping
//
// Resets entire rumble state
//
//==========================================================================
void Joy_ResetRumbleMapping()
{
if (haptics_debug) Printf("rumble reset\n");
RumbleMapping.Clear();
RumbleAlias.Clear();
RumbleMissed.Clear();
RumbleDefinition.Clear();
Haptics.channels.Clear();
Haptics.current.high_frequency = Haptics.current.low_frequency =
Haptics.current.left_trigger = Haptics.current.right_trigger =
Haptics.current.ticks = 0;
I_Rumble(0, 0, 0, 0);
}
//==========================================================================
//
// Joy_ReadyRumbleMapping
//
// Resets entire rumble state
//
//==========================================================================
void Joy_ReadyRumbleMapping()
{
if (haptics_debug) Printf("rumble ready\n");
TArray<FName> found;
TMapIterator<FName, FName> it(RumbleAlias);
TMap<FName, FName>::Pair* pair;
while (RumbleAlias.CountUsed())
{
while (it.NextPair(pair))
{
auto predefined = RumbleDefinition.CheckKey(pair->Key);
if (predefined)
{
Printf(DMSG_ERROR, TEXTCOLOR_RED "Rumble alias trying to redefine mapping! '%s'\n", pair->Key.GetChars());
continue;
}
auto mapping = RumbleDefinition.CheckKey(pair->Value);
if (!mapping) continue;
found.AddUnique(pair->Key);
RumbleDefinition.Insert(pair->Key, *mapping);
}
it.Reset();
if (found.Size() == 0)
{
FString list = "[";
while (it.NextPair(pair))
list.AppendFormat(" '%s'->'%s'", pair->Key.GetChars(), pair->Value.GetChars());
Printf(DMSG_ERROR, TEXTCOLOR_RED "Circular rumble alias found! (%d) %s ]\n", RumbleAlias.CountUsed(), list.GetChars());
break;
}
while (found.size() > 0)
{
RumbleAlias.Remove(found.Last());
found.Pop();
}
}
if (haptics_debug) RumbleDump();
}
//==========================================================================
//
// Joy_RumbleTick
//
// Called once per gametic. Manages rumble state.
//
//==========================================================================
void Joy_RumbleTick()
{
if (!Haptics.enabled) return;
// pause detection
if (AppActive != Haptics.active)
{
Haptics.active = AppActive;
if (Haptics.active && Haptics.current.ticks != 0)
{
I_Rumble(
Haptics.current.high_frequency,
Haptics.current.low_frequency,
Haptics.current.left_trigger,
Haptics.current.right_trigger
);
}
else
{
I_Rumble(0, 0, 0, 0);
}
}
if (Haptics.tic >= gametic) return;
Haptics.tic = gametic;
// new value OR time elapsed
Haptics.dirty |= Haptics.current.ticks != 0 && Haptics.current.ticks < Haptics.tic;
if (!Haptics.dirty) return;
// init
Haptics.current.ticks = 0;
Haptics.current.high_frequency = 0;
Haptics.current.low_frequency = 0;
Haptics.current.left_trigger = 0;
Haptics.current.right_trigger = 0;
TMapIterator<FName, struct Haptics> it(Haptics.channels);
TMap<FName, struct Haptics>::Pair* pair;
TArray<FName> stale;
// remove old rumble data
while (it.NextPair(pair))
{
if (pair->Value.ticks < Haptics.tic)
{
stale.Push(pair->Key);
}
}
for (auto key: stale)
{
Haptics.channels.Remove(key);
}
it.Reset();
while (it.NextPair(pair))
{
// grab upcoming event time
Haptics.current.ticks = Haptics.current.ticks == 0
? pair->Value.ticks
: std::min(Haptics.current.ticks, pair->Value.ticks);
// simple intensity mixing
Haptics.current.high_frequency += pair->Value.high_frequency;
Haptics.current.low_frequency += pair->Value.low_frequency;
Haptics.current.left_trigger += pair->Value.left_trigger;
Haptics.current.right_trigger += pair->Value.right_trigger;
}
// should this be clamped to [0,1]? Maybe a controller api will support "hdr" haptics lol
// Haptics.current.high_frequency = std::min(std::max(0.0, Haptics.current.high_frequency), 1.0);
// Haptics.current.low_frequency = std::min(std::max(0.0, Haptics.current.low_frequency), 1.0);
// Haptics.current.left_trigger = std::min(std::max(0.0, Haptics.current.left_trigger), 1.0);
// Haptics.current.right_trigger = std::min(std::max(0.0, Haptics.current.right_trigger), 1.0);
I_Rumble(
Haptics.current.high_frequency,
Haptics.current.low_frequency,
Haptics.current.left_trigger,
Haptics.current.right_trigger
);
Haptics.dirty = false;
}
void Joy_Rumble(const FName source, const struct Haptics data, double attenuation)
{
if (!Haptics.enabled) return;
if (data.ticks <= 0) return;
if (attenuation >= 1) return;
float strength = 1 - (attenuation < 0? 0: attenuation);
// this will overwrite stuff from same source mapping (weapons/pistol not W_BULLET)
Haptics.channels.Insert(source, {
Haptics.tic + data.ticks + 1,
data.high_frequency * Haptics.strength.high_frequency * strength,
data.low_frequency * Haptics.strength.low_frequency * strength,
data.left_trigger * Haptics.strength.left_trigger * strength,
data.right_trigger * Haptics.strength.right_trigger * strength,
});
Haptics.dirty = true;
}
//==========================================================================
//
// Joy_Rumble
//
// Requests haptic feedback based on provided data. Attenuation of >=1 results in no-op.
//
//==========================================================================
void Joy_Rumble(
const FName source,
int tic_count,
double high_frequency,
double low_frequency,
double left_trigger,
double right_trigger,
double attenuation
)
{
const struct Haptics rumble {tic_count, high_frequency, low_frequency, left_trigger, right_trigger};
if (haptics_debug) RumblePrint(source, &HapticDirect, &rumble, attenuation);
Joy_Rumble(source, rumble, attenuation);
}
//==========================================================================
//
// Joy_Rumble
//
// Requests haptic feedback. Attenuation of >=1 results in no-op.
//
//==========================================================================
void Joy_Rumble(const FName identifier, double attenuation)
{
const FName * mapping;
const struct Haptics * rumble;
rumble = (nullptr != (mapping = Joy_GetMapping(identifier)))
? Joy_GetRumble(*mapping)
: nullptr;
if (haptics_debug) RumblePrint(identifier, mapping, rumble, attenuation);
if (rumble) Joy_Rumble(identifier, * rumble, attenuation);
}
//==========================================================================
//
// rumble
//
// test command
//
//==========================================================================
CCMD (rumble)
{
int count = argv.argc()-1;
int ticks;
double high_freq, low_freq, left_trig, right_trig;
switch (count)
{
case 0: {
RumbleDump();
Printf("Testing rumble for 5s\n");
Joy_Rumble("", 5 * TICRATE, 1.0, 1.0, 1.0, 1.0);
}
break;
case 1:
Printf("Testing rumble for action '%s'\n", argv[1]);
Joy_Rumble(argv[1]);
break;
case 5:
try
{
ticks = std::stoi(argv[1], nullptr, 10);
high_freq = static_cast <double> (std::stof(argv[2], nullptr));
low_freq = static_cast <double> (std::stof(argv[3], nullptr));
left_trig = static_cast <double> (std::stof(argv[4], nullptr));
right_trig = static_cast <double> (std::stof(argv[5], nullptr));
} catch (...)
{
Printf("Failed to parse args\n");
return;
}
Printf("testing rumble with params (%d, %f, %f, %f, %f)\n", ticks, high_freq, low_freq, left_trig, right_trig);
Joy_Rumble("", ticks, high_freq, low_freq, left_trig, right_trig);
break;
default:
Printf(
"usage:\n %s\n %s\n %s\n",
"rumble",
"rumble string_id",
"rumble int_duration float_high_freq float_low_freq float_left_trig float_right_trigger"
);
break;
}
}
//==========================================================================
//
// _Rumble
//
// VM wrapper for Joy_Rumble
//
//==========================================================================
inline void _Rumble(const int identifier)
{
Joy_Rumble(ENamedName(identifier));
}
//==========================================================================
//
// Rumble
//
// VM function for named Joy_Rumble
//
//==========================================================================
DEFINE_ACTION_FUNCTION_NATIVE(DHaptics, Rumble, _Rumble)
{
PARAM_PROLOGUE;
PARAM_INT(identifier);
_Rumble(ENamedName(identifier));
return 0;
}
//==========================================================================
//
// _RumbleDirect
//
// VM wrapper for Joy_Rumble
//
//==========================================================================
inline void _RumbleDirect(
int source,
int tic_count,
double high_frequency,
double low_frequency,
double left_trigger,
double right_trigger
)
{
Joy_Rumble(
ENamedName(source),
tic_count,
high_frequency,
low_frequency,
left_trigger,
right_trigger
);
}
//==========================================================================
//
// Rumble
//
// VM function for direct Joy_Rumble
//
//==========================================================================
DEFINE_ACTION_FUNCTION_NATIVE(DHaptics, RumbleDirect, _RumbleDirect)
{
PARAM_PROLOGUE;
PARAM_INT(source);
PARAM_INT(tic_count);
PARAM_FLOAT(high_frequency);
PARAM_FLOAT(low_frequency);
PARAM_FLOAT(left_trigger);
PARAM_FLOAT(right_trigger);
_RumbleDirect(source, tic_count, high_frequency, low_frequency, left_trigger, right_trigger);
return 0;
}
//==========================================================================
//
// RumblePrint
//
// Debug printout
//
//==========================================================================
void RumblePrint(const FName identifier, const FName * mapping, const struct Haptics * rumble, double attenuation)
{
const char * color;
FString text = FStringf("r '%s'", identifier.GetChars());
if (!mapping)
{ color = TEXTCOLOR_ORANGE; text.AppendFormat(" -"); }
else
{
text.AppendFormat(" %s", mapping->GetChars());
if (!rumble)
{ color = TEXTCOLOR_RED; text.AppendFormat(" -"); }
else if (rumble->ticks == 0)
{ color = TEXTCOLOR_BLACK; text.AppendFormat(" 0"); }
else
{ color = TEXTCOLOR_CYAN; text.AppendFormat(" T%d H%.1g L%.1g A%.1g",
rumble->ticks, rumble->high_frequency, rumble->low_frequency, attenuation); }
}
Printf("%s%s\n", color, text.GetChars());
}
//==========================================================================
//
// RumbleDump
//
// Debug printout
//
//==========================================================================
void RumbleDump()
{
TArray<FString> unused, used;
{
TMapIterator<FName, struct Haptics> it(RumbleDefinition);
TMap<FName, struct Haptics>::Pair* pair;
while (it.NextPair(pair)) unused.AddUnique(pair->Key.GetChars());
}
{
TMapIterator<FName, FName> it(RumbleAlias);
TMap<FName, FName>::Pair* pair;
while (it.NextPair(pair)) unused.AddUnique(pair->Key.GetChars());
}
{
TMapIterator<FName, FName> it(RumbleAlias);
TMap<FName, FName>::Pair* pair;
while (it.NextPair(pair))
{
if (unused.Contains(pair->Value.GetChars()))
unused.Delete(unused.Find(pair->Value.GetChars()));
}
}
{
TMapIterator<FName, FName> it(RumbleMapping);
TMap<FName, FName>::Pair* pair;
Printf("Mappings:\n");
while (it.NextPair(pair))
{
used.AddUnique(pair->Value.GetChars());
if (unused.Contains(pair->Value.GetChars()))
unused.Delete(unused.Find(pair->Value.GetChars()));
auto mapping = Joy_GetRumble(pair->Value);
FString key = pair->Key.GetChars();
FString val = pair->Value.GetChars();
key.ToLower();
val.ToUpper();
FString a = FStringf("'%s'\t->\t'%s'", key.GetChars(), val.GetChars());
FString b = mapping
? FStringf(
"{ %d %g %g %g %g }",
mapping->ticks,
mapping->high_frequency,
mapping->low_frequency,
mapping->left_trigger,
mapping->right_trigger
) : "[undefined]";
Printf("\t%s\t->\t%s\n", a.GetChars(), b.GetChars());
}
}
if (unused.Size() > 0)
{
Printf("Unused:\n");
for (auto i:unused)
{
FString s = i.GetChars();
s.ToUpper();
Printf("\t'%s'\n", s.GetChars());
}
}
}

View file

@ -0,0 +1,39 @@
#ifndef M_HAPTICS_H
#define M_HAPTICS_H
#include "name.h"
enum EHapticCompat {
HAPTCOMPAT_WARN,
HAPTCOMPAT_NONE,
HAPTCOMPAT_MATCH,
HAPTCOMPAT_ALL,
NUM_HAPTCOMPAT
};
struct Haptics {
int ticks;
double high_frequency;
double low_frequency;
double left_trigger;
double right_trigger;
};
void I_Rumble(double high_freq, double low_freq, double left_trig, double right_trig);
void Joy_AddRumbleType(const FName idenifier, const struct Haptics data);
void Joy_AddRumbleAlias(const FName alias, const FName actual);
void Joy_MapRumbleType(const FName sound, const FName idenifier);
void Joy_ResetRumbleMapping();
void Joy_ReadyRumbleMapping();
void Joy_RumbleTick();
void Joy_Rumble(
const FName source, int tic_count,
double high_frequency, double low_frequency,
double left_trigger = 0, double right_trigger = 0,
double attenuation = 0
);
void Joy_Rumble(const FName identifier, double attenuation = 0);
#endif

View file

@ -1,8 +1,12 @@
/*
** m_joy.cpp
**
** Cross-platform joystick/gamepad management
**
**---------------------------------------------------------------------------
**
** Copyright 2005-2016 Randy Heit
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -27,20 +31,26 @@
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
**---------------------------------------------------------------------------
**
*/
// HEADER FILES ------------------------------------------------------------
#include <math.h>
#include "c_cvars.h"
#include "c_dispatch.h"
#include "vectors.h"
#include "m_joy.h"
#include "configfile.h"
#include "i_interface.h"
#include "d_eventbase.h"
#include "cmdlib.h"
#include "configfile.h"
#include "d_eventbase.h"
#include "i_interface.h"
#include "m_joy.h"
#include "name.h"
#include "printf.h"
#include "vectors.h"
#include "zstring.h"
// MACROS ------------------------------------------------------------------
@ -62,6 +72,8 @@ extern const float JOYDEADZONE_DEFAULT = 0.1; // reduced from 0.25
extern const float JOYSENSITIVITY_DEFAULT = 1.0;
extern const float JOYHAPSTRENGTH_DEFAULT = 1.0;
extern const float JOYTHRESH_DEFAULT = 0.05;
extern const float JOYTHRESH_TRIGGER = 0.05;
extern const float JOYTHRESH_STICK_X = 0.65;
@ -162,6 +174,12 @@ bool M_LoadJoystickConfig(IJoystickConfig *joy)
joy->SetSensitivity((float)atof(value));
}
value = GameConfig->GetValueForKey("Haptics");
if (value)
{
joy->SetHapticsStrength((float)atof(value));
}
numaxes = joy->GetNumAxes();
for (int i = 0; i < numaxes; ++i)
{
@ -222,18 +240,6 @@ bool M_LoadJoystickConfig(IJoystickConfig *joy)
{
joy->SetAxisResponseCurvePoint(i, 3, (float)atof(value));
}
mysnprintf(key + axislen, countof(key) - axislen, "map");
value = GameConfig->GetValueForKey(key);
if (value)
{
EJoyAxis gameaxis = (EJoyAxis)atoi(value);
if (gameaxis < JOYAXIS_None || gameaxis >= NUM_JOYAXIS)
{
gameaxis = JOYAXIS_None;
}
joy->SetAxisMap(i, gameaxis);
}
}
return true;
}
@ -264,12 +270,19 @@ void M_SaveJoystickConfig(IJoystickConfig *joy)
{
GameConfig->SetValueForKey("EnabledInBackground", "1");
}
if (!joy->IsSensitivityDefault())
{
mysnprintf(value, countof(value), "%g", joy->GetSensitivity());
GameConfig->SetValueForKey("Sensitivity", value);
}
if (!joy->IsHapticsStrengthDefault())
{
mysnprintf(value, countof(value), "%g", joy->GetHapticsStrength());
GameConfig->SetValueForKey("Haptics", value);
}
numaxes = joy->GetNumAxes();
for (int i = 0; i < numaxes; ++i)
{
@ -314,12 +327,6 @@ void M_SaveJoystickConfig(IJoystickConfig *joy)
mysnprintf(value, countof(value), "%g", joy->GetAxisResponseCurvePoint(i, 3));
GameConfig->SetValueForKey(key, value);
}
if (!joy->IsAxisMapDefault(i))
{
mysnprintf(key + axislen, countof(key) - axislen, "map");
mysnprintf(value, countof(value), "%d", joy->GetAxisMap(i));
GameConfig->SetValueForKey(key, value);
}
}
// If the joystick is entirely at its defaults, delete this section
// so that we don't write out a lone section header.
@ -355,7 +362,6 @@ CCMD (gamepad)
"\n\tgamepad curve-y1 pad.axis [float]"
"\n\tgamepad curve-x2 pad.axis [float]"
"\n\tgamepad curve-y2 pad.axis [float]"
"\n\tgamepad map pad.axis [-1|0|1|2|3|4]"
"\n"
);
};
@ -429,10 +435,7 @@ CCMD (gamepad)
if (command == "reset")
{
if (set) return usage();
sticks[pad]->SetDefaultConfig();
sticks[pad]->SetEnabled(true);
sticks[pad]->SetEnabledInBackground(sticks[pad]->AllowsEnabledInBackground());
sticks[pad]->SetSensitivity(1);
sticks[pad]->Reset();
return;
}
if (command == "enabled")
@ -490,49 +493,10 @@ CCMD (gamepad)
if (set) sticks[pad]->SetAxisResponseCurvePoint(axis, 3, value);
return (void) Printf("%g\n", sticks[pad]->GetAxisResponseCurvePoint(axis, 3));
}
if (command == "map")
{
if (set) sticks[pad]->SetAxisMap(axis, (EJoyAxis)value);
return (void) Printf("%d\n", sticks[pad]->GetAxisMap(axis));
}
return usage();
}
//===========================================================================
//
// Joy_RemoveDeadZone
//
//===========================================================================
double Joy_RemoveDeadZone(double axisval, double deadzone, uint8_t *buttons)
{
uint8_t butt;
// Cancel out deadzone.
if (fabs(axisval) < deadzone)
{
axisval = 0;
butt = 0;
}
// Make the dead zone the new 0.
else if (axisval < 0)
{
axisval = (axisval + deadzone) / (1.0 - deadzone);
butt = 2; // button minus
}
else
{
axisval = (axisval - deadzone) / (1.0 - deadzone);
butt = 1; // button plus
}
if (buttons != NULL)
{
*buttons = butt;
}
return axisval;
}
//===========================================================================
//
// Joy_ApplyResponseCurveBezier
@ -575,6 +539,54 @@ double Joy_ApplyResponseCurveBezier(const CubicBezier &curve, double input)
return sign*t;
}
//===========================================================================
//
// Joy_ManageSingleAxis
//
//===========================================================================
double Joy_ManageSingleAxis(double axisval, double deadzone, double threshold, const CubicBezier &curve, uint8_t *buttons)
{
uint8_t butt;
// Cancel out deadzone.
if (fabs(axisval) < deadzone)
{
axisval = 0;
butt = 0;
}
else
{
// Make the dead zone the new 0.
if (axisval < 0)
{
axisval = (axisval + deadzone) / (1.0 - deadzone);
butt = 2; // button minus
}
else
{
axisval = (axisval - deadzone) / (1.0 - deadzone);
butt = 1; // button plus
}
// Apply input response curve
axisval = Joy_ApplyResponseCurveBezier(curve, axisval);
if (abs(axisval) < threshold)
{
// Needs to meet digital threshold to send button
butt = 0;
}
}
if (buttons != NULL)
{
*buttons = butt;
}
return axisval;
}
//===========================================================================
//
// Joy_XYAxesToButtons
@ -612,6 +624,86 @@ int Joy_XYAxesToButtons(double x, double y)
return JoyAngleButtons[int(rad) & 7];
}
//===========================================================================
//
// Joy_ManageThumbstick
//
// Given two axes and their settings, handle applying deadzone, digital
// threshold, input response curve, and setting button state. This is
// necessary, because doing the two axes individually creates awkward
// behavior (such as cross shaped deadzones, sluggish input response when
// pressing diagonally, so on).
//
//===========================================================================
double Joy_ManageThumbstick(
double *axis_x, double *axis_y,
double deadzone_x, double deadzone_y,
double threshold_x, double threshold_y,
const CubicBezier &curve_x, const CubicBezier &curve_y,
uint8_t *buttons)
{
double ret_x = *axis_x;
double ret_y = *axis_y;
double x_abs = abs(*axis_x);
double y_abs = abs(*axis_y);
double magnitude = sqrt((x_abs * x_abs) + (y_abs * y_abs));
double ret_dist = 0;
uint8_t ret_butt = 0;
double xy_lerp = 0.5;
if (magnitude > 0)
{
// Later it would be nice to have a single deadzone / threshold / curve setting
// for the whole thumbstick instead of awkwardly trying to combine them, but
// that requires re-considering how axes are exposed to the rest of the engine.
// This will do for now.
xy_lerp = abs(cos(atan2(ret_y, ret_x)));
}
double deadzone = (xy_lerp * deadzone_x) + ((1.0 - xy_lerp) * deadzone_y);
if (magnitude < deadzone)
{
ret_x = 0;
ret_y = 0;
}
else
{
// Make the dead zone the new 0.
ret_dist = (magnitude - deadzone) / (1.0 - deadzone);
const CubicBezier curve = {{
(float)((xy_lerp * curve_x.x1) + ((1.0 - xy_lerp) * curve_y.x1)),
(float)((xy_lerp * curve_x.y1) + ((1.0 - xy_lerp) * curve_y.y1)),
(float)((xy_lerp * curve_x.x2) + ((1.0 - xy_lerp) * curve_y.x2)),
(float)((xy_lerp * curve_x.y2) + ((1.0 - xy_lerp) * curve_y.y2))
}};
ret_dist = Joy_ApplyResponseCurveBezier(curve, ret_dist);
ret_x = (ret_x / magnitude) * ret_dist;
ret_y = (ret_y / magnitude) * ret_dist;
double threshold = (xy_lerp * threshold_x) + ((1.0 - xy_lerp) * threshold_y);
if (ret_dist >= threshold)
{
ret_butt = Joy_XYAxesToButtons(ret_x, ret_y);
}
}
*axis_x = ret_x;
*axis_y = ret_y;
if (buttons != NULL)
{
*buttons = ret_butt;
}
return ret_dist;
}
//===========================================================================
//
// Joy_GenerateButtonEvent

View file

@ -1,10 +1,9 @@
#ifndef M_JOY_H
#define M_JOY_H
#include "basics.h"
#include "c_cvars.h"
#include "keydef.h"
#include "tarray.h"
#include "c_cvars.h"
union CubicBezier {
struct {
@ -26,22 +25,12 @@ enum EJoyCurve {
NUM_JOYCURVE
};
enum EJoyAxis
{
JOYAXIS_None = -1,
JOYAXIS_Yaw,
JOYAXIS_Pitch,
JOYAXIS_Forward,
JOYAXIS_Side,
JOYAXIS_Up,
// JOYAXIS_Roll, // Ha ha. No roll for you.
NUM_JOYAXIS,
};
extern const float JOYDEADZONE_DEFAULT;
extern const float JOYSENSITIVITY_DEFAULT;
extern const float JOYHAPSTRENGTH_DEFAULT;
extern const float JOYTHRESH_DEFAULT;
extern const float JOYTHRESH_TRIGGER;
@ -59,9 +48,12 @@ struct IJoystickConfig
virtual float GetSensitivity() = 0;
virtual void SetSensitivity(float scale) = 0;
virtual bool HasHaptics() = 0;
virtual float GetHapticsStrength() = 0;
virtual void SetHapticsStrength(float strength) = 0;
virtual int GetNumAxes() = 0;
virtual float GetAxisDeadZone(int axis) = 0;
virtual EJoyAxis GetAxisMap(int axis) = 0;
virtual const char *GetAxisName(int axis) = 0;
virtual float GetAxisScale(int axis) = 0;
virtual float GetAxisDigitalThreshold(int axis) = 0;
@ -69,7 +61,6 @@ struct IJoystickConfig
virtual float GetAxisResponseCurvePoint(int axis, int point) = 0;
virtual void SetAxisDeadZone(int axis, float zone) = 0;
virtual void SetAxisMap(int axis, EJoyAxis gameaxis) = 0;
virtual void SetAxisScale(int axis, float scale) = 0;
virtual void SetAxisDigitalThreshold(int axis, float threshold) = 0;
virtual void SetAxisResponseCurve(int axis, EJoyCurve preset) = 0;
@ -84,14 +75,22 @@ struct IJoystickConfig
// Used by the saver to not save properties that are at their defaults.
virtual bool IsSensitivityDefault() = 0;
virtual bool IsHapticsStrengthDefault() = 0;
virtual bool IsAxisDeadZoneDefault(int axis) = 0;
virtual bool IsAxisMapDefault(int axis) = 0;
virtual bool IsAxisScaleDefault(int axis) = 0;
virtual bool IsAxisDigitalThresholdDefault(int axis) = 0;
virtual bool IsAxisResponseCurveDefault(int axis) = 0;
virtual void SetDefaultConfig() = 0;
virtual FString GetIdentifier() = 0;
void Reset()
{
SetDefaultConfig();
SetEnabled(true);
SetEnabledInBackground(AllowsEnabledInBackground());
SetSensitivity(1);
}
};
EXTERN_CVAR(Bool, use_joystick);
@ -102,12 +101,16 @@ void M_SaveJoystickConfig(IJoystickConfig *joy);
void Joy_GenerateButtonEvent(bool down, EKeyCodes which);
void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, int base);
void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, const int *keys);
int Joy_XYAxesToButtons(double x, double y);
double Joy_RemoveDeadZone(double axisval, double deadzone, uint8_t *buttons);
double Joy_ApplyResponseCurveBezier(const CubicBezier &curve, double input);
double Joy_ManageSingleAxis(double axisval, double deadzone, double threshold, const CubicBezier &curve, uint8_t *buttons);
int Joy_XYAxesToButtons(double x, double y);
double Joy_ManageThumbstick(double *axis_x, double *axis_y, double deadzone_x, double deadzone_y,
double threshold_x, double threshold_y, const CubicBezier &curve_x, const CubicBezier &curve_y, uint8_t *buttons);
// These ought to be provided by a system-specific i_input.cpp.
void I_GetAxes(float axes[NUM_JOYAXIS]);
void I_GetAxes(float axes[NUM_AXIS_CODES]);
void I_GetJoysticks(TArray<IJoystickConfig *> &sticks);
IJoystickConfig *I_UpdateDeviceList();
extern void UpdateJoystickMenu(IJoystickConfig *);

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,12 @@
/*
** joystickmenu.cpp
**
** The joystick configuration menus
**
**---------------------------------------------------------------------------
**
** Copyright 2010 Christoph Oelckers
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -28,12 +31,13 @@
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
**---------------------------------------------------------------------------
**
*/
#include "menu.h"
#include "m_joy.h"
#include "menu.h"
#include "vm.h"
static TArray<IJoystickConfig *> Joysticks;
@ -52,6 +56,26 @@ DEFINE_ACTION_FUNCTION(IJoystickConfig, SetSensitivity)
return 0;
}
DEFINE_ACTION_FUNCTION(IJoystickConfig, HasHaptics)
{
PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig);
ACTION_RETURN_BOOL(self->HasHaptics());
}
DEFINE_ACTION_FUNCTION(IJoystickConfig, GetHapticsStrength)
{
PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig);
ACTION_RETURN_FLOAT(self->GetHapticsStrength());
}
DEFINE_ACTION_FUNCTION(IJoystickConfig, SetHapticsStrength)
{
PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig);
PARAM_FLOAT(strength);
self->SetHapticsStrength((float)strength);
return 0;
}
DEFINE_ACTION_FUNCTION(IJoystickConfig, GetAxisScale)
{
PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig);
@ -134,22 +158,6 @@ DEFINE_ACTION_FUNCTION(IJoystickConfig, SetAxisResponseCurvePoint)
return 0;
}
DEFINE_ACTION_FUNCTION(IJoystickConfig, GetAxisMap)
{
PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig);
PARAM_INT(axis);
ACTION_RETURN_INT(self->GetAxisMap(axis));
}
DEFINE_ACTION_FUNCTION(IJoystickConfig, SetAxisMap)
{
PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig);
PARAM_INT(axis);
PARAM_INT(map);
self->SetAxisMap(axis, (EJoyAxis)map);
return 0;
}
DEFINE_ACTION_FUNCTION(IJoystickConfig, GetName)
{
PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig);
@ -203,6 +211,12 @@ DEFINE_ACTION_FUNCTION(IJoystickConfig, SetEnabledInBackground)
return 0;
}
DEFINE_ACTION_FUNCTION(IJoystickConfig, Reset)
{
PARAM_SELF_STRUCT_PROLOGUE(IJoystickConfig);
self->Reset();
return 0;
}
void UpdateJoystickMenu(IJoystickConfig *selected)
{
@ -279,4 +293,3 @@ void UpdateJoystickMenu(IJoystickConfig *selected)
}
}
}

View file

@ -70,7 +70,7 @@ DMenu *CreateMessageBoxMenu(DMenu *parent, const char *message, int messagemode,
IFVIRTUALPTRNAME(p, NAME_MessageBoxMenu, Init)
{
p->PointerVar<void>(NAME_Handler) = reinterpret_cast<void*>(handler);
VMValue params[] = { p, parent, &namestr, messagemode, playsound, action.GetIndex() };
VMValue params[] = { p, parent, &namestr, messagemode, playsound, action.GetIndex(), (void*)nullptr };
VMCall(func, params, countof(params), nullptr, 0);
return (DMenu*)p;
}

View file

@ -4,6 +4,7 @@
**
**---------------------------------------------------------------------------
** Copyright 2010-2017 Christoph Oelckers
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -33,14 +34,14 @@
*/
#include "c_cvars.h"
#include "v_video.h"
#include "menu.h"
#include "vm.h"
// by request of Nash
CVARD(Bool, silence_menu_scroll, false, CVAR_GLOBALCONFIG | CVAR_ARCHIVE, "Silences cursor movement when using mouse wheel");
CVARD(Bool, silence_menu_hover, false, CVAR_GLOBALCONFIG | CVAR_ARCHIVE, "Silences cursor movement when implicitly selecting with mouse");
CVARD(Bool, silence_menu_scroll, true, CVAR_GLOBALCONFIG | CVAR_ARCHIVE, "Silences cursor movement when using mouse wheel");
CVARD(Bool, silence_menu_hover, true, CVAR_GLOBALCONFIG | CVAR_ARCHIVE, "Silences cursor movement when implicitly selecting with mouse");
CVARD(Int, menu_overscroll, 8, CVAR_GLOBALCONFIG | CVAR_ARCHIVE, "Number of lines you can scroll past the bottom of a menu");
//=============================================================================
//

View file

@ -5,6 +5,7 @@
**---------------------------------------------------------------------------
** Copyright 1998-2016 Randy Heit
** Copyright 2005-2016 Christoph Oelckers
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -816,7 +817,7 @@ void PClass::BuildFlatPointers() const
else
{
pairType *flat = (pairType*)ClassDataAllocator.Alloc(sizeof(pairType) * NativePointers.Size());
memcpy(flat, NativePointers.Data(), sizeof(pairType) * NativePointers.Size());
std::copy(NativePointers.Data(), NativePointers.Data() + NativePointers.Size(), flat);
FlatPointers = flat;
FlatPointersSize = NativePointers.Size();
@ -849,15 +850,15 @@ void PClass::BuildFlatPointers() const
if (ParentClass->FlatPointersSize > 0)
{
memcpy (flat, ParentClass->FlatPointers, sizeof(pairType) * ParentClass->FlatPointersSize);
std::copy(ParentClass->FlatPointers, ParentClass->FlatPointers + ParentClass->FlatPointersSize, flat);
}
if (NativePointers.Size() > 0)
{
memcpy(flat + ParentClass->FlatPointersSize, NativePointers.Data(), sizeof(pairType) * NativePointers.Size());
std::copy(NativePointers.Data(), NativePointers.Data() + NativePointers.Size(), flat + ParentClass->FlatPointersSize);
}
if (ScriptPointers.Size() > 0)
{
memcpy(flat + ParentClass->FlatPointersSize + NativePointers.Size(), &ScriptPointers[0], sizeof(pairType) * ScriptPointers.Size());
std::copy(&ScriptPointers[0], &ScriptPointers[0] + ScriptPointers.Size(), flat + ParentClass->FlatPointersSize + NativePointers.Size());
}
FlatPointers = flat;
FlatPointersSize = ParentClass->FlatPointersSize + NativePointers.Size() + ScriptPointers.Size();
@ -913,12 +914,12 @@ void PClass::BuildArrayPointers() const
pairType *flat = (pairType*)ClassDataAllocator.Alloc(sizeof(pairType) * (ParentClass->ArrayPointersSize + ScriptPointers.Size()));
if (ParentClass->ArrayPointersSize > 0)
{
memcpy(flat, ParentClass->ArrayPointers, sizeof(pairType) * ParentClass->ArrayPointersSize);
std::copy(ParentClass->ArrayPointers, ParentClass->ArrayPointers + ParentClass->ArrayPointersSize, flat);
}
if (ScriptPointers.Size() > 0)
{
memcpy(flat + ParentClass->ArrayPointersSize, ScriptPointers.Data(), sizeof(pairType) * ScriptPointers.Size());
std::copy(ScriptPointers.Data(), ScriptPointers.Data() + ScriptPointers.Size(), flat + ParentClass->ArrayPointersSize);
}
ArrayPointers = flat;
@ -974,12 +975,12 @@ void PClass::BuildMapPointers() const
pairType *flat = (pairType*)ClassDataAllocator.Alloc(sizeof(pairType) * (ParentClass->MapPointersSize + ScriptPointers.Size()));
if (ParentClass->MapPointersSize > 0)
{
memcpy(flat, ParentClass->MapPointers, sizeof(pairType) * ParentClass->MapPointersSize);
std::copy(ParentClass->MapPointers, ParentClass->MapPointers + ParentClass->MapPointersSize, flat);
}
if (ScriptPointers.Size() > 0)
{
memcpy(flat + ParentClass->MapPointersSize, ScriptPointers.Data(), sizeof(pairType) * ScriptPointers.Size());
std::copy(ScriptPointers.Data(), ScriptPointers.Data() + ScriptPointers.Size(), flat + ParentClass->MapPointersSize);
}
MapPointers = flat;
MapPointersSize = ParentClass->MapPointersSize + ScriptPointers.Size();

View file

@ -1,35 +1,38 @@
/*
** i_joystick.cpp
**
**---------------------------------------------------------------------------
** Copyright 2012-2015 Alexey Lysiuk
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
** i_joystick.cpp
**
**---------------------------------------------------------------------------
**
** Copyright 2012-2015 Alexey Lysiuk
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
**---------------------------------------------------------------------------
**
*/
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/IOMessage.h>
@ -44,9 +47,8 @@
#include "printf.h"
#include "keydef.h"
EXTERN_CVAR(Bool, joy_axespolling)
EXTERN_CVAR(Bool, use_joystick)
namespace
{
@ -75,10 +77,8 @@ FString ToFString(const CFStringRef string)
return FString(buffer);
}
// ---------------------------------------------------------------------------
class IOKitJoystick : public IJoystickConfig
{
public:
@ -89,9 +89,12 @@ public:
virtual float GetSensitivity();
virtual void SetSensitivity(float scale);
virtual bool HasHaptics();
virtual float GetHapticsStrength();
virtual void SetHapticsStrength(float strength);
virtual int GetNumAxes();
virtual float GetAxisDeadZone(int axis);
virtual EJoyAxis GetAxisMap(int axis);
virtual const char* GetAxisName(int axis);
virtual float GetAxisScale(int axis);
float GetAxisDigitalThreshold(int axis);
@ -99,15 +102,14 @@ public:
float GetAxisResponseCurvePoint(int axis, int point);
virtual void SetAxisDeadZone(int axis, float deadZone);
virtual void SetAxisMap(int axis, EJoyAxis gameAxis);
virtual void SetAxisScale(int axis, float scale);
void SetAxisDigitalThreshold(int axis, float threshold);
void SetAxisResponseCurve(int axis, EJoyCurve preset);
void SetAxisResponseCurvePoint(int axis, int point, float value);
virtual bool IsSensitivityDefault();
virtual bool IsHapticsStrengthDefault();
virtual bool IsAxisDeadZoneDefault(int axis);
virtual bool IsAxisMapDefault(int axis);
virtual bool IsAxisScaleDefault(int axis);
bool IsAxisDigitalThresholdDefault(int axis);
bool IsAxisResponseCurveDefault(int axis);
@ -122,7 +124,7 @@ public:
virtual void SetDefaultConfig();
virtual FString GetIdentifier();
void AddAxes(float axes[NUM_JOYAXIS]) const;
void AddAxes(float axes[NUM_AXIS_CODES]) const;
void Update();
@ -160,8 +162,7 @@ private:
EJoyCurve defaultResponseCurvePreset;
CubicBezier responseCurve;
EJoyAxis gameAxis;
EJoyAxis defaultGameAxis;
uint8_t buttonValue;
AnalogAxis()
{
@ -208,7 +209,6 @@ private:
void RemoveFromQueue(IOHIDElementCookie cookie);
};
IOHIDDeviceInterface** CreateDeviceInterface(const io_object_t device)
{
IOCFPlugInInterface** plugInInterface = NULL;
@ -287,7 +287,6 @@ IOHIDQueueInterface** CreateDeviceQueue(IOHIDDeviceInterface** const interface)
return queue;
}
IOKitJoystick::IOKitJoystick(const io_object_t device)
: m_interface(CreateDeviceInterface(device))
, m_queue(CreateDeviceQueue(m_interface))
@ -346,13 +345,11 @@ IOKitJoystick::~IOKitJoystick()
}
}
FString IOKitJoystick::GetName()
{
return m_name;
}
float IOKitJoystick::GetSensitivity()
{
return m_sensitivity;
@ -363,6 +360,20 @@ void IOKitJoystick::SetSensitivity(float scale)
m_sensitivity = scale;
}
bool IOKitJoystick::HasHaptics()
{
return false;
}
float IOKitJoystick::GetHapticsStrength()
{
return JOYHAPSTRENGTH_DEFAULT;
}
void IOKitJoystick::SetHapticsStrength(float strength)
{
// nope
}
int IOKitJoystick::GetNumAxes()
{
@ -376,11 +387,6 @@ float IOKitJoystick::GetAxisDeadZone(int axis)
return IS_AXIS_VALID ? m_axes[axis].deadZone : 0.0f;
}
EJoyAxis IOKitJoystick::GetAxisMap(int axis)
{
return IS_AXIS_VALID ? m_axes[axis].gameAxis : JOYAXIS_None;
}
const char* IOKitJoystick::GetAxisName(int axis)
{
return IS_AXIS_VALID ? m_axes[axis].name : "Invalid";
@ -414,16 +420,6 @@ void IOKitJoystick::SetAxisDeadZone(int axis, float deadZone)
}
}
void IOKitJoystick::SetAxisMap(int axis, EJoyAxis gameAxis)
{
if (IS_AXIS_VALID)
{
m_axes[axis].gameAxis = (gameAxis> JOYAXIS_None && gameAxis <NUM_JOYAXIS)
? gameAxis
: JOYAXIS_None;
}
}
void IOKitJoystick::SetAxisScale(int axis, float scale)
{
if (IS_AXIS_VALID)
@ -465,6 +461,11 @@ bool IOKitJoystick::IsSensitivityDefault()
return JOYSENSITIVITY_DEFAULT == m_sensitivity;
}
bool IOKitJoystick::IsHapticsStrengthDefault()
{
return true;
}
bool IOKitJoystick::IsAxisDeadZoneDefault(int axis)
{
return IS_AXIS_VALID
@ -472,13 +473,6 @@ bool IOKitJoystick::IsAxisDeadZoneDefault(int axis)
: true;
}
bool IOKitJoystick::IsAxisMapDefault(int axis)
{
return IS_AXIS_VALID
? (m_axes[axis].gameAxis == m_axes[axis].defaultGameAxis)
: true;
}
bool IOKitJoystick::IsAxisScaleDefault(int axis)
{
return IS_AXIS_VALID
@ -521,7 +515,6 @@ void IOKitJoystick::SetDefaultConfig()
{
m_axes[i].deadZone = JOYDEADZONE_DEFAULT;
m_axes[i].sensitivity = JOYSENSITIVITY_DEFAULT;
m_axes[i].gameAxis = JOYAXIS_None;
m_axes[i].digitalThreshold = JOYTHRESH_DEFAULT;
m_axes[i].responseCurvePreset = JOYCURVE_DEFAULT;
m_axes[i].responseCurve = JOYCURVE[JOYCURVE_DEFAULT];
@ -531,10 +524,7 @@ void IOKitJoystick::SetDefaultConfig()
if (2 == axisCount)
{
m_axes[0].gameAxis = JOYAXIS_Yaw;
m_axes[0].digitalThreshold = JOYTHRESH_STICK_X;
m_axes[1].gameAxis = JOYAXIS_Forward;
m_axes[1].digitalThreshold = JOYTHRESH_STICK_Y;
}
@ -542,20 +532,14 @@ void IOKitJoystick::SetDefaultConfig()
else if (axisCount >= 3)
{
m_axes[0].gameAxis = JOYAXIS_Side;
m_axes[0].digitalThreshold = JOYTHRESH_STICK_X;
m_axes[1].gameAxis = JOYAXIS_Forward;
m_axes[1].digitalThreshold = JOYTHRESH_STICK_Y;
m_axes[2].gameAxis = JOYAXIS_Yaw;
m_axes[2].digitalThreshold = JOYTHRESH_STICK_X;
// Four axes? First two are movement, last two are looking around.
if (axisCount >= 4)
{
m_axes[3].gameAxis = JOYAXIS_Pitch;
// ??? m_axes[3].sensitivity = 0.75f;
m_axes[3].digitalThreshold = JOYTHRESH_STICK_Y;
@ -563,7 +547,6 @@ void IOKitJoystick::SetDefaultConfig()
if (axisCount >= 5)
{
m_axes[4].gameAxis = JOYAXIS_Up;
m_axes[4].digitalThreshold = JOYTHRESH_STICK_Y;
}
}
@ -578,35 +561,43 @@ void IOKitJoystick::SetDefaultConfig()
{
m_axes[i].defaultDeadZone = m_axes[i].deadZone;
m_axes[i].defaultSensitivity = m_axes[i].sensitivity;
m_axes[i].defaultGameAxis = m_axes[i].gameAxis;
m_axes[i].defaultDigitalThreshold = m_axes[i].digitalThreshold;
m_axes[i].defaultResponseCurvePreset = m_axes[i].responseCurvePreset;
}
}
FString IOKitJoystick::GetIdentifier()
{
return m_identifier;
}
void IOKitJoystick::AddAxes(float axes[NUM_JOYAXIS]) const
void IOKitJoystick::AddAxes(float axes[NUM_AXIS_CODES]) const
{
for (size_t i = 0, count = m_axes.Size(); i < count; ++i)
{
const EJoyAxis axis = m_axes[i].gameAxis;
// Add to the game axis.
float axis_value = m_axes[i].value;
int code = AXIS_CODE_NULL;
if (JOYAXIS_None == axis)
if (i < NUM_JOYAXISBUTTONS)
{
continue;
if (axis_value > 0.0f)
{
code = AXIS_CODE_JOY1_PLUS + (i * 2);
}
else if (axis_value < 0.0f)
{
code = AXIS_CODE_JOY1_PLUS + (i * 2) + 1;
}
}
axes[axis] -= m_axes[i].value;
if (code != AXIS_CODE_NULL)
{
axes[code] += fabs(axis_value);
}
}
}
void IOKitJoystick::UseAxesPolling(const bool axesPolling)
{
m_useAxesPolling = axesPolling;
@ -626,7 +617,6 @@ void IOKitJoystick::UseAxesPolling(const bool axesPolling)
}
}
void IOKitJoystick::Update()
{
if (NULL == m_queue)
@ -654,7 +644,6 @@ void IOKitJoystick::Update()
if(m_enabled) ProcessAxes();
}
void IOKitJoystick::ProcessAxes()
{
if (NULL == m_interface || !m_useAxesPolling)
@ -664,30 +653,83 @@ void IOKitJoystick::ProcessAxes()
for (size_t i = 0, count = m_axes.Size(); i < count; ++i)
{
AnalogAxis& axis = m_axes[i];
static const double scaledMin = -1;
static const double scaledMax = 1;
IOHIDEventStruct event;
if (kIOReturnSuccess == (*m_interface)->getElementValue(m_interface, axis.cookie, &event))
if (i < NUM_JOYAXISBUTTONS && (i > 2 || m_axes.Size() == 1))
{
const double scaledValue = scaledMin +
(event.value - axis.minValue) * (scaledMax - scaledMin) / (axis.maxValue - axis.minValue);
const double filteredValue = Joy_RemoveDeadZone(scaledValue, axis.deadZone, NULL);
const double smoothedValue = Joy_ApplyResponseCurveBezier(axis.responseCurve, filteredValue);
AnalogAxis& axis = m_axes[i];
uint8_t buttonstate = 0;
axis.value = static_cast<float>(smoothedValue * m_sensitivity * axis.sensitivity);
IOHIDEventStruct event;
if (kIOReturnSuccess == (*m_interface)->getElementValue(m_interface, axis.cookie, &event))
{
const double scaledValue = scaledMin +
(event.value - axis.minValue) * (scaledMax - scaledMin) / (axis.maxValue - axis.minValue);
const double filteredValue = Joy_ManageSingleAxis(
scaledValue,
axis.deadZone,
axis.digitalThreshold,
axis.responseCurve,
&buttonstate
);
axis.value = static_cast<float>(filteredValue * m_sensitivity * axis.sensitivity);
}
else
{
axis.value = 0.0f;
}
Joy_GenerateButtonEvents(axis.buttonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2);
axis.buttonValue = buttonstate;
}
else
else if (i == 1)
{
axis.value = 0.0f;
// Since we sorted the axes, we know that the first two are definitely X and Y.
// They are probably a single stick, so treat them as one.
AnalogAxis& axis_x = m_axes[i - 1];
AnalogAxis& axis_y = m_axes[i];
uint8_t buttonstate = 0;
double axisval_x = 0.0f;
double axisval_y = 0.0f;
IOHIDEventStruct event;
if (kIOReturnSuccess == (*m_interface)->getElementValue(m_interface, axis_x.cookie, &event))
{
axisval_x = scaledMin +
(event.value - axis_x.minValue) * (scaledMax - scaledMin) / (axis_x.maxValue - axis_x.minValue);
}
if (kIOReturnSuccess == (*m_interface)->getElementValue(m_interface, axis_y.cookie, &event))
{
axisval_y = scaledMin +
(event.value - axis_y.minValue) * (scaledMax - scaledMin) / (axis_y.maxValue - axis_y.minValue);
}
Joy_ManageThumbstick(
&axisval_x, &axisval_y,
axis_x.deadZone, axis_y.deadZone,
axis_x.digitalThreshold, axis_y.digitalThreshold,
axis_x.responseCurve, axis_y.responseCurve,
&buttonstate
);
axis_x.value = static_cast<float>(axisval_x * m_sensitivity * axis_x.sensitivity);
axis_y.value = static_cast<float>(axisval_y * m_sensitivity * axis_y.sensitivity);
// We store all four buttons in the first axis and ignore the second.
Joy_GenerateButtonEvents(axis_x.buttonValue, buttonstate, 4, KEY_JOYAXIS1PLUS + (i-1)*2);
axis_x.buttonValue = buttonstate;
}
}
}
bool IOKitJoystick::ProcessAxis(const IOHIDEventStruct& event)
{
if (m_useAxesPolling)
@ -703,17 +745,29 @@ bool IOKitJoystick::ProcessAxis(const IOHIDEventStruct& event)
}
AnalogAxis& axis = m_axes[i];
uint8_t buttonstate = 0;
static const double scaledMin = -1;
static const double scaledMax = 1;
const double scaledValue = scaledMin +
(event.value - axis.minValue) * (scaledMax - scaledMin) / (axis.maxValue - axis.minValue);
const double filteredValue = Joy_RemoveDeadZone(scaledValue, axis.deadZone, NULL);
const double smoothedValue = Joy_ApplyResponseCurveBezier(axis.responseCurve, filteredValue);
axis.value = static_cast<float>(smoothedValue * m_sensitivity * axis.sensitivity);
const double filteredValue = Joy_ManageSingleAxis(
scaledValue,
axis.deadZone,
axis.digitalThreshold,
axis.responseCurve,
&buttonstate
);
axis.value = static_cast<float>(filteredValue * m_sensitivity * axis.sensitivity);
if (i < NUM_JOYAXISBUTTONS)
{
Joy_GenerateButtonEvents(axis.buttonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2);
}
axis.buttonValue = buttonstate;
return true;
}
@ -767,7 +821,6 @@ bool IOKitJoystick::ProcessPOV(const IOHIDEventStruct& event)
return false;
}
void IOKitJoystick::GatherDeviceInfo(const io_object_t device, const CFDictionaryRef properties)
{
assert(NULL != properties);
@ -872,7 +925,6 @@ void IOKitJoystick::GatherDeviceInfo(const io_object_t device, const CFDictionar
}
}
long GetElementValue(const CFDictionaryRef element, const CFStringRef key)
{
const CFNumberRef number =
@ -949,7 +1001,6 @@ void IOKitJoystick::GatherCollectionElements(const CFDictionaryRef properties)
CFArrayApplyFunction(topElement, range, GatherElementsHandler, this);
}
IOHIDElementCookie GetElementCookie(const CFDictionaryRef element)
{
// Use C-style cast to avoid 32/64-bit IOHIDElementCookie type issue
@ -997,7 +1048,6 @@ void IOKitJoystick::AddPOV(CFDictionaryRef element)
AddToQueue(pov.cookie);
}
void IOKitJoystick::AddToQueue(const IOHIDElementCookie cookie)
{
if (NULL == m_queue)
@ -1024,16 +1074,13 @@ void IOKitJoystick::RemoveFromQueue(const IOHIDElementCookie cookie)
}
}
io_object_t* IOKitJoystick::GetNotificationPtr()
{
return &m_notification;
}
// ---------------------------------------------------------------------------
class IOKitJoystickManager
{
public:
@ -1042,7 +1089,7 @@ public:
void GetJoysticks(TArray<IJoystickConfig*>& joysticks) const;
void AddAxes(float axes[NUM_JOYAXIS]) const;
void AddAxes(float axes[NUM_AXIS_CODES]) const;
// Updates axes/buttons states
void Update();
@ -1067,10 +1114,8 @@ private:
natural_t messageType, void* messageArgument);
};
IOKitJoystickManager* s_joystickManager;
IOKitJoystickManager::IOKitJoystickManager()
{
memset(m_notifications, 0, sizeof m_notifications);
@ -1118,7 +1163,6 @@ IOKitJoystickManager::~IOKitJoystickManager()
}
}
void IOKitJoystickManager::GetJoysticks(TArray<IJoystickConfig*>& joysticks) const
{
const size_t joystickCount = m_joysticks.Size();
@ -1133,7 +1177,7 @@ void IOKitJoystickManager::GetJoysticks(TArray<IJoystickConfig*>& joysticks) con
}
}
void IOKitJoystickManager::AddAxes(float axes[NUM_JOYAXIS]) const
void IOKitJoystickManager::AddAxes(float axes[NUM_AXIS_CODES]) const
{
for (size_t i = 0, count = m_joysticks.Size(); i < count; ++i)
{
@ -1141,7 +1185,6 @@ void IOKitJoystickManager::AddAxes(float axes[NUM_JOYAXIS]) const
}
}
void IOKitJoystickManager::Update()
{
for (size_t i = 0, count = m_joysticks.Size(); i < count; ++i)
@ -1150,7 +1193,6 @@ void IOKitJoystickManager::Update()
}
}
void IOKitJoystickManager::UseAxesPolling(const bool axesPolling)
{
for (size_t i = 0, count = m_joysticks.Size(); i < count; ++i)
@ -1159,14 +1201,12 @@ void IOKitJoystickManager::UseAxesPolling(const bool axesPolling)
}
}
void PostDeviceChangeEvent()
{
event_t event = { EV_DeviceChange };
D_PostEvent(&event);
}
void IOKitJoystickManager::Rescan(const int usagePage, const int usage, const size_t notificationPortIndex)
{
CFMutableDictionaryRef deviceMatching = IOServiceMatching(kIOHIDDeviceKey);
@ -1228,7 +1268,6 @@ void IOKitJoystickManager::AddDevices(const IONotificationPortRef notificationPo
}
}
void IOKitJoystickManager::OnDeviceAttached(void* const refcon, const io_iterator_t iterator)
{
assert(NULL != refcon);
@ -1267,10 +1306,8 @@ void IOKitJoystickManager::OnDeviceRemoved(void* const refcon, io_service_t, con
} // unnamed namespace
// ---------------------------------------------------------------------------
void I_ShutdownInput()
{
delete s_joystickManager;
@ -1296,9 +1333,9 @@ void I_GetJoysticks(TArray<IJoystickConfig*>& sticks)
}
}
void I_GetAxes(float axes[NUM_JOYAXIS])
void I_GetAxes(float axes[NUM_AXIS_CODES])
{
for (size_t i = 0; i < NUM_JOYAXIS; ++i)
for (size_t i = 0; i < NUM_AXIS_CODES; ++i)
{
axes[i] = 0.0f;
}
@ -1316,10 +1353,8 @@ IJoystickConfig* I_UpdateDeviceList()
return NULL;
}
// ---------------------------------------------------------------------------
void I_ProcessJoysticks()
{
if (NULL != s_joystickManager)
@ -1328,10 +1363,8 @@ void I_ProcessJoysticks()
}
}
// ---------------------------------------------------------------------------
CUSTOM_CVAR(Bool, joy_axespolling, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL)
{
if (NULL != s_joystickManager)
@ -1339,3 +1372,11 @@ CUSTOM_CVAR(Bool, joy_axespolling, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR
s_joystickManager->UseAxesPolling(self);
}
}
void I_Rumble(double high_freq, double low_freq, double left_trig, double right_trig)
{
if (!use_joystick) return;
// stub
}

View file

@ -3,6 +3,7 @@
**
**---------------------------------------------------------------------------
** Copyright 2012-2018 Alexey Lysiuk
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -34,28 +35,26 @@
#include "i_common.h"
#include "s_soundinternal.h"
#include <sys/sysctl.h>
#include <csignal>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include "c_console.h"
#include "c_cvars.h"
#include "cmdlib.h"
#include "engineerrors.h"
#include "i_system.h"
#include "m_argv.h"
#include "st_console.h"
#include "version.h"
#include "printf.h"
#include "s_music.h"
#include "engineerrors.h"
#include "st_console.h"
#include "version.h"
#include "zstring.h"
#define ZD_UNUSED(VARIABLE) ((void)(VARIABLE))
// ---------------------------------------------------------------------------
EXTERN_CVAR(Int, vid_defwidth )
EXTERN_CVAR(Int, vid_defheight)
EXTERN_CVAR(Bool, vid_vsync )
@ -65,7 +64,9 @@ int GameMain();
extern bool RunningAsTool;
// ---------------------------------------------------------------------------
void SignalHandler(int signal);
// ---------------------------------------------------------------------------
void Mac_I_FatalError(const char* const message)
{
@ -163,18 +164,12 @@ void I_DetectOS()
case 16: name = "Big Sur"; break;
}
break;
case 11:
name = "Big Sur";
break;
case 12:
name = "Monterey";
break;
case 13:
name = "Ventura";
break;
case 14:
name = "Sonoma";
break;
case 11: name = "Big Sur"; break;
case 12: name = "Monterey"; break;
case 13: name = "Ventura"; break;
case 14: name = "Sonoma"; break;
case 15: name = "Sequoia"; break;
case 26: name = "Tahoe"; break;
}
char release[16] = "unknown";
@ -212,6 +207,11 @@ TArray<FString> s_argv;
int DoMain(int argc, char** argv)
{
signal(SIGINT, SignalHandler);
signal(SIGTERM, SignalHandler);
// signal(SIGHUP, SignalHandler);
// signal(SIGQUIT, SignalHandler);
printf(GAMENAME" %s - %s - Cocoa version\nCompiled on %s\n\n",
GetVersionString(), GetGitTime(), __DATE__);

View file

@ -3,6 +3,7 @@
**
**---------------------------------------------------------------------------
** Copyright 2012-2018 Alexey Lysiuk
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -32,22 +33,24 @@
*/
#ifdef HAVE_VULKAN
#include <zvulkan/vulkansurface.h>
#include <zvulkan/vulkanbuilders.h>
#include <zvulkan/vulkansurface.h>
#endif
#include "i_common.h"
#include "i_interface.h"
#include "v_video.h"
#include "bitmap.h"
#include "c_dispatch.h"
#include "gl_framebuffer.h"
#include "hardware.h"
#include "i_system.h"
#include "m_argv.h"
#include "m_png.h"
#include "printf.h"
#include "st_console.h"
#include "v_text.h"
#include "v_video.h"
#include "version.h"
#include "printf.h"
@ -56,6 +59,7 @@
#endif
bool I_CreateVulkanSurface(VkInstance instance, VkSurfaceKHR *surface);
#endif
extern bool ToggleFullscreen;
@ -305,7 +309,9 @@ public:
~CocoaVideo()
{
#ifdef HAVE_VULKAN
m_vulkanSurface.reset();
#endif
ms_window = nil;
}

View file

@ -3,6 +3,7 @@
**
**---------------------------------------------------------------------------
** Copyright 2005-2016 Christoph Oelckers et.al.
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -33,17 +34,17 @@
// HEADER FILES ------------------------------------------------------------
#include "c_console.h"
#include "c_dispatch.h"
#include "i_module.h"
#include "i_soundinternal.h"
#include "i_system.h"
#include "i_video.h"
#include "i_interface.h"
#include "m_argv.h"
#include "printf.h"
#include "v_video.h"
#include "version.h"
#include "c_console.h"
#include "c_dispatch.h"
#include "printf.h"
#include "hardware.h"
@ -58,7 +59,7 @@
// MACROS ------------------------------------------------------------------
#if defined HAVE_VULKAN
#include <SDL_vulkan.h>
#include <SDL2/SDL_vulkan.h>
#endif // HAVE_VULKAN
// TYPES -------------------------------------------------------------------
@ -377,11 +378,6 @@ IVideo *gl_CreateVideo()
SystemBaseFrameBuffer::SystemBaseFrameBuffer (void *, bool fullscreen)
: DFrameBuffer (vid_defwidth, vid_defheight)
{
if (Priv::window != nullptr)
{
SDL_SetWindowFullscreen(Priv::window, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
SDL_ShowWindow(Priv::window);
}
}
int SystemBaseFrameBuffer::GetClientWidth()
@ -414,6 +410,7 @@ bool SystemBaseFrameBuffer::IsFullscreen ()
void SystemBaseFrameBuffer::ToggleFullscreen(bool yes)
{
SDL_ShowWindow(Priv::window);
SDL_SetWindowFullscreen(Priv::window, yes ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
if ( !yes )
{
@ -458,6 +455,7 @@ void SystemBaseFrameBuffer::SetWindowSize(int w, int h)
}
void ProcessSDLWindowEvent(const SDL_WindowEvent &event)
{
switch (event.event)

View file

@ -1,7 +1,7 @@
#ifndef __POSIX_SDL_GL_SYSFB_H__
#define __POSIX_SDL_GL_SYSFB_H__
#include <SDL.h>
#include <SDL2/SDL.h>
#include "v_video.h"
@ -26,4 +26,3 @@ protected:
};
#endif // __POSIX_SDL_GL_SYSFB_H__

View file

@ -4,6 +4,7 @@
**
**---------------------------------------------------------------------------
** Copyright 1998-2006 Randy Heit
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -32,21 +33,20 @@
**
*/
#include <SDL.h>
#include <SDL2/SDL.h>
#include <signal.h>
#include "i_system.h"
#include "hardware.h"
#include "c_dispatch.h"
#include "v_text.h"
#include "m_argv.h"
#include "c_console.h"
#include "c_dispatch.h"
#include "hardware.h"
#include "i_system.h"
#include "m_argv.h"
#include "printf.h"
#include "v_text.h"
IVideo *Video;
void I_ShutdownGraphics ()
{
if (screen)

View file

@ -3,6 +3,7 @@
**
**---------------------------------------------------------------------------
** Copyright 2008 Randy Heit
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -33,7 +34,7 @@
#include <string.h>
#include <SDL.h>
#include <SDL2/SDL.h>
#include "bitmap.h"
#include "textures.h"

View file

@ -1,8 +1,11 @@
/*
** i_input.cpp
**
** Handles input from keyboard, mouse, and joystick
**
**---------------------------------------------------------------------------
** Copyright 2005-2016 Randy Heit
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -30,25 +33,23 @@
**---------------------------------------------------------------------------
**
*/
#include <SDL.h>
#include <SDL_events.h>
#include "i_input.h"
#include "c_cvars.h"
#include "dobject.h"
#include "m_argv.h"
#include "m_joy.h"
#include "v_video.h"
#include "d_gui.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_events.h>
#include "c_buttons.h"
#include "c_console.h"
#include "c_dispatch.h"
#include "c_cvars.h"
#include "d_gui.h"
#include "dikeys.h"
#include "utf8.h"
#include "keydef.h"
#include "i_interface.h"
#include "engineerrors.h"
#include "i_input.h"
#include "i_interface.h"
#include "keydef.h"
#include "m_haptics.h"
#include "m_joy.h"
#include "utf8.h"
#include "v_video.h"
bool GUICapture;
static bool NativeMouse = true;
@ -451,6 +452,10 @@ void MessagePump (const SDL_Event &sev)
{
event.data2 = sev.key.keysym.sym;
}
SDL_Keymod kmod = SDL_GetModState();
event.data3 = ((kmod & KMOD_SHIFT) ? GKM_SHIFT : 0) |
((kmod & KMOD_CTRL) ? GKM_CTRL : 0) |
((kmod & KMOD_ALT) ? GKM_ALT : 0);
D_PostEvent (&event);
}
}
@ -607,6 +612,7 @@ void I_StartTic ()
I_CheckGUICapture ();
I_CheckNativeMouse ();
I_GetEvent ();
Joy_RumbleTick();
}
void I_ProcessJoysticks ();

View file

@ -1,8 +1,12 @@
/*
** i_joystick.cpp
**
** Handles sdl joysticks and gamepads
**
**---------------------------------------------------------------------------
**
** Copyright 2005-2016 Randy Heit
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -27,20 +31,37 @@
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
**---------------------------------------------------------------------------
**
*/
#include <SDL.h>
#include <SDL_gamecontroller.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_gamecontroller.h>
#include <cstdint>
#include <cstdlib>
#include "basics.h"
#include "cmdlib.h"
#include "d_eventbase.h"
#include "i_input.h"
#include "m_joy.h"
static const EAxisCodes ControllerAxisCodes[][2] =
{
{ AXIS_CODE_PAD_LTHUMB_RIGHT, AXIS_CODE_PAD_LTHUMB_LEFT },
{ AXIS_CODE_PAD_LTHUMB_DOWN, AXIS_CODE_PAD_LTHUMB_UP },
{ AXIS_CODE_PAD_RTHUMB_RIGHT, AXIS_CODE_PAD_RTHUMB_LEFT },
{ AXIS_CODE_PAD_RTHUMB_DOWN, AXIS_CODE_PAD_RTHUMB_UP },
{ AXIS_CODE_PAD_LTRIGGER, AXIS_CODE_NULL },
{ AXIS_CODE_PAD_RTRIGGER, AXIS_CODE_NULL }
};
#define HAPTICS 0b0001
#define HAPTICS_TRIGGERS 0b0010
EXTERN_CVAR(Bool, use_joystick)
class SDLInputJoystick: public IJoystickConfig
{
public:
@ -49,6 +70,8 @@ public:
InstanceID(SDL_JoystickGetDeviceInstanceID(DeviceIndex)),
Multiplier(JOYSENSITIVITY_DEFAULT),
Enabled(true),
Haptics(0),
HapticsStrength(JOYHAPSTRENGTH_DEFAULT),
SettingsChanged(false)
{
if (SDL_IsGameController(DeviceIndex))
@ -63,6 +86,7 @@ public:
{
NumAxes = SDL_CONTROLLER_AXIS_MAX;
NumHats = 0;
Haptics = SDL_GameControllerHasRumble(Mapping) | SDL_GameControllerHasRumbleTriggers(Mapping) << 1;
SetDefaultConfig();
}
@ -116,6 +140,25 @@ public:
Multiplier = scale;
}
bool HasHaptics()
{
return Haptics != 0;
}
float GetHapticsStrength()
{
return HasHaptics()
? HapticsStrength
: 0;
}
void SetHapticsStrength(float strength)
{
if (!HasHaptics()) return;
SettingsChanged = true;
HapticsStrength = clamp(strength, 0.f, 2.f);
}
int GetNumAxes()
{
return NumAxes + NumHats*2;
@ -124,10 +167,6 @@ public:
{
return Axes[axis].DeadZone;
}
EJoyAxis GetAxisMap(int axis)
{
return Axes[axis].GameAxis;
}
const char *GetAxisName(int axis)
{
return Axes[axis].Name.GetChars();
@ -156,11 +195,6 @@ public:
SettingsChanged = true;
Axes[axis].DeadZone = clamp(zone, 0.f, 1.f);
}
void SetAxisMap(int axis, EJoyAxis gameaxis)
{
SettingsChanged = true;
Axes[axis].GameAxis = gameaxis;
}
void SetAxisScale(int axis, float scale)
{
SettingsChanged = true;
@ -194,18 +228,16 @@ public:
{
return Multiplier == JOYSENSITIVITY_DEFAULT;
}
bool IsHapticsStrengthDefault()
{
return HapticsStrength == JOYHAPSTRENGTH_DEFAULT;
}
bool IsAxisDeadZoneDefault(int axis)
{
if(axis >= DefaultAxesCount)
return Axes[axis].DeadZone == JOYDEADZONE_DEFAULT;
return Axes[axis].DeadZone == DefaultAxes[axis].DeadZone;
}
bool IsAxisMapDefault(int axis)
{
if(axis >= DefaultAxesCount)
return Axes[axis].GameAxis == JOYAXIS_None;
return Axes[axis].GameAxis == DefaultAxes[axis].GameAxis;
}
bool IsAxisScaleDefault(int axis)
{
if(axis >= DefaultAxesCount)
@ -225,6 +257,29 @@ public:
return Axes[axis].ResponseCurvePreset == DefaultAxes[axis].ResponseCurvePreset;
}
void Rumble(float high_freq, float low_freq, float left_trig, float right_trig)
{
uint16_t duration_ms = -1; // turn on for max time (we'll turn it off later ourselves)
if (Haptics & HAPTICS)
{
SDL_GameControllerRumble(
Mapping,
static_cast<uint16_t> (0xffff * clamp(high_freq*HapticsStrength, 0.f, 1.f)),
static_cast<uint16_t> (0xffff * clamp(low_freq*HapticsStrength, 0.f, 1.f)),
duration_ms);
}
if (Haptics & HAPTICS_TRIGGERS)
{
SDL_GameControllerRumbleTriggers(
Mapping,
static_cast<uint16_t> (0xffff * clamp(left_trig*HapticsStrength, 0.f, 1.f)),
static_cast<uint16_t> (0xffff * clamp(right_trig*HapticsStrength, 0.f, 1.f)),
duration_ms);
}
}
void SetDefaultConfig()
{
if (Axes.size() == 0)
@ -259,7 +314,6 @@ public:
if (i < DefaultAxesCount)
{
Axes[i].GameAxis = DefaultAxes[i].GameAxis;
Axes[i].DeadZone = DefaultAxes[i].DeadZone;
Axes[i].Multiplier = DefaultAxes[i].Multiplier;
Axes[i].DigitalThreshold = DefaultAxes[i].DigitalThreshold;
@ -268,7 +322,6 @@ public:
}
else
{
Axes[i].GameAxis = JOYAXIS_None;
Axes[i].DeadZone = JOYDEADZONE_DEFAULT;
Axes[i].Multiplier = JOYSENSITIVITY_DEFAULT;
Axes[i].DigitalThreshold = JOYTHRESH_DEFAULT;
@ -300,112 +353,200 @@ public:
return id;
}
void AddAxes(float axes[NUM_JOYAXIS])
void AddAxes(float joyaxes[NUM_AXIS_CODES])
{
// Add to game axes.
for (int i = 0; i < GetNumAxes(); ++i)
{
if(Axes[i].GameAxis != JOYAXIS_None)
axes[Axes[i].GameAxis] -= float(Axes[i].Value * Multiplier * Axes[i].Multiplier);
float axis_value = float(Axes[i].Value * Multiplier * Axes[i].Multiplier);
int axis_code_pos = AXIS_CODE_NULL;
int axis_code_neg = AXIS_CODE_NULL;
int ret_code = AXIS_CODE_NULL;
if (Mapping)
{
if (i < SDL_CONTROLLER_AXIS_MAX)
{
axis_code_pos = ControllerAxisCodes[i][0];
axis_code_neg = ControllerAxisCodes[i][1];
}
}
else
{
if (i < NUM_JOYAXISBUTTONS)
{
axis_code_pos = AXIS_CODE_JOY1_PLUS + (i * 2);
axis_code_neg = AXIS_CODE_JOY1_PLUS + (i * 2) + 1;
}
}
if (axis_value > 0.0f)
{
ret_code = axis_code_pos;
}
else if (axis_value < 0.0f)
{
ret_code = axis_code_neg;
}
if (ret_code != AXIS_CODE_NULL)
{
joyaxes[ret_code] += fabs(axis_value);
}
}
}
void ProcessInput() {
void ProcessGameControllerThumbstick(int index1, int index2, int base)
{
AxisInfo &axis1 = Axes[index1];
AxisInfo &axis2 = Axes[index2];
uint8_t buttonstate;
double axisval1, axisval2;
axisval1 = SDL_GameControllerGetAxis(Mapping, static_cast<SDL_GameControllerAxis>(index1)) / 32767.0;
axisval2 = SDL_GameControllerGetAxis(Mapping, static_cast<SDL_GameControllerAxis>(index2)) / 32767.0;
Joy_ManageThumbstick(
&axisval1, &axisval2,
axis1.DeadZone, axis2.DeadZone,
axis1.DigitalThreshold, axis2.DigitalThreshold,
axis1.ResponseCurve, axis2.ResponseCurve,
&buttonstate
);
axis1.Value = float(axisval1);
axis2.Value = float(axisval2);
// We store all four buttons in the first axis and ignore the second.
Joy_GenerateButtonEvents(axis1.ButtonValue, buttonstate, 4, base);
axis1.ButtonValue = buttonstate;
}
void ProcessGameControllerTrigger(int index, int base)
{
AxisInfo &axis = Axes[index];
uint8_t buttonstate;
double axisval;
axisval = SDL_GameControllerGetAxis(Mapping, static_cast<SDL_GameControllerAxis>(index)) / 32767.0;
axisval = Joy_ManageSingleAxis(
axisval,
axis.DeadZone, axis.DigitalThreshold, axis.ResponseCurve,
&buttonstate
);
axis.Value = float(axisval);
Joy_GenerateButtonEvents(axis.ButtonValue, buttonstate, 1, base);
axis.ButtonValue = buttonstate;
}
void ProcessGameControllerAxes()
{
// Refactored to match more closely with XInput / raw PS2 on Windows
ProcessGameControllerThumbstick(SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY, KEY_PAD_LTHUMB_RIGHT);
ProcessGameControllerThumbstick(SDL_CONTROLLER_AXIS_RIGHTX, SDL_CONTROLLER_AXIS_RIGHTY, KEY_PAD_RTHUMB_RIGHT);
ProcessGameControllerTrigger(SDL_CONTROLLER_AXIS_TRIGGERLEFT, KEY_PAD_LTRIGGER);
ProcessGameControllerTrigger(SDL_CONTROLLER_AXIS_TRIGGERRIGHT, KEY_PAD_RTRIGGER);
}
void ProcessOldJoystickAxes()
{
uint8_t buttonstate;
for (int i = 0; i < NumAxes; ++i)
{
buttonstate = 0;
if (i < NUM_JOYAXISBUTTONS && (i > 2 || Axes.Size() == 1))
{
AxisInfo &info = Axes[i];
double axisval;
axisval = SDL_JoystickGetAxis(Device, i) / 32767.0;
info.Value = Joy_ManageSingleAxis(
axisval,
info.DeadZone,
info.DigitalThreshold,
info.ResponseCurve,
&buttonstate
);
Joy_GenerateButtonEvents(info.ButtonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2);
info.ButtonValue = buttonstate;
}
else if (i == 1)
{
AxisInfo &info_x = Axes[i - 1];
AxisInfo &info_y = Axes[i];
double axisval_x, axisval_y;
axisval_x = SDL_JoystickGetAxis(Device, i - 1) / 32767.0;
axisval_y = SDL_JoystickGetAxis(Device, i) / 32767.0;
Joy_ManageThumbstick(
&axisval_x, &axisval_y,
info_x.DeadZone, info_y.DeadZone,
info_x.DigitalThreshold, info_y.DigitalThreshold,
info_x.ResponseCurve, info_y.ResponseCurve,
&buttonstate
);
info_x.Value = axisval_x;
info_y.Value = axisval_y;
Joy_GenerateButtonEvents(info_x.ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS + (i-1)*2);
info_x.ButtonValue = buttonstate;
}
}
// Map POV hats to buttons and axes. Why axes? Well apparently I have
// a gamepad where the left control stick is a POV hat (instead of the
// d-pad like you would expect, no that's pressure sensitive). Also
// KDE's joystick dialog maps them to axes as well.
for (int i = 0; i < NumHats; ++i)
{
AxisInfo &x = Axes[NumAxes + i*2];
AxisInfo &y = Axes[NumAxes + i*2 + 1];
buttonstate = SDL_JoystickGetHat(Device, i);
// If we're going to assume that we can pass SDL's value into
// Joy_GenerateButtonEvents then we might as well assume the format here.
if(buttonstate & 0x1) // Up
y.Value = -1.0;
else if(buttonstate & 0x4) // Down
y.Value = 1.0;
else
y.Value = 0.0;
if(buttonstate & 0x2) // Left
x.Value = 1.0;
else if(buttonstate & 0x8) // Right
x.Value = -1.0;
else
x.Value = 0.0;
if (i < 4)
{
Joy_GenerateButtonEvents(x.ButtonValue, buttonstate, 4, KEY_JOYPOV1_UP + i*4);
x.ButtonValue = buttonstate;
}
}
}
void ProcessInput()
{
if (Mapping)
{
// GameController API available
auto lastTriggerL = Axes[SDL_CONTROLLER_AXIS_TRIGGERLEFT].Value > Axes[SDL_CONTROLLER_AXIS_TRIGGERLEFT].DigitalThreshold;
auto lastTriggerR = Axes[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].Value > Axes[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].DigitalThreshold;
for (auto i = 0; i < SDL_CONTROLLER_AXIS_MAX && i < NumAxes; ++i)
{
buttonstate = 0;
Axes[i].Value = SDL_GameControllerGetAxis(Mapping, static_cast<SDL_GameControllerAxis>(i))/32767.0;
Axes[i].Value = Joy_RemoveDeadZone(Axes[i].Value, Axes[i].DeadZone, &buttonstate);
Axes[i].Value = Joy_ApplyResponseCurveBezier(Axes[i].ResponseCurve, Axes[i].Value);
}
auto currTriggerL = Axes[SDL_CONTROLLER_AXIS_TRIGGERLEFT].Value > Axes[SDL_CONTROLLER_AXIS_TRIGGERLEFT].DigitalThreshold;
auto currTriggerR = Axes[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].Value > Axes[SDL_CONTROLLER_AXIS_TRIGGERRIGHT].DigitalThreshold;
if (lastTriggerL != currTriggerL) Joy_GenerateButtonEvent(currTriggerL, KEY_PAD_LTRIGGER);
if (lastTriggerR != currTriggerR) Joy_GenerateButtonEvent(currTriggerR, KEY_PAD_RTRIGGER);
// todo: right stick
buttonstate = Joy_XYAxesToButtons(
abs(Axes[0].Value) < Axes[0].DigitalThreshold ? 0 : Axes[0].Value,
abs(Axes[1].Value) < Axes[1].DigitalThreshold ? 0 : Axes[1].Value
);
Joy_GenerateButtonEvents(Axes[0].ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS);
Axes[0].ButtonValue = buttonstate;
ProcessGameControllerAxes();
}
else
{
// Joystick API fallback
for (int i = 0; i < NumAxes; ++i)
{
buttonstate = 0;
Axes[i].Value = SDL_JoystickGetAxis(Device, i)/32767.0;
Axes[i].Value = Joy_RemoveDeadZone(Axes[i].Value, Axes[i].DeadZone, &buttonstate);
Axes[i].Value = Joy_ApplyResponseCurveBezier(Axes[i].ResponseCurve, Axes[i].Value);
// Map button to axis
// X and Y are handled differently so if we have 2 or more axes then we'll use that code instead.
if (NumAxes == 1 || (i >= 2 && i < NUM_JOYAXISBUTTONS))
{
Joy_GenerateButtonEvents(Axes[i].ButtonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2);
Axes[i].ButtonValue = buttonstate;
}
}
if(NumAxes > 1)
{
buttonstate = Joy_XYAxesToButtons(
abs(Axes[0].Value) < Axes[0].DigitalThreshold ? 0 : Axes[0].Value,
abs(Axes[1].Value) < Axes[1].DigitalThreshold ? 0 : Axes[1].Value
);
Joy_GenerateButtonEvents(Axes[0].ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS);
Axes[0].ButtonValue = buttonstate;
}
// Map POV hats to buttons and axes. Why axes? Well apparently I have
// a gamepad where the left control stick is a POV hat (instead of the
// d-pad like you would expect, no that's pressure sensitive). Also
// KDE's joystick dialog maps them to axes as well.
for (int i = 0; i < NumHats; ++i)
{
AxisInfo &x = Axes[NumAxes + i*2];
AxisInfo &y = Axes[NumAxes + i*2 + 1];
buttonstate = SDL_JoystickGetHat(Device, i);
// If we're going to assume that we can pass SDL's value into
// Joy_GenerateButtonEvents then we might as well assume the format here.
if(buttonstate & 0x1) // Up
y.Value = -1.0;
else if(buttonstate & 0x4) // Down
y.Value = 1.0;
else
y.Value = 0.0;
if(buttonstate & 0x2) // Left
x.Value = 1.0;
else if(buttonstate & 0x8) // Right
x.Value = -1.0;
else
x.Value = 0.0;
if(i < 4)
{
Joy_GenerateButtonEvents(x.ButtonValue, buttonstate, 4, KEY_JOYPOV1_UP + i*4);
x.ButtonValue = buttonstate;
}
}
ProcessOldJoystickAxes();
}
}
@ -418,14 +559,12 @@ protected:
float DigitalThreshold;
EJoyCurve ResponseCurvePreset;
CubicBezier ResponseCurve;
EJoyAxis GameAxis;
double Value;
uint8_t ButtonValue;
};
struct DefaultAxisConfig
{
float DeadZone;
EJoyAxis GameAxis;
float Multiplier;
float DigitalThreshold;
EJoyCurve ResponseCurvePreset;
@ -445,6 +584,8 @@ protected:
TArray<AxisInfo> Axes;
int NumAxes;
int NumHats;
int Haptics;
float HapticsStrength;
bool SettingsChanged;
friend class SDLInputJoystickManager;
@ -452,21 +593,21 @@ protected:
// [Nash 4 Feb 2024] seems like on Linux, the third axis is actually the Left Trigger, resulting in the player uncontrollably looking upwards.
const SDLInputJoystick::DefaultAxisConfig SDLInputJoystick::DefaultJoystickAxes[5] = {
{JOYDEADZONE_DEFAULT, JOYAXIS_Side, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYAXIS_Forward, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYAXIS_None, JOYSENSITIVITY_DEFAULT, JOYTHRESH_DEFAULT, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYAXIS_Yaw, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYAXIS_Pitch, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT}
{JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_DEFAULT, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT}
};
// Defaults if we have access to the GameController API for this device
const SDLInputJoystick::DefaultAxisConfig SDLInputJoystick::DefaultControllerAxes[6] = {
{JOYDEADZONE_DEFAULT, JOYAXIS_Side, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYAXIS_Forward, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYAXIS_Yaw, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYAXIS_Pitch, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYAXIS_None, JOYSENSITIVITY_DEFAULT, JOYTHRESH_TRIGGER, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYAXIS_None, JOYSENSITIVITY_DEFAULT, JOYTHRESH_TRIGGER, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_X, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_STICK_Y, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_TRIGGER, JOYCURVE_DEFAULT},
{JOYDEADZONE_DEFAULT, JOYSENSITIVITY_DEFAULT, JOYTHRESH_TRIGGER, JOYCURVE_DEFAULT},
};
class SDLInputJoystickManager
@ -490,7 +631,7 @@ public:
}
}
void AddAxes(float axes[NUM_JOYAXIS])
void AddAxes(float axes[NUM_AXIS_CODES])
{
for(unsigned int i = 0;i < Joysticks.Size();i++)
Joysticks[i]->AddAxes(axes);
@ -505,6 +646,16 @@ public:
}
}
void Rumble(float high_freq, float low_freq, float left_trig, float right_trig)
{
for(unsigned int i = 0;i < Joysticks.Size();i++)
{
if (Joysticks[i]->Enabled) {
Joysticks[i]->Rumble(high_freq, low_freq, left_trig, right_trig);
}
}
}
void ProcessInput() const
{
for(unsigned int i = 0;i < Joysticks.Size();++i)
@ -532,7 +683,7 @@ static SDLInputJoystickManager *JoystickManager;
void I_StartupJoysticks()
{
#ifndef NO_SDL_JOYSTICK
if(SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) >= 0)
if(SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER) >= 0)
JoystickManager = new SDLInputJoystickManager();
#endif
}
@ -553,18 +704,26 @@ void I_GetJoysticks(TArray<IJoystickConfig *> &sticks)
JoystickManager->GetDevices(sticks);
}
void I_GetAxes(float axes[NUM_JOYAXIS])
void I_GetAxes(float axes[NUM_AXIS_CODES])
{
for (int i = 0; i < NUM_JOYAXIS; ++i)
for (int i = 0; i < NUM_AXIS_CODES; ++i)
{
axes[i] = 0;
axes[i] = 0.0f;
}
if (use_joystick && JoystickManager)
{
JoystickManager->AddAxes(axes);
}
}
void I_Rumble(double high_freq, double low_freq, double left_trig, double right_trig)
{
if (!use_joystick) return;
JoystickManager->Rumble(high_freq, low_freq, left_trig, right_trig);
}
void I_ProcessJoysticks()
{
if (use_joystick && JoystickManager)

View file

@ -4,6 +4,7 @@
**
**---------------------------------------------------------------------------
** Copyright 1998-2007 Randy Heit
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -34,24 +35,24 @@
// HEADER FILES ------------------------------------------------------------
#include <SDL.h>
#include <unistd.h>
#include <signal.h>
#include <new>
#include <sys/param.h>
#include <SDL2/SDL.h>
#include <csignal>
#include <locale.h>
#include <new>
#include <signal.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <unistd.h>
#include "engineerrors.h"
#include "m_argv.h"
#include "c_console.h"
#include "version.h"
#include "cmdlib.h"
#include "engineerrors.h"
#include "i_system.h"
#include "i_interface.h"
#include "i_system.h"
#include "m_argv.h"
#include "printf.h"
#include "version.h"
extern "C" int cc_install_handlers(int, char**, int, int*, const char*, int(*)(char*, char*));
extern bool RunningAsTool;
@ -65,6 +66,7 @@ void Linux_I_FatalError(const char* errortext);
#endif
int GameMain();
void SignalHandler(int signal);
FString sys_ostype;
FArgs *Args;
@ -180,6 +182,11 @@ int I_GameMain(int argc, char** argv)
{
InitCrashReporter(argc, argv);
SetEffectiveUser();
signal(SIGINT, SignalHandler);
signal(SIGTERM, SignalHandler);
// signal(SIGHUP, SignalHandler);
// signal(SIGQUIT, SignalHandler);
InitLocale();
printf(GAMENAME" %s - %s - SDL version\nCompiled on %s\n", GetVersionString(), GetGitTime(), __DATE__);

Some files were not shown because too many files have changed in this diff Show more