Compare commits

...

213 Commits

Author SHA1 Message Date
bicarus-dev 15bfffa1d3 Merge branch 'main' of https://github.com/spice2x/spice2x.github.io 2026-04-18 23:07:10 -07:00
bicarus-dev 18a9a47c33 remove dead code 2026-04-18 23:07:05 -07:00
bicarus 6838e69ebe popn: rest of the new cab lights (#651)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Add rest of the LED strip lights.

## Testing
Tested U region.
2026-04-18 22:17:20 -07:00
bicarus 26b3b64794 rawinput, touch: discard invalid touch points from device in hybrid mode (#649)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #648

## Description of change
Deal with devices in hybrid packet mode that send garbage data in second
packet when it's out of bounds (outside the number of contact points).
MSDN says they're supposed to be (0, 0), but some devices don't follow
the spec.

## Testing
I don't have a device that reports garbage values but this should fix it
in theory.

I tested on my Dell touch monitor which is in hybrid mode & sends 5
touches at a time, but can split across two packets to send up to 10
touches. It sends (0, 0) for the remaining slots when there are 6-9
touch points, but confirmed that there was no regression.
2026-04-18 16:22:00 -07:00
bicarus aadd2bdd59 cfg: deprecate -sdvx720, add new category for camera/printer options (#650) 2026-04-18 15:56:15 -07:00
Emma d0e24e31ca ftt: allow windowed mode resize options, add title (#646)
## Description of change
This code adds a window title to FutureTomTom when running in windowed
mode and allows the windowed graphics hooks to take effect.

## Testing
I ran the game and made sure fullscreen mode still worked as well as all
window move/resize functionality.
2026-04-16 14:32:06 -07:00
bicarus 4fb7f20c7b iidx: fix crash when ASIO compatibility fix patching fails (#645)
Logging method was crashing when the patching failed.
2026-04-16 00:42:40 -07:00
bicarus 6ddae70c5a util: fix dangling pointer issues with improper std::stringstream usage (#644)
## Description of change
`std::ostringstream.str()` returns a temporary string so it's invalid to
obtain c_str from it and pass it around.

## Testing
wip
2026-04-15 23:25:38 -07:00
bicarus b38160d6c2 popn: native touch option, keypad warning (#640)
#639 

the usual
2026-04-15 19:31:26 -07:00
Emma 231c998cc0 acio: implement ICCA slotted reader emulation for iidx18 (#642)
## Link to GitHub Issue or related Pull Request, if one exists
#121

## Description of change
This implements a seperate ICCA card workflow for the slotted readers
acting how Resort Anthem expects, enabling cards to be inserted.
Additionally this fixes a bug where both card units would be treated the
same (for keypad reads) under Resort Anthem.

The slotted reader workflow was implemented as a seperate function for
two reasons - it would be nice to not mess with any existing code to
avoid breaking all the functional games, and the way the slotted readers
are handled by RA seemed different enough to how everything was already
designed to where it didn't make sense to keep adding special edge cases
for RA.

## Testing
IIDX18 was tested and cards could be inserted on both P1 and P2 side.
IIDX19 through 22 was tested to make sure nothing had broken for
wavepass emulation.

## Known Issues
In IIDX18, when a card isn't set but the insert key is pressed, after
the card timeout the game will error out with an invalid state error.
2026-04-15 18:53:21 -07:00
Emma 127d31a85c graphics: add option to disable win11 rounded corners on windows (#641)
## Description of change
This adds a toggleable option to remove the rounded window corners when
running a game, or a subscreen, in a window on Windows 11 and upwards.

## Testing
On Windows 11: Resort Anthem, MÚSECA and EPOLIS were tested and the
window corners were removed from both main windows and the TDJ subscreen
when running in windowed mode, with no change in fullscreen mode.

On Windows 7: IIDX18 was tested with no change, the call to
`DwmSetWindowAttribute` is ignored as `DWMWA_WINDOW_CORNER_PREFERENCE`
doesn't exist.

For XP builds, the call is compiled out to avoid linking against dwmapi
and windows_dll_compat_checker returns successfully when testing against
the XP Embedded image. I can't test it on XP, but it ran on Vista.
2026-04-15 18:51:53 -07:00
bicarus a8419bbaa0 grpahics: fix borderless for ddr/gitadora (#638)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Allow DDR to be launched in borderless window (honor the spicecfg
option).

Prevent Gitadora arena model from changing the window decoration once
launched as it hangs the game.

## Testing
Tested 32/64bit ddr, gitadora arena.
2026-04-14 18:11:42 -07:00
bicarus 0fc430ceb3 popn: add io emulation for more lights and coin (#637)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Wire up coin and more lights to bi3a_hook.

## Testing
Tested on U region
2026-04-14 09:46:13 -07:00
bicarus e59a50b64c popn: boot with 3 monitors (#636)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Take the monitor hooks added for IIDX/SDVX #612 and apply it to popn as
well now that popn has a subscreen.

There is a debug option in game that actually uses three monitors, which
can be activated via hex edits. This will prevent that from working.

## Testing
Tested with 3 monitors, fs and windowed.
2026-04-13 23:02:56 -07:00
bicarus 2042b629e9 popn: allow game to launch on PCs with only one monitor (#633)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
The game insists on having two monitors. Hook various DX9 and Win32
monitor APIs to fake a monitor if the user only has one. This correctly
convinces the game that it needs to draw two screens instead of just
one.

## Testing
WIP
2026-04-13 03:00:47 -07:00
bicarus 440b9ed749 overlay: fix how input is handled when window loses focus (#632)
## Link to GitHub Issue or related Pull Request, if one exists
#286 

## Description of change
When we migrated to event-based I/O for ImGui, it introduced an issue
where a button would be held down even when the window is not in focus,
because we didn't notify ImGui that the button was released (after the
window lost focus).

Fix the ImGui custom I/O handler so that it immediately releases all
buttons when the window focus is lost.

## Testing
tested on 32-bit windowed mode
2026-04-12 04:44:46 -07:00
bicarus 9936088286 popn: subscreen overlay (#631)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change

Add overlay for subscreen.

Add an option for no subscreen window.

Use the same "force redraw subscreen" logic we used in SDVX to fix the
same issue.

Known issues:

* still crashing when running windowed if there is only one monitor
* having only one monitor causes sunscreen overlay to not work
(NumberOfAdaptersInGroup issue?)

## Testing
2026-04-12 02:11:24 -07:00
bicarus 4d8a6c1952 graphics: fix window position if out of bounds (#630)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Before calling `CreateWindowEx[A/W]`, check dimensions of the window
about to be created, and check if it will be out of bounds (completely
or partially).

If it's completely out of bounds, move it to `(0, 0)` to prevent crashes
or rendering issues.

## Testing
Tested popn and sdvx.
2026-04-11 20:12:43 -07:00
bicarus af69cf9251 popn: new cab lights (#629)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Basic support for lights - subwoofer RGB and button lights.

## Testing
Tested on title screen and in-game.

Tested beat pop as well since that lights up the colors in white/blue.
2026-04-11 18:08:45 -07:00
bicarus 1d5550b1e8 popn: implement bi3a-hook (bio3) (#626)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Turns out running this with bi2x doesn't process buttons properly, so we
have to use bi3a.

## Testing
Seems playable now.
2026-04-11 14:43:45 -07:00
bicarus ec3f6639b7 ccj: fix locale issues with carding in (#628)
## Link to GitHub Issue or related Pull Request, if one exists
#624 

## Description of change
See bug. Add locale hooks specifically for CCJ.

also, fix y axis direction for XInput, it was flipped because XInput
uses opposite values from what RawInput does

## Testing
Tested card in / play one round with Windows regional format set to
French (France) and other language set to English.
2026-04-11 14:42:51 -07:00
bicarus 727b99effd rawinput: fix ID for rawinput devices (#627)
## Link to GitHub Issue or related Pull Request, if one exists
#616 

## Description of change
The IDs don't really get used anywhere if it's not a real HID or MIDI
device, but putting them for completeness since they show up in Controls
overlay menu.

## Testing
2026-04-11 12:04:12 -07:00
bicarus 8afff3aaca popn: implement bi2x hook (#623)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Implement BI2X hook I/O for service/test/coin/headphones and the deka
pop-kuns.
2026-04-10 17:46:12 -07:00
bicarus b281517429 popn: hook DisplayConfigGetDeviceInfo to allow for any GPU port to be used for monitor, get past I/O check (#622)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
The game expects the main display to be *not* HDMI, and at port 2.

Hooking DisplayConfigGetDeviceInfo allows us to boot the game on any
monitor.

Add basic I/O hook. It gets stuck in `USB I/O Checking...` for a couple
minutes but it'll eventually skip and boot to title screen.

## Testing
ugh.
2026-04-10 02:56:32 -07:00
bicarus 54ec9f272e popn: new cab windowed mode (#621)
## Link to GitHub Issue or related Pull Request, if one exists
#618 

## Description of change
Get windowed mode to work.

## Testing
tbd... i/o doesn't work right now but it launches two windows at
least...
2026-04-10 01:05:44 -07:00
bicarus a159691097 xinput: bind UI, vibration, hotplug (#620)
## Link to GitHub Issue or related Pull Request, if one exists
#616 

## Description of change
`Naive` button is now called `Naive/XInput` and detects XInput
controllers

Add vibration output as "Lights"

Add proper hotplug support and multiple controllers

Fix misc bugs on x86

## Testing
I tested with two xbox controllers.
2026-04-10 01:04:20 -07:00
bicarus ca70f7b19c popn: distinguish between hpm and new popn (#619)
## Link to GitHub Issue or related Pull Request, if one exists
#618

## Description of change
Enhance game detection logic.

Fix heap size.
2026-04-09 19:54:27 -07:00
bicarus 7b4227d2a9 rawinput: add basic support for xinput (#617)
## Link to GitHub Issue or related Pull Request, if one exists
#616 

## Description of change
Adds preliminary support for XInput controllers. Enough to manually bind
buttons and map analogs.
2026-04-09 02:49:20 -07:00
bicarus f89efa2558 build: flatten file structure in release archives (#615)
Before:

```
.\spice2x\spice.exe
.\spice2x\spicecfg.exe
.\spice2x\spice64.exe
.\spice2x\stubs\..
.\spice2x\extras\..
```

after:

```
.\spice.exe
.\spicecfg.exe
.\spice64.exe
.\stubs\..
.\extras\..
```
2026-04-07 16:48:31 -07:00
bicarus 6f4824b648 misc: improve monitor logging (#614) 2026-04-06 22:22:34 -07:00
bicarus 45debeb5c9 iidx, sdvx: booting fullscreen with three or more monitors (#612)
## Link to GitHub Issue or related Pull Request, if one exists
#345 

Hoping that #180 is also addressed by this change, but I can't test it.

## Description of change

IIDX (TDJ) and SDVX (Valk) will now be able to boot in full screen even
with 3 or more monitors attached.

spice will "hide" from the game any monitors other than the primary
monitor and one other monitor for subscreen, allowing it to boot without
crashing - accomplished by clearing `DISPLAY_DEVICE_ATTACHED_TO_DESKTOP`
flag when monitors are enumerated over `EnumDisplayDevicesA`, and
fortunately both games seem to respect that flag.

Add options (one for IIDX, another for SDVX) that lets you specify the
monitor ID of the second monitor.

When the option is set, user-specified monitor will be used as the
subscreen. When the option is not set, spice will just pick the
lowest-indexed monitor as the subscreen, not counting the primary one of
course.

## Testing
Tested both games with three monitors. Should probably work for four or
more.
2026-04-06 02:59:12 -07:00
bicarus-dev a01f452eba fix build script 2026-04-06 02:37:40 -07:00
Will e452734bc1 Build a WinXP compatible version of Spice (#611)
This was a lot more background work than expected, but that is the
nature of unnecessary hobbies I guess. Most things in here are obvious,
so here's the non-obvious thoughts and notes:

1. All the XP stuff is optional, if the toolchain isn't available it
won't even try
2. The docker build is the "known good" build and now has all the
required tools for XP and checking binaries
a. If you're OK with it, can add the XP_MUST_BUILD flag to
build_docker.sh
4. New `SPICE_XP` flag to comment out blocks
5. We could technically build using the Docker image I've made for my XP
compatible LLVM toolchain, but combining the binaries into a single zip
at the end of it is somewhat tedious, so I've chosen to grab the
binaries from my release
6. I also wrote a DLL checker to make sure the final binaries are
_actually_ XP compatible, which found some issues on x86_64 (I don't
know a single game that runs on 64 bit XP, but it wasn't too much effort
so whatever). This also won't run if missing, just to save the trouble.
7. The build script is still pretty shit, I've made some minor
improvements
  a. `/usr/bin/env bash` instead of `/bin/bash` as it's more correct
  b. Adding `-it` to the docker build, so Ctrl+C actually halts it
c. Main issue is that failures don't halt, because of the custom error
catching function. This has caused a lot of frustration, maybe we can
change that

## Testing
It runs on 32 and 64 bit installs of XP. My jubeat cab is on Win10 these
days and I haven't got around to testing an actual game, but I've
distributed test builds of this to people who *have* confirmed it works
as expected.
2026-04-06 01:14:29 -07:00
bicarus d1779b93fa overlay: remove tab stop from invisible selectable used in options tab (#613)
#598 added extra tab stops to the invisible Selectable widgets, which is
only used for detecting mouse hover
2026-04-06 01:02:56 -07:00
bicarus fe9aac7e29 overlay: fix cosmetic issue in options tab when there are no options for the game (#610)
Number of columns was modified in #598 but it didn't correctly account
for the case where a game doesn't have any game-specific options.
2026-04-05 19:01:09 -07:00
bicarus-dev f3d887ebcf update strings 2026-04-05 17:03:23 -07:00
bicarus 336500c8ae ccj: fix D3D10Warp.dll_unloaded issue (#609)
## Link to GitHub Issue or related Pull Request, if one exists
#0

## Description of change
Fix random crashes with CCJ.

This should remove the need to add `d3d10warp.dll` to `
preload_libraries` in `ee-config.xml`.

## Testing
Tried a couple games and didn't run into the crash..
2026-04-05 16:18:19 -07:00
bicarus ee3fa58bfa graphics: option to change primary monitor before launching game (#608)
## Description of change

Adds `-mainmonitor` option which changes the monitor layout before
launching the game. This is much more reliable than the old `-monitor`
option which operated at DX9 level. This works for TDJ/UFC subscreens,
for example.

Rename `-monitor` option to `-dx9mainadapter` to discourage use.
`-monitor` will continue to work, of course.

Changing of primary monitor happens first before any orientation changes
/ refresh rate changes, which means those options will result in the
logically correct configuration.

Create a new option category for `Monitor` (rotate, refresh rate, etc).

Add a monitor selection widget for `-mainmonitor` option in the
configurator.

Improve how we log names of monitors in the log during boot - should be
less of "Generic PnP Monitor" after this.
2026-04-04 23:26:38 -07:00
bicarus a4c3eddc2f graphics: rewrite auto-rotate and refresh rate options to be more reliable (#607)
## Description of change

Rotate monitor & change the refresh rate **well before** the game is
launched.

This is much faster and more reliable compared to what we do now, which
is trying to do monitor changes during CreateWindow hooks. For example,
SDVX makes a bunch of OS / NVAPI calls to figure out monitor geometry
while it's booting, and by the time CreateWindow is called, the game
already made up its mind, which leads to weird graphical issues.

This affects both windowed mode and full screen.

In addition, add options for normal orientation (landscape) and upside
down (landscape, flipped) options. This can be used if the monitor is
normally rotated to portrait, but the game needs to be in landscape.
2026-04-04 03:23:28 -07:00
bicarus e0f6cb2cbd api: dump key binding strings (#606)
Print out human readable text for vKey when dumping bindings.
2026-04-03 20:22:24 -07:00
bicarus 1ad6a9412e imgui: update imgui to v1.92.7, update dx9 backend to latest (#605) 2026-04-03 01:51:08 -07:00
bicarus 5af3e0d494 imgui: migrate to new event-based IO (#604)
## Link to GitHub Issue or related Pull Request, if one exists
#287

## Description of change
Switch over to new event-based I/O system in ImGui so that we can keep
up with ImGui updates.

Unlike #287, this change does not modify how mouse position is handled.
That continues to be supported by newer versions of ImGui.

## Testing
wip
2026-04-02 21:00:42 -07:00
bicarus-dev b1ee828457 add some logging to device reload 2026-04-01 02:51:03 -07:00
bicarus 0d60649715 overlay: small tweaks to cards tab (#602)
## Link to GitHub Issue or related Pull Request, if one exists
#593 

## Description of change
* Display a confirmation dialog when clicking on `Generate` for the
first time (to prevent accidental loss of card number)
* If `cardN.txt` contents are read back successfully, show card number
in green to indicate success, and red in case of failure.
2026-03-31 00:07:34 -07:00
bicarus 220b27159b ea3: don't treat failure from dll_entry_init as fatal (#601)
## Link to GitHub Issue or related Pull Request, if one exists
fixes #600
2026-03-30 02:31:08 -07:00
bicarus b06a0a7d47 overlay: card override input should be disabled when overridden with cmd line (#599) 2026-03-29 20:36:42 -07:00
bicarus d66af8e16b overlay: options tab(s) clean up (#598)
## Description of change
Merge option name and command line parameter columns.
Expand the size of widget column.
In Search tab, add a new column to display the category.
Add a highlight effect to rows when hovered over - apply this to all
tables in the configurator UI.
2026-03-28 18:21:53 -07:00
bicarus-dev ad366ecdd0 update fatal crash message 2026-03-27 18:46:08 -07:00
llm96 22cb4d689c utils: print DLL info for early hooks (#597)
## Link to GitHub Issue or related Pull Request, if one exists
#596

## Description of change
Adds call to `libutils::print_dll_info` for early hooks too.

## Testing
```
[2026/03/27 09:51:16] I:launcher: SpiceTools Bootstrap (x64) (spice2x)
[2026/03/27 09:51:16] I:launcher: 1.0-V-2026-03-27T07:46:10
[2026/03/27 09:51:16] I:launcher: spice2x is free & open source; if you paid money for it, you got scammed
[2026/03/27 09:51:16] I:launcher: visit https://spice2x.github.io to download the latest version for free
[2026/03/27 09:51:16] I:launcher: arguments:
                                      [...]
                                      -z C:\Users\Noe\AppData\Local\Programs\SpiceTools\hooks\64\mempatcher64.dll
                                      [...]
[2026/03/27 09:51:16] I:launcher: loading early hook DLL C:\Users\Noe\AppData\Local\Programs\SpiceTools\hooks\64\mempatcher64.dll
[2026/03/27 09:51:16] I:superexit: enabled
[2026/03/27 09:51:16] I:libutils: DLL info for mempatcher64.dll: CompanyName = aixxe, ProductName = mempatcher, Version = 4975386c637a86e8daa89d479a0c67ab28d792ec
```
2026-03-27 12:19:32 -07:00
bicarus b38ea463aa utils: print out DLL metadata (#596)
## Link to GitHub Issue or related Pull Request, if one exists
#592 

## Description of change
* when custom DLLs are detected (`d3d9.dll`) print out some basic data
from the PE header
* for our own NVIDIA DLL stubs, add manifest files to identify ourselves
* as a bonus, when DLL hooks are loaded from `-k`, print DLL info there
as well

This is mainly to detect when DXVK is in use.
2026-03-27 02:34:56 -07:00
bicarus 056d1b2d90 ea3: warn about missing ea3 config (#595)
Show better warning message when `ea3-config.xml` / `eamuse-config.xml`
is missing. Right now we just say `failed to load prop/eamuseconfig.xml`
which is really confusing when people look for `eamuse-config.xml` that
never existed in the first place.
2026-03-27 01:56:32 -07:00
bicarus 37693916d9 overlay: show warning when binding analog axis as a button for TT/knobs (#594)
## Link to GitHub Issue or related Pull Request, if one exists
#345 

## Description of change
When X or Y axis is being bound to any button labeled `TT` or `Knob` or
`VOL-`, show a warning and ask if the user really meant to do that,
encouraging them to use the Analog tab instead.
2026-03-26 21:17:51 -07:00
bicarus 889e8b43eb overlay: redesign Cards tab (#593)
* add `-card0` `-card1` options at the top of Cards tab to clearly
indicate that they take precedence over card files
* reorganize UI / clean up
* remove ability to directly edit the contents of `card0.txt` /
`card1.txt` as that wasn't very intuitive; instead, encourage users to
use overrides directly or use an external text editor (`Edit` button
opens notepad)
2026-03-26 01:32:48 -07:00
bicarus 9db2db2340 presets: clear out applied status when switching profiles (#591)
## Link to GitHub Issue or related Pull Request, if one exists
#581 

## Description of change
Ensure "applied" status is not persisted through.
2026-03-24 22:46:38 -07:00
bicarus 1bb612f3c9 overlay: tweak imgui theme (#590)
## Link to GitHub Issue or related Pull Request, if one exists
#589
2026-03-24 08:57:58 -07:00
bicarus e886ab77af overlay: update imgui theme (#589)
Switch to CrimsonVesuvius theme from
`https://github.com/ocornut/imgui/issues/707#issuecomment-4107169777`
2026-03-24 01:59:13 -07:00
bicarus-dev fbb3c1d4c3 fix option 2026-03-24 00:42:50 -07:00
bicarus 73a69b2e8f touch: native touch hooks to honor Invert Touch option (#588)
## Link to GitHub Issue or related Pull Request, if one exists
For #587 

## Description of change
`Invert Touch` option is now honored by the IIDX/SDVX touch handler.
2026-03-23 20:42:55 -07:00
bicarus 63a0acac24 presets: minor UI fixes, add new controller profiles (#584)
## Link to GitHub Issue or related Pull Request, if one exists
#579 

## Description of change

* Fix profiles being loaded twice on tab switch
* Add more default profiles for some controllers
* Remove borders from tables since they look broken in software renderer
* Add check boxes to import/export only certain parts of of a profile
* Show device identifier string in drop down when applying profiles
since some controllers have multiple identically named interfaces
* Show completion message when action buttons are clicked (`Apply`
button, `Clear all bindings` button both become disabled & shows `done`
message)
2026-03-21 19:43:17 -07:00
ichijyo-hotaru 75ba49ff4a fix qma directory create CreateFileW_Hook to prevent recursive calls (#585)
## Link to GitHub Issue or related Pull Request, if one exists

N/A

## Description of change

Move `dirs_created` flag assignment before `dir_create_recursive` calls
in `CreateFileW_Hook` to prevent stack overflow. `dir_create_recursive`
calls `_wstat64` (ucrtbase), which internally calls `CreateFileW`,
re-entering the hook before the flag is set. GCC 13 preserved the source
order, but GCC 15 reorders the flag store after the function calls,
causing infinite recursion.

Note: I'm not fully confident the root cause is specific to GCC version
differences. Also, I cannot determine whether this directory creation
hook is truly necessary. Without these directories the game boots but
quizzes won't load, making gameplay impossible. It may be worth removing
this hook entirely, but for now this fix is needed to prevent the crash.

## Testing

Verified title screen transition on LMA-2019022700. Crashes on the
latest build without this fix.
2026-03-21 14:23:49 -07:00
ichijyo-hotaru b31ddb1ffc Add controller presets management (#581)
## Link to GitHub Issue or related Pull Request, if one exists
Implements #579.

## Description of change

### Controller Presets

Add a new "Presets" tab to spicecfg that allows users to save, load, and
manage controller binding presets.

#### Save Presets
- Capture all current button, analog, and light bindings as a preset
- "Assign Labels" dialog prompts the user to name each source device
(e.g. "Player 1", "Player 2") before saving
- Device IDs are replaced with these labels, making presets portable
across different machines
- Presets are stored in `%APPDATA%/spice2x/spicetools_presets.xml`

#### Load / Apply Presets
- Preset list shows name, type (Built-in / User), and binding counts
- Apply dialog maps each preset source label to a connected device or
keyboard
- Bindings are applied per-source, supporting multi-device setups (e.g.
two PHOENIXWAN controllers)

#### Edit / Delete Presets
- Rename source labels in saved presets (propagates to all bindings)
- Delete user presets

#### Built-in Presets
- PHOENIXWAN preset for Beatmania IIDX: P1/P2 buttons (1-7, Start,
EFFECT, VEFX), turntable analogs, and button lights

## Testing

* [x] Open spicecfg, go to Buttons tab, verify Presets section loads
* [x] Verify PHOENIXWAN builtin preset appears for Beatmania IIDX
* [x] Save a new preset: confirm "Assign Labels" dialog appears with
device descriptions as defaults
* [x] Apply a preset: confirm source column shows labels, popup does not
shrink
* [x] Edit labels on a saved preset: confirm rename propagates to all
bindings
* [x] Hover over a device source tooltip: confirm vKey shows as number
2026-03-20 16:46:17 -07:00
skogaby 458a1495c8 Fix stale frame returned by capture API on rapid successive calls (#583)
Fix stale frame returned by capture API on rapid successive calls

> [!NOTE]
> Before submitting code changes... 
> * Please do note that this is a GPL v3.0 open source project.
> * Please read the
[CONTRIBUTING](https://github.com/spice2x/spice2x.github.io/blob/main/CONTRIBUTING.md)
guide.
> * Maintainers reserve the right to reject or modify your submission
without reason.
> * No new compiler warnings must be introduced. Check the CI build
results.
> 
> Feel free to remove this section after you have read it.

## Link to GitHub Issue or related Pull Request, if one exists
https://github.com/spice2x/spice2x.github.io/issues/582

## Description of change
When using the SpiceAPI `capture` endpoint for taking screenshots, if
you call multiple times in a row in a short period, there's a good
chance for duplicate / stale data to be returned, and you end up with
multiple identical screenshots. This is due to a race condition in the
graphics capture pipeline because the capture data is never reset to
null between invocations.

`graphics_capture_receive_jpeg()` never resets the capture buffer's data
pointer to `nullptr` after consuming it. The condition variable wait
predicate checks `data != nullptr`, so subsequent calls find the
predicate already satisfied and return immediately with the previous
frame instead of waiting for the render thread to produce a fresh one.

Fix: null out `capture.data` after copying the shared_ptr (which keeps
the pixel data alive via refcount) but before releasing the lock.

## Testing
Tested this locally and invoked the capture API 10+ times in a row
without any duplicates. Previously, every other invocation would be a
duplicate, basically.
2026-03-20 14:43:20 -07:00
bicarus eb029faf2e Update CONTRIBUTING.md 2026-03-19 02:59:55 -07:00
bicarus d65eb6ed4f Update CONTRIBUTING.md 2026-03-19 02:59:23 -07:00
bicarus 97ec2e5d37 overlay: minor UI fixes for clear all / reset all (#580)
## Link to GitHub Issue or related Pull Request, if one exists
#578 

## Description of change
Use `Clear All` when there are no defaults, and `Reset All` when there
are defaults.

Clearing analogs should reset values to 0.5, not 0.
2026-03-18 02:51:02 -07:00
bicarus 97d65b137a overlay: "reset all" buttons for buttons/overlay/analog tabs (#578)
## Link to GitHub Issue or related Pull Request, if one exists
#576 

## Description of change
Add `Reset All` buttons to these tabs which clears out bindings to none
/ default.

For keypads, introduce a special `Use Preset` button which lets users
pick between numpad and top row number keys.

Update UI-visible string used for vKeys, especially the numpad ones
(e.g., `.` is now `Numpad .` to distinguish from the regular `.`
period).

Breaking change: if the user never had `P1 Keypad 00` set, the default
will change from `Enter` to `Numpad -` since both enter keys trigger the
same. This will affect a small number of popn/ddr players. They can just
change it back.

## Testing
*how was the code tested?*
2026-03-17 18:47:54 -07:00
bicarus a9f0e86aa3 overlay: categorize lights for different cab types (#576)
## Link to GitHub Issue or related Pull Request, if one exists
#574 

## Description of change
Refactor so that grouping of lights are in common I/O code. Add this to
IIDX/SDVX/DDR/GitaDora.

## Testing
2026-03-17 02:14:48 -07:00
bicarus 88679b7cb2 sdvx, troubleshooter: detect layer missing error (#577)
Detect `W:BM2D: CreateLayer() 指定したレイヤーは存在しません` and add to deferred log.
2026-03-16 11:22:08 -07:00
bicarus 9ec62a61ac overlay: small tweaks to auto light matching (#575)
## Link to GitHub Issue or related Pull Request, if one exists
#574 

## Description of change

* Add binds for Nostroller
* Update matching logic to ignore common device-side output names like
"Button" "Light" and "LED" (e.g., `Button 1` from a device now matches
on `P1 1`)
* For RGB matching, in addition to `Light R` on the device also try
`LightR` (Nostroller needs this)
* Add a confirmation dialog for `Clear All`
* Address minor bugs

## Testing
Seems to work on arcin, and Nostroller. Still need to retest Faucetwo.
Hopefully I didn't break Phoenixwan.
2026-03-15 19:04:36 -07:00
Horo 44befb7e9a cfg: auto light binding, sdvx light formatting, all light test/clear binds (#574)
<img width="784" height="561" alt="image"
src="https://github.com/user-attachments/assets/3ac6bf6d-2ca8-40e5-80ea-b0fd9e080d7b"
/>

## Link to GitHub Issue or related Pull Request, if one exists

n/a

## Description of change

I've always been annoyed with how long it takes to bind controller
lights, made a few improvements

1) added a button+window for automatically matching device lights to
game lights with the same name
2) added button(s) to cycle through all bound lights to visually confirm
location/function
3) added button to clear all bound lights
4) split SDVX lights into sections (Buttons, Valkyrie, Nemsys, Other)
with formatting, sorted based on current game spec

## Testing
Tested with multiple games and controllers (Faucetwo, custom con) and
matching always works if the descriptor strings are labeled correctly.

Not every controller labels their LEDs the same was as spice, but for
those that do this saves a ton of time clicking every box and testing
one by one.

The SDVX table split/formatting is important because myself (and several
friends) have tried binding to Wing/Controller lights and wondered why
they weren't working in game, when they are legacy Nemsys lights instead
of the strip lights handled by Valk/bi2x. I separated the sections
visually, with tooltips, plus sort the two sections based on active spec
to further minimize the chance of someone trying to use the wrong lights
for their game.
2026-03-15 02:19:24 -07:00
bicarus be388f7b49 otoca: add cam hook, fix printer rotation (#573)
## Link to GitHub Issue or related Pull Request, if one exists
related to #154 though this PR doesn't fix it

## Description of change

1. Add an option for cam hook so that the game can be launched without
any camera
1. Rotate image printed out by print-to-file so that it's right side up
(portrait orientation)

## Testing
Able to boot the game without any cameras, and tested printing in test
menu.

"Holo" printing remains broken.
2026-03-09 02:54:04 -07:00
bicarus 34ef034345 overlay: automatically hide mouse cursor when idle (#571)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change
Hide the ImGui-rendered cursor in the overlay when the mouse is idle for
more than 2 seconds.

## Testing
Tested windowed and fullscreen.
2026-03-08 20:06:18 -07:00
bicarus ba4623b009 patchmanager: various UI fixes (#572)
## Description of change
* Show more descriptive error when connection fails (for
`ERROR_WINHTTP_NAME_NOT_RESOLVED`)
* ensure patches are reloaded even if importing fails, since existing
checkboxes get reset
2026-03-08 19:43:15 -07:00
Will a0a7b86508 ddr p3io: fix neon pulse mapping (#566)
Logic was wrong; caught by compiling with a newer clang and it raising a
warning about the useless expression (since the second clause always
eval'd as true).

Changed to the intended functionality, but I have no ddr3 with p3io so I
can't test it. But it seems nobody ever really noticed this.
2026-03-06 19:05:44 -08:00
Will 6d7927927c Final changes for clang (#570)
- Allow overriding the toolchains so I can avoid changes in my working
tree
- Squash warnings that the deps use, can't just add them as PRIVATE
compile flags because it's in the headers :(
- Squash those d3d9 warnings with a suppression instead of trying to
rewrite 400 lines of code
2026-03-06 15:18:38 -08:00
Will 837c8a46d6 add some more _WIN32_WINNT defines to hook Win vista+ APIs when compiler is targeting XP (#568)
There's already a bunch of these in the codebase, this just catches a
few locations where they were missed when the compiler is targeting XP.
2026-03-06 14:21:55 -08:00
Will 22aeb64ff9 fix various minor issues causing compilation failure with clang (#567)
- Include order and forward decls were breaking button.h because a
declared but not defined struct can't be initiated (honestly this file
was cooked, crazy include order).
- `MAXINT` is GCC specific, `INT_MAX` is portable.
- InterlockedDecrement takes a LONG, not ULONG
- an imgui printf-style call using a direct string instead of %s (let's
ignore the fact that it's actually safe based on how the str is
constructed)
- missing `override` on a virtual subclass
- `CALLBACK` on a lambda threw me for a loop, but I believe moving it
after the arg list is the correct approach (see if it builds on gcc I
guess)
- `std::result_of` was removed in C++20, which the project is built with
2026-03-06 14:21:10 -08:00
bicarus e4b08064b7 rawinput: fix incorrect usage of HidP_GetUsages when descriptor is not range (#565)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #563 

## Description of change
When buttons are presented as NotRange and instead an array of single
buttons, we are calling `HidP_GetUsages` expecting at most one result
back, but in reality we need to expect all possible buttons on that
usage page since `HidP_GetUsages` returns all buttons in the usage page
+ link collection which would be the entire array of buttons - see
documentation for `HidP_GetUsages` on MSDN.

## Testing
Tested with my own controller modifying USB descriptors... more
controller testing is needed.
2026-03-05 20:41:47 -08:00
bicarus-dev 1e6b4c16cc asio bit difference message 2026-03-04 03:59:03 -08:00
bicarus-dev d6eb8f7125 fix duplicated text 2026-03-04 02:50:02 -08:00
bicarus dc6850e479 overlay: helper modal dialog for picking option values (#561)
## Link to GitHub Issue or related Pull Request, if one exists

## Description of change
Introduce a new modal dialog for picking out some of the options to
improve UX.

Implemented a few for now:

1. UI for picking ASIO drivers from a list (for -iidxasio, -sdvxasio,
-asioconvert, etc)
2. Button to generate card numbers for -card0 and card1
3. Processor Affinity selector (very similar to what's in Windows Task
Manager, list of checkboxes)
4. file selector (file open dialog for various file path overrides like
log file)
5. folder selector (folder override)

I wrote a selector for COM ports as well but how we use the value was
inconsistent (\\\\.\\COMx vs. COMx) and didn't see a huge point in it so
I didn't check it in.

## Testing
2026-03-02 12:08:20 -08:00
bicarus 2d623e179b audio: simplify ASIO backend conversion (#560)
## Link to GitHub Issue or related Pull Request, if one exists

## Description of change
Deprecate backend conversion options and replace with one unified option
for enabling WASAPI exclusive conversion to ASIO.

The new option takes in a string (ASIO driver name) and when filled out,
it automatically enables the ASIO backend.

This is to avoid confusion that was caused by the previous option - the
fact that it was split into two (one to enable the conversion, another
to optionally pick which ASIO driver to use) and that user needed to
specify an integer as opposed to the driver name.

## Testing
Tested on IIDX33. Old options should continue to work.
2026-03-01 13:06:18 -08:00
bicarus 884d665c1c sdvx, touch: account for 270 degree monitor rotation in wintouchemu (#559)
## Link to GitHub Issue or related Pull Request, if one exists
#558 

## Description of change
Same as #558 but this PR fixes the non-native touch cases (when
wintouchemu is active).

## Testing
Tested 90 and 270 degree rotation, 1080p and 1440p.
2026-02-27 17:17:00 -08:00
bicarus 2842fe7ec6 Update pull_request_template.md 2026-02-27 16:56:03 -08:00
bicarus 954e6022d9 sdvx, touch: hook Windows touch API when main monitor is rotated 270 degrees (portrait flipped) (#558)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Game expects primary monitor to be in 90 deg rotation, and does the
touch calculation accordingly.

If the user does 270 deg rotation ("portrait, flipped") then the game's
touch calculation is flipped upside down. Detect this and provide the
fixed up values in the touch hook.

## Testing
Tested with SDVX in 90 deg, 270 deg rotation, and 1080p, forced 1440p.
2026-02-27 14:22:56 -08:00
Emma 955c50a9f3 api: fix for setting drs touch state (#557)
## Link to GitHub Issue, if one exists
N/A

## Description of change
Changes the array size used for collecting touch inputs from the API to
be dynamic according to the size of the input data, rather than fixed.
This fixes a crash.

## Testing
An API client was made sending touch states, both valid and invalid
(oversized). The valid states are handled identically in the test menu
while the oversized states no longer crash.
2026-02-26 10:47:08 -08:00
bicarus dc82c980b1 iidx: show error message for emulated I/O mismatch in LDJ/TDJ sub overlay (#555)
## Link to GitHub Issue, if one exists
#345 

## Description of change
When DLL is configured to use TDJ I/O but spice is in LDJ mode, show an
error in LED ticker suggesting the user to turn on `-iidxtdj`.

When DLL is configured to use LDJ I/O but spice is in TDJ mode, show an
error in TDJ overaly suggesting the user to fix the DLL.

Fix a small bug that caused a white window to be created when `-w` is
enabled even when `-iidxtdj` is not, but DLL is using TDJ I/O.
2026-02-15 19:49:07 -08:00
bicarus 408ed17521 cfg: turn a handful of parameter parsing errors into fatal (#554)
For parameters that require two values (`width, height` or `x, y`), if
the string fails to parse, turn into runtime failure.
2026-02-13 03:22:20 -08:00
bicarus f4050e9adb graphics: custom resolution for subscreen (#553)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Allow user to specify a custom resolution for the sub monitor, in
addition to the existing refresh rate option

This would allow practically any touch monitor (including ones with
weird resolution / aspect ratio / refresh rate) to be used with
IIDX/SDVX.

## Testing
Tested with various resolution / refresh rate combinations on IIDX /
SDVX.
2026-02-12 17:51:38 -08:00
din badae01357 bt5api fix (#552)
## Description of change
use case: user with two ICCA readers in a daisy chain configuration (as
is wired in og IIDX/DDR) wanted to play DDR using their existing
equipment.

Existing `reader/` implementation is only designed for a single reader
per com port vs two readers on one daisy chained com port. This can be
implemented and improved at a future date.

Due to time constraints `eamio-icca.dll` from btools did support both
wavepass and slotted in a daisy chain configuration, and spice does
support bt5api calls.

However, the implementation was not sufficient to allow use of both
modules correctly, often stalling, not did it allow the ability to eject
the card.

I have cleaned up the implementation such that it matches the btools
`eamiotest` implementation and allows the user to use decimal to eject
the card as desired.

As well as properly staging the requests for state of the two individual
readers, like `eamio-icca` expects.

## Testing
Tested with both wavepass and slotted (ICCA and ICCB) modules and in
combination with released `eamio-icca.dll` binaries from btools 5.49 as
released by their github using `MDX` as a test subject.

Works as expected and allows end users to use real readers (if they have
them) as virtual readers in spice's emulation layer.
2026-02-04 19:34:25 -08:00
bicarus 38d9939eb7 cfg: fix early game detection for Polaris Chord and MFG (#551)
## Link to GitHub Issue, if one exists


## Description of change
Some parts of the UI (e.g., Card tab) rely on early auto detection of
the game, which relies on file hints and not the game code. Two games
(PC and MFG) rely on directory presence and not file presence.
2026-02-04 11:40:07 -08:00
bicarus f5a3caf536 signal: avoid spamming log with hook hit messages (#550)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Some people have weird DLLs that keep calling
`AddVectoredExceptionHandler` which ends up spamming the log.

## Testing
Tested IIDX with error + signaldisable to confirm the warning message
being shown, ran Polaris Chord once to check "hook hit" message.
2026-02-03 20:08:15 -08:00
bicarus 027445afb1 overlay: add context menu for overflow operations (#547)
Remove the green + button, remove the bottom checkbox row, and instead
put extra operations like `Add alternate` and `Bind many` to a context
menu.
2026-01-31 23:57:08 -08:00
bicarus 63a3900b79 overlay: add skip button to bind many (#546) 2026-01-31 20:58:15 -08:00
bicarus f542a78347 overlay: add hover delay to tooltip (#545)
Clean up flags used for tooltip display, which adds a tiny delay by
default.
2026-01-31 15:47:23 -08:00
bicarus 4d32dde83e overlay: remove multiple pages from Buttons/Overlay/Lights tabs, replace with tree layout (#541)
The old pages UI was not intuitive and often led to questions like:

* How do I bind multiple controllers to a single input?? (failure to
discover)
* I have a ghost input that won't go away, how do I fix this? Is this a
bug? (non-intuitive UI - there are too many pages and finding out where
each binding is potentially requires scrolling through 100 pages)
* Potential perf issue - user binds too many by accident, or puts
bindings all the way in page 99, requiring us to walk each element in
the vector, wasting precious CPU cycles during an input poll.

This PR removes the multiple pages, and puts everything on a single
page, showing multiple binds in a tree structure.

It also puts a hard limit on how many alternate bindings can be made (8
for buttons, 16 for lights), though if the user has configured more in
older version they will be respected.
2026-01-31 15:16:19 -08:00
bicarus 927170ce73 api: fix unbounded access in getting analog state (#544)
## Link to GitHub Issue, if one exists
#0

## Description of change
Occasionally we would read a bad "vKey" for an analog axis and end up
accessing invalid memory (beyond the size of a vector) and potentially
crash or read junk values.

## Testing
Fixed based on minidump, this was a rare repro though.
2026-01-30 20:53:13 -08:00
bicarus 875a0f0765 overlay: address small polish issues (#543)
* Make a custom widget for truncated text that shows a tooltip only when
truncated
* Use the above widget more consistently in various places
* Ensure tooltips still show even when the preceding widget is disabled
2026-01-30 19:17:44 -08:00
bicarus 3ad88ef15c overlay: fixes for naive binding (#542)
## Description of change
Three bugs:

* Some keys are IME-dependent. Right Alt can be IME mode switch for
Japanese / Korean, and Right Ctrl is often the Kanji button.
* If you press modifiers like Ctrl during bind-many-naive, it will skip
over a button, because of broken for loop logic that ends up calling
`inc_buttons_many_index` too many times
* trying to cancel out of bind-many-naive with left and right mouse
buttons swap doesn't work

## Testing
Validating various IME. I don't have a keyboard in another language
though..
2026-01-30 13:16:14 -08:00
bicarus 7a6cadb176 overlay: refactor code for lights (#539)
No functional change.
2026-01-29 16:13:56 -08:00
Will 4feca23240 Build scripts: support paths with spaces (#540)
Doing dumb things and building inside a path that contains spaces.
Quoted the relevant bits in the build script (and a couple more
shellcheck brought up that realistically won't ever be an issue, but the
squigglies annoy me.
2026-01-29 16:12:23 -08:00
bicarus 2e52bebd3d overlay: bring back "-" placeholder for unbound io, clean up code (#538) 2026-01-29 11:53:16 -08:00
bicarus aa218d5be7 overlay: fix colors of new extension widgets (#537) 2026-01-29 01:58:43 -08:00
bicarus 94f009991a overlay: simplify various input controls by introducing new custom ImGUI widgets (#536)
## Description of change

* Add a red `x` button to replace `Clear` button in binding tabs
(Buttons/Analog/Lights).
* Add a gray `x` button to replace `Clear` button in options tabs next
to input widgets
* Add helper for truncating text and adding ellipsis (...) to deal with
text overflow in tables.
* Reword some UI text for clarity
2026-01-28 02:17:40 -08:00
bicarus c3b9ce3fdc cfg: fix mouse names out of bounds (#535)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Improper bounds check resulting in crash when binding mouse buttons
beyond Mouse 5.

## Testing
Tested with `Edit` function.
2026-01-26 23:42:12 -08:00
bicarus 1517cf9a7c overlay: refactor UI code for I/O binding tabs (#533)
No functional changes, refactor of code only.
2026-01-26 22:24:14 -08:00
bicarus f0f46296f2 cfg: clean up stale buttons and lights alt bindings on save (#534)
## Link to GitHub Issue, if one exists
n/a

## Description of change
For buttons and lights, it's possible to have multiple alternatives.

When you create alternatives and then clear then, the config files
continue to have stale entries. This is because alternatives are stored
in a vector and elements are never deleted when unbound; they get
written out to the XML on modification.

These stale XML entries live forever, and get loaded on next startup of
spice.

This PR fixes that by deleting stale entries - basically, XML nodes will
be kept only until the last valid node (properly bound button or light),
trimming the trailing invalid entries.

## Testing
Manual testing, validating XML.
2026-01-26 22:12:04 -08:00
bicarus 1d1981fcde rb: prevent window moves and resizes in windowed mode (#532)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Disable all window options that change dimensions or position as they
cause problems with touch overlay in windowed mode. Always On Top is the
only thing that's still possible.

## Testing
2026-01-24 18:49:16 -08:00
bicarus 2f538fe2e4 gitadora: fix order of drums in Buttons tab (#531)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Before this PR, drums were in the order they appear in test menu.

This is really confusing. Change it so that they appear in the order
they do in default drummania layout.

## Testing
validated manually in gw
2026-01-23 10:22:24 -08:00
bicarus-dev 8e01b2e5c8 update gitadora help text 2026-01-23 03:03:18 -08:00
bicarus cd14ca800e graphics: fix window resize not working properly in some games (#530)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Need to filter out system-created windows (such as `CicMarshalWnd`) when
looking up which window to mess with when user interacts with F11 menu.

While I'm here, clean up the hack for gfdm arena model that remembers
the main window.

## Testing
Tested:

DDR
gfdm arena 4 / 1 window modes
iidx (windowed mode, with/without nosub option)
sdvx with sub windows

Need to check:
full screen games
2026-01-22 19:06:31 -08:00
bicarus cb59fe14e9 gitadora: prevent window resize (#529)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Gitadora (pre-Delta) tends to soft lock, have graphical issues (menu bg
render as black), or take a long time (minutes) between scene
transitions when window is resized.
2026-01-22 00:51:12 -08:00
bicarus 9fd9290cd3 gitadora: option for guitar picking algorithm (#528)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Adds an option to pick from four algorithms for guitar picking input.

The default behavior is being switched from legacy (rising edge only,
always 1 frame of input) to SOCD "prefer recent" algorithm. This makes
it slightly easier to navigate the menu with the guitar.

## Testing
Validated gw, gwdelta, and sdvx just in case.
2026-01-21 21:14:28 -08:00
bicarus-dev 1dd99aac74 update error message for module auto detection failure 2026-01-20 18:04:54 -08:00
bicarus 9e376f373e patcher: load auto-enable settings even if dest/spec/rev don't match (#527)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Before this change, if user changes `dest`/`spec`/`rev`, then the "auto
enable patches" settings doesn't get loaded. This is because the patch
manager config file relies on having an exact match of
`model+dest+spec+rev+ext`.

This causes a lot of confusion for:

* people who edit prop files - e.g., to switch between sdvx nemsys/valk
cab modes - and forget to re-enable patches
* people who use batch scripts to change xml files on launch - e.g., for
gitadora, omnimix, etc - even if they checked auto-apply in spicecfg,
when they use the batch script to launch the game, the setting won't be
loaded

This PR solves this by just ignoring the `dest`/`spec`/`rev` fields when
checking if patches should be auto applied on launch. Yes, this may
cause false positive cases, but probably very unlikely, and it's pretty
harmless anyway.

For future consideration: should we fix up gitadora XG disparity (K32 vs
K33 etc)?

## Testing
Manually validated
2026-01-20 04:00:15 -08:00
bicarus d4e51b77d1 overlay: options tab tooltip formatting consistency (#526) 2026-01-20 00:24:40 -08:00
bicarus 61181836b0 nvapi: set refresh rate / framerate settings (#525) 2026-01-20 00:23:14 -08:00
bicarus 4e26c8afa5 drs: rgb camera hook (#524)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Add plug&play hooks for RGB camera in DRS.

## Testing
Seems to work with my crappy Logitech C270

Camera options (brightness etc) seem to work in test menu as well).

Tested by another user with realsense camera + logitech camera, both
cams were detected correctly.
2026-01-19 02:54:01 -08:00
bicarus 7684028f99 io: fix bi2x_hook implementation in various games (#522)
## Link to GitHub Issue, if one exists
similar issue as #499

## Description of change
Fix broken bi2x_hook implementations that did not properly allocate I/O
buffers with correct sizes.

People keep copy-pasting other implementation that allocate 0 bytes of
memory, but we run the risk of memory corruption or/or game crashes if
we keep doing this.

We've seen some mystery crashes with CCJ for example, maybe this will
fix that.

While I'm here, implement SOCD cleaner for Polaris Chord.

## Testing
Tested I/O menu with CCJ, play through tutorial (didn't test matches)
Tested GWDelta (test menu and a few songs in guitar)
Tested PC (played a credit)
Tested QKS (still can't get past the title screen)
2026-01-18 18:06:15 -08:00
bicarus-dev 1d6be8fe4b fix print message 2026-01-17 20:24:18 -08:00
bicarus c70cf40af3 graphics: fix monitor auto-rotate for reflec beat (#521)
## Link to GitHub Issue, if one exists
Fixes #341

## Description of change
fix monitor auto-rotate for reflec beat

## Testing
Tested KBR, LBR, MBR (volzza2), MBR (reflesia)
2026-01-17 19:57:47 -08:00
bicarus 5715d4e605 gitadora: (arena model) override app-config (#520)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Just like the old `sys_code_get_cmdline`, override command line
parameters in `app-config*` so that we can control windowed mode / full
screen.

## Testing
Tested both GF and DM.

Technically both games can boot into the correct mode using `-d -DM` but
we'll try to use the right one just in case.
2026-01-17 17:49:50 -08:00
bicarus 821a705a7d gitadora: (arena model) single window mode + subscreen overlay (#486)
## Link to GitHub Issue, if one exists
#477 

## Description of change
This is a work in progress.

What works:
- [x] subscreen overlay shows the touch screen image (in windowed mode)
- [x] touch works in the overlay
- [x] only the main window is shown, other windows are not launched

What doesn't:
- [ ] game occasionally crashes when entering test menu?
- [ ] full screen mode is not tested at all due to monitor requirements
- [ ] need more polish (various options to control the behavior)
2026-01-17 02:17:33 -08:00
bicarus-dev 86f2a1fd39 remove asio failure message 2026-01-14 10:05:54 -08:00
bicarus-dev 86f218329a fix typo 2026-01-14 01:07:46 -08:00
Isaac Drew 66094de38a Add coin support for SDVX BI2X (#518)
## Description of change
Add coin handling to SDVX BI2X. I also tried to populate the structs as
best I could.

## Testing
Tested via running spice2x on EG final, using F1 to insert coins.
2026-01-13 09:59:49 -08:00
bicarus c00cff2afd patcher: print patch identifier for each DLL (#517) 2026-01-13 04:21:22 -08:00
bicarus b2cc8d404c gitadora: digital wailing sensitivity (#516)
## Link to GitHub Issue, if one exists
#512 

## Description of change
Add an option to adjust the hold period for digital wailing.
2026-01-13 04:19:55 -08:00
bicarus a0a04ab62f gitadora: various fixes for wailing (#513)
## Link to GitHub Issue, if one exists
Fixes #512

## Description of change
Fix digital wailing not being recognized consistently.

Also address the fact that wailing was completely broken when lefty mode
was on. This requires a new option that the user needs to check off in
the configurator since we can't magically guess if the user is holding
the guitar in lefty mode.

- [x] fix downward wail
- [x] digital lefty mode
- [x] implement all of this for arena model i/o
- [x] test dx cab 2p
- [x] lefty toggle in overlay
- [x] fix analog not working for 2p guitar
- [ ] analog lefty mode?

## Testing
Checked GW and GW Delta
2026-01-12 21:52:48 -08:00
bicarus a3a59e689e ftt: clean up i/o (#515)
No functional change, just a code clean up.
2026-01-12 19:32:24 -08:00
bicarus a6f29d6a5a audio: implement wrapped IAudioClient3 (#511)
## Link to GitHub Issue, if one exists
Fixes #509

## Description of change
Implement `WrappedIAudioClient3`.

For now, we have only seen SDVX use it (EG final) and only when the
shared mode patch is enabled. Exclusive still goes through version 1.
This means that for V3 we can forego implementing backend conversion
logic (for now).

Only significant addition is `InitializeSharedAudioStream` and this IS
called by SDVX when shared mode patch is enabled.

## Testing
Tested EG final, tested year 1 data, also IIDX shared/exclusive wasapi.
2026-01-10 17:41:10 -08:00
bicarus 9be618d791 utils: log primary display adapter (#508)
## Description of change
Log a message so you can tell which display adapter is primary.
2026-01-10 03:03:08 -08:00
bicarus 6bf7ae9074 graphics: submonitor refresh rate option (#507)
## Description of change
Add an option that forces requested refresh rate value for the second
monitor; i.e., instead of 60Hz allow the user to override the adapter to
be 70Hz.

Useful for people who have a touch screen that can't do 60Hz.

I don't know if this would cause any timing issues in IIDX or SDVX, but
it seems to boot and monitor check is unaffected.

## Testing
Tested TDJ And UFC mode with a submonitor that can only do 61Hz.
2026-01-09 19:50:07 -08:00
bicarus e3d63c65c1 overlay: fps window clean up (#506)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Use fmt library for formatting time

Round down sub-second units so that the FPS counter only updates once
per second (instead of Time and Uptime ticking independently)

clean up usage of `localtime` in `logging.cpp`
2026-01-08 21:52:13 -08:00
bicarus 5d2a9fa1cf audio: update logging for low latency option (#505)
## Description of change
Spit out an error if the minimum latency is not any lower than the
default.
2026-01-08 13:56:10 -08:00
bicarus 0078b72b03 graphics: address performance regression with Image Resize (#504)
## Link to GitHub Issue, if one exists
Regressed by #401 

## Description of change
Two things from testing SDVX Live2D -

1. ColorFill is almost always too expensive and will lead to drops
1. Scale factor of 0.5 causes GPU to sync and causes drops

(ARGB/XRGB doesn't matter, linear filtering doesn't matter)

1 is fixed by removing ColorFill calls on every frame - instead only do
this once when the user changes the duplicate option (on the next frame)

2 is fixed by updating the UI (limit the slider) though people can
ctrl+click to override these slider limits.

The warning label will be kept with slightly reworded text.

## Testing
tested in sdvx eg final (sudden death) - on my RTX 4070, my FPS is rock
solid 120 fps even with image resize on now.
2026-01-07 16:35:08 -08:00
bicarus b1ee37a066 troubleshooter: diagnose ddr codec issues (#503)
## Link to GitHub Issue, if one exists
#345 

## Description of change
Turn existing warnings for DDR codec issues into deferred error
messages.

## Testing
Tested DDR World with faked codec issues.
2026-01-06 23:20:05 -08:00
bicarus ebf6382e7f sdvx: add warning about Image Resize feature affecting Live2D performance (#502)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Turning on Image Resize feature completely tanks performance of Live2D
in some SDVX songs. Add a giant warning next to the UI.

On my system with RTX 4070, Sudden Death is normally a rock solid
120FPS, but drops to 30-40FPS when resizing is on.

* linear filter does not affect this at all
* window resize options also have no effect

It's really just rendering on a larger surface + calling `StretchRect`
that does this.

Additionally, hide the resolution swap option since it sees very low
usage & has potential gotchas likes this.
2026-01-06 16:40:54 -08:00
bicarus 8e23ef224a Update CONTRIBUTING.md 2026-01-05 18:59:06 -08:00
bicarus 6706ffc321 Fix wording in CONTRIBUTING.md for clarity 2026-01-05 17:26:29 -08:00
bicarus e755b06e35 Update CONTRIBUTING.md 2026-01-05 17:25:10 -08:00
bicarus 9ef22712c0 Update CONTRIBUTING.md 2026-01-05 17:24:13 -08:00
bicarus 7b903d35e2 iidx, sdvx: SOCD cleaner for knobs and turntables (#497)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Apply SOCD cleaner algorithm to deal with the case where both
counter-clockwise and clockwise buttons are pressed at the same time.

In SDVX, default algorithm is to prefer the most recently pressed
direction. This is better for dealing with slams in rapid succession,
for example, as you don't have to completely let go of one direction to
hit the other now.

In IIDX, default algorithm is neutral (same behavior as before). Using
last or first algorithm results in double scratches which means you'll
likely end up with an excessive poor after hitting a scratch, so neutral
is actually beneficial. Besides, if you are serious about playing on the
keyboard, you should be using `TT +/-` and `TT +/- Alternate`.

## Testing
Tested SDVX 3, EG (old cab), EG valk cab modes, IIDX 24, 27, 33
(tdj/ldj)
2026-01-04 19:50:11 -08:00
bicarus 7f281a50c5 sdvx: clean up valk cab check (#498) 2026-01-04 19:49:55 -08:00
bicarus 1354d63b85 iidx, sdvx: bi2x hook fix (attempt 2) (#499)
## Link to GitHub Issue, if one exists
see my previous attempt #496, which has been reverted by #500

## Description of change
Two issues:

1. the original implementation of bi2x_hook for iidx misunderstood what
`aioNMgrIob2_Create` does. In the original I/O code it returns a pointer
to a C++ class instance, which would also have a vptr (pointer to a
vtable) in the beginning. The original author of this code of the hook
in spice thought that `aioNMgrIob2_Create` returns a pointer to a
pointer. This may happen to work but it's not 100% accurate, so this has
been fixed by separating out the class and the vtable allocation.
Without this, the game could have accessed memory outside this buffer.

2. And then the SDVX implementation blindly copy-pasted the IIDX
`bi2x_hook`. Along with the issue above, they also forgot to check the
I/O DLLs to see how much memory needs to be allocated and change that.
Instead, they kept the same value as IIDX, which was too small. Fix that
as well.

## Testing
Doing some extensive testing with IIDX (27-33) and SDVX versions (EG).
2026-01-04 19:10:50 -08:00
bicarus e745cdcac8 Revert "iidx, sdvx: bi2x hook fix" (#500)
Reverts spice2x/spice2x.github.io#496
2026-01-04 17:50:21 -08:00
bicarus bf7666939a iidx, sdvx: bi2x hook fix (#496)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Both games take the address returned by `aioNMgrIob2_Create` and reads
values offset from that address as part of the I/O routine. This means
that the I/O routines of these games have been accessing random
variables in data/bss segments for years, they just happened to get a
value that doesn't lead to a crash.

Stumbled upon this while trying to add a new feature that required
adding more globals, but suddenly the game stopped polling I/O when I
changed a value of my global. Scary.

## Testing
Tested both games I/O
2026-01-03 19:27:53 -08:00
bicarus-dev 7e89037db3 update github download url 2026-01-03 05:30:50 -08:00
bicarus 6ffaa77f58 overlay: UI scaling option (#495)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Add a new option for scaling the overlay.

Gitadora Arena will receive 250% by default.

Various overlay windows have been updated to scale properly, not perfect
but they are at least usable.
2026-01-02 21:54:25 -08:00
bicarus 7a1f46d1be gitadora: (arena model) fix window settings not working (#494)
## Link to GitHub Issue, if one exists
#477 

## Description of change
F11 menu (window settings) did not work for arena model because the game
engine races to create many windows, so our logic of grabbing the first
window created doesn't always get the main window.

## Testing
2026-01-02 15:07:32 -08:00
bicarus cf59ae5f89 gitadora: (arena model) lights (#492)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Add lights for J region cab. I can't figure out how the extra lights on
U region cab are wired up.

## Testing
Tested guitar and drums, J and U regions.
2026-01-02 03:49:01 -08:00
bicarus 9c06dd36a0 graphics: tone down adapter mode removal logging (#493)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Get rid of log messages when adapter modes are removed, since these have
never been all that useful.
2026-01-02 03:48:28 -08:00
bicarus d5d082ec37 troubleshooter: deferred message for fatal errors (#491)
## Description of change
When the game logs a fatal error message, queue up a deferred message
for the troubleshooter.
2025-12-31 05:44:55 -08:00
bicarus 17723d290a troubleshooter: update formatting (#490)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Make the text color cyan to make the messages stand out.

## Testing
Verified in terminal.
2025-12-31 01:15:19 -08:00
bicarus 8de7e5a9e4 sdvx: prevent the game from trying a random ASIO driver (#488)
## Link to GitHub Issue, if one exists
n/a

## Description of change
SDVX EG final started to pick an ASIO driver at random and tries to open
it, which causes crashes / hangs if the driver is not compatible
(ASIO4ALL in particular)

Only allow the game to access `HKLM\SOFTWARE\ASIO` if the user has a
Xonar AE, or if ASIO was configured using -sdvxasio.

## Testing
Tested year 1 data and EG final.
2025-12-30 17:58:28 -08:00
bicarus b8e99e75e5 gitadora: (arena model) prevent the small subscreen from being closed (#487)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Ensure the small subscreen can't be resized or closed by accident.
2025-12-30 14:30:59 -08:00
bicarus 46dc22dd63 gitadora: (arena model) headphones i/o (#485)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Plumb through headphone jack detection. Touch screen menu for headphone
volume lights up.

## Testing
Tested guitar.
2025-12-30 14:30:33 -08:00
oleg238948234 69d78e0a87 gitadora: (arena model) fix j32d encoding & use velocity (#483)
## Link to GitHub Issue, if one exists
N/A

## Description of change
in current implementation j32d analog values are not encoded properly,
values are supposed to go up to 1023, but they show up as 771 in game

## Testing
tested with M32-007-2025092400, used DM PAD CALIBRATION menu
2025-12-30 00:29:43 -08:00
oleg238948234 ab3d41432a gitadora: (arena model) F: drive fix (#484)
## Link to GitHub Issue, if one exists
N/A

## Description of change
prevents the game from creating folders on F: drive

## Testing
tested with M32-007-2025092400
2025-12-30 00:26:47 -08:00
bicarus 73e10cac0c gitadora: (arena model) text encoding hook (#482)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Fix Japanese text broken in the game UI when system ACP is not
SHIFT-JIS.

## Testing
Checked song select and in-game title display.
2025-12-29 23:27:15 -08:00
bicarus c6242233a7 gitadora: (arena model) implement wailing, improve picking (#481)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Add analog and digital wail for the arena model guitar.

Fix the picking code to be the same algorithm as extdev - detect rising
edges and insert 1 frame of input.

## Testing
Tested x/y/z and digital y up/down. Only Y gets used in game though.
2025-12-29 23:26:44 -08:00
bicarus 6b8335780f ddr: fix up init code when using old I/O with gold cab mode (#480)
## Link to GitHub Issue, if one exists
Fixes #479 

## Description of change
When p3io / p4io is in use with Gold Cab spec, use the old init code.

## Testing
Verified p3io / p4io / bio2 booting with 2024040200.
2025-12-29 16:26:30 -08:00
bicarus-dev 21f469b260 gitadora: arena cab volume change prevention (#478)
## Link to GitHub Issue, if one exists
#477 

## Description of change
Arena cab has two sound cards and it uses `WrappedIMMDeviceCollection`
to set the volume to 100% on boot on both, instead of the typical
`GetDefaultAudioEndpoint`.

## Testing
Testing Delta, testing other games as well as they are affected.
2025-12-29 03:23:58 -08:00
zxcwdta 07a0df5fdc gitadora: Added support for GITADORA Delta (#476)
## Link to GitHub Issue, if one exists
None

## Description of change
- Added bi2x_hook
- Added J32D (drum) and J33I (guitar) emulations
- AMI2000 card reader (this game uses this instead of ICCA)

## Testing
Played through a 15-minute session on Drummania.
Guitar mode works with keyboard input.
2025-12-28 16:17:06 -08:00
zxcwdta d4f1768bbd avs: Increased heap size for later gitadora games (#475)
## Link to GitHub Issue, if one exists
None

## Description of change
Increase the heap size for GITADORA games.

Later releases of GITADORA attempt to allocate a block of memory (size
of 0x7ECF78) for notes_info.xml but fail due to heap size being too
small, leading to HDD ERROR 5-1502-0008.

## Testing
Tested with current and previous GITADORA releases.
2025-12-26 23:00:57 -08:00
bicarus-dev 96e7e4949f iidx, linux: cache lstat result for movie thumbnails (#474)
## Link to GitHub Issue, if one exists
https://codeberg.org/nixac/spicetools/issues/4

## Description of change
Inspired by the Linux issue above in the fork, cache lstat calls for
thumbnail. IIDX 32 and 33 make a call for this every frame which can
cause significant overhead on Linux.

This is being enabled for all versions and not just the Linux variant of
the executables since it provides some marginal performance gain on
Windows.

## Testing
Tested IIDX 32/33, with the option on/off, and with AVS verbose logging
on/off.

Tested with ifs_layeredfs hook as well just to verify functionality.
2025-12-26 20:17:20 -08:00
bicarus-dev 24d3062c66 update superstep audio error troubleshooter message 2025-12-24 21:10:26 -08:00
bicarus-dev 6cd212aac2 iidx,sdvx: disable subscreen overlay if -monitor is in use (#471)
## Link to GitHub Issue, if one exists
#345 

## Description of change
If TDJ / VM mode is active AND user is enabled `-monitor` option (and
the value isn't 0, the primary monitor)

then disable the subscreen overlay, show an error message instead.

We're doing this because both games cannot accept emulated touch input
if `-monitor` option is in use. There is not much point in displaying
the second screen's image, other than to REALLY confuse people when
nothing happens when they click on it.

## Testing
Tested both games with `-monitor`.
2025-12-24 17:35:15 -08:00
bicarus-dev 064031bc12 Update CONTRIBUTING.md 2025-12-22 19:20:47 -08:00
bicarus-dev 4c8d4ebab6 iidx: detect ldj/tdj I/O mismatch (#470)
## Link to GitHub Issue, if one exists
#345 

## Description of change
For IIDX31+, detect if BI2A or BI2X I/O emulation becomes active, and
see if there is a mismatch between the -iidxtdj setting.

Right now a mismatch results in warning in log + deferred error log, but
in the future we could turn this into a fatal error and crash the game
to make it more obvious.

## Testing
Tested 4 combinations:

1. DLL in LDJ mode + -iidxtdj off (pass)
2. DLL in LDJ mode + -iidxtdj on (error)
3. DLL in TDJ mode + -iidxtdj off (error)
4. DLL in TDJ mode + -iidxtdj on (pass)
2025-12-22 18:52:37 -08:00
bicarus-dev 462a07680b Update CONTRIBUTING.md 2025-12-22 18:51:15 -08:00
bicarus-dev 081def30db overlay: clarify subscreen types (#469)
## Link to GitHub Issue, if one exists
#345 

## Description of change
In the main menu (Esc key), instead of just saying `Show subscreen`,
detect game and say `Show TDJ Subscreen` `Show LDJ LED Ticker` etc.

For the LED ticker window, show the title bar by default, and add an
option to hide it back.

This is to help with cases where people get stuck with being in LDJ mode
when they meant to turn on TDJ.

## Testing
Tested all configurations (IIDX LDJ/TDJ, SDVX UFC, DRS)
2025-12-22 15:59:31 -08:00
cchike 6776b08bce MDX: Refactored p4io.cpp (#467)
## Link to GitHub Issue, if one exists
#452 

## Description of change
Added `Sleep(4)` call to regulate how fast the `device_io` function will
call `getState` for each button. The panel buttons were commented out
because those are handled by the MDXF Control Status Buffer functions
anyways, so the work was redundant. It's also why the timing precision
from this function doesn't matter, so it can afford a longer sleep

## Testing
- Verified IO still functions properly
- Verified decrease in overall CPU usage
2025-12-21 20:49:39 -08:00
bicarus-dev 6cb507f4fa Update CONTRIBUTING.md 2025-12-21 18:57:42 -08:00
bicarus-dev de05af052f build: turn warnings into errors for gcc (#466)
for CI
2025-12-21 03:52:30 -08:00
bicarus-dev c767918729 nost: avoid pass-by-value in PANB I/O (#465) 2025-12-20 02:33:25 -08:00
bicarus-dev fbc8bc2502 ddr: tweaks to mdxf i/o (#464)
## Link to GitHub Issue, if one exists
#452

## Description of change
* Split the MDXF I/O option into three: auto, thread, and backfill.
`auto` is the same as what `thread` used to be before this change (based
on I/O poll rate), but `thread` now always forces thread poll mode.
`auto` is the default (behavior wise, same as before)
* For the poll rate measurement, start polling after 20s mark to avoid
frame drops during game boot
* Minor code clean up

## Testing
manual test in windowed + full screen
2025-12-20 01:37:56 -08:00
bicarus-dev c38f42cb19 update options text 2025-12-19 02:15:56 -08:00
cchike 42d8bbc92f MDX: Exposed P4IO Buffer Filling Mode to Spicecfg (#463)
## Description of change
Extension of #460 
An option was exposed to `spicecfg` (`P4IOBufferMode`) for toggling
between the Thread and Backfill buffer fill implementations from the
prior PR.

## Testing
- Verified the option properly toggled between the two implementations
2025-12-19 02:14:10 -08:00
bicarus-dev a09905792d iidx: workaround for touch monitors that report contact area (#462)
## Link to GitHub Issue, if one exists
n/a

## Description of change
When touch monitors send touch area size information
(`TOUCHEVENTFMASK_CONTACTAREA` flag and `cxContact` / `cyContact` fields
of `TOUCHINPUT` struct) from `GetTouchInputInfo`, IIDX gets confused and
does some unexpected things when interacting with the subscreen.

As a workaround, hook `GetTouchInputInfo` and clear out the flag &
values.

This is only done for IIDX AND when -iidxnativetouch is on.

## Testing
Tested on a Dell touch monitor, simulating "bad" values.
2025-12-18 15:19:09 -08:00
cchike f113eb4f52 MDX: Merged Thread and Backfill Implementations (P4IO Timing Fix) (#460)
## Link to GitHub Issue, if one exists
#452 

## Description of change
(Another extension of #450)
Merged the old threading implementation into the current backfill
implementation because it was showing signs of having slightly better
timing performance. The following description is taken from the
strikethrough text from the referenced PR above:
>When the module is booted, it counts how many times
ac_io_mdxf_update_control_status_buffer was called in the first three
seconds, then calculates the approximate refresh rate of the current
game instance. If the refresh rate is under THRESHOLD_REFRESH_RATE
(120Hz), a low-priority thread dedicated to inserting polls into the
ring buffers is started and run at THREAD_REFRESH_RATE_HZ (125Hz).
Combined with the polls inserted by the main arkmdxp4 thread and
rawinput real-time events, the 125Hz update cadence is enough to bring
the span of poll history represented by the ring buffers closer to
parity with what arkmdxp4 expects.

The default mode is `THREAD_MODE` but can be switched to `BACKFILL_MODE`
(which could possibly be made as a `spicecfg` option).

There was also a fixed bug in the `ac_io_mdxf_get_control_status_buffer`
function, which should not have been writing to the `head` pointer. This
was allowing for an edge case situation where the `head` pointer was
advanced in another thread then rewound back to the previous `head` in
this thread. The real implementation only reads the pointer and returns
it.

## Testing
- Logged the ring buffer states after many iterations and verified
regular structure
- Same playtesting as the prior PRs
2025-12-18 09:27:33 -08:00
bicarus-dev f726748ebe build: fix debug build (#461)
Fix build break of DEBUG build after updating FMT lib
2025-12-18 02:48:26 -08:00
bicarus-dev 371c957025 cfg: cap max length on preview for buttons edit dialog (#459)
## Link to GitHub Issue, if one exists
Related to #458

## Description of change
Truncate text if button's identifier string might overflow the popup
dialog width

## Testing
2025-12-17 16:04:45 -08:00
llm96 83aa907b13 rawinput: append HID product string to description (#458)
## Link to GitHub Issue, if one exists
N/A

## Description of change
Append product strings to descriptions. Useful for differentiating
devices in the analog binding dialog.

Before | After
------ | ------

![0](https://github.com/user-attachments/assets/803c5a69-2ed9-40f8-a147-f1b4f3a7e220)
|
![1](https://github.com/user-attachments/assets/7407a3cc-eebb-4529-be43-2eb177645bb7)

## Testing
`spice64 -iidx -cfg` ⟶ Analogs ⟶ Bind
2025-12-17 15:03:16 -08:00
bicarus-dev 71b144918c rawinput: stop iterating devices if one was found (#457)
## Link to GitHub Issue, if one exists
n/a

## Description of change
This would save a handful of instructions at best but it was bothering
me

## Testing
Tested with two controllers + naive with ddr
2025-12-17 05:40:27 -08:00
cchike e618a05b08 MDX: Updated Ring Buffer Backfill Implementation (P4IO Timing Fix) (#456)
## Link to GitHub Issue, if one exists
#452  

## Description of change
- Extension of #450 
- Moved the call to `arkGetTickTime64()` outside the update function to
bring the timestamps closer to real-time
- Changed the update function to only call `getState()` if called from
`rawinput`, otherwise just fill the ring buffer entries with the last
known state
- Changed the update function to only write one ring buffer entry if
called from `rawinput`
- Moved the call to `mdxf_poll()` within `rawinput.cpp` outside the loop
that iterates over all devices

## Testing
- Logging was done to verify the span of poll history covered by each
request for 7 ring buffer entries was ~28ms, as the game expects
- Observed some improvement in timing precision since the prior
implementation
2025-12-17 02:45:22 -08:00
bicarus-dev d0f3ffd12f linux, audio: add hooks acmFormatSuggest (#455)
## Link to GitHub Issue, if one exists
n/a

## Description of change

`acmFormatSuggest` is known to cause significant perf issues on Linux;
add a hook to return cached results.

The fix is only active in the Linux variant.

For discussion, see
* https://codeberg.org/nixac/spicetools/issues/2
* https://codeberg.org/nixac/spicetools/issues/4

## Testing
I've only tested this on Windows, checking if iidx and popn can boot,
play songs without audio issues.
2025-12-16 22:56:22 -08:00
bicarus-dev 6d18d6a496 troubleshooter: address false positive (#454)
## Link to GitHub Issue, if one exists
#447

## Description of change
Allow GetAdaptersInfo to fail with ERROR_BUFFER_OVERFLOW.
2025-12-16 16:47:02 -08:00
bicarus-dev b0810558a7 iidx,sdvx: error message for subscreen unavailable does not draw a proper ImGui window (#453)
## Link to GitHub Issue, if one exists
n/a

## Description of change
In cases where the subscreen overlay window does not have anything to
show (either via misconfiguration or an error), ensure that the window
border is shown with the close button. Additionally, add a small button
to close the dialog to help the user.

## Testing
manual validation
2025-12-16 15:24:46 -08:00
bicarus-dev 6ae2e4003c scard: stop polling if scard service dies (#451)
## Link to GitHub Issue, if one exists
n/a

## Description of change
If `SCardGetStatusChange` returns `SCARD_E_SERVICE_STOPPED`, stop
polling.

Add some sleep if it fails for other reasons to prevent busy loops.

## Testing
Tested with ACR122U reader.
2025-12-15 20:55:14 -08:00
cchike 8545f1cbdc MDX: Added Backfill Logic For Ring Buffer Health (P4IO Timing Fix) (#450)
## Context
While testing the emulator on 60Hz for a while, I noticed there was a
pretty consistent timing bug with steps that had quick releases in
between them (mainly footswitches and sometimes jacks). As mentioned in
my previous commits, `arkmdxp4` determines press/release times for steps
based on a 7-poll history on every frame, which is normally populated at
~250Hz from the MDXF board in `libacio2`, yielding around 28ms of
polling history. When emulated, this is instead being populated
internally from `arkmdxp4` once per frame, meaning the 7-poll history
could have up to `1000/60 * 7 = 117ms` worth of step history at 60Hz. In
the case of a footswitch/jack, if you account for the real-time press
and release event that's inserted from `rawinput` during that time,
there'd actually be up `1000/60 * (7-2) = 83ms` worth of step history.
This is still a large enough window of time for `arkmdxp4` to start
confusing older on-state polls with more recent ones. Here is a real
ring buffer I logged when I reproduced the bug:

```
Frame #6: 82ms | 1
          70ms | 1 <- rawinput real-time press
Frame #5: 66ms | 0
Frame #4: 49ms | 0
Frame #3: 34ms | 0 <- rawinput real-time release (coincided with frame update)
Frame #2: 16ms | 1 <- stale "held" poll
Frame #1:  0ms | 1 <- stale "held" poll
```

When `arkmdxp4` determines that a new press event occurred, it uses the
timestamp of the oldest on-state poll in the ring buffer as the press
time. In this case, it uses time 0ms instead of the correct 70ms,
resulting in the game thinking the player stepped 70ms before they
actually did. For reference, that same ring buffer snapshot would've
looked like this in the real implementation:

```
Frame #6: 82ms | 1
          78ms | 1
          74ms | 1
          70ms | 1
Frame #5: 66ms | 0
          62ms | 0
          58ms | 0
```

The correct time 70ms would be assigned to that step in this case. This
demonstrates the importance of keeping the ring buffers filled with only
recent poll history, closer to the threshold of around ~28ms that the
real implementation expects.

## Description of change
~~When the module is booted, it counts how many times
`ac_io_mdxf_update_control_status_buffer` was called in the first three
seconds, then calculates the approximate refresh rate of the current
game instance. If the refresh rate is under `THRESHOLD_REFRESH_RATE`
(120Hz), a low-priority thread dedicated to inserting polls into the
ring buffers is started and run at `THREAD_REFRESH_RATE_HZ` (125Hz).
Combined with the polls inserted by the main `arkmdxp4` thread and
`rawinput` real-time events, the 125Hz update cadence is enough to bring
the span of poll history represented by the ring buffers closer to
parity with what `arkmdxp4` expects.~~

EDIT: The prior thread implementation was replaced with new logic that,
on each call to the update function, backfills the ring buffer with
entries representing the last known state of the controller. For
example, given the ring buffer with a single entry:

```
0ms | 0
```
When the update function is called on the next frame at 60Hz, the
function will insert intermediate entries as "padding" before updating
to its current state:

```
16ms | 1 <- state at current frame
12ms | 0 <- "padding"
8ms  | 0 <- "padding"
4ms  | 0 <- "padding"
0ms  | 0 <- last known state
```

## Considerations
~~In the future, it might be worth exploring a route that interpolates
polls instead of relying on a thread for inserting polls (in other
words, creating intermediate poll entries based on the known real-time
press and release events from `rawinput`). For example, if `rawinput`
reported a press at time 10ms and a release at time 30ms, it's known
that any poll in between those times would report an on-state for that
arrow. However, considering this would involve adding multiple entries
per call (for times in the past) and how the update function can be
called from two separate threads, it may be introduce hard-to-debug edge
cases and add unnecessary complexity overall.~~

EDIT: Implemented

## Testing
~~-Verified the thread was inserting polls at the expected cadence
-Verified the thread is only created when the game's refresh rate is
below the threshold
-Verified the thread introduced no significant increase in CPU~~
- Verified the entries were being backfilled correctly on several
different refresh rate instances
- Verified the bug could no longer be reproduced
2025-12-15 19:46:41 -08:00
bicarus-dev e0530fedad build: update fmt library to 12.1.0 (#449) 2025-12-14 14:36:38 -08:00
bicarus-dev b220cbf336 build: remove linalg library (#448)
I think this was used for DRS VR support.
2025-12-14 04:37:28 -08:00
bicarus-dev f68a3439d3 troubleshooter: detect lack of ipv4 (#447)
## Link to GitHub Issue, if one exists
#345 

## Description of change
Most games will fail show a network error if there is no network adapter
with a valid IPv4 address

Detect this case in netfix code and show a warning message in log +
deferred error message.

## Testing
Tested with no network adapters, with IPv6 only adapter, and a
disconnected NIC.
2025-12-14 00:07:22 -08:00
cchike 6f58d9ab68 MDX: Decoupled Input Polling Rate From Game Refresh Rate (P4IO Timing Fix) (#446)
## Context
In the real implementation of `ac_io_mdxf_update_control_status_buffer`,
it doesn't actually add entries to the pad state ring buffers (it only
marks the head entry as the "last consumed" entry), and `arkmdxp4.dll`
expects the ring buffers to be updated at regular intervals inside
`libacio2.dll` at ~250Hz per player. Instead, in the emulated version,
the updates happen in this function and that regular interval is
established internally using the game's refresh rate (60Hz, 120Hz,
144Hz, etc.). This effectively locks the polling rate of pad input to
that refresh rate, which is quite low for pad input.

## Description of change
The polling rate lock was circumvented by calling
`ac_io_mdxf_update_control_status_buffer` as soon as the game receives
controller input in `rawinput.cpp`. A simple wrapper function
`mdxf_poll` was exposed so press and release events could be added to
the ring buffers in real time as they happen. After each controller
event in `rawinput.cpp`, `mdxf_poll` calls
`ac_io_mdxf_update_control_status_buffer` for each player, checking the
pad's current state and adding it to the ring buffer if it's changed
since the last call. This results in the 0 -> 1 (press) and 1 -> 0
(release) state transitions used for gameplay to be based only on the
real-time events inserted from `rawinput.cpp`, while leaving the rest of
the entries generated from the main `arkmdxp4.dll` thread as "padding"
the game expects (I actually tried reducing the ring buffers to *only*
write entries on state transitions, but it broke gameplay, which means
the game still depends regular reporting of the pad state). Here's an
example of how a ring buffer would look if an arrow was pressed at some
time 32ms (assume 165Hz game refresh rate, ~6ms updates):

```
Old (only frame updates):
1 | 36ms
0 | 30ms
0 | 24ms
0 | 18ms
0 | 12ms
0 | 6ms
0 | 0ms
```
The game only sees the press event at time 36ms on frame update, so time
36ms is assigned to the press time of that arrow (4ms later than the
real press time).

```
New (frame updates + event updates from rawinput.cpp):
1 | 36ms
1 | 32ms <- from rawinput.cpp
0 | 30ms
0 | 24ms
0 | 18ms
0 | 12ms
0 | 6ms
```
The game sees the real press time 32ms on frame update, so the correct
press time 32ms is assigned.

## Testing
- Played on various refresh rates and verified the controller input
remains highly responsive.
- Inspected the ring buffers with a debugger and verified entries for
press and release events from `rawinput.cpp` were correctly being added
for their respective players.
2025-12-12 19:03:04 -08:00
bicarus-dev c3428236f8 iidx: option to disable recording function (#445)
## Link to GitHub Issue, if one exists
#442 

## Description of change
Adds a new option that intentionally fails the recording functionality.

## Testing
Tested on iidx32.
2025-12-11 22:03:51 -08:00
bicarus-dev 6856a0950c iidx: fix play record feature broken by new NVIDIA driver update (#444)
## Link to GitHub Issue, if one exists
fixes #442

## Description of change
New NVIDIA driver update (591.44) dropped support for older NVENC API.
Specifically,

* TDJ uses `NV_ENC_PRESET_HQ_GUID` but this is no longer supported; must
be swapped with new encoder presets
* `NvEncGetEncodePresetConfig` does not take new encoder presets, the
-Ex version must be used

All of this happens because bm2dx.dll takes code from an older NVENC
sample.

To address this,

* Install additional hooks for NVENC API
* Hijack calls to `NvEncGetEncodePresetConfig` and instead swap with
`NvEncGetEncodePresetConfigEx` (only if the initial call fails, which is
how we detect old vs new driver), replacing `NV_ENC_PRESET_HQ_GUID` with
`NV_ENC_PRESET_P4_GUID` and `NV_ENC_TUNING_INFO_HIGH_QUALITY`
(functionally equivalent according to NVIDIA docs -
https://docs.nvidia.com/video-technologies/video-codec-sdk/11.1/nvenc-preset-migration-guide/index.html)
* When calling `nvEncInitializeEncoder`, hijack and swap in the preset
GUID again.

## Testing
Tested on 591.44 driver on a RTX 4070.

The resulting videos were compared as well, and they are practically
identical from encoder perspective.
2025-12-11 16:09:09 -08:00
cchike dc7d8515d3 MDX: Fixed Ring Buffer Traversal Direction (P4IO Timing Fix) (#441)
## Description of change
In the real `libacio2.dll` implementation,
`ac_io_mdxf_get_control_status_buffer` returns the ring buffer entry at
`(head - index) mod size` but this function was using `(head + index)
mod size` instead. Basically, `index` is supposed to indicate how many
entries back, not forward. This was causing the polling history of each
player to be returned backwards. A few other things were updated to
bring the function's behavior closer to the real implementation:

1. Number of ring buffer entries per player were increased to 16 (this
is the size of them in the real implementation)
2. `ac_io_mdxf_get_control_status_buffer` now returns the current head
of the ring buffer (instead of a success/failure boolean), which is what
`arkmdxp4.dll` expects.

## Testing
I played a few songs to make sure nothing was broken. I also attached a
debugger and verified the ring buffer entries are *actually* now being
returned in reverse chronological order as intended.
2025-12-11 10:38:47 -08:00
bicarus-dev e100093ead iidx: dump more log from nvenc hooks (#443)
## Link to GitHub Issue, if one exists
For #442

## Description of change
Logs more info about nvenc hook calls to detect failures

## Testing
2025-12-11 09:39:23 -08:00
cchike e5808257ca MDX: Fixed Status Control Buffer Emulators (P4IO Timing Fix) (#439)
## Description of change
In its current state, the emulated `libacio2.dll` functions
`ac_io_mdxf_get_control_status_buffer` and
`ac_io_mdxf_update_control_status_buffer` only operate using a single
head pointer (`COUNTER`) for the ring buffers of
`[panel_state,timestamp]` (simplified for brevity here) polls for both
players, when they should be maintaining a separate head pointer for
each ring buffer. On every frame, `arkmdxp4.dll` makes calls to
`ac_io_mdxf_update_control_status_buffer`, which [in this emulated
version] advances the head pointer, creates a new entry for the latest
panel state info along with the current time, and stores it at the head
of the ring buffer. This function is called for each player: After the
call for 1P, the head pointer is advanced by 1, so by the time it's
called for 2P, the head pointer had already been advanced by 1, meaning
the new entry for 2P's panel state ring buffer is written two entries
ahead of where the last entry for 2P was written. This is an example of
how the entries were written for each ring buffer:

```
Frame 1:
1P[0] = t0
2P[1] = t0

Frame 2: 
1P[2] = t1
2P[3] = t1

Frame 3:
1P[4] = t2
2P[5] = t2

Frame 4:
1P[6] = t3
2P[0] = t3

...

Frame 7:
1P[5] = t6
2P[6] = t6

Frame 8:
1P[0] = t7
2P[1] = t7
```

This is an issue because `arkmdxp4.dll` calls
`ac_io_mdxf_get_control_status_buffer` on every frame to load the last 7
polls for each player, which depends on the ring buffer entries being in
reverse chronological order to properly determine when an arrow is
pressed and when it was released, but this is what it sees from the ring
buffers after 8 frames:

```
1P: t7 -> t3 -> t6 -> t2 -> t5 -> t1 -> t4
2P: t7 -> t3 -> t6 -> t2 -> t5 -> t1 -> t4
```

In this case, `arkmdxp4.dll` assigns step times based on polls that are
out of order, resulting in unpredictable timestamp assignment to each
step, with the issue compounding at lower framerates since the timestamp
deltas between each poll will be greater (see side note). After
implementing two separate head pointers for each ring buffer, those same
two arrays become:

```
1P: t7 -> t6 -> t5 -> t4 -> t3 -> t2 -> t1
2P: t7 -> t6 -> t5 -> t4 -> t3 -> t2 -> t1
```

This is how `arkmdxp4.dll` expects this data to be structured, so timing
should become more consistent after this change.

Side note: in this particular p4io implementation, the only place the
polls are updated is once per frame on the calls to
`ac_io_mdxf_update_control_status_buffer`, meaning the polling rate of
the controller used is locked to the framerate of the game. There should
be another thread not tied to the main one that listens to input from
the OS and updates these ring buffers on a much faster cadence.

## Testing
I played a few songs to make sure nothing was broken. I also attached a
debugger and verified the ring buffer entries are now being returned in
reverse chronological order as intended.
2025-12-09 19:15:15 -08:00
bicarus-dev 283ec3960a ea3: add warning message for missing <soft> node (#440)
## Link to GitHub Issue, if one exists
#345 

## Description of change
Add some descriptive error message for the case where we couldn't find
softid in prop files.

## Testing
manual
2025-12-09 19:14:58 -08:00
bicarus-dev 0ec37ac6ea troubleshooter: log soft id (#436)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Log soft ID (game datecode etc) at the top of auto-troubleshooter output
2025-12-08 17:35:59 -08:00
duel0213 0ef0b84308 mdx: change init_code instead of security_code for gold cab (#438)
## Link to GitHub Issue, if one exists
n/a

## Description of change
This changes previous security code change to init code change (#435)

## Testing
2025-12-08 17:14:35 -08:00
duel0213 62c30d8895 mdx: fix io mode bio2 boot crash (#435)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Currently when 'bio2' specified on 'io' parameter of app-config the game
crashed due to security code check.
These codes changes security code to corresponding one and adds fake
BIO2 entry to satisfy BIO2 check.

## Testing
Tested with MDX-003 and J:I:B ident.
The game boots and A3 now displays gold cab theme and displays MATCHING
GROUP, MATCHING SIDE on NETWORK OPTIONS test mode menu.
2025-12-08 10:16:16 -08:00
bicarus-dev 4e86cb16a2 cfg: tweaks to analog binding UI (#434)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Add the raw device handle string to tooltip.
Add `Reset` button that clears all values.

## Testing
manual testing
2025-12-06 01:47:00 -08:00
bicarus-dev 76f401de95 iidx: rework wasapi/asio detection for iidx33+ (#433)
## Link to GitHub Issue, if one exists
Fixes #432 

## Description of change
Newer versions of iidx33+ reintroduces `SOUND_OUTPUT_DEVICE` env var.
iidx module must account for its return

## Testing
2025-12-05 15:33:09 -08:00
bicarus-dev 5947779502 iidx,sdvx: warn user when using -monitor with TDJ/UFC mode (#431)
## Link to GitHub Issue, if one exists
#345 

## Description of change
Warn user via warning message in log + auto-troubleshooter entry when
-monitor option is used in full screen with TDJ and UFC mode.

This covers yet another case where TDJ subscreen does not respond to
mouse clicks (because the game expects them to come through the primary
display). For UFC, -monitor usually results in a failure to create DX
adapter.

## Testing
Manual verification with TDJ/UFC modes and two monitors.
2025-12-04 21:57:36 -08:00
bicarus-dev 61c97ccaab net: ignore -ea if -url is set (#430)
## Link to GitHub Issue, if one exists
#345 

## Description of change
Ignore -ea if -url is provided

## Testing
2025-12-01 16:04:32 -08:00
bicarus-dev 12d6a88c60 iidx: turn off camera access by default (#428)
## Link to GitHub Issue, if one exists
For #345 
Similar idea as https://github.com/spice2x/spice2x.github.io/pull/350

## Description of change
Disable camera access by default by setting `CONNECT_CAMERA` environment
var to 0 before launching. This was one of those "required" things
people clicked on, but now it's done automatically to reduce friction
for new users.

On cab setups (i.e., running without -iidx module), cameras will remain
on, unless user overrides it.

On franken setups (partial I/O) - user will need to specify what they
want via the new option, we can't guess correctly.

## Testing
2025-11-30 16:13:34 -08:00
bicarus-dev 984b41a1ae cfg: fix combo boxes (#429)
## Link to GitHub Issue, if one exists
n/a

## Description of change
Combo boxes for options were not highlighting the currently selected
item. Also, when there were no description provided for an option, it
was still showing the empty `()` in the selected value.

## Testing
2025-11-30 01:59:25 -08:00
duel0213 d06b8bd731 mfc: fix 2025 version touch (#426)
## Link to GitHub Issue, if one exists
n/a

## Description of change
HG cabinet uses wintouch and it requires TOUCHEVENTF_PRIMARY flag in
order to accept touch inputs.
Added wintouchemu on game hook and TOUCHEVENTF_PRIMARY flag on
wintouchemu.
 
## Testing
The game boots into test menu, touch now works. (tested in I/O TEST ->
TOUCH PANEL CHECK with '-s' flag)
TDJ subscreen overlay and nostalgia seems works fine regardless added
TOUCHEVENTF_PRIMARY flag but more testing required.
2025-11-25 17:14:07 -08:00
bicarus-dev 0dad96b876 check for standalone cfg for fatal options 2025-11-23 09:36:38 -08:00
bicarus-dev def296095b reword log message to avoid confusion 2025-11-22 16:45:27 -08:00
282 changed files with 49031 additions and 27876 deletions
+13 -3
View File
@@ -1,8 +1,7 @@
## Contributing ## Contributing
Baseline rules for patch submissions are as follows. Any patches violating the rules below will not be accepted. Baseline rules for patch submissions are as follows. Any patches violating the rules below will not be accepted.
### New as of March 2025 To contribute, fork the repo (just the main branch), make changes in your fork, and contribute to upstream by opening a pull request to the main repo.
Pull requests in GitHub are open. You don't need to bother with patch file submissions anymore. To contribute, fork the repo (just the main branch), make changes in your fork, and contribute to upstream by opening a pull request to the main repo.
### Adding support for games ### Adding support for games
@@ -12,6 +11,14 @@ Pull requests in GitHub are open. You don't need to bother with patch file submi
* For a new game that was never supported (not a new version of a game, but rather a new series): please wait 1 year after official AC release in Japan. * For a new game that was never supported (not a new version of a game, but rather a new series): please wait 1 year after official AC release in Japan.
* For new version of an already supported game: proceed with caution and use generally-accepted community guidelines. * For new version of an already supported game: proceed with caution and use generally-accepted community guidelines.
### Forbidden "features" that will never be accepted
* Performing decryption of encrypted files
* Including (bundling), distributing, or linking to game data, or making it easier to find/download game data
* Any features that [phone home](https://en.wikipedia.org/wiki/Phoning_home)
* This includes automatic updaters.
* Exception: we have allowed the "patch import from URL" feature, but this has only been done under very careful considerations and plenty of warnings to the user.
### Avoiding regressions ### Avoiding regressions
The biggest risk in making code changes to spice is the risk of regressions. There is a **lot** of shared code used by many different games, reaching back game versions that are more than a decade old. It is practically impossible to test all supported games and versions. The biggest risk in making code changes to spice is the risk of regressions. There is a **lot** of shared code used by many different games, reaching back game versions that are more than a decade old. It is practically impossible to test all supported games and versions.
@@ -22,7 +29,7 @@ Therefore, when making code changes, please be extremely careful about containin
#### Config file compatibility #### Config file compatibility
Do not change the names of options, buttons binds, analogs, etc - since they are used as unique identifiers in config files. If they are changed, you will introduce an incompatibility with previous versions of config file. Do not change the names of buttons binds, analogs, strings for command line parameters, etc - since they are used as unique identifiers in config files. If they are changed, you will introduce an incompatibility with previous versions of config file.
### Code quality requirements ### Code quality requirements
@@ -49,6 +56,9 @@ Watch out for legacy OS compatibility. Currently, the minimum support floor is W
### Code style requirements ### Code style requirements
* Indents are four spaces. * Indents are four spaces.
* Try to keep under 100 characters per line. This includes comments and string literals.
* Avoid making unnecessary formatting or whitespace changes in a PR; e.g., watch out for text editor automatically stripping trailing whitespace. It pollutes the diff and slows down code review.
* For signed/unsigned integer types, prefer `cstdint` types; e.g., use `int32_t`, `uint64_t` and not `int` and `unsigned long long`.
* Always use \{ curly braces \} when appropriate; do not omit them even when it's optional; such as `for` `if` `else`, etc. * Always use \{ curly braces \} when appropriate; do not omit them even when it's optional; such as `for` `if` `else`, etc.
OK: OK:
+1 -1
View File
@@ -7,7 +7,7 @@
> >
> Feel free to remove this section after you have read it. > Feel free to remove this section after you have read it.
## Link to GitHub Issue, if one exists ## Link to GitHub Issue or related Pull Request, if one exists
#0 #0
## Description of change ## Description of change
+49 -17
View File
@@ -109,6 +109,12 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# RapidJSON does this # RapidJSON does this
add_compile_definitions(_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING) add_compile_definitions(_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING)
add_compile_options("-Wno-nontrivial-memcall") # imgui, rapidjson
add_compile_options("-Wno-deprecated-builtins") # robin_hood
add_compile_options("-Wno-deprecated-declarations") # rapidjson
add_compile_options("-Wno-inconsistent-missing-override") # headsocket
add_compile_options("-Wno-microsoft-exception-spec") # gcc/clang have different noexcept specifiers on d3d9 COM interfaces
# warnings # warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunknown-warning-option") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunknown-warning-option")
@@ -118,6 +124,9 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static")
else() else()
# warnings as errors
set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
# warnings # warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") # enable stuff set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") # enable stuff
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-pointer-arith") # but we love pointer arithmetic :) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-pointer-arith") # but we love pointer arithmetic :)
@@ -167,8 +176,8 @@ else()
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG")
# file prefix map for relative working directory # file prefix map for relative working directory
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -ffile-prefix-map=${CMAKE_SOURCE_DIR}=.") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -ffile-prefix-map=\"${CMAKE_SOURCE_DIR}=.\"")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffile-prefix-map=${CMAKE_SOURCE_DIR}=.") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -ffile-prefix-map=\"${CMAKE_SOURCE_DIR}=.\"")
endif() endif()
# release with debug info flags # release with debug info flags
@@ -257,6 +266,7 @@ set_source_files_properties(cfg/manifest.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(cfg/icon.rc PROPERTIES LANGUAGE RC) set_source_files_properties(cfg/icon.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(cfg/Win32D.rc PROPERTIES LANGUAGE RC) set_source_files_properties(cfg/Win32D.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(build/manifest64.rc PROPERTIES LANGUAGE RC) set_source_files_properties(build/manifest64.rc PROPERTIES LANGUAGE RC)
set_source_files_properties(stubs/manifest.rc PROPERTIES LANGUAGE RC)
# sources # sources
######### #########
@@ -343,6 +353,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
cfg/configurator.cpp cfg/configurator.cpp
cfg/configurator_wnd.cpp cfg/configurator_wnd.cpp
cfg/screen_resize.cpp cfg/screen_resize.cpp
cfg/controller_presets.cpp
# easrv # easrv
easrv/easrv.cpp easrv/easrv.cpp
@@ -372,6 +383,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/shared/twtouch.cpp games/shared/twtouch.cpp
games/popn/popn.cpp games/popn/popn.cpp
games/popn/io.cpp games/popn/io.cpp
games/popn/bi3a_hook.cpp
games/bbc/bbc.cpp games/bbc/bbc.cpp
games/bbc/io.cpp games/bbc/io.cpp
games/hpm/hpm.cpp games/hpm/hpm.cpp
@@ -398,6 +410,10 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/nost/poke.cpp games/nost/poke.cpp
games/gitadora/gitadora.cpp games/gitadora/gitadora.cpp
games/gitadora/io.cpp games/gitadora/io.cpp
games/gitadora/handle.cpp
games/gitadora/j32d.cpp
games/gitadora/j33i.cpp
games/gitadora/bi2x_hook.cpp
games/mga/mga.cpp games/mga/mga.cpp
games/mga/io.cpp games/mga/io.cpp
games/mga/gunio.cpp games/mga/gunio.cpp
@@ -434,6 +450,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/scotto/io.cpp games/scotto/io.cpp
games/drs/drs.cpp games/drs/drs.cpp
games/drs/io.cpp games/drs/io.cpp
games/drs/rgb_cam.cpp
games/we/we.cpp games/we/we.cpp
games/we/io.cpp games/we/io.cpp
games/we/touchpanel.cpp games/we/touchpanel.cpp
@@ -467,12 +484,15 @@ set(SOURCE_FILES ${SOURCE_FILES}
games/pc/bi2x_hook.cpp games/pc/bi2x_hook.cpp
# hooks # hooks
hooks/audio/acm.cpp
hooks/audio/audio.cpp hooks/audio/audio.cpp
hooks/audio/buffer.cpp hooks/audio/buffer.cpp
hooks/audio/mme.cpp
hooks/audio/util.cpp hooks/audio/util.cpp
hooks/audio/backends/dsound/dsound_backend.cpp hooks/audio/backends/dsound/dsound_backend.cpp
hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp hooks/audio/backends/mmdevice/audio_endpoint_volume.cpp
hooks/audio/backends/mmdevice/device.cpp hooks/audio/backends/mmdevice/device.cpp
hooks/audio/backends/mmdevice/device_collection.cpp
hooks/audio/backends/mmdevice/device_enumerator.cpp hooks/audio/backends/mmdevice/device_enumerator.cpp
hooks/audio/backends/wasapi/audio_client.cpp hooks/audio/backends/wasapi/audio_client.cpp
hooks/audio/backends/wasapi/audio_render_client.cpp hooks/audio/backends/wasapi/audio_render_client.cpp
@@ -525,9 +545,11 @@ set(SOURCE_FILES ${SOURCE_FILES}
misc/device.cpp misc/device.cpp
misc/eamuse.cpp misc/eamuse.cpp
misc/extdev.cpp misc/extdev.cpp
misc/nativetouchhook.cpp
misc/sciunit.cpp misc/sciunit.cpp
misc/sde.cpp misc/sde.cpp
misc/wintouchemu.cpp misc/wintouchemu.cpp
misc/ami2000.cpp
# nvapi # nvapi
nvapi/nvapi.cpp nvapi/nvapi.cpp
@@ -542,9 +564,11 @@ set(SOURCE_FILES ${SOURCE_FILES}
overlay/windows/camera_control.cpp overlay/windows/camera_control.cpp
overlay/windows/card_manager.cpp overlay/windows/card_manager.cpp
overlay/windows/drs_dancefloor.cpp overlay/windows/drs_dancefloor.cpp
overlay/windows/gfdm_sub.cpp
overlay/windows/screen_resize.cpp overlay/windows/screen_resize.cpp
overlay/windows/sdvx_sub.cpp overlay/windows/sdvx_sub.cpp
overlay/windows/config.cpp overlay/windows/config.cpp
overlay/windows/controller_presets.cpp
overlay/windows/control.cpp overlay/windows/control.cpp
overlay/windows/eadev.cpp overlay/windows/eadev.cpp
overlay/windows/fps.cpp overlay/windows/fps.cpp
@@ -559,6 +583,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
overlay/windows/log.cpp overlay/windows/log.cpp
overlay/windows/midi.cpp overlay/windows/midi.cpp
overlay/windows/patch_manager.cpp overlay/windows/patch_manager.cpp
overlay/windows/popn_sub.cpp
overlay/windows/wnd_manager.cpp overlay/windows/wnd_manager.cpp
# rawinput # rawinput
@@ -573,6 +598,8 @@ set(SOURCE_FILES ${SOURCE_FILES}
rawinput/smxstage.h rawinput/smxstage.h
rawinput/smxdedicab.cpp rawinput/smxdedicab.cpp
rawinput/smxdedicab.h rawinput/smxdedicab.h
rawinput/xinput.cpp
rawinput/xinput.h
# reader # reader
reader/reader.cpp reader/reader.cpp
@@ -612,6 +639,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
util/execexe.cpp util/execexe.cpp
util/dependencies.cpp util/dependencies.cpp
util/deferlog.cpp util/deferlog.cpp
util/socd_cleaner.cpp
) )
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES}) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOURCE_FILES})
@@ -623,7 +651,7 @@ set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/W
add_executable(spicetools_spice ${SOURCE_FILES} ${RESOURCE_FILES}) add_executable(spicetools_spice ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_spice target_link_libraries(spicetools_spice
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
target_link_libraries(spicetools_spice PUBLIC winscard) target_link_libraries(spicetools_spice PUBLIC winscard)
set_target_properties(spicetools_spice PROPERTIES PREFIX "") set_target_properties(spicetools_spice PROPERTIES PREFIX "")
set_target_properties(spicetools_spice PROPERTIES OUTPUT_NAME "spice") set_target_properties(spicetools_spice PROPERTIES OUTPUT_NAME "spice")
@@ -639,7 +667,7 @@ set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/W
add_executable(spicetools_spice_laa ${SOURCE_FILES} ${RESOURCE_FILES}) add_executable(spicetools_spice_laa ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_spice_laa target_link_libraries(spicetools_spice_laa
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
target_link_libraries(spicetools_spice_laa PUBLIC winscard) target_link_libraries(spicetools_spice_laa PUBLIC winscard)
set_target_properties(spicetools_spice_laa PROPERTIES PREFIX "") set_target_properties(spicetools_spice_laa PROPERTIES PREFIX "")
set_target_properties(spicetools_spice_laa PROPERTIES OUTPUT_NAME "spice_laa") set_target_properties(spicetools_spice_laa PROPERTIES OUTPUT_NAME "spice_laa")
@@ -656,7 +684,7 @@ set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/W
add_executable(spicetools_spice_linux ${SOURCE_FILES} ${RESOURCE_FILES}) add_executable(spicetools_spice_linux ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_spice_linux target_link_libraries(spicetools_spice_linux
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
set_target_properties(spicetools_spice_linux PROPERTIES PREFIX "") set_target_properties(spicetools_spice_linux PROPERTIES PREFIX "")
set_target_properties(spicetools_spice_linux PROPERTIES OUTPUT_NAME "spice_linux") set_target_properties(spicetools_spice_linux PROPERTIES OUTPUT_NAME "spice_linux")
target_compile_definitions(spicetools_spice_linux PRIVATE NO_SCARD=1 PRIVATE SPICE_LINUX=1) target_compile_definitions(spicetools_spice_linux PRIVATE NO_SCARD=1 PRIVATE SPICE_LINUX=1)
@@ -674,7 +702,7 @@ add_executable(spicetools_spice64 ${SOURCE_FILES} ${RESOURCE_FILES})
# do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break # do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break
target_link_libraries(spicetools_spice64 target_link_libraries(spicetools_spice64
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2 PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
target_link_libraries(spicetools_spice64 PUBLIC winscard) target_link_libraries(spicetools_spice64 PUBLIC winscard)
set_target_properties(spicetools_spice64 PROPERTIES PREFIX "") set_target_properties(spicetools_spice64 PROPERTIES PREFIX "")
set_target_properties(spicetools_spice64 PROPERTIES OUTPUT_NAME "spice64") set_target_properties(spicetools_spice64 PROPERTIES OUTPUT_NAME "spice64")
@@ -693,7 +721,7 @@ add_executable(spicetools_spice64_linux ${SOURCE_FILES} ${RESOURCE_FILES})
# do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break # do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break
target_link_libraries(spicetools_spice64_linux target_link_libraries(spicetools_spice64_linux
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2 PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
set_target_properties(spicetools_spice64_linux PROPERTIES PREFIX "") set_target_properties(spicetools_spice64_linux PROPERTIES PREFIX "")
set_target_properties(spicetools_spice64_linux PROPERTIES OUTPUT_NAME "spice64_linux") set_target_properties(spicetools_spice64_linux PROPERTIES OUTPUT_NAME "spice64_linux")
target_compile_definitions(spicetools_spice64_linux PRIVATE SPICE64=1) target_compile_definitions(spicetools_spice64_linux PRIVATE SPICE64=1)
@@ -711,7 +739,7 @@ set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D.
add_executable(spicetools_cfg WIN32 ${SOURCE_FILES} ${RESOURCE_FILES}) add_executable(spicetools_cfg WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_cfg target_link_libraries(spicetools_cfg
PUBLIC ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids PUBLIC ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
target_link_libraries(spicetools_cfg PUBLIC winscard) target_link_libraries(spicetools_cfg PUBLIC winscard)
set_target_properties(spicetools_cfg PROPERTIES PREFIX "") set_target_properties(spicetools_cfg PROPERTIES PREFIX "")
set_target_properties(spicetools_cfg PROPERTIES OUTPUT_NAME "spicecfg") set_target_properties(spicetools_cfg PROPERTIES OUTPUT_NAME "spicecfg")
@@ -729,7 +757,7 @@ set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D.
add_executable(spicetools_cfg_linux WIN32 ${SOURCE_FILES} ${RESOURCE_FILES}) add_executable(spicetools_cfg_linux WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
target_link_libraries(spicetools_cfg_linux target_link_libraries(spicetools_cfg_linux
PUBLIC ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids PUBLIC ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features) PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
set_target_properties(spicetools_cfg_linux PROPERTIES PREFIX "") set_target_properties(spicetools_cfg_linux PROPERTIES PREFIX "")
set_target_properties(spicetools_cfg_linux PROPERTIES OUTPUT_NAME "spicecfg_linux") set_target_properties(spicetools_cfg_linux PROPERTIES OUTPUT_NAME "spicecfg_linux")
target_compile_definitions(spicetools_cfg_linux PRIVATE SPICETOOLS_SPICECFG_STANDALONE=1) target_compile_definitions(spicetools_cfg_linux PRIVATE SPICETOOLS_SPICECFG_STANDALONE=1)
@@ -745,7 +773,7 @@ endif()
# kbt.dll # kbt.dll
set(SOURCE_FILES stubs/stubs.cpp) set(SOURCE_FILES stubs/stubs.cpp)
add_library(spicetools_stubs_kbt SHARED ${SOURCE_FILES} stubs/stubs.def) add_library(spicetools_stubs_kbt SHARED ${SOURCE_FILES} stubs/stubs.def)
target_link_libraries(spicetools_stubs_kbt PRIVATE fmt-header-only) target_link_libraries(spicetools_stubs_kbt PRIVATE fmt::fmt-header-only)
set_target_properties(spicetools_stubs_kbt PROPERTIES PREFIX "") set_target_properties(spicetools_stubs_kbt PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_kbt PROPERTIES OUTPUT_NAME "kbt") set_target_properties(spicetools_stubs_kbt PROPERTIES OUTPUT_NAME "kbt")
target_compile_definitions(spicetools_stubs_kbt PRIVATE STUB=1) target_compile_definitions(spicetools_stubs_kbt PRIVATE STUB=1)
@@ -756,7 +784,7 @@ endif()
# kbt.dll 64bit # kbt.dll 64bit
add_library(spicetools_stubs_kbt64 SHARED ${SOURCE_FILES} stubs/stubs.def) add_library(spicetools_stubs_kbt64 SHARED ${SOURCE_FILES} stubs/stubs.def)
target_link_libraries(spicetools_stubs_kbt64 PRIVATE fmt-header-only) target_link_libraries(spicetools_stubs_kbt64 PRIVATE fmt::fmt-header-only)
set_target_properties(spicetools_stubs_kbt64 PROPERTIES PREFIX "") set_target_properties(spicetools_stubs_kbt64 PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_kbt64 PROPERTIES OUTPUT_NAME "kbt") set_target_properties(spicetools_stubs_kbt64 PROPERTIES OUTPUT_NAME "kbt")
target_compile_definitions(spicetools_stubs_kbt64 PRIVATE STUB=1) target_compile_definitions(spicetools_stubs_kbt64 PRIVATE STUB=1)
@@ -768,7 +796,7 @@ endif()
# kld.dll # kld.dll
set(SOURCE_FILES stubs/stubs.cpp) set(SOURCE_FILES stubs/stubs.cpp)
add_library(spicetools_stubs_kld SHARED ${SOURCE_FILES} stubs/stubs.def) add_library(spicetools_stubs_kld SHARED ${SOURCE_FILES} stubs/stubs.def)
target_link_libraries(spicetools_stubs_kld PRIVATE fmt-header-only) target_link_libraries(spicetools_stubs_kld PRIVATE fmt::fmt-header-only)
set_target_properties(spicetools_stubs_kld PROPERTIES PREFIX "") set_target_properties(spicetools_stubs_kld PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_kld PROPERTIES OUTPUT_NAME "kld") set_target_properties(spicetools_stubs_kld PROPERTIES OUTPUT_NAME "kld")
target_compile_definitions(spicetools_stubs_kld PRIVATE STUB=1) target_compile_definitions(spicetools_stubs_kld PRIVATE STUB=1)
@@ -779,7 +807,7 @@ endif()
# kld.dll 64bit # kld.dll 64bit
add_library(spicetools_stubs_kld64 SHARED ${SOURCE_FILES} stubs/stubs.def) add_library(spicetools_stubs_kld64 SHARED ${SOURCE_FILES} stubs/stubs.def)
target_link_libraries(spicetools_stubs_kld64 PRIVATE fmt-header-only) target_link_libraries(spicetools_stubs_kld64 PRIVATE fmt::fmt-header-only)
set_target_properties(spicetools_stubs_kld64 PROPERTIES PREFIX "") set_target_properties(spicetools_stubs_kld64 PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_kld64 PROPERTIES OUTPUT_NAME "kld") set_target_properties(spicetools_stubs_kld64 PROPERTIES OUTPUT_NAME "kld")
target_compile_definitions(spicetools_stubs_kld64 PRIVATE STUB=1) target_compile_definitions(spicetools_stubs_kld64 PRIVATE STUB=1)
@@ -790,7 +818,8 @@ endif()
# nvcuda.dll # nvcuda.dll
set(SOURCE_FILES stubs/nvcuda.cpp) set(SOURCE_FILES stubs/nvcuda.cpp)
add_library(spicetools_stubs_nvcuda SHARED ${SOURCE_FILES} stubs/nvcuda.def) set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_nvcuda SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvcuda.def)
set_target_properties(spicetools_stubs_nvcuda PROPERTIES PREFIX "") set_target_properties(spicetools_stubs_nvcuda PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvcuda PROPERTIES OUTPUT_NAME "nvcuda") set_target_properties(spicetools_stubs_nvcuda PROPERTIES OUTPUT_NAME "nvcuda")
@@ -800,7 +829,8 @@ endif()
# nvcuvid.dll # nvcuvid.dll
set(SOURCE_FILES stubs/nvcuvid.cpp) set(SOURCE_FILES stubs/nvcuvid.cpp)
add_library(spicetools_stubs_nvcuvid SHARED ${SOURCE_FILES} stubs/nvcuvid.def) set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_nvcuvid SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvcuvid.def)
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES PREFIX "") set_target_properties(spicetools_stubs_nvcuvid PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvcuvid PROPERTIES OUTPUT_NAME "nvcuvid") set_target_properties(spicetools_stubs_nvcuvid PROPERTIES OUTPUT_NAME "nvcuvid")
@@ -810,7 +840,8 @@ endif()
# nvEncodeAPI64.dll # nvEncodeAPI64.dll
set(SOURCE_FILES stubs/nvEncodeAPI64.cpp) set(SOURCE_FILES stubs/nvEncodeAPI64.cpp)
add_library(spicetools_stubs_nvEncodeAPI64 SHARED ${SOURCE_FILES} stubs/nvEncodeAPI64.def) set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_nvEncodeAPI64 SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/nvEncodeAPI64.def)
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES PREFIX "") set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES OUTPUT_NAME "nvEncodeAPI64") set_target_properties(spicetools_stubs_nvEncodeAPI64 PROPERTIES OUTPUT_NAME "nvEncodeAPI64")
@@ -820,7 +851,8 @@ endif()
# cpusbxpkm.dll (32 bit) # cpusbxpkm.dll (32 bit)
set(SOURCE_FILES stubs/cpusbxpkm.cpp) set(SOURCE_FILES stubs/cpusbxpkm.cpp)
add_library(spicetools_stubs_cpusbxpkm SHARED ${SOURCE_FILES} stubs/cpusbxpkm.def) set(RESOURCE_FILES stubs/manifest.rc)
add_library(spicetools_stubs_cpusbxpkm SHARED ${SOURCE_FILES} ${RESOURCE_FILES} stubs/cpusbxpkm.def)
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES PREFIX "") set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES PREFIX "")
set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES OUTPUT_NAME "cpusbxpkm") set_target_properties(spicetools_stubs_cpusbxpkm PROPERTIES OUTPUT_NAME "cpusbxpkm")
+30 -6
View File
@@ -9,11 +9,22 @@
#include "games/drs/drs.h" #include "games/drs/drs.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h" #include "util/utils.h"
#include "util/tapeled.h" #include "util/tapeled.h"
using namespace GameAPI; using namespace GameAPI;
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
// state // state
static uint8_t STATUS_BUFFER[272] {}; static uint8_t STATUS_BUFFER[272] {};
static bool STATUS_BUFFER_FREEZE = false; static bool STATUS_BUFFER_FREEZE = false;
@@ -94,18 +105,25 @@ static bool __cdecl ac_io_bi2a_update_control_status_buffer() {
} }
// volume left // volume left
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) { const auto now = get_performance_milliseconds();
const auto vol_l_state = socd::socd_clean(0,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right)),
now);
if (vol_l_state == socd::SocdCCW) {
BI2A_VOLL = (BI2A_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023; BI2A_VOLL = (BI2A_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} } else if (vol_l_state == socd::SocdCW) {
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
BI2A_VOLL = (BI2A_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023; BI2A_VOLL = (BI2A_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} }
// volume right // volume right
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) { const auto vol_r_state = socd::socd_clean(1,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right)),
now);
if (vol_r_state == socd::SocdCCW) {
BI2A_VOLR = (BI2A_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023; BI2A_VOLR = (BI2A_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} } else if (vol_r_state == socd::SocdCW) {
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
BI2A_VOLR = (BI2A_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023; BI2A_VOLR = (BI2A_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} }
@@ -127,6 +145,12 @@ static bool __cdecl ac_io_bi2a_update_control_status_buffer() {
// save volumes in buffer // save volumes in buffer
*((uint16_t*) &STATUS_BUFFER[17]) = (uint16_t) ((vol_left) << 2); *((uint16_t*) &STATUS_BUFFER[17]) = (uint16_t) ((vol_left) << 2);
*((uint16_t*) &STATUS_BUFFER[19]) = (uint16_t) ((vol_right) << 2); *((uint16_t*) &STATUS_BUFFER[19]) = (uint16_t) ((vol_right) << 2);
log_debug(
"bi2a",
"knobs = {} {}",
*((uint16_t*) &STATUS_BUFFER[17]),
*((uint16_t*) &STATUS_BUFFER[19]));
} }
// DanceDanceRevolution // DanceDanceRevolution
+122 -20
View File
@@ -35,24 +35,33 @@ struct ICCA_STATUS_LA9 {
static_assert(sizeof(struct ICCA_STATUS) == 24, "ICCA_STATUS must be 24 bytes"); static_assert(sizeof(struct ICCA_STATUS) == 24, "ICCA_STATUS must be 24 bytes");
enum ICCA_WORKFLOW { enum ICCA_WORKFLOW {
STEP, STEP = 0,
SLEEP, SLEEP = 1,
START, START = 2,
INIT, INIT = 3,
READY, READY = 4,
GET_USERID, GET_USERID = 5,
ACTIVE, ACTIVE = 6,
EJECT, EJECT = 7,
EJECT_CHECK, EJECT_CHECK = 8,
END, END = 9,
CLOSE_EJECT, CLOSE_EJECT = 10,
CLOSE_E_CHK, CLOSE_E_CHK = 11,
CLOSE_END, CLOSE_END = 12,
ERR_GETUID = -2 ERR_GETUID = -2,
ERR_REMOVED = -11
}; };
// used by RA as the status_code
enum ICCA_STATUS_CODE {
EMPTY = 1,
ENGAGED = 2
};
struct ICCA_UNIT { struct ICCA_UNIT {
struct ICCA_STATUS status {}; struct ICCA_STATUS status {};
enum ICCA_WORKFLOW state = STEP; enum ICCA_WORKFLOW state = STEP;
enum ICCA_WORKFLOW state_jdz = SLEEP;
bool card_cmd_pressed = false; bool card_cmd_pressed = false;
bool card_in = false; bool card_in = false;
double card_in_time = 0.0; double card_in_time = 0.0;
@@ -75,6 +84,11 @@ static inline int icca_get_active_count() {
} }
static inline int icca_get_unit_id(int unit_id) { static inline int icca_get_unit_id(int unit_id) {
// on RA the unit_id is zero-indexed
if (avs::game::is_model("JDZ")) {
return unit_id;
}
if (icca_get_active_count() < 2) if (icca_get_active_count() < 2)
return 1; return 1;
else { else {
@@ -122,9 +136,15 @@ static inline void update_card(int unit_id) {
IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid); IS_LAST_CARD_FELICA = is_card_uid_felica(unit->status.uid);
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE; unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
unit->state_jdz = ACTIVE;
unit->status.error = 0; unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
} else { } else {
unit->state = ERR_GETUID; unit->state = ERR_GETUID;
unit->state_jdz = ERR_GETUID;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
memset(unit->status.uid, 0, 8); memset(unit->status.uid, 0, 8);
} }
unit->card_in = true; unit->card_in = true;
@@ -132,10 +152,25 @@ static inline void update_card(int unit_id) {
} else if (unit->state == EJECT_CHECK) { } else if (unit->state == EJECT_CHECK) {
unit->state = SLEEP; unit->state = SLEEP;
unit->card_in = false; unit->card_in = false;
unit->status.front_sensor = 0;
unit->status.rear_sensor = 0;
} else if (unit->state_jdz == READY) {
unit->state = GET_USERID;
unit->state_jdz = GET_USERID;
unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
unit->card_in = true;
unit->card_in_time = get_performance_seconds();
} }
} else { } else {
unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE; unit->state = acio::ICCA_COMPAT_ACTIVE ? START : ACTIVE;
if (unit->state_jdz == READY) {
unit->state_jdz = GET_USERID;
}
unit->status.error = 0; unit->status.error = 0;
unit->status.front_sensor = 1;
unit->status.rear_sensor = 1;
unit->card_in = true; unit->card_in = true;
unit->card_in_time = get_performance_seconds(); unit->card_in_time = get_performance_seconds();
} }
@@ -144,6 +179,12 @@ static inline void update_card(int unit_id) {
unit->state = CLOSE_EJECT; unit->state = CLOSE_EJECT;
if (fabs(get_performance_seconds() - unit->card_in_time) > CARD_TIMEOUT) { if (fabs(get_performance_seconds() - unit->card_in_time) > CARD_TIMEOUT) {
unit->card_in = false; unit->card_in = false;
memset(unit->status.uid, 0, 8);
if (unit->state_jdz == ACTIVE || unit->state_jdz == GET_USERID) {
unit->state_jdz = ERR_REMOVED;
}
unit->status.front_sensor = 0;
unit->status.rear_sensor = 0;
} }
} }
@@ -302,7 +343,11 @@ static char __cdecl ac_io_icca_get_status(void *a1, void *a2) {
// copy state to output buffer // copy state to output buffer
ICCA_UNIT *unit = &ICCA_UNITS[unit_id]; ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
unit->status.status_code = unit->state; if (avs::game::is_model("JDZ")) {
unit->status.status_code = unit->card_in ? ENGAGED : EMPTY;
} else {
unit->status.status_code = unit->state;
}
memcpy(status, &unit->status, sizeof(struct ICCA_STATUS)); memcpy(status, &unit->status, sizeof(struct ICCA_STATUS));
// funny workaround // funny workaround
@@ -387,6 +432,11 @@ static char __cdecl ac_io_icca_req_uid(int unit_id) {
static int __cdecl ac_io_icca_req_uid_isfinished(int unit_id, DWORD *read_state) { static int __cdecl ac_io_icca_req_uid_isfinished(int unit_id, DWORD *read_state) {
unit_id = icca_get_unit_id(unit_id); unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id]; ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
if (avs::game::is_model("JDZ")) {
// hack for RA - only ever seen it as this
*read_state = 8;
return 1;
}
if (unit->card_in) { if (unit->card_in) {
if (fabs(get_performance_seconds() - unit->card_in_time) < CARD_TIMEOUT) { if (fabs(get_performance_seconds() - unit->card_in_time) < CARD_TIMEOUT) {
unit->state = END; unit->state = END;
@@ -403,16 +453,61 @@ static int __cdecl ac_io_icca_send_keep_alive_packet(int a1, int a2, int a3) {
return 0; return 0;
} }
static int __cdecl ac_io_icca_workflow_jdz(int workflow, int unit_id) {
unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
switch (workflow) {
// RA uses "STEP" as more of a polling thing it seems
case STEP:
switch (unit->state_jdz) {
case CLOSE_EJECT:
unit->state_jdz = CLOSE_E_CHK;
break;
case CLOSE_E_CHK:
unit->state_jdz = CLOSE_END;
break;
case CLOSE_END:
unit->state_jdz = SLEEP;
break;
default:
break;
}
break;
case SLEEP:
unit->state = SLEEP;
break;
case START:
unit->state_jdz = INIT;
break;
case INIT:
case READY:
unit->state_jdz = READY;
break;
case CLOSE_EJECT:
unit->state_jdz = CLOSE_EJECT;
return CLOSE_E_CHK;
case CLOSE_END:
unit->state_jdz = SLEEP;
break;
default:
// should probably log a warning here
break;
}
return unit->state_jdz;
}
static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) { static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
// RA uses a different workflow process for the slotted readers; treat it differently
if (avs::game::is_model("JDZ")) {
return ac_io_icca_workflow_jdz(workflow, unit_id);
}
unit_id = icca_get_unit_id(unit_id); unit_id = icca_get_unit_id(unit_id);
ICCA_UNIT *unit = &ICCA_UNITS[unit_id]; ICCA_UNIT *unit = &ICCA_UNITS[unit_id];
switch (workflow) { switch (workflow) {
case STEP: case STEP:
if (avs::game::is_model("JDZ")) unit->state = STEP;
unit->state = SLEEP;
else
unit->state = STEP;
break; break;
case SLEEP: case SLEEP:
unit->state = SLEEP; unit->state = SLEEP;
@@ -441,7 +536,6 @@ static int __cdecl ac_io_icca_workflow(int workflow, int unit_id) {
default: default:
break; break;
} }
return unit->state; return unit->state;
} }
@@ -467,6 +561,14 @@ acio::ICCAModule::ICCAModule(HMODULE module, acio::HookMode hookMode) : ACIOModu
void acio::ICCAModule::attach() { void acio::ICCAModule::attach() {
ACIOModule::attach(); ACIOModule::attach();
// workaround for RA; ac_io_icca_cardunit_init is never called so treat like both are initialized
if (avs::game::is_model("JDZ")) {
ICCA_UNITS[0].initialized = true;
ICCA_UNITS[1].initialized = true;
CARD_TIMEOUT = 10.0; // keep the card in the slot for longer
}
// hooks // hooks
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init); ACIO_MODULE_HOOK(ac_io_icca_cardunit_init);
ACIO_MODULE_HOOK(ac_io_icca_cardunit_init_isfinished); ACIO_MODULE_HOOK(ac_io_icca_cardunit_init_isfinished);
+5 -5
View File
@@ -52,7 +52,7 @@ static bool __cdecl ac_io_j32d_update_control_status_buffer(size_t a1) {
float pad3_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad3)); float pad3_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad3));
float pad4_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad4)); float pad4_vel = Buttons::getVelocity(RI_MGR, buttons.at(games::ftt::Buttons::Pad4));
// FIXME(felix): this logic seems wrong for analog handling but correct for digital inputs // FIXME(felix): this logic seems wrong for analog handling but correct for digital inputs
if (pad1_vel > 0.f) { if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad1))) {
STATUS_BUFFER[6] = (int) (51.f * pad1_vel + 0.5f); STATUS_BUFFER[6] = (int) (51.f * pad1_vel + 0.5f);
} }
if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad2))) { if (Buttons::getState(RI_MGR, buttons.at(games::ftt::Buttons::Pad2))) {
@@ -67,10 +67,10 @@ static bool __cdecl ac_io_j32d_update_control_status_buffer(size_t a1) {
// process analogs // process analogs
auto &analogs = games::ftt::get_analogs(); auto &analogs = games::ftt::get_analogs();
auto pad1_analog = analogs.at(games::ftt::Analogs::Pad1); auto &pad1_analog = analogs.at(games::ftt::Analogs::Pad1);
auto pad2_analog = analogs.at(games::ftt::Analogs::Pad2); auto &pad2_analog = analogs.at(games::ftt::Analogs::Pad2);
auto pad3_analog = analogs.at(games::ftt::Analogs::Pad3); auto &pad3_analog = analogs.at(games::ftt::Analogs::Pad3);
auto pad4_analog = analogs.at(games::ftt::Analogs::Pad4); auto &pad4_analog = analogs.at(games::ftt::Analogs::Pad4);
if (pad1_analog.isSet()) { if (pad1_analog.isSet()) {
auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad1_analog) + 0.5f); auto val = (uint32_t) (51.f * Analogs::getState(RI_MGR, pad1_analog) + 0.5f);
STATUS_BUFFER[6] = MAX(STATUS_BUFFER[6], val); STATUS_BUFFER[6] = MAX(STATUS_BUFFER[6], val);
+26 -6
View File
@@ -8,10 +8,21 @@
#include "games/sdvx/io.h" #include "games/sdvx/io.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
#include "util/socd_cleaner.h"
#include "util/time.h"
#include "util/utils.h" #include "util/utils.h"
using namespace GameAPI; using namespace GameAPI;
#define DEBUG_VERBOSE 0
#if DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
// globals // globals
uint8_t KFCA_VOL_SOUND = 96; uint8_t KFCA_VOL_SOUND = 96;
uint8_t KFCA_VOL_HEADPHONE = 96; uint8_t KFCA_VOL_HEADPHONE = 96;
@@ -342,18 +353,25 @@ static char __cdecl ac_io_kfca_update_control_status_buffer() {
} }
// volume left // volume left
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) { const auto now = get_performance_milliseconds();
const auto vol_l_state = socd::socd_clean(0,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right)),
now);
if (vol_l_state == socd::SocdCCW) {
KFCA_VOLL = (KFCA_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023; KFCA_VOLL = (KFCA_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} } else if (vol_l_state == socd::SocdCW) {
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
KFCA_VOLL = (KFCA_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023; KFCA_VOLL = (KFCA_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} }
// volume right // volume right
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) { const auto vol_r_state = socd::socd_clean(1,
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left)),
Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right)),
now);
if (vol_r_state == socd::SocdCCW) {
KFCA_VOLR = (KFCA_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023; KFCA_VOLR = (KFCA_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} } else if (vol_r_state == socd::SocdCW) {
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
KFCA_VOLR = (KFCA_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023; KFCA_VOLR = (KFCA_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
} }
@@ -372,6 +390,8 @@ static char __cdecl ac_io_kfca_update_control_status_buffer() {
vol_left %= 1024; vol_left %= 1024;
vol_right %= 1024; vol_right %= 1024;
log_debug("kfca", "knobs = {} {}", vol_left, vol_right);
// save volumes in buffer // save volumes in buffer
STATUS_BUFFER[input_offset + 16 + 0] |= (unsigned char) ((vol_left << 6) & 0xFF); STATUS_BUFFER[input_offset + 16 + 0] |= (unsigned char) ((vol_left << 6) & 0xFF);
STATUS_BUFFER[input_offset + 16 + 1] |= (unsigned char) ((vol_left >> 2) & 0xFF); STATUS_BUFFER[input_offset + 16 + 1] |= (unsigned char) ((vol_left >> 2) & 0xFF);
+350 -56
View File
@@ -1,4 +1,5 @@
#include "mdxf.h" #include "mdxf.h"
#include "mdxf_poll.h"
#include "avs/game.h" #include "avs/game.h"
#include "games/ddr/io.h" #include "games/ddr/io.h"
@@ -6,63 +7,243 @@
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/utils.h" #include "util/utils.h"
#include <mutex>
#define MDFX_DEBUG_VERBOSE 0
#if MDFX_DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
// constants // constants
const size_t STATUS_BUFFER_SIZE = 32; const size_t STATUS_BUFFER_SIZE = 32;
const size_t STATUS_BUFFER_NUM_ENTRIES = 16;
// static stuff // static stuff
static uint8_t COUNTER = 0; static uint8_t HEAD_P1 = 0;
static uint8_t HEAD_P2 = 0;
static uint16_t PREV_STATE_P1 = 0;
static uint16_t PREV_STATE_P2 = 0;
static uint64_t PREV_TIME_P1 = 0;
static uint64_t PREV_TIME_P2 = 0;
static std::mutex MUTEX_P1;
static std::mutex MUTEX_P2;
static bool IS_MDXF_ACTIVE = false;
static const uint8_t BACKFILL_INTERVAL_MS = 4;
static const uint8_t BACKFILL_PADDING_MS = 2;
// These are used to determine if a thread needs to be spun up to keep pad state ring buffers populated with enough recent polls
static uint64_t START_TIME = 0;
static int CALL_COUNT = 0;
static const int THRESHOLD_REFRESH_RATE = 120;
static std::atomic<bool> IS_REFRESH_RATE_MEASUREMENT_STARTED{false};
static std::atomic<bool> IS_REFRESH_RATE_DETERMINED{false};
static std::atomic<bool> IS_THREAD_NEEDED{false};
static std::atomic<bool> MDXF_THREAD_RUNNING{false};
static std::thread MDXF_THREAD;
static constexpr int THREAD_REFRESH_RATE_HZ = 125;
static constexpr auto THREAD_PERIOD = std::chrono::milliseconds(1000 / THREAD_REFRESH_RATE_HZ);
// buffers // buffers
#pragma pack(push, 1) #pragma pack(push, 1)
static struct { static struct {
uint8_t STATUS_BUFFER_P1[7][STATUS_BUFFER_SIZE] {}; uint8_t STATUS_BUFFER_P1[STATUS_BUFFER_NUM_ENTRIES][STATUS_BUFFER_SIZE] {};
uint8_t STATUS_BUFFER_P2[7][STATUS_BUFFER_SIZE] {}; uint8_t STATUS_BUFFER_P2[STATUS_BUFFER_NUM_ENTRIES][STATUS_BUFFER_SIZE] {};
} BUFFERS {}; } BUFFERS {};
#pragma pack(pop) #pragma pack(pop)
static bool STATUS_BUFFER_FREEZE = false; static bool STATUS_BUFFER_FREEZE = false;
// Decides which method to use for populating ring buffer entries for "padding".
// Overwritten in spicecfg using P4IO Buffer Algorithm option.
// THREAD_MODE: Spins a thread running at THREAD_REFRESH_RATE_HZ which periodically fills the ring
// buffer with auxiliary entries. Falls back on BACKFILL_MODE
// BACKFILL_MODE: On every update cycle, fill the ring buffer with entries for the last known state
// BACKFILL_INTERVAL_MS apart from each other from the time of the last entry to the
// current time before adding the entry for the current state.
// AUTO_MODE: thread mode if <120Hz, backfill if >=120Hz
acio::MDXFBufferFillMode acio::MDXF_BUFFER_FILL_MODE = acio::MDXFBufferFillMode::AUTO_MODE;
typedef enum {
ARKMDXP4_POLL = 0,
INTERNAL_POLL = 1,
EXTERNAL_POLL = 2
} MDXFPollSource;
typedef uint64_t (__cdecl *ARK_GET_TICK_TIME64_T)(); typedef uint64_t (__cdecl *ARK_GET_TICK_TIME64_T)();
static uint64_t arkGetTickTime64() { static uint64_t arkGetTickTime64() {
static ARK_GET_TICK_TIME64_T getTickTime64 = static ARK_GET_TICK_TIME64_T getTickTime64 = nullptr;
(ARK_GET_TICK_TIME64_T)GetProcAddress(avs::game::DLL_INSTANCE, "arkGetTickTime64");
if (getTickTime64 == nullptr) { if (!getTickTime64) {
// this works on 32-bit versions of avs, but not on 64. HMODULE h = avs::game::DLL_INSTANCE;
// it's better than nothing though. if (h) {
return timeGetTime(); getTickTime64 = (ARK_GET_TICK_TIME64_T)GetProcAddress(h, "arkGetTickTime64");
}
}
// this works on 32-bit versions of avs, but not on 64.
// it's better than nothing though.
return getTickTime64 ? getTickTime64() : timeGetTime();
}
// Used to keep the ring buffer populated with steady updates. 60Hz interval is too slow
static void mdxf_thread_start() {
bool expected = false;
if (!MDXF_THREAD_RUNNING.compare_exchange_strong(expected, true)) {
return;
}
log_info("mdxf", "starting poll thread");
MDXF_THREAD = std::thread([] {
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
while (MDXF_THREAD_RUNNING.load(std::memory_order_acquire)) {
mdxf_poll(false);
std::this_thread::sleep_for(THREAD_PERIOD);
}
});
}
static void mdxf_thread_stop() {
if (!MDXF_THREAD_RUNNING.exchange(false)) {
return;
}
if (MDXF_THREAD.joinable()) {
MDXF_THREAD.join();
}
}
// Snaps measured refresh rate to best fit
static int snap_refresh_rate(int measured_hz) {
static constexpr std::array<int, 6> rates = {
60, 120, 144, 165, 180, 240
};
int best = rates[0];
int best_err = std::fabs(measured_hz - best);
for (int r : rates) {
int err = std::fabs(measured_hz - r);
if (err < best_err) {
best = r;
best_err = err;
}
}
return best;
}
// Increments the number of times the update function was called,
// then calculates the current refresh rate of the game
// (20 seconds after the game starts, until 25 seconds)
static void count_calls_from_game() {
if (IS_REFRESH_RATE_DETERMINED) {
return;
}
const uint64_t current_time = arkGetTickTime64();
if (!IS_REFRESH_RATE_MEASUREMENT_STARTED) {
if (START_TIME == 0) {
START_TIME = current_time;
}
// boot screen takes about 10 seconds, so let's wait for double that
if ((current_time - START_TIME) < 20000) {
// too early, do nothing
return;
} else {
// 20s has passed for the first time, start measuring on next call
IS_REFRESH_RATE_MEASUREMENT_STARTED = true;
START_TIME = current_time;
log_debug("mdxf", "measurement begin");
return;
}
}
const uint64_t elapsed_time = current_time - START_TIME;
CALL_COUNT++;
if (elapsed_time >= 5000) {
double measured_hz = static_cast<double>(CALL_COUNT) * 1000.0 / static_cast<double>(elapsed_time);
// Account for the main loop calling this twice per iteration
measured_hz *= 0.5;
const int snapped_hz = snap_refresh_rate(static_cast<int>(measured_hz));
IS_REFRESH_RATE_DETERMINED = true;
IS_THREAD_NEEDED = (snapped_hz < THRESHOLD_REFRESH_RATE);
log_info(
"mdxf",
"detected: {} Hz, best fit: {} Hz",
static_cast<int>(measured_hz),
snapped_hz);
if (IS_THREAD_NEEDED) {
mdxf_thread_start();
}
} }
return getTickTime64();
} }
/* /*
* Implementations * Implementations
*/ */
static bool __cdecl ac_io_mdxf_get_control_status_buffer(int node, void *buffer, uint8_t a3, uint8_t a4) { static uint64_t __cdecl ac_io_mdxf_get_control_status_buffer(int node, void *out, uint8_t index, uint8_t head_in) {
// Default error value (matches original mask behavior)
auto error_ret = static_cast<uint64_t>(node - 0x11) & 0xFFFFFFFFFFFFFF00;
// Dance Dance Revolution // Dance Dance Revolution
if (avs::game::is_model("MDX")) { if (avs::game::is_model("MDX")) {
// Select player-specific state
std::mutex* mutex = nullptr;
uint8_t* head = nullptr;
uint8_t (*buffer)[STATUS_BUFFER_SIZE];
size_t size = STATUS_BUFFER_NUM_ENTRIES;
// get buffer index
auto i = (COUNTER + a3) % std::size(BUFFERS.STATUS_BUFFER_P1);
// copy buffer
if (node == 17 || node == 25) { if (node == 17 || node == 25) {
memcpy(buffer, BUFFERS.STATUS_BUFFER_P1[i], STATUS_BUFFER_SIZE); mutex = &MUTEX_P1;
head = &HEAD_P1;
buffer = BUFFERS.STATUS_BUFFER_P1;
} else if (node == 18 || node == 26) { } else if (node == 18 || node == 26) {
memcpy(buffer, BUFFERS.STATUS_BUFFER_P2[i], STATUS_BUFFER_SIZE); mutex = &MUTEX_P2;
head = &HEAD_P2;
buffer = BUFFERS.STATUS_BUFFER_P2;
} else { } else {
memset(out, 0, STATUS_BUFFER_SIZE);
// fill with zeros on unknown node return error_ret;
memset(buffer, 0, STATUS_BUFFER_SIZE);
return false;
} }
std::lock_guard<std::mutex> lock(*mutex);
const uint8_t start_index = (head_in == 0xFF) ? *head : head_in;
// Compute ring index: walk backwards from start_index as index increases
// Assumes ring buffer size is a power of two
const size_t mask = size - 1;
const size_t offset = static_cast<size_t>(index) & mask;
const size_t i = (static_cast<size_t>(start_index) - offset + size) & mask;
// Copy the chosen entry
memcpy(out, buffer[i], STATUS_BUFFER_SIZE);
// Return the start value actually used
return static_cast<uint64_t>(start_index);
} }
// return success return error_ret;
return true;
} }
static bool __cdecl ac_io_mdxf_set_output_level(unsigned int a1, unsigned int a2, uint8_t value) { static bool __cdecl ac_io_mdxf_set_output_level(unsigned int a1, unsigned int a2, uint8_t value) {
@@ -104,36 +285,57 @@ static bool __cdecl ac_io_mdxf_set_output_level(unsigned int a1, unsigned int a2
return true; return true;
} }
static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) { static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFPollSource source, uint64_t current_time) {
// increase counter
COUNTER = (COUNTER + 1) % std::size(BUFFERS.STATUS_BUFFER_P1);
// check freeze // check freeze
if (STATUS_BUFFER_FREEZE) { if (STATUS_BUFFER_FREEZE) {
return true; return true;
} }
// clear buffer
uint8_t *buffer = nullptr;
switch (node) {
case 17:
case 25:
buffer = BUFFERS.STATUS_BUFFER_P1[COUNTER];
break;
case 18:
case 26:
buffer = BUFFERS.STATUS_BUFFER_P2[COUNTER];
break;
default:
// return failure on unknown node
return false;
}
memset(buffer, 0, STATUS_BUFFER_SIZE);
// Dance Dance Revolution // Dance Dance Revolution
if (avs::game::is_model("MDX")) { if (avs::game::is_model("MDX")) {
// Marks this module as actively being used, allowing this function to be called from other sources
if (source == ARKMDXP4_POLL) {
if (!IS_MDXF_ACTIVE) {
log_debug("mdxf", "initializing mdxf I/O support");
IS_MDXF_ACTIVE = true;
if (acio::MDXF_BUFFER_FILL_MODE == acio::MDXFBufferFillMode::THREAD_MODE) {
IS_THREAD_NEEDED = true;
mdxf_thread_start();
}
}
if (acio::MDXF_BUFFER_FILL_MODE == acio::MDXFBufferFillMode::AUTO_MODE) {
count_calls_from_game();
}
}
uint8_t (*buffer)[STATUS_BUFFER_SIZE];
uint8_t *head = nullptr;
uint16_t *prev_state = nullptr;
uint64_t *prev_time = nullptr;
std::mutex* mutex = nullptr;
switch (node) {
case 17:
case 25:
mutex = &MUTEX_P1;
head = &HEAD_P1;
prev_state = &PREV_STATE_P1;
prev_time = &PREV_TIME_P1;
buffer = BUFFERS.STATUS_BUFFER_P1;
break;
case 18:
case 26:
mutex = &MUTEX_P2;
head = &HEAD_P2;
prev_state = &PREV_STATE_P2;
prev_time = &PREV_TIME_P2;
buffer = BUFFERS.STATUS_BUFFER_P2;
break;
default:
// return failure on unknown node
return false;
}
// Sensor Map (LDUR): // Sensor Map (LDUR):
// FOOT DOWN = bit 32-35 = byte 4, bit 0-3 // FOOT DOWN = bit 32-35 = byte 4, bit 0-3
@@ -166,29 +368,115 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) {
break; break;
} }
*(uint64_t*)&buffer[0x18] = arkGetTickTime64(); uint16_t current_state;
// get buttons // Only call getState() if called externally when actual input events happen, otherwise use previous known state
auto &buttons = games::ddr::get_buttons(); if (source == EXTERNAL_POLL) {
// get buttons
auto &buttons = games::ddr::get_buttons();
uint8_t up_down = 0;
uint8_t left_right = 0;
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0]))) { if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0]))) {
buffer[4] |= 0xF0; up_down |= 0xF0;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1]))) {
up_down |= 0x0F;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2]))) {
left_right |= 0xF0;
}
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3]))) {
left_right |= 0x0F;
}
current_state = (uint16_t(up_down) << 8) | left_right;
} else {
current_state = *prev_state;
} }
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1]))) {
buffer[4] |= 0x0F; std::lock_guard<std::mutex> lock(*mutex);
const bool has_state_changed = *prev_state != current_state;
const bool has_time_changed = *prev_time < current_time;
// If state hasn't changed and either the update was triggered externally or the time hasn't changed, then don't advance head pointer or write a new entry
if (!has_state_changed && (source == EXTERNAL_POLL || !has_time_changed)) {
return true;
} }
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2]))) {
buffer[5] |= 0xF0; // The start and stop time cutoffs for backfilling entries. Min(..) ensures times aren't negative.
// The stop time is just before the current_time, set by BACKFILL_PADDING_MS, which avoids the last backfilled entry being too close to current_time.
uint64_t start_time = *prev_time;
const uint64_t stop_time = current_time - std::min<uint64_t>(current_time, BACKFILL_PADDING_MS);
// Ensures the first iteration will write the first entry at current_time and not backfill to time 0ms.
if (start_time == 0) {
start_time = current_time - std::min<uint64_t>(current_time, BACKFILL_INTERVAL_MS);
} }
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3]))) {
buffer[5] |= 0x0F; // Ensures only STATUS_BUFFER_NUM_ENTRIES entries at most are backfilled
const uint64_t max_backfill = BACKFILL_INTERVAL_MS * STATUS_BUFFER_NUM_ENTRIES;
const uint64_t min_time = current_time - std::min<uint64_t>(current_time, max_backfill);
if (start_time < min_time) {
start_time = min_time;
} }
// Don't backfill entries if called externally or if a separate thread is being used to fill auxiliary entries
if (source == EXTERNAL_POLL || IS_THREAD_NEEDED) {
start_time = stop_time - 1;
}
uint64_t time = start_time;
uint16_t state = *prev_state;
// Backfill entries a fixed interval apart from each other between prev_time and current_time
while (time < stop_time) {
// Advance head pointer
*head = (*head + 1) % STATUS_BUFFER_NUM_ENTRIES;
uint8_t* buffer_entry = buffer[*head];
// Clear buffer
memset(buffer_entry, 0, STATUS_BUFFER_SIZE);
time += BACKFILL_INTERVAL_MS;
// If the stop time is reached, then write current_time and current_state instead for this final iteration
const bool isEdge = (time >= stop_time);
if (isEdge) {
state = current_state;
time = current_time;
}
// Write button state
buffer_entry[4] = (state >> 8) & 0xFF;
buffer_entry[5] = state & 0xFF;
// Write game time
*(uint64_t*)&buffer_entry[0x18] = time;
}
*prev_state = current_state;
*prev_time = current_time;
} }
// return success // return success
return true; return true;
} }
static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) {
return ac_io_mdxf_update_control_status_buffer_impl(node, ARKMDXP4_POLL, arkGetTickTime64());
}
// Used for triggering updates of the controller states from outside arkmdxp4.dll main refresh loop (i.e. within rawinput.cpp on controller events)
void mdxf_poll(bool isExternal) {
if (IS_MDXF_ACTIVE) {
const MDXFPollSource source = isExternal ? EXTERNAL_POLL : INTERNAL_POLL;
const uint64_t call_time_ms = arkGetTickTime64();
ac_io_mdxf_update_control_status_buffer_impl(17, source, call_time_ms);
ac_io_mdxf_update_control_status_buffer_impl(18, source, call_time_ms);
}
}
/* /*
* Module stuff * Module stuff
*/ */
@@ -207,3 +495,9 @@ void acio::MDXFModule::attach() {
ACIO_MODULE_HOOK(ac_io_mdxf_set_output_level); ACIO_MODULE_HOOK(ac_io_mdxf_set_output_level);
ACIO_MODULE_HOOK(ac_io_mdxf_update_control_status_buffer); ACIO_MODULE_HOOK(ac_io_mdxf_update_control_status_buffer);
} }
acio::MDXFModule::~MDXFModule() {
if (IS_THREAD_NEEDED) {
mdxf_thread_stop();
}
}
+14
View File
@@ -4,10 +4,24 @@
namespace acio { namespace acio {
enum class MDXFBufferFillMode {
// backfill mode, but if <120Hz, enable poll thread
AUTO_MODE,
// forces poll thread
THREAD_MODE,
// forces backfill mode (no poll thread)
BACKFILL_MODE
};
extern MDXFBufferFillMode MDXF_BUFFER_FILL_MODE;
class MDXFModule : public ACIOModule { class MDXFModule : public ACIOModule {
public: public:
MDXFModule(HMODULE module, HookMode hookMode); MDXFModule(HMODULE module, HookMode hookMode);
virtual void attach() override; virtual void attach() override;
~MDXFModule() override;
}; };
} }
+5
View File
@@ -0,0 +1,5 @@
// mdxf_poll.h
#pragma once
// Called from rawinput thread whenever inputs have just been updated
void mdxf_poll(bool isExternal);
+1 -1
View File
@@ -84,7 +84,7 @@ static bool __cdecl ac_io_panb_start_auto_input() {
return true; return true;
} }
static uint8_t panb_get_button_velocity(Button button, Button button_soft, Button button_medium, Button button_hard) { static uint8_t panb_get_button_velocity(Button& button, Button& button_soft, Button& button_medium, Button& button_hard) {
const auto velocity = Buttons::getVelocity(RI_MGR, button); const auto velocity = Buttons::getVelocity(RI_MGR, button);
const auto velocity_soft = Buttons::getVelocity(RI_MGR, button_soft); const auto velocity_soft = Buttons::getVelocity(RI_MGR, button_soft);
const auto velocity_medium = Buttons::getVelocity(RI_MGR, button_medium); const auto velocity_medium = Buttons::getVelocity(RI_MGR, button_medium);
+9 -2
View File
@@ -95,12 +95,19 @@ namespace acio2emu {
break; break;
default: default:
log_fatal("acio2emu", "cannot set step: unknown value: {}", s); log_fatal(
"acio2emu",
"cannot set step: unknown value: {}",
static_cast<uint32_t>(s));
break; break;
} }
if (!valid) { if (!valid) {
log_fatal("acio2emu", "illegal transition detected: {} -> {}", step_, s); log_fatal(
"acio2emu",
"illegal transition detected: {} -> {}",
static_cast<uint32_t>(step_),
static_cast<uint32_t>(s));
} }
#endif #endif
step_ = s; step_ = s;
+5 -11
View File
@@ -2,6 +2,7 @@
#include "acio/icca/icca.h" #include "acio/icca/icca.h"
#include "avs/game.h" #include "avs/game.h"
#include "games/sdvx/sdvx.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/utils.h" #include "util/utils.h"
@@ -101,13 +102,10 @@ bool ICCADevice::parse_msg(MessageData *msg_in,
// send version data // send version data
auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE); auto msg = this->create_msg(msg_in, MSG_VERSION_SIZE);
if ( if (avs::game::is_model({"LDJ", "TBS", "UJK", "XIF"}) ||
avs::game::is_model({"LDJ", "TBS", "UJK", "XIF"}) || games::sdvx::is_valkyrie_model()) {
// SDVX Valkyrie cabinet mode
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))
) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA"); this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCA");
} else if (avs::game::is_model({"VFG"})) { } else if (avs::game::is_model("VFG")) {
this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCB"); this->set_version(msg, 0x3, 0, 1, 7, 0, "ICCB");
} else { } else {
this->set_version(msg, 0x3, 0, 1, 6, 0, "ICCA"); this->set_version(msg, 0x3, 0, 1, 6, 0, "ICCA");
@@ -520,11 +518,7 @@ void ICCADevice::update_status(int unit) {
buffer[1] = felica ? 0x01 : 0x00; buffer[1] = felica ? 0x01 : 0x00;
buffer[10] = felica ? 0x01 : 0x00; buffer[10] = felica ? 0x01 : 0x00;
} else if ( } else if (avs::game::is_model({"LDJ", "TBS", "XIF"}) || games::sdvx::is_valkyrie_model()) {
avs::game::is_model({"LDJ", "TBS", "XIF"}) ||
// SDVX Valkyrie cabinet mode
(avs::game::is_model("KFC") && (avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H'))) {
// set status to 0 otherwise reader power on fails // set status to 0 otherwise reader power on fails
buffer[0] = 0x00; buffer[0] = 0x00;
} else { } else {
+2 -2
View File
@@ -34,7 +34,7 @@ namespace api::modules {
void DRS::touch_set(Request &req, Response &res) { void DRS::touch_set(Request &req, Response &res) {
// get all touch points // get all touch points
games::drs::drs_touch_t touches[16]; auto touches = std::make_unique<games::drs::drs_touch_t[]>(req.params.Size());
size_t i = 0; size_t i = 0;
for (Value &param : req.params.GetArray()) { for (Value &param : req.params.GetArray()) {
@@ -86,6 +86,6 @@ namespace api::modules {
} }
// apply touch points // apply touch points
games::drs::fire_touches(touches, i); games::drs::fire_touches(touches.get(), i);
} }
} }
+1 -1
View File
@@ -22,7 +22,7 @@ Response::Response(uint64_t id) {
// check for error // check for error
auto error = document.GetParseError(); auto error = document.GetParseError();
if (error) if (error)
log_warning("api", "response template parse error: {}", error); log_warning("api", "response template parse error: {}", static_cast<uint32_t>(error));
// set ID // set ID
document["id"].SetUint64(id); document["id"].SetUint64(id);
+2 -2
View File
@@ -263,7 +263,7 @@ namespace avs::automap {
int property_node_read(avs::core::node_ptr node, avs::core::node_type type, void *data, uint32_t data_size) { int property_node_read(avs::core::node_ptr node, avs::core::node_type type, void *data, uint32_t data_size) {
auto result = avs::core::property_node_read(node, type, data, data_size); auto result = avs::core::property_node_read(node, type, data, data_size);
if (result < 0 && property_patches_enabled(nullptr, node)) { if (result < 0 && property_patches_enabled(nullptr, node)) {
log_warning("automap", "property_node_read failed ({}): {}", type, result); log_warning("automap", "property_node_read failed ({}): {}", static_cast<uint32_t>(type), result);
// probably wrong type // probably wrong type
memset(data, 0, data_size); memset(data, 0, data_size);
@@ -278,7 +278,7 @@ namespace avs::automap {
if (parent_node && parent_node != node) { if (parent_node && parent_node != node) {
char name_buf[256] {}; char name_buf[256] {};
if (avs::core::property_node_name(node, name_buf, sizeof(name_buf)) >= 0) { if (avs::core::property_node_name(node, name_buf, sizeof(name_buf)) >= 0) {
log_warning("automap", "property_node_read replace type ({}): {}", type, result); log_warning("automap", "property_node_read replace type ({}): {}", static_cast<uint32_t>(type), result);
avs::core::property_node_remove(node); avs::core::property_node_remove(node);
avs::core::property_node_create(nullptr, parent_node, type, name_buf, data); avs::core::property_node_create(nullptr, parent_node, type, name_buf, data);
} }
+14
View File
@@ -7,6 +7,7 @@
#include <stdint.h> #include <stdint.h>
#include "external/robin_hood.h" #include "external/robin_hood.h"
#include "games/popn/popn.h"
#include "launcher/logger.h" #include "launcher/logger.h"
#include "launcher/signal.h" #include "launcher/signal.h"
#include "util/deferlog.h" #include "util/deferlog.h"
@@ -1108,6 +1109,8 @@ namespace avs {
// SOUND VOLTEX // SOUND VOLTEX
{"soundvoltex.dll", 0x10000000}, {"soundvoltex.dll", 0x10000000},
// GITADORA
{"gdxg.dll", 0x2000000},
#endif #endif
// jubeat // jubeat
{"jubeat.dll", 0x2000000}, {"jubeat.dll", 0x2000000},
@@ -1177,6 +1180,15 @@ namespace avs {
return; return;
} }
// hack, both hello! popn and popn hc using popn.dll
if (games::popn::is_pikapika_model()) {
auto old_size = HEAP_SIZE;
HEAP_SIZE = 0x20000000;
DEFAULT_HEAP_SIZE_SET = true;
log_info("avs-core", "updated heap size: {} -> {}", old_size, avs::core::HEAP_SIZE);
return;
}
if (HEAP_SIZE_DEFAULTS.find(dll_name) != HEAP_SIZE_DEFAULTS.end()) { if (HEAP_SIZE_DEFAULTS.find(dll_name) != HEAP_SIZE_DEFAULTS.end()) {
auto old_size = HEAP_SIZE; auto old_size = HEAP_SIZE;
@@ -1232,6 +1244,7 @@ namespace avs {
break; break;
case 'F': case 'F':
new_style = logger::Style::RED; new_style = logger::Style::RED;
deferredlogs::report_fatal_message();
break; break;
default: default:
position = 0; position = 0;
@@ -1300,6 +1313,7 @@ namespace avs {
break; break;
case 'F': case 'F':
style = logger::Style::RED; style = logger::Style::RED;
deferredlogs::report_fatal_message();
break; break;
case 'W': case 'W':
style = logger::Style::YELLOW; style = logger::Style::YELLOW;
+72 -7
View File
@@ -5,9 +5,11 @@
#include "build/defs.h" #include "build/defs.h"
#include "easrv/easrv.h" #include "easrv/easrv.h"
#include "easrv/smartea.h" #include "easrv/smartea.h"
#include "games/mfc/mfc.h" #include "games/gitadora/gitadora.h"
#include "hooks/avshook.h" #include "hooks/avshook.h"
#include "hooks/graphics/graphics.h"
#include "util/detour.h" #include "util/detour.h"
#include "util/deferlog.h"
#include "util/fileutils.h" #include "util/fileutils.h"
#include "util/libutils.h" #include "util/libutils.h"
#include "util/logging.h" #include "util/logging.h"
@@ -215,7 +217,23 @@ namespace avs {
ea3_config_name = "prop/eamuse-config.xml"; ea3_config_name = "prop/eamuse-config.xml";
} }
log_info("avs-ea3", "booting (using {})", ea3_config_name); if (avs::core::file_exists(ea3_config_name)) {
log_info("avs-ea3", "booting (using {})", ea3_config_name);
} else {
log_warning("avs-ea3", "looked for the following files in order:");
log_warning("avs-ea3", " * prop/ea3-config.xml");
log_warning("avs-ea3", " * prop/ea3-cfg.xml");
if (avs::game::DLL_NAME == "beatstream1.dll") {
log_warning("avs-ea3", " * prop/ea3-config-1.xml");
}
if (avs::game::DLL_NAME == "beatstream2.dll") {
log_warning("avs-ea3", " * prop/ea3-config-2.xml");
}
log_warning("avs-ea3", " * prop/eamuse-config.xml");
log_warning("avs-ea3", "none of the files above were found, game will fail to boot");
log_warning("avs-ea3", "usually, this means your game data is incomplete");
log_fatal("avs-ea3", "no ea3 config file found in prop directory; check log messages");
}
// remember new config path // remember new config path
CFG_PATH = ea3_config_name; CFG_PATH = ea3_config_name;
@@ -282,6 +300,7 @@ namespace avs {
// read software nodes // read software nodes
if (ea3_soft != nullptr) { if (ea3_soft != nullptr) {
log_misc("avs-ea3", "reading soft id from {}...", ea3_config_name);
avs::core::property_node_refer(ea3_config, ea3_soft, "model", avs::core::property_node_refer(ea3_config, ea3_soft, "model",
avs::core::NODE_TYPE_str, EA3_MODEL, 4); avs::core::NODE_TYPE_str, EA3_MODEL, 4);
avs::core::property_node_refer(ea3_config, ea3_soft, "dest", avs::core::property_node_refer(ea3_config, ea3_soft, "dest",
@@ -293,6 +312,7 @@ namespace avs {
avs::core::property_node_refer(ea3_config, ea3_soft, "ext", avs::core::property_node_refer(ea3_config, ea3_soft, "ext",
avs::core::NODE_TYPE_str, EA3_EXT, 11); avs::core::NODE_TYPE_str, EA3_EXT, 11);
} else if (fileutils::file_exists("prop/ea3-ident.xml")) { } else if (fileutils::file_exists("prop/ea3-ident.xml")) {
log_misc("avs-ea3", "reading soft id from prop/ea3-ident.xml...");
// read ident config // read ident config
auto ea3_ident = avs::core::config_read("prop/ea3-ident.xml"); auto ea3_ident = avs::core::config_read("prop/ea3-ident.xml");
@@ -316,7 +336,14 @@ namespace avs {
// clean up // clean up
avs::core::config_destroy(ea3_ident); avs::core::config_destroy(ea3_ident);
} else { } else {
log_fatal("avs-ea3", "node not found in '{}': /ea3/soft", ea3_config_name); // <ea3><soft> node missing error
log_warning("avs-ea3", "soft id (datecode) not found in prop XML files");
log_warning("avs-ea3", "get fixed prop files and try again - you have incomplete data");
log_warning("avs-ea3", "for experts only:");
log_warning("avs-ea3", " * add the missing <soft>...</soft> node to {}", ea3_config_name);
log_warning("avs-ea3", " * alternatively, provide ea3-ident.xml with <ea3_conf><soft>...");
log_fatal("avs-ea3", "datecode missing in '{}'; 'prop/ea3-ident.xml' also missing", ea3_config_name);
} }
// set account id (`EA3_PCBID` is valid if and only if `/ea3/id` is present) // set account id (`EA3_PCBID` is valid if and only if `/ea3/id` is present)
@@ -484,8 +511,15 @@ namespace avs {
std::ostringstream init_code; std::ostringstream init_code;
init_code << EA3_MODEL; init_code << EA3_MODEL;
init_code << EA3_DEST; init_code << EA3_DEST;
init_code << EA3_SPEC; if (avs::game::DLL_NAME == "arkmdxbio2.dll" &&
init_code << EA3_REV; strcmp(EA3_MODEL, "MDX") == 0 && strcmp(EA3_SPEC, "I") == 0) {
// ddr gold cab
init_code << "G";
init_code << "B";
} else {
init_code << EA3_SPEC;
init_code << EA3_REV;
}
init_code << EA3_EXT; init_code << EA3_EXT;
std::string init_code_str = init_code.str(); std::string init_code_str = init_code.str();
@@ -511,12 +545,42 @@ namespace avs {
} }
} }
// update -WINDOW in app-config
if (games::gitadora::is_arena_model()) {
avs::core::property_search_remove_safe(app_config, nullptr, "/param/cmdline");
std::string cmdline{};
if (games::gitadora::is_drum()) {
if (GRAPHICS_WINDOWED) {
cmdline = "-d -DM -WINDOW";
} else {
cmdline = "-d -DM";
}
} else {
if (GRAPHICS_WINDOWED) {
cmdline = "-g -GF -WINDOW";
} else {
cmdline = "-g -GF";
}
}
log_info("avs-ea3", "using cmdline: `{}`", cmdline);
avs::core::property_node_create(app_config, nullptr,
avs::core::NODE_TYPE_str, "/param/cmdline", cmdline.c_str());
}
auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param"); auto app_param = avs::core::property_search_safe(app_config, nullptr, "/param");
// call the game init // call the game init
log_info("avs-ea3", "calling entry init"); log_info("avs-ea3", "calling dll_entry_init (init code = {})", init_code_str);
if (!avs::game::entry_init(init_code_str.data(), app_param)) { if (!avs::game::entry_init(init_code_str.data(), app_param)) {
log_fatal("avs-ea3", "entry init failed :("); for (size_t i = 0; i < 255; i++) {
log_warning("avs-ea3", "dll_entry_init failed :(");
}
deferredlogs::defer_error_messages({
"dll_entry_init failed",
" init code: " + init_code_str,
" this is a fatal error that causes boot failure",
" most likely, your prop files have errors"
});
} }
// accommodate changes to soft id code // accommodate changes to soft id code
@@ -563,6 +627,7 @@ namespace avs {
soft_id_code << EA3_EXT; soft_id_code << EA3_EXT;
std::string soft_id_code_str = soft_id_code.str(); std::string soft_id_code_str = soft_id_code.str();
log_info("avs-ea3", "soft id code: {}", soft_id_code_str); log_info("avs-ea3", "soft id code: {}", soft_id_code_str);
deferredlogs::set_softid(soft_id_code_str);
// set soft ID code // set soft ID code
avs::core::avs_std_setenv("/env/profile/soft_id_code", soft_id_code_str.c_str()); avs::core::avs_std_setenv("/env/profile/soft_id_code", soft_id_code_str.c_str());
+327
View File
@@ -0,0 +1,327 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<!-- PHOENIXWAN controller preset for Beatmania IIDX
Button indices 0-6: keys 1-7
Button indices 8-11: E1 (Start for Deluxe Model Style), E2 (Effect), E3 (VEFX), E4 (P1 Start for Lightning Model Style)
P1 Side P2 Side
8 9 10 11 8 9 10 11
2 4 6 2 4 6
TT 1 3 5 7 1 3 5 7 TT -->
<template name="DJ DAO PHOENIXWAN" game="Beatmania IIDX">
<buttons>
<button name="P1 1" vkey="0" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 2" vkey="1" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 3" vkey="2" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 4" vkey="3" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 5" vkey="4" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 6" vkey="5" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 7" vkey="6" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 1" vkey="0" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 2" vkey="1" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 3" vkey="2" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 4" vkey="3" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 5" vkey="4" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 6" vkey="5" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 7" vkey="6" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<!-- P1 Start: E1 primary (index 8), E4 alternative (index 11) -->
<button name="P1 Start" vkey="8" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 Start" vkey="11" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<!-- P2 Start: E1 primary (index 8), E4 alternative (index 11) -->
<button name="P2 Start" vkey="8" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 Start" vkey="11" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<!-- EFFECT: E2 (index 9), primary P2 side, alternative P1 side -->
<button name="EFFECT" vkey="9" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="EFFECT" vkey="9" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<!-- VEFX: E3 (index 10), primary P2 side, alternative P1 side -->
<button name="VEFX" vkey="10" analogtype="0" devid="Player 2" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="VEFX" vkey="10" analogtype="0" devid="Player 1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons>
<analogs>
<analog name="Turntable P1" devid="Player 1" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Turntable P2" devid="Player 2" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
</analogs>
<lights>
<light name="P1 1" devid="Player 1" index="0"/>
<light name="P1 2" devid="Player 1" index="1"/>
<light name="P1 3" devid="Player 1" index="2"/>
<light name="P1 4" devid="Player 1" index="3"/>
<light name="P1 5" devid="Player 1" index="4"/>
<light name="P1 6" devid="Player 1" index="5"/>
<light name="P1 7" devid="Player 1" index="6"/>
<light name="P2 1" devid="Player 2" index="0"/>
<light name="P2 2" devid="Player 2" index="1"/>
<light name="P2 3" devid="Player 2" index="2"/>
<light name="P2 4" devid="Player 2" index="3"/>
<light name="P2 5" devid="Player 2" index="4"/>
<light name="P2 6" devid="Player 2" index="5"/>
<light name="P2 7" devid="Player 2" index="6"/>
<!-- Start buttons bind both E1 (index 8) and E4 (index 11), but lights use
Lightning Model style only (one per side) to avoid both lighting up at once -->
<light name="P1 Start" devid="Player 1" index="10"/>
<light name="P2 Start" devid="Player 2" index="7"/>
<light name="VEFX" devid="Player 1" index="8"/>
<light name="VEFX" devid="Player 2" index="8"/>
<light name="Effect" devid="Player 1" index="9"/>
<light name="Effect" devid="Player 2" index="9"/>
</lights>
</template>
<!-- Common "double scratch" BMS playstyle: LSHIFT A S D SPACE L ; ' RSHIFT -->
<!-- Uses Up/Down arrows for turntable navigation -->
<template name="Keyboard (linear, double scratch, QWERTY)" game="Beatmania IIDX">
<buttons>
<button name="P1 1" vkey="65" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 2" vkey="83" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 3" vkey="68" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 4" vkey="32" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 5" vkey="76" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 6" vkey="186" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 7" vkey="222" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 TT+" vkey="40" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 TT-" vkey="38" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 TT+/-" vkey="160" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 TT+/- Alternate" vkey="161" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 Start" vkey="13" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 Start" vkey="69" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="EFFECT" vkey="81" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="VEFX" vkey="87" analogtype="0" devid="" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons>
<keypad_buttons/>
<analogs/>
<lights/>
</template>
<template name="arcin (arcin-infinitas)" game="Beatmania IIDX">
<buttons>
<button name="P1 1" vkey="0" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 2" vkey="1" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 3" vkey="2" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 4" vkey="3" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 5" vkey="4" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 6" vkey="5" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 7" vkey="6" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P1 Start" vkey="9" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 1" vkey="0" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 2" vkey="1" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 3" vkey="2" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 4" vkey="3" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 5" vkey="4" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 6" vkey="5" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 7" vkey="6" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="P2 Start" vkey="9" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="EFFECT" vkey="11" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="EFFECT" vkey="11" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="VEFX" vkey="10" analogtype="0" devid="arcin (1p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="VEFX" vkey="10" analogtype="0" devid="arcin (2p)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons>
<keypad_buttons/>
<!-- Assumes analog mode. Need to invert direction since this FW is designed for INF. -->
<analogs>
<analog name="Turntable P1" devid="arcin (1p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Turntable P2" devid="arcin (2p)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="true" smoothing="false" multiplier="1" relative="false" delay="0"/>
</analogs>
<lights>
<light name="P1 1" devid="arcin (1p)" index="0"/>
<light name="P1 2" devid="arcin (1p)" index="1"/>
<light name="P1 3" devid="arcin (1p)" index="2"/>
<light name="P1 4" devid="arcin (1p)" index="3"/>
<light name="P1 5" devid="arcin (1p)" index="4"/>
<light name="P1 6" devid="arcin (1p)" index="5"/>
<light name="P1 7" devid="arcin (1p)" index="6"/>
<light name="P2 1" devid="arcin (2p)" index="0"/>
<light name="P2 2" devid="arcin (2p)" index="1"/>
<light name="P2 3" devid="arcin (2p)" index="2"/>
<light name="P2 4" devid="arcin (2p)" index="3"/>
<light name="P2 5" devid="arcin (2p)" index="4"/>
<light name="P2 6" devid="arcin (2p)" index="5"/>
<light name="P2 7" devid="arcin (2p)" index="6"/>
<light name="P1 Start" devid="arcin (1p)" index="9"/>
<light name="P2 Start" devid="arcin (2p)" index="9"/>
</lights>
</template>
<!-- This controller has many modes; here, we use HID Analog mode which is meant for data -->
<template name="DJ DAO Nostroller (Analog mode)" game="Nostalgia">
<buttons/>
<keypad_buttons/>
<analogs>
<analog name="Key 1" devid="Analogs key 01-28" index="4" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 2" devid="Analogs key 01-28" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 3" devid="Analogs key 01-28" index="6" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 4" devid="Analogs key 01-28" index="7" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 5" devid="Analogs key 01-28" index="8" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 6" devid="Analogs key 01-28" index="9" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 7" devid="Analogs key 01-28" index="10" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 8" devid="Analogs key 01-28" index="11" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 9" devid="Analogs key 01-28" index="12" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 10" devid="Analogs key 01-28" index="13" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 11" devid="Analogs key 01-28" index="14" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 12" devid="Analogs key 01-28" index="15" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 13" devid="Analogs key 01-28" index="16" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 14" devid="Analogs key 01-28" index="17" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 15" devid="Analogs key 01-28" index="18" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 16" devid="Analogs key 01-28" index="19" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 17" devid="Analogs key 01-28" index="20" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 18" devid="Analogs key 01-28" index="21" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 19" devid="Analogs key 01-28" index="22" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 20" devid="Analogs key 01-28" index="23" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 21" devid="Analogs key 01-28" index="24" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 22" devid="Analogs key 01-28" index="25" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 23" devid="Analogs key 01-28" index="26" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 24" devid="Analogs key 01-28" index="27" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 25" devid="Analogs key 01-28" index="28" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 26" devid="Analogs key 01-28" index="29" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 27" devid="Analogs key 01-28" index="30" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Key 28" devid="Analogs key 01-28" index="31" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
</analogs>
<lights>
<light name="Key 1 R" devid="Lights 01-14" index="0"/>
<light name="Key 1 G" devid="Lights 01-14" index="1"/>
<light name="Key 1 B" devid="Lights 01-14" index="2"/>
<light name="Key 2 R" devid="Lights 01-14" index="3"/>
<light name="Key 2 G" devid="Lights 01-14" index="4"/>
<light name="Key 2 B" devid="Lights 01-14" index="5"/>
<light name="Key 3 R" devid="Lights 01-14" index="6"/>
<light name="Key 3 G" devid="Lights 01-14" index="7"/>
<light name="Key 3 B" devid="Lights 01-14" index="8"/>
<light name="Key 4 R" devid="Lights 01-14" index="9"/>
<light name="Key 4 G" devid="Lights 01-14" index="10"/>
<light name="Key 4 B" devid="Lights 01-14" index="11"/>
<light name="Key 5 R" devid="Lights 01-14" index="12"/>
<light name="Key 5 G" devid="Lights 01-14" index="13"/>
<light name="Key 5 B" devid="Lights 01-14" index="14"/>
<light name="Key 6 R" devid="Lights 01-14" index="15"/>
<light name="Key 6 G" devid="Lights 01-14" index="16"/>
<light name="Key 6 B" devid="Lights 01-14" index="17"/>
<light name="Key 7 R" devid="Lights 01-14" index="18"/>
<light name="Key 7 G" devid="Lights 01-14" index="19"/>
<light name="Key 7 B" devid="Lights 01-14" index="20"/>
<light name="Key 8 R" devid="Lights 01-14" index="21"/>
<light name="Key 8 G" devid="Lights 01-14" index="22"/>
<light name="Key 8 B" devid="Lights 01-14" index="23"/>
<light name="Key 9 R" devid="Lights 01-14" index="24"/>
<light name="Key 9 G" devid="Lights 01-14" index="25"/>
<light name="Key 9 B" devid="Lights 01-14" index="26"/>
<light name="Key 10 R" devid="Lights 01-14" index="27"/>
<light name="Key 10 G" devid="Lights 01-14" index="28"/>
<light name="Key 10 B" devid="Lights 01-14" index="29"/>
<light name="Key 11 R" devid="Lights 01-14" index="30"/>
<light name="Key 11 G" devid="Lights 01-14" index="31"/>
<light name="Key 11 B" devid="Lights 01-14" index="32"/>
<light name="Key 12 R" devid="Lights 01-14" index="33"/>
<light name="Key 12 G" devid="Lights 01-14" index="34"/>
<light name="Key 12 B" devid="Lights 01-14" index="35"/>
<light name="Key 13 R" devid="Lights 01-14" index="36"/>
<light name="Key 13 G" devid="Lights 01-14" index="37"/>
<light name="Key 13 B" devid="Lights 01-14" index="38"/>
<light name="Key 14 R" devid="Lights 01-14" index="39"/>
<light name="Key 14 G" devid="Lights 01-14" index="40"/>
<light name="Key 14 B" devid="Lights 01-14" index="41"/>
<light name="Key 15 R" devid="Lights 15-28" index="0"/>
<light name="Key 15 G" devid="Lights 15-28" index="1"/>
<light name="Key 15 B" devid="Lights 15-28" index="2"/>
<light name="Key 16 R" devid="Lights 15-28" index="3"/>
<light name="Key 16 G" devid="Lights 15-28" index="4"/>
<light name="Key 16 B" devid="Lights 15-28" index="5"/>
<light name="Key 17 R" devid="Lights 15-28" index="6"/>
<light name="Key 17 G" devid="Lights 15-28" index="7"/>
<light name="Key 17 B" devid="Lights 15-28" index="8"/>
<light name="Key 18 R" devid="Lights 15-28" index="9"/>
<light name="Key 18 G" devid="Lights 15-28" index="10"/>
<light name="Key 18 B" devid="Lights 15-28" index="11"/>
<light name="Key 19 R" devid="Lights 15-28" index="12"/>
<light name="Key 19 G" devid="Lights 15-28" index="13"/>
<light name="Key 19 B" devid="Lights 15-28" index="14"/>
<light name="Key 20 R" devid="Lights 15-28" index="15"/>
<light name="Key 20 G" devid="Lights 15-28" index="16"/>
<light name="Key 20 B" devid="Lights 15-28" index="17"/>
<light name="Key 21 R" devid="Lights 15-28" index="18"/>
<light name="Key 21 G" devid="Lights 15-28" index="19"/>
<light name="Key 21 B" devid="Lights 15-28" index="20"/>
<light name="Key 22 R" devid="Lights 15-28" index="21"/>
<light name="Key 22 G" devid="Lights 15-28" index="22"/>
<light name="Key 22 B" devid="Lights 15-28" index="23"/>
<light name="Key 23 R" devid="Lights 15-28" index="24"/>
<light name="Key 23 G" devid="Lights 15-28" index="25"/>
<light name="Key 23 B" devid="Lights 15-28" index="26"/>
<light name="Key 24 R" devid="Lights 15-28" index="27"/>
<light name="Key 24 G" devid="Lights 15-28" index="28"/>
<light name="Key 24 B" devid="Lights 15-28" index="29"/>
<light name="Key 25 R" devid="Lights 15-28" index="30"/>
<light name="Key 25 G" devid="Lights 15-28" index="31"/>
<light name="Key 25 B" devid="Lights 15-28" index="32"/>
<light name="Key 26 R" devid="Lights 15-28" index="33"/>
<light name="Key 26 G" devid="Lights 15-28" index="34"/>
<light name="Key 26 B" devid="Lights 15-28" index="35"/>
<light name="Key 27 R" devid="Lights 15-28" index="36"/>
<light name="Key 27 G" devid="Lights 15-28" index="37"/>
<light name="Key 27 B" devid="Lights 15-28" index="38"/>
<light name="Key 28 R" devid="Lights 15-28" index="39"/>
<light name="Key 28 G" devid="Lights 15-28" index="40"/>
<light name="Key 28 B" devid="Lights 15-28" index="41"/>
</lights>
</template>
<!-- Gitaller has two modes - digital tilt and analog axis. This binds both of them at the same time. -->
<template name="DJ DAO Gitaller" game="GitaDora">
<buttons>
<button name="Guitar P1 Start" vkey="9" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Help" vkey="8" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Pick Up" vkey="5" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Pick Down" vkey="6" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 R" vkey="0" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 G" vkey="1" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 B" vkey="2" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Y" vkey="3" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 P" vkey="4" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Wail Up" vkey="10" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Guitar P1 Wail Down" vkey="11" analogtype="0" devid="Gitaller P1" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons>
<keypad_buttons/>
<analogs>
<analog name="Guitar P1 Wail X" devid="Gitaller P1" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Guitar P1 Wail Y" devid="Gitaller P1" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="Guitar P1 Wail Z" devid="Gitaller P1" index="5" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
</analogs>
<lights/>
<!-- Gen 1 FAUCETWO in HID mode; should also work for later gens -->
</template>
<template name="DJ DAO FAUCETWO" game="Sound Voltex">
<buttons>
<button name="BT-A" vkey="1" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="BT-B" vkey="2" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="BT-C" vkey="3" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="BT-D" vkey="4" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="FX-L" vkey="5" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="FX-R" vkey="6" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
<button name="Start" vkey="0" analogtype="0" devid="Buttons (MI_01)" invert="false" debounce_up="0" debounce_down="0" velocity_threshold="0"/>
</buttons>
<keypad_buttons/>
<analogs>
<analog name="VOL-L" devid="Buttons (MI_01)" index="1" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
<analog name="VOL-R" devid="Buttons (MI_01)" index="0" sensivity="1" deadzone="0" deadzone_mirror="false" invert="false" smoothing="false" multiplier="1" relative="false" delay="0"/>
</analogs>
<lights>
<light name="BT-A" devid="Lights (MI_02)" index="24"/>
<light name="BT-B" devid="Lights (MI_02)" index="25"/>
<light name="BT-C" devid="Lights (MI_02)" index="26"/>
<light name="BT-D" devid="Lights (MI_02)" index="27"/>
<light name="FX-L" devid="Lights (MI_02)" index="28"/>
<light name="FX-R" devid="Lights (MI_02)" index="29"/>
<light name="Start" devid="Lights (MI_02)" index="30"/>
<light name="Upper Left Speaker Avg R" devid="Lights (MI_02)" index="0"/>
<light name="Upper Left Speaker Avg G" devid="Lights (MI_02)" index="1"/>
<light name="Upper Left Speaker Avg B" devid="Lights (MI_02)" index="2"/>
<light name="Upper Right Speaker Avg R" devid="Lights (MI_02)" index="9"/>
<light name="Upper Right Speaker Avg G" devid="Lights (MI_02)" index="10"/>
<light name="Upper Right Speaker Avg B" devid="Lights (MI_02)" index="11"/>
<light name="Lower Left Speaker Avg R" devid="Lights (MI_02)" index="3"/>
<light name="Lower Left Speaker Avg G" devid="Lights (MI_02)" index="4"/>
<light name="Lower Left Speaker Avg B" devid="Lights (MI_02)" index="5"/>
<light name="Lower Right Speaker Avg R" devid="Lights (MI_02)" index="6"/>
<light name="Lower Right Speaker Avg G" devid="Lights (MI_02)" index="7"/>
<light name="Lower Right Speaker Avg B" devid="Lights (MI_02)" index="8"/>
<light name="Control Panel Avg R" devid="Lights (MI_02)" index="15"/>
<light name="Control Panel Avg G" devid="Lights (MI_02)" index="16"/>
<light name="Control Panel Avg B" devid="Lights (MI_02)" index="17"/>
</lights>
</template>
</templates>
+97 -11
View File
@@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# set to EN-US for consistency # set to EN-US for consistency
LANG=en_us_8859_1 LANG=en_us_8859_1
@@ -41,12 +41,18 @@ done
# settings # settings
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "none") GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2> /dev/null || echo "none")
GIT_HEAD=$(git rev-parse HEAD || echo "none") GIT_HEAD=$(git rev-parse HEAD || echo "none")
TOOLCHAIN_32="/usr/share/mingw/toolchain-i686-w64-mingw32.cmake" TOOLCHAIN_32="${TOOLCHAIN_32:-"/usr/share/mingw/toolchain-i686-w64-mingw32.cmake"}"
TOOLCHAIN_64="/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake" TOOLCHAIN_64="${TOOLCHAIN_64:-"/usr/share/mingw/toolchain-x86_64-w64-mingw32.cmake"}"
TOOLCHAIN_WINXP_32="${TOOLCHAIN_WINXP_32:-"/opt/llvm-mingw-xp/toolchain-files/cmake/i686-mingw32-clang.cmake"}"
TOOLCHAIN_WINXP_64="${TOOLCHAIN_WINXP_64:-"/opt/llvm-mingw-xp/toolchain-files/cmake/x86_64-mingw32-clang.cmake"}"
BUILDDIR_32_RELEASE="./cmake-build-release-32" BUILDDIR_32_RELEASE="./cmake-build-release-32"
BUILDDIR_32_DEBUG="./cmake-build-debug-32" BUILDDIR_32_DEBUG="./cmake-build-debug-32"
BUILDDIR_64_RELEASE="./cmake-build-release-64" BUILDDIR_64_RELEASE="./cmake-build-release-64"
BUILDDIR_64_DEBUG="./cmake-build-debug-64" BUILDDIR_64_DEBUG="./cmake-build-debug-64"
BUILDDIR_WINXP_32_RELEASE="./cmake-build-release-winxp-32"
BUILDDIR_WINXP_32_DEBUG="./cmake-build-debug-winxp-32"
BUILDDIR_WINXP_64_RELEASE="./cmake-build-release-winxp-64"
BUILDDIR_WINXP_64_DEBUG="./cmake-build-debug-winxp-64"
DEBUG=0 DEBUG=0
OUTDIR="./bin/spice2x" OUTDIR="./bin/spice2x"
OUTDIR_EXTRAS="./bin/spice2x/extras" OUTDIR_EXTRAS="./bin/spice2x/extras"
@@ -64,17 +70,34 @@ DIST_NAME_EXTRAS="spice2x-$(date +%y)-$(date +%m)-$(date +%d)-full.zip"
DIST_COMMENT=${DIST_NAME}$'\n'"$GIT_BRANCH - $GIT_HEAD"$'\nThank you for playing.' DIST_COMMENT=${DIST_NAME}$'\n'"$GIT_BRANCH - $GIT_HEAD"$'\nThank you for playing.'
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm" TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_cfg_linux spicetools_spice spicetools_spice_laa spicetools_spice_linux spicetools_stubs_cpusbxpkm"
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux" TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_stubs_nvEncodeAPI64 spicetools_stubs_nvcuvid spicetools_stubs_nvcuda spicetools_spice64 spicetools_spice64_linux"
TARGETS_XP32="spicetools_cfg spicetools_spice"
TARGETS_XP64="spicetools_spice64"
# determine build type # determine build type
BUILD_TYPE="Release" BUILD_TYPE="Release"
BUILDDIR_32=${BUILDDIR_32_RELEASE} BUILDDIR_32=${BUILDDIR_32_RELEASE}
BUILDDIR_64=${BUILDDIR_64_RELEASE} BUILDDIR_64=${BUILDDIR_64_RELEASE}
BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_RELEASE}
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_RELEASE}
if ((DEBUG > 0)) if ((DEBUG > 0))
then then
BUILD_TYPE="Debug" BUILD_TYPE="Debug"
BUILDDIR_32=${BUILDDIR_32_DEBUG} BUILDDIR_32=${BUILDDIR_32_DEBUG}
BUILDDIR_64=${BUILDDIR_64_DEBUG} BUILDDIR_64=${BUILDDIR_64_DEBUG}
DIST_NAME=$(echo ${DIST_NAME} | sed 's/\.[^.]*$/-dbg&/') BUILDDIR_WINXP_32=${BUILDDIR_WINXP_32_DEBUG}
BUILDDIR_WINXP_64=${BUILDDIR_WINXP_64_DEBUG}
DIST_NAME=$(echo "${DIST_NAME}" | sed 's/\.[^.]*$/-dbg&/')
fi
# is the XP-compatible toolchain installed?
XP_MUST_BUILD=0
BUILD_XP=0
if [ -f "$TOOLCHAIN_WINXP_32" ] && [ -f "$TOOLCHAIN_WINXP_64" ]; then
BUILD_XP=1;
elif ((XP_MUST_BUILD > 0))
then
echo "WinXP toolchain not available, aborting"
exit 1
fi fi
# determine number of cores # determine number of cores
@@ -90,6 +113,13 @@ echo "Git Branch: $GIT_BRANCH"
echo "Git Head: $GIT_HEAD" echo "Git Head: $GIT_HEAD"
echo "Toolchain for 32bit targets: $TOOLCHAIN_32" echo "Toolchain for 32bit targets: $TOOLCHAIN_32"
echo "Toolchain for 64bit targets: $TOOLCHAIN_64" echo "Toolchain for 64bit targets: $TOOLCHAIN_64"
if ((BUILD_XP > 0))
then
echo "Toolchain for WinXP 32bit targets: $TOOLCHAIN_WINXP_32"
echo "Toolchain for WinXP 64bit targets: $TOOLCHAIN_WINXP_64"
else
echo "WinXP toolchain not available, skipping WinXP builds"
fi
echo "Distribution Name: $DIST_NAME" echo "Distribution Name: $DIST_NAME"
echo "Build Type: $BUILD_TYPE" echo "Build Type: $BUILD_TYPE"
echo "Cores: $CORES" echo "Cores: $CORES"
@@ -114,7 +144,7 @@ time (
fi fi
mkdir -p ${BUILDDIR_32} mkdir -p ${BUILDDIR_32}
pushd ${BUILDDIR_32} > /dev/null pushd ${BUILDDIR_32} > /dev/null
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} $OLDPWD && ninja ${TARGETS_32} cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_32}
popd > /dev/null popd > /dev/null
# 64 bit # 64 bit
@@ -127,14 +157,60 @@ time (
fi fi
mkdir -p ${BUILDDIR_64} mkdir -p ${BUILDDIR_64}
pushd ${BUILDDIR_64} > /dev/null pushd ${BUILDDIR_64} > /dev/null
cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} $OLDPWD && ninja ${TARGETS_64} cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_64}
popd > /dev/null popd > /dev/null
if ((BUILD_XP > 0))
then
# 32 bit Windows XP
echo "Building 32bit targets (WinXP toolchain)..."
echo "========================="
if ((CLEAN_BUILD > 0))
then
rm -rf ${BUILDDIR_WINXP_32}
fi
mkdir -p ${BUILDDIR_WINXP_32}
pushd ${BUILDDIR_WINXP_32} > /dev/null
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_32} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP32}
popd > /dev/null
# 64 bit Windows XP
echo ""
echo "Building 64bit targets (WinXP toolchain)..."
echo "========================="
if ((CLEAN_BUILD > 0))
then
rm -rf ${BUILDDIR_WINXP_64}
fi
mkdir -p ${BUILDDIR_WINXP_64}
pushd ${BUILDDIR_WINXP_64} > /dev/null
CXXFLAGS="$CXXFLAGS -DSPICE_XP=1" cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_WINXP_64} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} "$OLDPWD" && ninja ${TARGETS_XP64}
popd > /dev/null
else
echo "Skipping WinXP builds, toolchain not specified"
fi
echo "" echo ""
echo "Compilation process done :)" echo "Compilation process done :)"
echo "===========================" echo "==========================="
) )
if ((BUILD_XP > 0))
then
echo ""
echo "Checking XP compatibility..."
echo "============================="
if ! command -v windows_dll_compat_checker &> /dev/null; then
echo "WARNING: windows_dll_compat_checker not found, skipping XP compatibility check"
else
windows_dll_compat_checker -s PREMADE/winxp_x86_64.ini \
${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe
windows_dll_compat_checker -s PREMADE/winxp_x86_64_32bit_dlls.ini \
${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe \
${BUILDDIR_WINXP_32}/spicetools/32/spice.exe
fi
fi
# generate PDBs # generate PDBs
if false # ((DEBUG > 0)) if false # ((DEBUG > 0))
then then
@@ -191,6 +267,10 @@ rm -rf ${OUTDIR_EXTRAS}
mkdir -p ${OUTDIR_EXTRAS} mkdir -p ${OUTDIR_EXTRAS}
mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware mkdir -p ${OUTDIR_EXTRAS}/largeaddressaware
mkdir -p ${OUTDIR_EXTRAS}/linux mkdir -p ${OUTDIR_EXTRAS}/linux
if ((BUILD_XP > 0))
then
mkdir -p ${OUTDIR_EXTRAS}/winxp
fi
if false # ((DEBUG > 0)) if false # ((DEBUG > 0))
then then
@@ -226,6 +306,12 @@ else
cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null cp ${BUILDDIR_64}/spicetools/64/nvcuda.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null cp ${BUILDDIR_64}/spicetools/64/nvcuvid.dll ${OUTDIR}/stubs/64 2>/dev/null
cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null cp ${BUILDDIR_32}/spicetools/32/cpusbxpkm.dll ${OUTDIR}/stubs/32 2>/dev/null
if ((BUILD_XP > 0))
then
cp ${BUILDDIR_WINXP_32}/spicetools/spicecfg.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
cp ${BUILDDIR_WINXP_32}/spicetools/32/spice.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
cp ${BUILDDIR_WINXP_64}/spicetools/64/spice64.exe ${OUTDIR_EXTRAS}/winxp 2>/dev/null
fi
fi fi
# pack source files to output directory # pack source files to output directory
@@ -234,7 +320,7 @@ mkdir -p ${OUTDIR}/src
if ((INCLUDE_SRC > 0)) if ((INCLUDE_SRC > 0))
then then
echo "Generating source file archive..." echo "Generating source file archive..."
git archive --format tar.gz --prefix=spice2x/ HEAD ./ > ${OUTDIR}/src/spice2x-${GIT_BRANCH}.tar.gz 2>/dev/null || \ git archive --format tar.gz --prefix=spice2x/ HEAD ./ > "${OUTDIR}/src/spice2x-${GIT_BRANCH}.tar.gz" 2>/dev/null || \
echo "WARNING: Couldn't get git to create the archive. Is this a git repository?" echo "WARNING: Couldn't get git to create the archive. Is this a git repository?"
fi fi
@@ -249,11 +335,11 @@ if ((DIST_ENABLE > 0))
then then
echo "Building dist..." echo "Building dist..."
mkdir -p ${DIST_FOLDER} mkdir -p ${DIST_FOLDER}
rm -rf ${DIST_FOLDER}/${DIST_NAME} rm -rf "${DIST_FOLDER}/${DIST_NAME}"
pushd ${OUTDIR}/.. > /dev/null pushd ${OUTDIR} > /dev/null
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME} spice2x -x "spice2x/extras/*" -z <<< "$DIST_COMMENT" zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME}" . -x "extras/*" -z <<< "$DIST_COMMENT"
echo "Building extras..." echo "Building extras..."
zip -qrXT9 $OLDPWD/${DIST_FOLDER}/${DIST_NAME_EXTRAS} spice2x -z <<< "$DIST_COMMENT" zip -qrXT9 "$OLDPWD/${DIST_FOLDER}/${DIST_NAME_EXTRAS}" . -z <<< "$DIST_COMMENT"
popd > /dev/null popd > /dev/null
fi fi
+12 -4
View File
@@ -1,4 +1,12 @@
#!/bin/bash #!/usr/bin/env bash
docker build --pull $PWD/external/docker -t spicetools/deps --platform linux/x86_64
docker build --build-context gitroot=$PWD/../../.git . -t spicetools/spice:latest set -eu
docker run --rm -v $PWD/dist:/src/src/spice2x/dist -v $PWD/bin:/src/src/spice2x/bin -v $PWD/.ccache:/src/src/spice2x/.ccache spicetools/spice "$@"
docker build --pull "$PWD/external/docker" -t spicetools/deps --platform linux/x86_64
docker build --build-context gitroot="$PWD/../../.git" . -t spicetools/spice:latest
# Interactive TTY if available, so docker build can be Ctrl+C'd
DOCKER_FLAGS=""
[ -t 0 ] && DOCKER_FLAGS="-it"
docker run $DOCKER_FLAGS --rm -v "$PWD/dist:/src/src/spice2x/dist" -v "$PWD/bin:/src/src/spice2x/bin" -v "$PWD/.ccache:/src/src/spice2x/.ccache" spicetools/spice "$@"
+1
View File
@@ -13,5 +13,6 @@ IDR_LICENSES RCDATA "../licenses.txt"
IDR_PATCHES RCDATA "../build/patches.json" IDR_PATCHES RCDATA "../build/patches.json"
IDR_README RCDATA "../readme.txt" IDR_README RCDATA "../readme.txt"
IDR_DSEGFONT RCDATA "../external/dseg/DSEG14Classic-Italic.ttf" IDR_DSEGFONT RCDATA "../external/dseg/DSEG14Classic-Italic.ttf"
IDR_CONTROLLER_PRESETS_BUILTIN RCDATA "../build/controller_presets.xml"
#endif #endif
+4
View File
@@ -90,6 +90,10 @@ std::string Analog::getDisplayString(rawinput::RawInputManager *manager) {
return "MIDI Unknown Index " + indexString + " (" + device->desc + ")"; return "MIDI Unknown Index " + indexString + " (" + device->desc + ")";
} }
} }
case rawinput::XINPUT_GAMEPAD:
return fmt::format("{} ({})",
xinput::get_analog_string(static_cast<xinput::XInputAnalogEnum>(index)),
device->desc);
case rawinput::DESTROYED: case rawinput::DESTROYED:
return "Device unplugged (" + indexString + ")"; return "Device unplugged (" + indexString + ")";
default: default:
+9 -3
View File
@@ -82,16 +82,22 @@ public:
return this->index != 0xFF; return this->index != 0xFF;
} }
inline void clearBindings() { inline void resetValues() {
device_identifier = "";
index = 0xFF;
setSensitivity(1.f); setSensitivity(1.f);
setDeadzone(0.f); setDeadzone(0.f);
invert = false; invert = false;
smoothing = false; smoothing = false;
deadzone_mirror = false;
setMultiplier(1); setMultiplier(1);
setRelativeMode(false); setRelativeMode(false);
setDelayBufferDepth(0); setDelayBufferDepth(0);
setLastState(0.5f);
}
inline void clearBindings() {
device_identifier = "";
index = 0xFF;
resetValues();
} }
inline const std::string &getName() const { inline const std::string &getName() const {
+90 -10
View File
@@ -1,5 +1,6 @@
#include "api.h" #include "api.h"
#include <cassert>
#include <optional> #include <optional>
#include "launcher/superexit.h" #include "launcher/superexit.h"
@@ -37,7 +38,11 @@ std::vector<Button> GameAPI::Buttons::sortButtons(
for (auto &bt : buttons) { for (auto &bt : buttons) {
if (name == bt.getName()) { if (name == bt.getName()) {
button_found = true; button_found = true;
sorted.push_back(bt); Button button_new = bt;
if (vkey_defaults) {
button_new.setVKeyDefault(vkey_defaults->at(index));
}
sorted.push_back(button_new);
break; break;
} }
} }
@@ -47,6 +52,7 @@ std::vector<Button> GameAPI::Buttons::sortButtons(
if (vkey_defaults) { if (vkey_defaults) {
button.setVKey(vkey_defaults->at(index)); button.setVKey(vkey_defaults->at(index));
button.setVKeyDefault(vkey_defaults->at(index));
} }
} }
@@ -384,6 +390,16 @@ GameAPI::Buttons::State GameAPI::Buttons::getState(rawinput::RawInputManager *ma
} }
case rawinput::PIUIO_DEVICE: { case rawinput::PIUIO_DEVICE: {
state = device->piuioDev->IsPressed(vKey) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED; state = device->piuioDev->IsPressed(vKey) ? BUTTON_PRESSED : BUTTON_NOT_PRESSED;
break;
}
case rawinput::XINPUT_GAMEPAD: {
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
state = manager->XINPUT_MGR->is_button_pressed(
player,
static_cast<xinput::XInputButtonEnum>(vKey)) ?
BUTTON_PRESSED : BUTTON_NOT_PRESSED;
break;
} }
default: default:
break; break;
@@ -581,7 +597,7 @@ float Buttons::getVelocity(std::unique_ptr<rawinput::RawInputManager> &manager,
} }
} }
float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) { float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, rawinput::Device *device, Analog &analog) {
float value = 0.5f; float value = 0.5f;
if (!device) { if (!device) {
return value; return value;
@@ -644,10 +660,12 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
case rawinput::HID: { case rawinput::HID: {
// get value // get value
if (inverted) { if (index < device->hidInfo->value_states.size()) {
value = 1.f - device->hidInfo->value_states[index]; if (inverted) {
} else { value = 1.f - device->hidInfo->value_states[index];
value = device->hidInfo->value_states[index]; } else {
value = device->hidInfo->value_states[index];
}
} }
// deadzone // deadzone
@@ -760,6 +778,18 @@ float GameAPI::Analogs::getState(rawinput::Device *device, Analog &analog) {
} }
break; break;
} }
case rawinput::XINPUT_GAMEPAD: {
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
value = manager->XINPUT_MGR->get_analog_state(
player,
static_cast<xinput::XInputAnalogEnum>(index));
// invert value
if (inverted) {
value = 1.f - value;
}
break;
}
default: default:
break; break;
} }
@@ -815,7 +845,7 @@ float GameAPI::Analogs::getState(rawinput::RawInputManager *manager, Analog &ana
return analog.getLastState(); return analog.getLastState();
} }
float state = getState(device, analog); float state = getState(manager, device, analog);
analog.setLastState(state); analog.setLastState(state);
return state; return state;
@@ -859,7 +889,45 @@ std::vector<Light> GameAPI::Lights::sortLights(
return sorted; return sorted;
} }
void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float value) { static std::vector<Light> sortLightsWithCategoryInternal(
const std::vector<Light> &lights,
const std::initializer_list<GameAPI::Lights::LightAndCategory> list)
{
std::vector<Light> sorted;
bool light_found;
for (auto &name : list) {
light_found = false;
for (auto &light : lights) {
if (name.light_name == light.getName()) {
light_found = true;
Light light_new = light;
light_new.setCategory(name.category_name);
sorted.push_back(light_new);
break;
}
}
if (!light_found) {
sorted.emplace_back(name.light_name, name.category_name);
}
}
return sorted;
}
void GameAPI::Lights::sortLightsWithCategory(
std::vector<Light> *lights,
const std::initializer_list<LightAndCategory> list) {
if (lights) {
*lights = sortLightsWithCategoryInternal(*lights, list);
}
}
void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, rawinput::Device *device, int index, float value) {
// check device // check device
if (!device) { if (!device) {
@@ -946,6 +1014,18 @@ void GameAPI::Lights::writeLight(rawinput::Device *device, int index, float valu
} }
break; break;
} }
case rawinput::XINPUT_GAMEPAD: {
assert(reinterpret_cast<uintptr_t>(device->handle) < XUSER_MAX_COUNT);
const auto player = static_cast<uint8_t>(reinterpret_cast<uintptr_t>(device->handle));
if (index < static_cast<int>(xinput::XInputOutputEnum::COUNT)) {
manager->XINPUT_MGR->set_output_state(
player, static_cast<xinput::XInputOutputEnum>(index), value);
// no need to set output_pending; xinput output handled immediately
} else {
log_warning("api", "invalid xinput light index: {}", index);
}
break;
}
default: default:
break; break;
} }
@@ -971,9 +1051,9 @@ void GameAPI::Lights::writeLight(rawinput::RawInputManager *manager, Light &ligh
// write state // write state
if (light.override_enabled) { if (light.override_enabled) {
writeLight(device, light.getIndex(), light.override_state); writeLight(manager, device, light.getIndex(), light.override_state);
} else { } else {
writeLight(device, light.getIndex(), value); writeLight(manager, device, light.getIndex(), value);
} }
} }
+17 -23
View File
@@ -6,24 +6,20 @@
#include <windows.h> #include <windows.h>
#include "option.h" #include "option.h"
#include "button.h"
#include "analog.h"
#include "light.h"
namespace rawinput { namespace rawinput {
class RawInputManager; class RawInputManager;
struct Device; struct Device;
} }
class Button;
class Analog;
class Light;
class Game; class Game;
class Option; class Option;
namespace GameAPI { namespace GameAPI {
namespace Buttons { namespace Buttons {
enum State {
BUTTON_PRESSED = true,
BUTTON_NOT_PRESSED = false
};
/** /**
* Parses the config and returns the buttons set by the user. * Parses the config and returns the buttons set by the user.
* *
@@ -107,27 +103,19 @@ namespace GameAPI {
} }
} }
float getState(rawinput::Device *device, Analog &analog); float getState(rawinput::RawInputManager *manager, rawinput::Device *device, Analog &analog);
float getState(rawinput::RawInputManager *manager, Analog &analog); float getState(rawinput::RawInputManager *manager, Analog &analog);
float getState(std::unique_ptr<rawinput::RawInputManager> &manager, Analog &analog); float getState(std::unique_ptr<rawinput::RawInputManager> &manager, Analog &analog);
} }
namespace Lights { namespace Lights {
std::vector<Light> getLights(const std::string &game_name); std::vector<Light> getLights(const std::string &game_name);
std::vector<Light> sortLights( std::vector<Light> sortLights(
const std::vector<Light> &lights, const std::vector<Light> &lights,
const std::vector<std::string> &light_names); const std::vector<std::string> &light_names);
template<typename T>
void sortLights(std::vector<Light> *lights, T t) {
const std::vector<std::string> light_names { t };
if (lights) {
*lights = GameAPI::Lights::sortLights(*lights, light_names);
}
}
template<typename T, typename... Rest> template<typename T, typename... Rest>
void sortLights(std::vector<Light> *lights, T t, Rest... rest) { void sortLights(std::vector<Light> *lights, T t, Rest... rest) {
const std::vector<std::string> light_names { t, rest... }; const std::vector<std::string> light_names { t, rest... };
@@ -137,7 +125,17 @@ namespace GameAPI {
} }
} }
void writeLight(rawinput::Device *device, int index, float value); struct LightAndCategory {
const std::string category_name;
const std::string light_name;
LightAndCategory(std::string category_name, std::string light_name) :
category_name(std::move(category_name)), light_name(std::move(light_name)) {}
};
void sortLightsWithCategory(std::vector<Light> *lights, const std::initializer_list<LightAndCategory> list);
void writeLight(rawinput::RawInputManager *manager, rawinput::Device *device, int index, float value);
void writeLight(rawinput::RawInputManager *manager, Light &light, float value); void writeLight(rawinput::RawInputManager *manager, Light &light, float value);
void writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value); void writeLight(std::unique_ptr<rawinput::RawInputManager> &manager, Light &light, float value);
@@ -152,7 +150,3 @@ namespace GameAPI {
void sortOptions(std::vector<Option> &, const std::vector<OptionDefinition> &); void sortOptions(std::vector<Option> &, const std::vector<OptionDefinition> &);
} }
} }
#include "button.h"
#include "analog.h"
#include "light.h"
+56 -30
View File
@@ -44,7 +44,7 @@ std::string Button::getVKeyString() {
case 0x0C: case 0x0C:
return "Clear"; return "Clear";
case 0x0D: case 0x0D:
return "Enter"; return "Enter/Return";
case 0x10: case 0x10:
return "Shift"; return "Shift";
case 0x11: case 0x11:
@@ -58,8 +58,28 @@ std::string Button::getVKeyString() {
return "Pause"; return "Pause";
case 0x14: case 0x14:
return "Caps Lock"; return "Caps Lock";
case 0x15:
return "IME Kana/Hangul";
case 0x16:
return "IME On";
case 0x17:
return "IME Junja";
case 0x18:
return "IME Final";
case 0x19:
return "IME Hanja/Kanji";
case 0x1A:
return "IME Off";
case 0x1B: case 0x1B:
return "Escape"; return "Escape";
case 0x1C:
return "IME Convert";
case 0x1D:
return "IME Nonconvert";
case 0x1E:
return "IME Accept";
case 0x1F:
return "IME Mode Change";
case 0x20: case 0x20:
return "Space"; return "Space";
case 0x21: case 0x21:
@@ -163,37 +183,37 @@ std::string Button::getVKeyString() {
case 0x5D: case 0x5D:
return "Apps"; return "Apps";
case 0x60: case 0x60:
return "Num 0"; return "Numpad 0";
case 0x61: case 0x61:
return "Num 1"; return "Numpad 1";
case 0x62: case 0x62:
return "Num 2"; return "Numpad 2";
case 0x63: case 0x63:
return "Num 3"; return "Numpad 3";
case 0x64: case 0x64:
return "Num 4"; return "Numpad 4";
case 0x65: case 0x65:
return "Num 5"; return "Numpad 5";
case 0x66: case 0x66:
return "Num 6"; return "Numpad 6";
case 0x67: case 0x67:
return "Num 7"; return "Numpad 7";
case 0x68: case 0x68:
return "Num 8"; return "Numpad 8";
case 0x69: case 0x69:
return "Num 9"; return "Numpad 9";
case 0x6A: case 0x6A:
return "*"; return "Numpad *";
case 0x6B: case 0x6B:
return "+"; return "Numpad +";
case 0x6C: case 0x6C:
return "Seperator"; return "Numpad Seperator";
case 0x6D: case 0x6D:
return "-"; return "Numpad -";
case 0x6E: case 0x6E:
return "."; return "Numpad .";
case 0x6F: case 0x6F:
return "/"; return "Numpad /";
case 0x70: case 0x70:
return "F1"; return "F1";
case 0x71: case 0x71:
@@ -255,9 +275,11 @@ std::string Button::getVKeyString() {
case 0xA3: case 0xA3:
return "Right Control"; return "Right Control";
case 0xA4: case 0xA4:
return "Left Menu"; return "Left Alt";
case 0xA5: case 0xA5:
return "Right Menu"; return "Right Alt";
case INVALID_VKEY:
return "Invalid";
default: default:
// check win API // check win API
@@ -301,17 +323,17 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
switch (device->type) { switch (device->type) {
case rawinput::MOUSE: { case rawinput::MOUSE: {
const char *btn = "Unknown"; const char *btn = "Unknown";
static const char *MOUSE_NAMES[] = { static const std::array<const char *, 8>MOUSE_NAMES = {
"Left Mouse", "Left Mouse",
"Right Mouse", "Right Mouse",
"Middle Mouse", "Middle Mouse",
"Mouse 1", "Mouse 1",
"Mouse 2", "Mouse 2",
"Mouse 3", "Mouse 3",
"Mouse 4", "Mouse 4",
"Mouse 5", "Mouse 5",
}; };
if (vKey < sizeof(MOUSE_NAMES)) { if (vKey < MOUSE_NAMES.size()) {
btn = MOUSE_NAMES[vKey]; btn = MOUSE_NAMES[vKey];
} }
return fmt::format("{} ({})", btn, device->desc); return fmt::format("{} ({})", btn, device->desc);
@@ -395,6 +417,10 @@ std::string Button::getDisplayString(rawinput::RawInputManager* manager) {
} }
case rawinput::PIUIO_DEVICE: case rawinput::PIUIO_DEVICE:
return "PIUIO " + vKeyString; return "PIUIO " + vKeyString;
case rawinput::XINPUT_GAMEPAD:
return fmt::format("{} ({})",
xinput::get_button_string(static_cast<xinput::XInputButtonEnum>(vKey)),
device->desc);
case rawinput::DESTROYED: case rawinput::DESTROYED:
return "Device unplugged (" + vKeyString + ")"; return "Device unplugged (" + vKeyString + ")";
default: default:
+55 -4
View File
@@ -1,15 +1,23 @@
#pragma once #pragma once
#include <cstdint>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "api.h"
namespace rawinput { namespace rawinput {
class RawInputManager; class RawInputManager;
} }
namespace GameAPI {
namespace Buttons {
enum State {
BUTTON_PRESSED = true,
BUTTON_NOT_PRESSED = false
};
}
}
enum ButtonAnalogType { enum ButtonAnalogType {
BAT_NONE = 0, BAT_NONE = 0,
BAT_POSITIVE = 1, BAT_POSITIVE = 1,
@@ -41,19 +49,24 @@ private:
std::string name; std::string name;
std::string device_identifier = ""; std::string device_identifier = "";
unsigned short vKey = INVALID_VKEY; unsigned short vKey = INVALID_VKEY;
// default bindings are always naive
unsigned short vKey_default = INVALID_VKEY;
ButtonAnalogType analog_type = BAT_NONE; ButtonAnalogType analog_type = BAT_NONE;
double debounce_up = 0.0; double debounce_up = 0.0;
double debounce_down = 0.0; double debounce_down = 0.0;
bool invert = false; bool invert = false;
bool is_temporary = false;
GameAPI::Buttons::State last_state = GameAPI::Buttons::BUTTON_NOT_PRESSED; GameAPI::Buttons::State last_state = GameAPI::Buttons::BUTTON_NOT_PRESSED;
float last_velocity = 0.f; float last_velocity = 0.f;
unsigned short velocity_threshold = 0; unsigned short velocity_threshold = 0;
std::string getVKeyString();
std::string getMidiNoteString(); std::string getMidiNoteString();
public: public:
std::string getVKeyString();
// overrides // overrides
bool override_enabled = false; bool override_enabled = false;
@@ -70,12 +83,20 @@ public:
if (this->override_enabled) { if (this->override_enabled) {
return true; return true;
} }
// has a device
if (!this->device_identifier.empty()) {
return true;
}
// naive, and has a valid vKey
if (this->vKey != INVALID_VKEY) { if (this->vKey != INVALID_VKEY) {
return true; return true;
} }
for (auto &alternative : this->alternatives) { for (auto &alternative : this->alternatives) {
if (alternative.vKey != INVALID_VKEY) { if (!alternative.getDeviceIdentifier().empty() ||
alternative.getVKey() != INVALID_VKEY) {
return true; return true;
} }
} }
@@ -116,6 +137,14 @@ public:
this->vKey = vKey; this->vKey = vKey;
} }
inline unsigned short getVKeyDefault() const {
return this->vKey_default;
}
inline void setVKeyDefault(unsigned short vKey_default) {
this->vKey_default = vKey_default;
}
inline ButtonAnalogType getAnalogType() const { inline ButtonAnalogType getAnalogType() const {
return this->analog_type; return this->analog_type;
} }
@@ -164,6 +193,14 @@ public:
this->last_velocity = last_velocity; this->last_velocity = last_velocity;
} }
inline bool isTemporary() const {
return this->is_temporary;
}
inline void setTemporary(bool is_temporary) {
this->is_temporary = is_temporary;
}
inline unsigned short getVelocityThreshold() const { inline unsigned short getVelocityThreshold() const {
return this->velocity_threshold; return this->velocity_threshold;
} }
@@ -172,6 +209,20 @@ public:
this->velocity_threshold = velocity_threshold; this->velocity_threshold = velocity_threshold;
} }
inline bool isValid() {
// temporarily created by UI
if (isTemporary()) {
return true;
}
// nothing is set
if (getDeviceIdentifier().empty() && getVKey() == INVALID_VKEY) {
return false;
}
return true;
}
void getMidiVKey(int& channel, int& index); void getMidiVKey(int& channel, int& index);
void setMidiVKey(rawinput::RawInputManager* manager, bool is_note, int channel, int index); void setMidiVKey(rawinput::RawInputManager* manager, bool is_note, int channel, int index);
+77 -2
View File
@@ -1,5 +1,6 @@
#include "config.h" #include "config.h"
#include "util/logging.h" #include "util/logging.h"
#include "cfg/button.h"
/* /*
* This code absolutely sucks. * This code absolutely sucks.
@@ -10,6 +11,14 @@
// settings // settings
std::string CONFIG_PATH_OVERRIDE = ""; std::string CONFIG_PATH_OVERRIDE = "";
#define CFG_DEBUG_VERBOSE 0
#if CFG_DEBUG_VERBOSE
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
/////////////////// ///////////////////
/// Constructor /// /// Constructor ///
@@ -68,7 +77,7 @@ Config::Config() {
this->firstFillConfigFile(); this->firstFillConfigFile();
break; break;
default: default:
log_warning("cfg", "Unknown XML error reading config: {}", configLoadError); log_warning("cfg", "Unknown XML error reading config: {}", static_cast<uint32_t>(configLoadError));
break; break;
} }
} while (configLoadError != tinyxml2::XMLError::XML_SUCCESS); } while (configLoadError != tinyxml2::XMLError::XML_SUCCESS);
@@ -510,8 +519,10 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
return false; return false;
} }
tinyxml2::XMLElement *gameButtonNode = nullptr;
// iterate button nodes // iterate button nodes
tinyxml2::XMLElement *gameButtonNode = gameButtonsNode->FirstChildElement("button"); gameButtonNode = gameButtonsNode->FirstChildElement("button");
int button_count = 0; int button_count = 0;
while (gameButtonNode != nullptr) { while (gameButtonNode != nullptr) {
const char *buttonNodeName = gameButtonNode->Attribute("name"); const char *buttonNodeName = gameButtonNode->Attribute("name");
@@ -574,6 +585,39 @@ bool Config::updateBinding(const Game &game, const Button &button, int alternati
} }
} }
// trim the trailing invalid (unbound) alternatives
tinyxml2::XMLElement *to_delete = nullptr;
gameButtonNode = gameButtonsNode->LastChildElement("button");
while (gameButtonNode != nullptr) {
const char *name = gameButtonNode->Attribute("name");
const char *devid = gameButtonNode->Attribute("devid");
int vkey = INVALID_VKEY;
gameButtonNode->QueryIntAttribute("vkey", &vkey);
// wrong button, ignore and keep iterating backwards
if (name == nullptr || std::string(name) != button.getName()) {
gameButtonNode = gameButtonNode->PreviousSiblingElement("button");
continue;
}
// found a node for this button, which means to_delete is not the first one
// delete the node for to_delete
if (to_delete != nullptr) {
log_debug("cfg", "deleting excessive button binding for {}", name);
gameButtonsNode->DeleteChild(to_delete);
to_delete = nullptr;
}
if ((devid == nullptr || strlen(devid) == 0) && vkey == INVALID_VKEY) {
// unbound button with no device ID, make a note of it so it can be deleted later
to_delete = gameButtonNode;
gameButtonNode = gameButtonNode->PreviousSiblingElement("button");
} else {
// valid binding found; stop iterating
break;
}
}
// check if button was not found // check if button was not found
if (button_count == 0) { if (button_count == 0) {
return false; return false;
@@ -760,6 +804,37 @@ bool Config::updateBinding(const Game &game, const Light &light, int alternative
} }
} }
// trim the trailing invalid (unbound) alternatives
tinyxml2::XMLElement *to_delete = nullptr;
gameLightNode = gameLightsNode->LastChildElement("light");
while (gameLightNode != nullptr) {
const char *name = gameLightNode->Attribute("name");
const char *devid = gameLightNode->Attribute("devid");
// wrong light, ignore and keep iterating backwards
if (name == nullptr || std::string(name) != light.getName()) {
gameLightNode = gameLightNode->PreviousSiblingElement("light");
continue;
}
// found a node for this button, which means to_delete is not the first one
// delete the node for to_delete
if (to_delete != nullptr) {
log_debug("cfg", "deleting excessive light binding for {}", name);
gameLightsNode->DeleteChild(to_delete);
to_delete = nullptr;
}
if (devid == nullptr || strlen(devid) == 0) {
// unbound light, make a note of it so it can be deleted later
to_delete = gameLightNode;
gameLightNode = gameLightNode->PreviousSiblingElement("light");
} else {
// valid binding found; stop iterating
break;
}
}
// check if light was not found // check if light was not found
if (light_count == 0) { if (light_count == 0) {
return false; return false;
+460
View File
@@ -0,0 +1,460 @@
#include "overlay/windows/controller_presets.h"
#include <filesystem>
#include "cfg/resource.h"
#include "external/tinyxml2/tinyxml2.h"
#include "util/fileutils.h"
#include "util/logging.h"
#include "util/resutils.h"
namespace overlay::windows {
static std::filesystem::path get_templates_path() {
return fileutils::get_config_file_path("ControllerPresets", "spicetools_presets.xml");
}
static void write_button_entry(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent,
const std::string &name, const TemplateButtonEntry &entry) {
auto *el = doc.NewElement("button");
el->SetAttribute("name", name.c_str());
el->SetAttribute("vkey", entry.vKey);
el->SetAttribute("analogtype", (int)entry.analog_type);
el->SetAttribute("devid", entry.device_identifier.c_str());
el->SetAttribute("invert", entry.invert);
el->SetAttribute("debounce_up", entry.debounce_up);
el->SetAttribute("debounce_down", entry.debounce_down);
el->SetAttribute("velocity_threshold", entry.velocity_threshold);
parent->InsertEndChild(el);
}
static TemplateButtonEntry read_button_entry(tinyxml2::XMLElement *el) {
TemplateButtonEntry entry;
int vkey = INVALID_VKEY;
el->QueryIntAttribute("vkey", &vkey);
entry.vKey = (unsigned short)vkey;
int atype = 0;
el->QueryIntAttribute("analogtype", &atype);
entry.analog_type = (ButtonAnalogType)atype;
const char *devid = el->Attribute("devid");
entry.device_identifier = devid ? devid : "";
el->QueryBoolAttribute("invert", &entry.invert);
el->QueryDoubleAttribute("debounce_up", &entry.debounce_up);
el->QueryDoubleAttribute("debounce_down", &entry.debounce_down);
int vel = 0;
el->QueryIntAttribute("velocity_threshold", &vel);
entry.velocity_threshold = (unsigned short)vel;
return entry;
}
static void write_analog(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent,
const TemplateAnalogBinding &analog) {
auto *el = doc.NewElement("analog");
el->SetAttribute("name", analog.name.c_str());
el->SetAttribute("devid", analog.device_identifier.c_str());
el->SetAttribute("index", analog.index);
el->SetAttribute("sensivity", analog.sensitivity); // keep typo for compat
el->SetAttribute("deadzone", analog.deadzone);
el->SetAttribute("deadzone_mirror", analog.deadzone_mirror);
el->SetAttribute("invert", analog.invert);
el->SetAttribute("smoothing", analog.smoothing);
el->SetAttribute("multiplier", analog.multiplier);
el->SetAttribute("relative", analog.relative_mode);
el->SetAttribute("delay", analog.delay_buffer_depth);
parent->InsertEndChild(el);
}
static TemplateAnalogBinding read_analog(tinyxml2::XMLElement *el) {
TemplateAnalogBinding a;
const char *name = el->Attribute("name");
a.name = name ? name : "";
const char *devid = el->Attribute("devid");
a.device_identifier = devid ? devid : "";
int idx = 0xFF;
el->QueryIntAttribute("index", &idx);
a.index = (unsigned short)idx;
el->QueryFloatAttribute("sensivity", &a.sensitivity); // keep typo
el->QueryFloatAttribute("deadzone", &a.deadzone);
el->QueryBoolAttribute("deadzone_mirror", &a.deadzone_mirror);
el->QueryBoolAttribute("invert", &a.invert);
el->QueryBoolAttribute("smoothing", &a.smoothing);
el->QueryIntAttribute("multiplier", &a.multiplier);
el->QueryBoolAttribute("relative", &a.relative_mode);
el->QueryIntAttribute("delay", &a.delay_buffer_depth);
return a;
}
static void write_light_entry(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent,
const std::string &name, const TemplateLightEntry &entry) {
auto *el = doc.NewElement("light");
el->SetAttribute("name", name.c_str());
el->SetAttribute("devid", entry.device_identifier.c_str());
el->SetAttribute("index", entry.index);
parent->InsertEndChild(el);
}
static TemplateLightEntry read_light_entry(tinyxml2::XMLElement *el) {
TemplateLightEntry entry;
const char *devid = el->Attribute("devid");
entry.device_identifier = devid ? devid : "";
int idx = 0;
el->QueryIntAttribute("index", &idx);
entry.index = (unsigned int)idx;
return entry;
}
// Parse all templates from an already-loaded XMLDocument.
// is_builtin controls the is_builtin flag on each returned template.
static std::vector<ControllerTemplate> load_templates_from_doc(
tinyxml2::XMLDocument &doc, bool is_builtin) {
std::vector<ControllerTemplate> templates;
auto *root = doc.RootElement();
if (!root) {
return templates;
}
auto *tmpl_el = root->FirstChildElement("template");
while (tmpl_el) {
ControllerTemplate tmpl;
const char *name = tmpl_el->Attribute("name");
const char *game = tmpl_el->Attribute("game");
tmpl.name = name ? name : "";
tmpl.game_name = game ? game : "";
tmpl.is_builtin = is_builtin;
// buttons
auto *buttons_el = tmpl_el->FirstChildElement("buttons");
if (buttons_el) {
auto *btn_el = buttons_el->FirstChildElement("button");
while (btn_el) {
const char *btn_name = btn_el->Attribute("name");
std::string btn_name_str = btn_name ? btn_name : "";
TemplateButtonBinding *binding = nullptr;
for (auto &b : tmpl.buttons) {
if (b.name == btn_name_str) { binding = &b; break; }
}
if (!binding) {
tmpl.buttons.push_back({btn_name_str, read_button_entry(btn_el), {}});
} else {
binding->alternatives.push_back(read_button_entry(btn_el));
}
btn_el = btn_el->NextSiblingElement("button");
}
}
// keypad buttons
auto *kp_el = tmpl_el->FirstChildElement("keypad_buttons");
if (kp_el) {
auto *btn_el = kp_el->FirstChildElement("button");
while (btn_el) {
const char *btn_name = btn_el->Attribute("name");
std::string btn_name_str = btn_name ? btn_name : "";
TemplateButtonBinding *binding = nullptr;
for (auto &b : tmpl.keypad_buttons) {
if (b.name == btn_name_str) { binding = &b; break; }
}
if (!binding) {
tmpl.keypad_buttons.push_back({btn_name_str, read_button_entry(btn_el), {}});
} else {
binding->alternatives.push_back(read_button_entry(btn_el));
}
btn_el = btn_el->NextSiblingElement("button");
}
}
// analogs
auto *analogs_el = tmpl_el->FirstChildElement("analogs");
if (analogs_el) {
auto *analog_el = analogs_el->FirstChildElement("analog");
while (analog_el) {
tmpl.analogs.push_back(read_analog(analog_el));
analog_el = analog_el->NextSiblingElement("analog");
}
}
// lights
auto *lights_el = tmpl_el->FirstChildElement("lights");
if (lights_el) {
auto *light_el = lights_el->FirstChildElement("light");
while (light_el) {
const char *light_name = light_el->Attribute("name");
std::string light_name_str = light_name ? light_name : "";
TemplateLightBinding *binding = nullptr;
for (auto &l : tmpl.lights) {
if (l.name == light_name_str) { binding = &l; break; }
}
if (!binding) {
tmpl.lights.push_back({light_name_str, read_light_entry(light_el), {}});
} else {
binding->alternatives.push_back(read_light_entry(light_el));
}
light_el = light_el->NextSiblingElement("light");
}
}
templates.push_back(std::move(tmpl));
tmpl_el = tmpl_el->NextSiblingElement("template");
}
return templates;
}
static std::vector<ControllerTemplate> load_builtin_templates() {
auto xml = resutil::load_file_string(IDR_CONTROLLER_PRESETS_BUILTIN);
tinyxml2::XMLDocument doc;
auto err = doc.Parse(xml.c_str());
if (err != tinyxml2::XML_SUCCESS) {
log_warning("templates", "failed to parse builtin templates XML: {}", (int)err);
return {};
}
return load_templates_from_doc(doc, true);
}
std::vector<ControllerTemplate> load_user_templates() {
std::vector<ControllerTemplate> templates;
auto path = get_templates_path();
tinyxml2::XMLDocument doc;
if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS) {
return templates;
}
return load_templates_from_doc(doc, false);
}
bool save_user_template(
const ControllerTemplate &tmpl,
const bool save_buttons,
const bool save_keypads,
const bool save_analogs,
const bool save_lights) {
auto path = get_templates_path();
auto path_tmp = path;
path_tmp.replace_extension(L"tmp");
tinyxml2::XMLDocument doc;
if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS) {
auto *decl = doc.NewDeclaration();
doc.InsertFirstChild(decl);
auto *root = doc.NewElement("templates");
doc.InsertEndChild(root);
}
auto *root = doc.RootElement();
if (!root) {
root = doc.NewElement("templates");
doc.InsertEndChild(root);
}
// remove existing template with same name and game
auto *existing = root->FirstChildElement("template");
while (existing) {
auto *next = existing->NextSiblingElement("template");
const char *ename = existing->Attribute("name");
const char *egame = existing->Attribute("game");
if (ename && egame &&
std::string(ename) == tmpl.name &&
std::string(egame) == tmpl.game_name) {
root->DeleteChild(existing);
}
existing = next;
}
auto *tmpl_el = doc.NewElement("template");
tmpl_el->SetAttribute("name", tmpl.name.c_str());
tmpl_el->SetAttribute("game", tmpl.game_name.c_str());
// buttons — skip unbound entries
auto *buttons_el = doc.NewElement("buttons");
if (save_buttons) {
for (auto &btn : tmpl.buttons) {
if (!btn.primary.is_unbound()) {
write_button_entry(doc, buttons_el, btn.name, btn.primary);
}
for (auto &alt : btn.alternatives) {
if (!alt.is_unbound()) {
write_button_entry(doc, buttons_el, btn.name, alt);
}
}
}
}
tmpl_el->InsertEndChild(buttons_el);
// keypad buttons — skip unbound entries
if (!tmpl.keypad_buttons.empty()) {
auto *kp_el = doc.NewElement("keypad_buttons");
if (save_keypads) {
for (auto &btn : tmpl.keypad_buttons) {
if (!btn.primary.is_unbound()) {
write_button_entry(doc, kp_el, btn.name, btn.primary);
}
for (auto &alt : btn.alternatives) {
if (!alt.is_unbound()) {
write_button_entry(doc, kp_el, btn.name, alt);
}
}
}
}
tmpl_el->InsertEndChild(kp_el);
}
// analogs — skip unbound entries
auto *analogs_el = doc.NewElement("analogs");
if (save_analogs) {
for (auto &a : tmpl.analogs) {
if (!a.is_unbound()) {
write_analog(doc, analogs_el, a);
}
}
}
tmpl_el->InsertEndChild(analogs_el);
// lights — skip unbound entries
auto *lights_el = doc.NewElement("lights");
if (save_lights) {
for (auto &l : tmpl.lights) {
if (!l.primary.is_unbound()) {
write_light_entry(doc, lights_el, l.name, l.primary);
}
for (auto &alt : l.alternatives) {
if (!alt.is_unbound()) {
write_light_entry(doc, lights_el, l.name, alt);
}
}
}
}
tmpl_el->InsertEndChild(lights_el);
root->InsertEndChild(tmpl_el);
// ensure directory exists
if (!path.parent_path().empty() && !std::filesystem::exists(path.parent_path())) {
fileutils::dir_create_recursive(path.parent_path());
}
// save atomically via temp file
auto save_err = doc.SaveFile(path_tmp.c_str());
if (save_err != tinyxml2::XML_SUCCESS) {
log_warning("templates", "failed to save templates file: {}", (int)save_err);
return false;
}
std::error_code ec;
std::filesystem::rename(path_tmp, path, ec);
if (ec) {
log_warning("templates", "failed to rename templates file: {}", ec.message());
return false;
}
return true;
}
bool delete_user_template(const std::string &game_name, const std::string &template_name) {
auto path = get_templates_path();
tinyxml2::XMLDocument doc;
if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS) return false;
auto *root = doc.RootElement();
if (!root) return false;
bool found = false;
auto *tmpl_el = root->FirstChildElement("template");
while (tmpl_el) {
auto *next = tmpl_el->NextSiblingElement("template");
const char *ename = tmpl_el->Attribute("name");
const char *egame = tmpl_el->Attribute("game");
if (ename && egame &&
std::string(ename) == template_name &&
std::string(egame) == game_name) {
root->DeleteChild(tmpl_el);
found = true;
}
tmpl_el = next;
}
if (found) {
auto path_tmp = path;
path_tmp.replace_extension(L"tmp");
if (doc.SaveFile(path_tmp.c_str()) == tinyxml2::XML_SUCCESS) {
std::error_code ec;
std::filesystem::rename(path_tmp, path, ec);
}
}
return found;
}
bool rename_user_template(const std::string &game_name,
const std::string &old_name, const std::string &new_name) {
auto path = get_templates_path();
tinyxml2::XMLDocument doc;
if (doc.LoadFile(path.c_str()) != tinyxml2::XML_SUCCESS) return false;
auto *root = doc.RootElement();
if (!root) return false;
bool found = false;
auto *tmpl_el = root->FirstChildElement("template");
while (tmpl_el) {
const char *ename = tmpl_el->Attribute("name");
const char *egame = tmpl_el->Attribute("game");
if (ename && egame &&
std::string(ename) == old_name &&
std::string(egame) == game_name) {
tmpl_el->SetAttribute("name", new_name.c_str());
found = true;
break;
}
tmpl_el = tmpl_el->NextSiblingElement("template");
}
if (found) {
auto path_tmp = path;
path_tmp.replace_extension(L"tmp");
if (doc.SaveFile(path_tmp.c_str()) == tinyxml2::XML_SUCCESS) {
std::error_code ec;
std::filesystem::rename(path_tmp, path, ec);
}
}
return found;
}
std::vector<ControllerTemplate> get_templates_for_game(const std::string &game_name) {
std::vector<ControllerTemplate> result;
log_misc("templates", "loading built-in templates for {}", game_name);
for (auto &t : load_builtin_templates()) {
if (t.game_name == game_name) {
result.push_back(std::move(t));
}
}
log_misc("templates", "loading user templates for {}", game_name);
for (auto &t : load_user_templates()) {
if (t.game_name == game_name) {
result.push_back(std::move(t));
}
}
return result;
}
}
+9
View File
@@ -85,6 +85,15 @@ std::string Light::getDisplayString(rawinput::RawInputManager* manager) {
return "Invalid SMX Dedicab Light (" + index_string + ")"; return "Invalid SMX Dedicab Light (" + index_string + ")";
} }
case rawinput::XINPUT_GAMEPAD: {
if (index < static_cast<size_t>(xinput::XInputOutputEnum::COUNT)) {
return fmt::format("{} ({})",
xinput::get_output_string(static_cast<xinput::XInputOutputEnum>(index)),
device->desc);
}
return "Invalid XInput Gamepad Light (" + index_string + ")";
}
case rawinput::DESTROYED: case rawinput::DESTROYED:
return "Unplugged device (" + index_string + ")"; return "Unplugged device (" + index_string + ")";
default: default:
+34
View File
@@ -11,8 +11,10 @@ class Light {
private: private:
std::vector<Light> alternatives; std::vector<Light> alternatives;
std::string lightName; std::string lightName;
std::string lightCategory;
std::string deviceIdentifier = ""; std::string deviceIdentifier = "";
unsigned int index = 0; unsigned int index = 0;
bool is_temporary = false;
public: public:
float last_state = 0.f; float last_state = 0.f;
@@ -22,6 +24,8 @@ public:
float override_state = 0.f; float override_state = 0.f;
explicit Light(std::string lightName) : lightName(std::move(lightName)) {}; explicit Light(std::string lightName) : lightName(std::move(lightName)) {};
explicit Light(std::string lightName, std::string lightCategory) :
lightName(std::move(lightName)), lightCategory(std::move(lightCategory)) {};
std::string getDisplayString(rawinput::RawInputManager* manager); std::string getDisplayString(rawinput::RawInputManager* manager);
@@ -50,6 +54,14 @@ public:
return this->lightName; return this->lightName;
} }
inline const std::string &getCategory() const {
return this->lightCategory;
}
inline void setCategory(std::string category) {
this->lightCategory = std::move(category);
}
inline const std::string &getDeviceIdentifier() const { inline const std::string &getDeviceIdentifier() const {
return this->deviceIdentifier; return this->deviceIdentifier;
} }
@@ -65,4 +77,26 @@ public:
inline void setIndex(unsigned int index) { inline void setIndex(unsigned int index) {
this->index = index; this->index = index;
} }
inline bool isTemporary() const {
return this->is_temporary;
}
inline void setTemporary(bool is_temporary) {
this->is_temporary = is_temporary;
}
inline bool isValid() {
// temporarily created by UI
if (isTemporary()) {
return true;
}
// nothing is set
if (getDeviceIdentifier().empty()) {
return false;
}
return true;
}
}; };
+4
View File
@@ -3,6 +3,10 @@
#include "util/logging.h" #include "util/logging.h"
#include "util/utils.h" #include "util/utils.h"
static auto format_as(OptionType f) {
return fmt::underlying(f);
}
void Option::value_add(std::string new_value) { void Option::value_add(std::string new_value) {
// put in primary slot if possible // put in primary slot if possible
+14
View File
@@ -13,6 +13,16 @@ enum class OptionType {
Hex, Hex,
}; };
enum class OptionPickerType {
None,
AsioDriver,
EACard,
CpuAffinity,
FilePath,
DirectoryPath,
Monitor,
};
struct OptionDefinition { struct OptionDefinition {
std::string title; std::string title;
// unique identifier used for flag matching but also stored in config files // unique identifier used for flag matching but also stored in config files
@@ -32,6 +42,10 @@ struct OptionDefinition {
bool sensitive = false; bool sensitive = false;
std::vector<std::pair<std::string, std::string>> elements = {}; std::vector<std::pair<std::string, std::string>> elements = {};
bool disabled = false; bool disabled = false;
OptionPickerType picker = OptionPickerType::None;
// for OptionPickerType::FilePath
std::string file_extension = "";
}; };
class Option { class Option {
+1
View File
@@ -4,3 +4,4 @@
#define IDR_PATCHES 132 #define IDR_PATCHES 132
#define IDR_README 133 #define IDR_README 133
#define IDR_DSEGFONT 134 #define IDR_DSEGFONT 134
#define IDR_CONTROLLER_PRESETS_BUILTIN 135
+7 -1
View File
@@ -1,9 +1,11 @@
#include "screen_resize.h" #include "screen_resize.h"
#include "avs/game.h"
#include "external/rapidjson/document.h" #include "external/rapidjson/document.h"
#include "external/rapidjson/pointer.h" #include "external/rapidjson/pointer.h"
#include "external/rapidjson/prettywriter.h" #include "external/rapidjson/prettywriter.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/deferlog.h"
#include "util/utils.h" #include "util/utils.h"
#include "util/fileutils.h" #include "util/fileutils.h"
#include "hooks/graphics/graphics.h" #include "hooks/graphics/graphics.h"
@@ -48,7 +50,7 @@ namespace cfg {
// check parse error // check parse error
auto error = doc.GetParseError(); auto error = doc.GetParseError();
if (error) { if (error) {
log_warning("ScreenResize", "config parse error: {}", error); log_warning("ScreenResize", "config parse error: {}", static_cast<uint32_t>(error));
return; return;
} }
@@ -78,6 +80,10 @@ namespace cfg {
root); root);
load_bool_value(doc, root + "enable_screen_resize", this->enable_screen_resize); load_bool_value(doc, root + "enable_screen_resize", this->enable_screen_resize);
if (this->enable_screen_resize) {
log_misc("ScreenResize", "enabled by config file");
}
load_bool_value(doc, root + "enable_linear_filter", this->enable_linear_filter); load_bool_value(doc, root + "enable_linear_filter", this->enable_linear_filter);
for (size_t i = 0; i < std::size(this->scene_settings); i++) { for (size_t i = 0; i < std::size(this->scene_settings); i++) {
auto& scene = this->scene_settings[i]; auto& scene = this->scene_settings[i];
+6
View File
@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <atomic>
#include <memory> #include <memory>
#include <string> #include <string>
#include <optional> #include <optional>
@@ -51,11 +52,15 @@ namespace cfg {
bool enable_linear_filter = true; bool enable_linear_filter = true;
fullscreen_setting scene_settings[4]; fullscreen_setting scene_settings[4];
// state
std::atomic<bool> need_surface_clean = false;
// windowed mode sizing // windowed mode sizing
// Windows terminology: // Windows terminology:
// window = rectangle including the frame // window = rectangle including the frame
// client = just the content area without frames. // client = just the content area without frames.
bool window_always_on_top = false; bool window_always_on_top = false;
bool window_disable_round_corners = false;
bool client_keep_aspect_ratio = true; bool client_keep_aspect_ratio = true;
bool enable_window_resize = false; bool enable_window_resize = false;
int window_decoration = 0; // enum type WindowDecorationMode int window_decoration = 0; // enum type WindowDecorationMode
@@ -69,6 +74,7 @@ namespace cfg {
uint32_t init_client_height = 0; uint32_t init_client_height = 0;
float init_client_aspect_ratio = 1.f; float init_client_aspect_ratio = 1.f;
uint32_t init_window_style = 0; uint32_t init_window_style = 0;
uint32_t init_window_style_ex = 0;
uint32_t window_deco_width = 0; uint32_t window_deco_width = 0;
uint32_t window_deco_height = 0; uint32_t window_deco_height = 0;
+8
View File
@@ -15,3 +15,11 @@ RUN pacman --noconfirm -Syu git \
ccache ccache
RUN useradd user -m && echo "ALL ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers RUN useradd user -m && echo "ALL ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
RUN su user -c "cd /tmp/ && git clone https://aur.archlinux.org/yay-bin.git --depth=1 && cd yay-bin && makepkg --noconfirm -si && yay --noconfirm -S mingw-w64-cmake" RUN su user -c "cd /tmp/ && git clone https://aur.archlinux.org/yay-bin.git --depth=1 && cd yay-bin && makepkg --noconfirm -si && yay --noconfirm -S mingw-w64-cmake"
RUN mkdir -p /opt/llvm-mingw-xp \
&& curl -fsSL "https://github.com/mon/llvm-mingw-xp/releases/download/llvm-mingw-xp-22.1.0/llvm-mingw-llvm-mingw-xp-22.1.0-msvcrt-ubuntu-22.04-x86_64.tar.xz" \
| tar -xJ --strip-components=1 -C /opt/llvm-mingw-xp
ENV PATH="$PATH:/opt/llvm-mingw-xp/bin"
RUN curl -fsSL "https://github.com/mon/windows-dll-compat-checker/releases/download/v1.3/windows_dll_compat_checker-linux-x86_64.tar.xz" \
| tar -xJ -C /usr/local/bin
-8
View File
@@ -1,8 +0,0 @@
# Run manually to reformat a file:
# clang-format -i --style=file <file>
Language: Cpp
BasedOnStyle: Google
IndentPPDirectives: AfterHash
IndentCaseLabels: false
AlwaysBreakTemplateDeclarations: false
DerivePointerAlignment: false
-37
View File
@@ -1,37 +0,0 @@
.vscode/
.vs/
*.iml
.idea/
.externalNativeBuild/
.gradle/
gradle/
gradlew*
local.properties
build/
support/.cxx
bin/
/_CPack_Packages
/CMakeScripts
/doc/doxyxml
/doc/html
/doc/node_modules
virtualenv
/Testing
/install_manifest.txt
*~
*.a
*.so*
*.xcodeproj
*.zip
cmake_install.cmake
CPack*.cmake
fmt-*.cmake
CTestTestfile.cmake
CMakeCache.txt
CMakeFiles
FMT.build
Makefile
run-msbuild.bat
fmt.pc
+229 -105
View File
@@ -1,15 +1,17 @@
cmake_minimum_required(VERSION 3.1...3.18) cmake_minimum_required(VERSION 3.8...3.28)
# Fallback for using newer policies on CMake <3.12. # Fallback for using newer policies on CMake <3.12.
if(${CMAKE_VERSION} VERSION_LESS 3.12) if (${CMAKE_VERSION} VERSION_LESS 3.12)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
endif() endif ()
# Determine if fmt is built as a subproject (using add_subdirectory) # Determine if fmt is built as a subproject (using add_subdirectory)
# or if it is the master project. # or if it is the master project.
if (NOT DEFINED FMT_MASTER_PROJECT) if (NOT DEFINED FMT_MASTER_PROJECT)
set(FMT_MASTER_PROJECT OFF) set(FMT_MASTER_PROJECT OFF)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) # NOTE: source vs current_source detection is unreliable
# this heuristic is more generally applicable esp w.r.t FetchContent
if (NOT DEFINED PROJECT_NAME)
set(FMT_MASTER_PROJECT ON) set(FMT_MASTER_PROJECT ON)
message(STATUS "CMake version: ${CMAKE_VERSION}") message(STATUS "CMake version: ${CMAKE_VERSION}")
endif () endif ()
@@ -17,22 +19,108 @@ endif ()
# Joins arguments and places the results in ${result_var}. # Joins arguments and places the results in ${result_var}.
function(join result_var) function(join result_var)
set(result ) set(result "")
foreach (arg ${ARGN}) foreach (arg ${ARGN})
set(result "${result}${arg}") set(result "${result}${arg}")
endforeach () endforeach ()
set(${result_var} "${result}" PARENT_SCOPE) set(${result_var} "${result}" PARENT_SCOPE)
endfunction() endfunction()
# DEPRECATED! Should be merged into add_module_library.
function(enable_module target) function(enable_module target)
if (MSVC) if (MSVC)
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc) if(NOT CMAKE_GENERATOR STREQUAL "Ninja")
target_compile_options(${target} set(BMI_DIR "${CMAKE_CURRENT_BINARY_DIR}")
PRIVATE /interface /ifcOutput ${BMI} file(TO_NATIVE_PATH "${BMI_DIR}/${target}.ifc" BMI)
INTERFACE /reference fmt=${BMI}) target_compile_options(${target}
PRIVATE /interface /ifcOutput ${BMI}
INTERFACE /reference fmt=${BMI})
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
endif()
endif () endif ()
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI}) endfunction()
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
set(FMT_USE_CMAKE_MODULES FALSE)
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.28 AND
CMAKE_GENERATOR STREQUAL "Ninja")
set(FMT_USE_CMAKE_MODULES TRUE)
endif ()
# Adds a library compiled with C++20 module support.
# `enabled` is a CMake variables that specifies if modules are enabled.
# If modules are disabled `add_module_library` falls back to creating a
# non-modular library.
#
# Usage:
# add_module_library(<name> [sources...] FALLBACK [sources...] [IF enabled])
function(add_module_library name)
cmake_parse_arguments(AML "" "IF" "FALLBACK" ${ARGN})
set(sources ${AML_UNPARSED_ARGUMENTS})
add_library(${name})
set_target_properties(${name} PROPERTIES LINKER_LANGUAGE CXX)
if (NOT ${${AML_IF}})
# Create a non-modular library.
target_sources(${name} PRIVATE ${AML_FALLBACK})
set_target_properties(${name} PROPERTIES CXX_SCAN_FOR_MODULES OFF)
return()
endif ()
# Modules require C++20.
target_compile_features(${name} PUBLIC cxx_std_20)
if (CMAKE_COMPILER_IS_GNUCXX)
target_compile_options(${name} PUBLIC -fmodules-ts)
endif ()
if (FMT_USE_CMAKE_MODULES)
target_sources(${name} PUBLIC FILE_SET fmt TYPE CXX_MODULES
FILES ${sources})
else()
# `std` is affected by CMake options and may be higher than C++20.
get_target_property(std ${name} CXX_STANDARD)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(pcms)
foreach (src ${sources})
get_filename_component(pcm ${src} NAME_WE)
set(pcm ${pcm}.pcm)
# Propagate -fmodule-file=*.pcm to targets that link with this library.
target_compile_options(
${name} PUBLIC -fmodule-file=${CMAKE_CURRENT_BINARY_DIR}/${pcm})
# Use an absolute path to prevent target_link_libraries prepending -l
# to it.
set(pcms ${pcms} ${CMAKE_CURRENT_BINARY_DIR}/${pcm})
add_custom_command(
OUTPUT ${pcm}
COMMAND ${CMAKE_CXX_COMPILER}
-std=c++${std} -x c++-module --precompile -c
-o ${pcm} ${CMAKE_CURRENT_SOURCE_DIR}/${src}
"-I$<JOIN:$<TARGET_PROPERTY:${name},INCLUDE_DIRECTORIES>,;-I>"
# Required by the -I generator expression above.
COMMAND_EXPAND_LISTS
DEPENDS ${src})
endforeach ()
# Add .pcm files as sources to make sure they are built before the library.
set(sources)
foreach (pcm ${pcms})
get_filename_component(pcm_we ${pcm} NAME_WE)
set(obj ${pcm_we}.o)
# Use an absolute path to prevent target_link_libraries prepending -l.
set(sources ${sources} ${pcm} ${CMAKE_CURRENT_BINARY_DIR}/${obj})
add_custom_command(
OUTPUT ${obj}
COMMAND ${CMAKE_CXX_COMPILER} $<TARGET_PROPERTY:${name},COMPILE_OPTIONS>
-c -o ${obj} ${pcm}
DEPENDS ${pcm})
endforeach ()
endif ()
target_sources(${name} PRIVATE ${sources})
endif()
endfunction() endfunction()
include(CMakeParseArguments) include(CMakeParseArguments)
@@ -79,28 +167,28 @@ option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT})
option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT}) option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
option(FMT_FUZZ "Generate the fuzz target." OFF) option(FMT_FUZZ "Generate the fuzz target." OFF)
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF) option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
option(FMT_OS "Include core requiring OS (Windows/Posix) " ON) option(FMT_OS "Include OS-specific APIs." ON)
option(FMT_MODULE "Build a module instead of a traditional library." OFF) option(FMT_MODULE "Build a module instead of a traditional library." OFF)
option(FMT_SYSTEM_HEADERS "Expose headers with marking them as system." OFF)
option(FMT_UNICODE "Enable Unicode support." ON)
set(FMT_CAN_MODULE OFF)
if (CMAKE_CXX_STANDARD GREATER 17 AND
# msvc 16.10-pre4
MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.29.30035)
set(FMT_CAN_MODULE ON)
endif ()
if (NOT FMT_CAN_MODULE)
set(FMT_MODULE OFF)
message(STATUS "Module support is disabled.")
endif ()
if (FMT_TEST AND FMT_MODULE) if (FMT_TEST AND FMT_MODULE)
# The tests require {fmt} to be compiled as traditional library # The tests require {fmt} to be compiled as traditional library
message(STATUS "Testing is incompatible with build mode 'module'.") message(STATUS "Testing is incompatible with build mode 'module'.")
endif () endif ()
set(FMT_SYSTEM_HEADERS_ATTRIBUTE "")
if (FMT_SYSTEM_HEADERS)
set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM)
endif ()
if (CMAKE_SYSTEM_NAME STREQUAL "MSDOS")
set(FMT_TEST OFF)
message(STATUS "MSDOS is incompatible with gtest")
endif ()
# Get version from core.h # Get version from base.h
file(READ include/fmt/core.h core_h) file(READ include/fmt/base.h base_h)
if (NOT core_h MATCHES "FMT_VERSION ([0-9]+)([0-9][0-9])([0-9][0-9])") if (NOT base_h MATCHES "FMT_VERSION ([0-9]+)([0-9][0-9])([0-9][0-9])")
message(FATAL_ERROR "Cannot get FMT_VERSION from core.h.") message(FATAL_ERROR "Cannot get FMT_VERSION from base.h.")
endif () endif ()
# Use math to skip leading zeros if any. # Use math to skip leading zeros if any.
math(EXPR CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1}) math(EXPR CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1})
@@ -108,29 +196,19 @@ math(EXPR CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2})
math(EXPR CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3}) math(EXPR CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3})
join(FMT_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}. join(FMT_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.
${CPACK_PACKAGE_VERSION_PATCH}) ${CPACK_PACKAGE_VERSION_PATCH})
message(STATUS "Version: ${FMT_VERSION}") message(STATUS "{fmt} version: ${FMT_VERSION}")
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
endif () endif ()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
include(cxx14)
include(CheckCXXCompilerFlag) include(CheckCXXCompilerFlag)
include(JoinPaths) include(JoinPaths)
list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index)
if (${index} GREATER -1)
# Use cxx_variadic_templates instead of more appropriate cxx_std_11 for
# compatibility with older CMake versions.
set(FMT_REQUIRED_FEATURES cxx_variadic_templates)
endif ()
message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}")
if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET) if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET)
set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING
"Preset for the export of private symbols") "Preset for the export of private symbols")
@@ -151,7 +229,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
-Wcast-align -Wcast-align
-Wctor-dtor-privacy -Wdisabled-optimization -Wctor-dtor-privacy -Wdisabled-optimization
-Winvalid-pch -Woverloaded-virtual -Winvalid-pch -Woverloaded-virtual
-Wconversion -Wswitch-enum -Wundef -Wconversion -Wundef
-Wno-ctor-dtor-privacy -Wno-format-nonliteral) -Wno-ctor-dtor-privacy -Wno-format-nonliteral)
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6) if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
@@ -164,7 +242,13 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
endif () endif ()
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2 set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2
-Wnull-dereference -Wduplicated-cond) -Wduplicated-cond)
# Workaround for GCC regression
# [12/13/14/15 regression] New (since gcc 12) false positive null-dereference in vector.resize
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108860
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wnull-dereference)
endif ()
endif () endif ()
set(WERROR_FLAG -Werror) set(WERROR_FLAG -Werror)
endif () endif ()
@@ -204,18 +288,6 @@ if (FMT_MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio")
${CMAKE_MAKE_PROGRAM} -p:FrameworkPathOverride=\"${netfxpath}\" %*") ${CMAKE_MAKE_PROGRAM} -p:FrameworkPathOverride=\"${netfxpath}\" %*")
endif () endif ()
set(strtod_l_headers stdlib.h)
if (APPLE)
set(strtod_l_headers ${strtod_l_headers} xlocale.h)
endif ()
include(CheckSymbolExists)
if (WIN32)
check_symbol_exists(_strtod_l "${strtod_l_headers}" HAVE_STRTOD_L)
else ()
check_symbol_exists(strtod_l "${strtod_l_headers}" HAVE_STRTOD_L)
endif ()
function(add_headers VAR) function(add_headers VAR)
set(headers ${${VAR}}) set(headers ${${VAR}})
foreach (header ${ARGN}) foreach (header ${ARGN})
@@ -225,29 +297,22 @@ function(add_headers VAR)
endfunction() endfunction()
# Define the fmt library, its includes and the needed defines. # Define the fmt library, its includes and the needed defines.
add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h set(FMT_HEADERS)
format-inl.h locale.h os.h ostream.h printf.h ranges.h add_headers(FMT_HEADERS args.h base.h chrono.h color.h compile.h core.h format.h
format-inl.h os.h ostream.h printf.h ranges.h std.h
xchar.h) xchar.h)
if (FMT_MODULE) set(FMT_SOURCES src/format.cc)
set(FMT_SOURCES src/fmt.cc)
elseif (FMT_OS)
set(FMT_SOURCES src/format.cc src/os.cc)
else()
set(FMT_SOURCES src/format.cc)
endif ()
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst) add_module_library(fmt src/fmt.cc FALLBACK
${FMT_SOURCES} ${FMT_HEADERS} README.md ChangeLog.md
IF FMT_MODULE)
add_library(fmt::fmt ALIAS fmt) add_library(fmt::fmt ALIAS fmt)
if (FMT_MODULE)
if (HAVE_STRTOD_L) enable_module(fmt)
target_compile_definitions(fmt PUBLIC FMT_LOCALE) elseif (FMT_OS)
endif () target_sources(fmt PRIVATE src/os.cc)
else()
if (MINGW) target_compile_definitions(fmt PRIVATE FMT_OS=0)
check_cxx_compiler_flag("Wa,-mbig-obj" FMT_HAS_MBIG_OBJ)
if (${FMT_HAS_MBIG_OBJ})
target_compile_options(fmt PUBLIC "-Wa,-mbig-obj")
endif()
endif () endif ()
if (FMT_WERROR) if (FMT_WERROR)
@@ -256,13 +321,14 @@ endif ()
if (FMT_PEDANTIC) if (FMT_PEDANTIC)
target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS}) target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS})
endif () endif ()
if (FMT_MODULE)
enable_module(fmt) if (cxx_std_11 IN_LIST CMAKE_CXX_COMPILE_FEATURES)
target_compile_features(fmt PUBLIC cxx_std_11)
else ()
message(WARNING "Feature cxx_std_11 is unknown for the CXX compiler")
endif () endif ()
target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES}) target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} BEFORE PUBLIC
target_include_directories(fmt PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${FMT_INC_DIR}>) $<INSTALL_INTERFACE:${FMT_INC_DIR}>)
@@ -270,7 +336,16 @@ set(FMT_DEBUG_POSTFIX d CACHE STRING "Debug library postfix.")
set_target_properties(fmt PROPERTIES set_target_properties(fmt PROPERTIES
VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR}
DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}") PUBLIC_HEADER "${FMT_HEADERS}"
DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}"
# Workaround for Visual Studio 2017:
# Ensure the .pdb is created with the same name and in the same directory
# as the .lib. Newer VS versions already do this by default, but there is no
# harm in setting it for those too. Ignored by other generators.
COMPILE_PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
COMPILE_PDB_NAME "fmt"
COMPILE_PDB_NAME_DEBUG "fmt${FMT_DEBUG_POSTFIX}")
# Set FMT_LIB_NAME for pkg-config fmt.pc. We cannot use the OUTPUT_NAME target # Set FMT_LIB_NAME for pkg-config fmt.pc. We cannot use the OUTPUT_NAME target
# property because it's not set by default. # property because it's not set by default.
@@ -280,25 +355,30 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
endif () endif ()
if (BUILD_SHARED_LIBS) if (BUILD_SHARED_LIBS)
if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND target_compile_definitions(fmt PRIVATE FMT_LIB_EXPORT INTERFACE FMT_SHARED)
NOT EMSCRIPTEN)
# Fix rpmlint warning:
# unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.
target_link_libraries(fmt -Wl,--as-needed)
endif ()
target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED)
endif () endif ()
if (FMT_SAFE_DURATION_CAST) if (FMT_SAFE_DURATION_CAST)
target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST) target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST)
endif() endif ()
add_library(fmt-header-only INTERFACE) add_library(fmt-header-only INTERFACE)
add_library(fmt::fmt-header-only ALIAS fmt-header-only) add_library(fmt::fmt-header-only ALIAS fmt-header-only)
target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1) if (NOT MSVC)
target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES}) # Unicode is always supported on compilers other than MSVC.
elseif (FMT_UNICODE)
# Unicode support requires compiling with /utf-8.
target_compile_options(fmt PUBLIC $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>)
target_compile_options(fmt-header-only INTERFACE $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>)
else ()
target_compile_definitions(fmt PUBLIC FMT_UNICODE=0)
endif ()
target_include_directories(fmt-header-only INTERFACE target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
target_compile_features(fmt-header-only INTERFACE cxx_std_11)
target_include_directories(fmt-header-only
${FMT_SYSTEM_HEADERS_ATTRIBUTE} BEFORE INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${FMT_INC_DIR}>) $<INSTALL_INTERFACE:${FMT_INC_DIR}>)
@@ -318,7 +398,7 @@ if (FMT_INSTALL)
"Installation directory for libraries, a relative path that " "Installation directory for libraries, a relative path that "
"will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.") "will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.")
set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE STRING
"Installation directory for pkgconfig (.pc) files, a relative " "Installation directory for pkgconfig (.pc) files, a relative "
"path that will be joined with ${CMAKE_INSTALL_PREFIX} or an " "path that will be joined with ${CMAKE_INSTALL_PREFIX} or an "
"absolute path.") "absolute path.")
@@ -343,11 +423,20 @@ if (FMT_INSTALL)
set(INSTALL_TARGETS fmt fmt-header-only) set(INSTALL_TARGETS fmt fmt-header-only)
set(INSTALL_FILE_SET)
if (FMT_USE_CMAKE_MODULES)
set(INSTALL_FILE_SET FILE_SET fmt DESTINATION "${FMT_INC_DIR}/fmt")
endif()
# Install the library and headers. # Install the library and headers.
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name} install(TARGETS ${INSTALL_TARGETS}
COMPONENT fmt_core
EXPORT ${targets_export_name}
LIBRARY DESTINATION ${FMT_LIB_DIR} LIBRARY DESTINATION ${FMT_LIB_DIR}
ARCHIVE DESTINATION ${FMT_LIB_DIR} ARCHIVE DESTINATION ${FMT_LIB_DIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt"
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
${INSTALL_FILE_SET})
# Use a namespace because CMake provides better diagnostics for namespaced # Use a namespace because CMake provides better diagnostics for namespaced
# imported targets. # imported targets.
@@ -355,20 +444,56 @@ if (FMT_INSTALL)
FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake) FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake)
# Install version, config and target files. # Install version, config and target files.
install( install(FILES ${project_config} ${version_config}
FILES ${project_config} ${version_config} DESTINATION ${FMT_CMAKE_DIR}
DESTINATION ${FMT_CMAKE_DIR}) COMPONENT fmt_core)
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR} install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
NAMESPACE fmt::) NAMESPACE fmt::
COMPONENT fmt_core)
install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}> install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}"
DESTINATION ${FMT_LIB_DIR} OPTIONAL) COMPONENT fmt_core)
install(FILES ${FMT_HEADERS} DESTINATION "${FMT_INC_DIR}/fmt")
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}")
endif () endif ()
function(add_doc_target)
find_program(DOXYGEN doxygen
PATHS "$ENV{ProgramFiles}/doxygen/bin"
"$ENV{ProgramFiles\(x86\)}/doxygen/bin")
if (NOT DOXYGEN)
message(STATUS "Target 'doc' disabled because doxygen not found")
return ()
endif ()
find_program(MKDOCS mkdocs)
if (NOT MKDOCS)
message(STATUS "Target 'doc' disabled because mkdocs not found")
return ()
endif ()
set(sources )
foreach (source api.md index.md syntax.md get-started.md fmt.css fmt.js)
set(sources ${sources} doc/${source})
endforeach()
add_custom_target(
doc
COMMAND
${CMAKE_COMMAND}
-E env PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/support/python
${MKDOCS} build -f ${CMAKE_CURRENT_SOURCE_DIR}/support/mkdocs.yml
# MkDocs requires the site dir to be outside of the doc dir.
--site-dir ${CMAKE_CURRENT_BINARY_DIR}/doc-html
--no-directory-urls
SOURCES ${sources})
include(GNUInstallDirs)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc-html/
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt
COMPONENT fmt_doc OPTIONAL)
endfunction()
if (FMT_DOC) if (FMT_DOC)
add_subdirectory(doc) add_doc_target()
endif () endif ()
if (FMT_TEST) if (FMT_TEST)
@@ -398,13 +523,12 @@ if (FMT_MASTER_PROJECT AND EXISTS ${gitignore})
string(REPLACE "*" ".*" line "${line}") string(REPLACE "*" ".*" line "${line}")
set(ignored_files ${ignored_files} "${line}$" "${line}/") set(ignored_files ${ignored_files} "${line}$" "${line}/")
endforeach () endforeach ()
set(ignored_files ${ignored_files} set(ignored_files ${ignored_files} /.git /build/doxyxml .vagrant)
/.git /breathe /format-benchmark sphinx/ .buildinfo .doctrees)
set(CPACK_SOURCE_GENERATOR ZIP) set(CPACK_SOURCE_GENERATOR ZIP)
set(CPACK_SOURCE_IGNORE_FILES ${ignored_files}) set(CPACK_SOURCE_IGNORE_FILES ${ignored_files})
set(CPACK_SOURCE_PACKAGE_FILE_NAME fmt-${FMT_VERSION}) set(CPACK_SOURCE_PACKAGE_FILE_NAME fmt-${FMT_VERSION})
set(CPACK_PACKAGE_NAME fmt) set(CPACK_PACKAGE_NAME fmt)
set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.rst) set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.md)
include(CPack) include(CPack)
endif () endif ()
-20
View File
@@ -1,20 +0,0 @@
Contributing to {fmt}
=====================
By submitting a pull request or a patch, you represent that you have the right
to license your contribution to the {fmt} project owners and the community,
agree that your contributions are licensed under the {fmt} license, and agree
to future changes to the licensing.
All C++ code must adhere to [Google C++ Style Guide](
https://google.github.io/styleguide/cppguide.html) with the following
exceptions:
* Exceptions are permitted
* snake_case should be used instead of UpperCamelCase for function and type
names
All documentation must adhere to the [Google Developer Documentation Style
Guide](https://developers.google.com/style).
Thanks for contributing!
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,4 @@
Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the
+486
View File
@@ -0,0 +1,486 @@
<img src="https://user-images.githubusercontent.com/576385/156254208-f5b743a9-88cf-439d-b0c0-923d53e8d551.png" alt="{fmt}" width="25%"/>
[![image](https://github.com/fmtlib/fmt/workflows/linux/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux)
[![image](https://github.com/fmtlib/fmt/workflows/macos/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos)
[![image](https://github.com/fmtlib/fmt/workflows/windows/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows)
[![fmt is continuously fuzzed at oss-fuzz](https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?\%0Acolspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\%0ASummary&q=proj%3Dfmt&can=1)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8880/badge)](https://www.bestpractices.dev/projects/8880)
[![image](https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge)](https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt)
[![Ask questions at StackOverflow with the tag fmt](https://img.shields.io/badge/stackoverflow-fmt-blue.svg)](https://stackoverflow.com/questions/tagged/fmt)
**{fmt}** is an open-source formatting library providing a fast and safe
alternative to C stdio and C++ iostreams.
If you like this project, please consider donating to one of the funds
that help victims of the war in Ukraine: <https://www.stopputin.net/>.
[Documentation](https://fmt.dev)
[Cheat Sheets](https://hackingcpp.com/cpp/libs/fmt.html)
Q&A: ask questions on [StackOverflow with the tag
fmt](https://stackoverflow.com/questions/tagged/fmt).
Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v).
# Features
- Simple [format API](https://fmt.dev/latest/api/) with positional
arguments for localization
- Implementation of [C++20
std::format](https://en.cppreference.com/w/cpp/utility/format) and
[C++23 std::print](https://en.cppreference.com/w/cpp/io/print)
- [Format string syntax](https://fmt.dev/latest/syntax/) similar
to Python\'s
[format](https://docs.python.org/3/library/stdtypes.html#str.format)
- Fast IEEE 754 floating-point formatter with correct rounding,
shortness and round-trip guarantees using the
[Dragonbox](https://github.com/jk-jeon/dragonbox) algorithm
- Portable Unicode support
- Safe [printf
implementation](https://fmt.dev/latest/api/#printf-formatting)
including the POSIX extension for positional arguments
- Extensibility: [support for user-defined
types](https://fmt.dev/latest/api/#formatting-user-defined-types)
- High performance: faster than common standard library
implementations of `(s)printf`, iostreams, `to_string` and
`to_chars`, see [Speed tests](#speed-tests) and [Converting a
hundred million integers to strings per
second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html)
- Small code size both in terms of source code with the minimum
configuration consisting of just three files, `base.h`, `format.h`
and `format-inl.h`, and compiled code; see [Compile time and code
bloat](#compile-time-and-code-bloat)
- Reliability: the library has an extensive set of
[tests](https://github.com/fmtlib/fmt/tree/master/test) and is
[continuously fuzzed](https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1)
- Safety: the library is fully type-safe, errors in format strings can
be reported at compile time, automatic memory management prevents
buffer overflow errors
- Ease of use: small self-contained code base, no external
dependencies, permissive MIT
[license](https://github.com/fmtlib/fmt/blob/master/LICENSE)
- [Portability](https://fmt.dev/latest/#portability) with
consistent output across platforms and support for older compilers
- Clean warning-free codebase even on high warning levels such as
`-Wall -Wextra -pedantic`
- Locale independence by default
- Optional header-only configuration enabled with the
`FMT_HEADER_ONLY` macro
See the [documentation](https://fmt.dev) for more details.
# Examples
**Print to stdout** ([run](https://godbolt.org/z/Tevcjh))
``` c++
#include <fmt/base.h>
int main() {
fmt::print("Hello, world!\n");
}
```
**Format a string** ([run](https://godbolt.org/z/oK8h33))
``` c++
std::string s = fmt::format("The answer is {}.", 42);
// s == "The answer is 42."
```
**Format a string using positional arguments**
([run](https://godbolt.org/z/Yn7Txe))
``` c++
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
// s == "I'd rather be happy than right."
```
**Print dates and times** ([run](https://godbolt.org/z/c31ExdY3W))
``` c++
#include <fmt/chrono.h>
int main() {
auto now = std::chrono::system_clock::now();
fmt::print("Date and time: {}\n", now);
fmt::print("Time: {:%H:%M}\n", now);
}
```
Output:
Date and time: 2023-12-26 19:10:31.557195597
Time: 19:10
**Print a container** ([run](https://godbolt.org/z/MxM1YqjE7))
``` c++
#include <vector>
#include <fmt/ranges.h>
int main() {
std::vector<int> v = {1, 2, 3};
fmt::print("{}\n", v);
}
```
Output:
[1, 2, 3]
**Check a format string at compile time**
``` c++
std::string s = fmt::format("{:d}", "I am not a number");
```
This gives a compile-time error in C++20 because `d` is an invalid
format specifier for a string.
**Write a file from a single thread**
``` c++
#include <fmt/os.h>
int main() {
auto out = fmt::output_file("guide.txt");
out.print("Don't {}", "Panic");
}
```
This can be [5 to 9 times faster than
fprintf](http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html).
**Print with colors and text styles**
``` c++
#include <fmt/color.h>
int main() {
fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold,
"Hello, {}!\n", "world");
fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) |
fmt::emphasis::underline, "Olá, {}!\n", "Mundo");
fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic,
"你好{}\n", "世界");
}
```
Output on a modern terminal with Unicode support:
![image](https://github.com/fmtlib/fmt/assets/%0A576385/2a93c904-d6fa-4aa6-b453-2618e1c327d7)
# Benchmarks
## Speed tests
| Library | Method | Run Time, s |
|-------------------|---------------|-------------|
| libc | printf | 0.91 |
| libc++ | std::ostream | 2.49 |
| {fmt} 9.1 | fmt::print | 0.74 |
| Boost Format 1.80 | boost::format | 6.26 |
| Folly Format | folly::format | 1.87 |
{fmt} is the fastest of the benchmarked methods, \~20% faster than
`printf`.
The above results were generated by building `tinyformat_test.cpp` on
macOS 12.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and
taking the best of three runs. In the test, the format string
`"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` or equivalent is filled 2,000,000
times with output sent to `/dev/null`; for further details refer to the
[source](https://github.com/fmtlib/format-benchmark/blob/master/src/tinyformat-test.cc).
{fmt} is up to 20-30x faster than `std::ostringstream` and `sprintf` on
IEEE754 `float` and `double` formatting
([dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark)) and faster
than [double-conversion](https://github.com/google/double-conversion)
and [ryu](https://github.com/ulfjack/ryu):
[![image](https://user-images.githubusercontent.com/576385/95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png)](https://fmt.dev/unknown_mac64_clang12.0.html)
## Compile time and code bloat
The script [bloat-test.py][test] from [format-benchmark][bench] tests compile
time and code bloat for nontrivial projects. It generates 100 translation units
and uses `printf()` or its alternative five times in each to simulate a
medium-sized project. The resulting executable size and compile time (Apple
clang version 15.0.0 (clang-1500.1.0.2.5), macOS Sonoma, best of three) is shown
in the following tables.
[test]: https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py
[bench]: https://github.com/fmtlib/format-benchmark
**Optimized build (-O3)**
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|---------------|-----------------|----------------------|--------------------|
| printf | 1.6 | 54 | 50 |
| IOStreams | 25.9 | 98 | 84 |
| fmt 83652df | 4.8 | 54 | 50 |
| tinyformat | 29.1 | 161 | 136 |
| Boost Format | 55.0 | 530 | 317 |
{fmt} is fast to compile and is comparable to `printf` in terms of per-call
binary size (within a rounding error on this system).
**Non-optimized build**
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|---------------|-----------------|----------------------|--------------------|
| printf | 1.4 | 54 | 50 |
| IOStreams | 23.4 | 92 | 68 |
| {fmt} 83652df | 4.4 | 89 | 85 |
| tinyformat | 24.5 | 204 | 161 |
| Boost Format | 36.4 | 831 | 462 |
`libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries
to compare formatting function overhead only. Boost Format is a
header-only library so it doesn\'t provide any linkage options.
## Running the tests
Please refer to [Building the
library](https://fmt.dev/latest/get-started/#building-from-source) for
instructions on how to build the library and run the unit tests.
Benchmarks reside in a separate repository,
[format-benchmarks](https://github.com/fmtlib/format-benchmark), so to
run the benchmarks you first need to clone this repository and generate
Makefiles with CMake:
$ git clone --recursive https://github.com/fmtlib/format-benchmark.git
$ cd format-benchmark
$ cmake .
Then you can run the speed test:
$ make speed-test
or the bloat test:
$ make bloat-test
# Migrating code
[clang-tidy](https://clang.llvm.org/extra/clang-tidy/) v18 provides the
[modernize-use-std-print](https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-print.html)
check that is capable of converting occurrences of `printf` and
`fprintf` to `fmt::print` if configured to do so. (By default it
converts to `std::print`.)
# Notable projects using this library
- [0 A.D.](https://play0ad.com/): a free, open-source, cross-platform
real-time strategy game
- [AMPL/MP](https://github.com/ampl/mp): an open-source library for
mathematical programming
- [Apple's FoundationDB](https://github.com/apple/foundationdb): an open-source,
distributed, transactional key-value store
- [Aseprite](https://github.com/aseprite/aseprite): animated sprite
editor & pixel art tool
- [AvioBook](https://www.aviobook.aero/en): a comprehensive aircraft
operations suite
- [Blizzard Battle.net](https://battle.net/): an online gaming
platform
- [Celestia](https://celestia.space/): real-time 3D visualization of
space
- [Ceph](https://ceph.com/): a scalable distributed storage system
- [ccache](https://ccache.dev/): a compiler cache
- [ClickHouse](https://github.com/ClickHouse/ClickHouse): an
analytical database management system
- [ContextVision](https://www.contextvision.com/): medical imaging software
- [Contour](https://github.com/contour-terminal/contour/): a modern
terminal emulator
- [CUAUV](https://cuauv.org/): Cornell University\'s autonomous
underwater vehicle
- [Drake](https://drake.mit.edu/): a planning, control, and analysis
toolbox for nonlinear dynamical systems (MIT)
- [Envoy](https://github.com/envoyproxy/envoy): C++ L7 proxy and
communication bus (Lyft)
- [FiveM](https://fivem.net/): a modification framework for GTA V
- [fmtlog](https://github.com/MengRao/fmtlog): a performant
fmtlib-style logging library with latency in nanoseconds
- [Folly](https://github.com/facebook/folly): Facebook open-source
library
- [GemRB](https://gemrb.org/): a portable open-source implementation
of Bioware's Infinity Engine
- [Grand Mountain
Adventure](https://store.steampowered.com/app/1247360/Grand_Mountain_Adventure/):
a beautiful open-world ski & snowboarding game
- [HarpyWar/pvpgn](https://github.com/pvpgn/pvpgn-server): Player vs
Player Gaming Network with tweaks
- [KBEngine](https://github.com/kbengine/kbengine): an open-source
MMOG server engine
- [Keypirinha](https://keypirinha.com/): a semantic launcher for
Windows
- [Kodi](https://kodi.tv/) (formerly xbmc): home theater software
- [Knuth](https://kth.cash/): high-performance Bitcoin full-node
- [libunicode](https://github.com/contour-terminal/libunicode/): a
modern C++17 Unicode library
- [MariaDB](https://mariadb.org/): relational database management
system
- [Microsoft Verona](https://github.com/microsoft/verona): research
programming language for concurrent ownership
- [MongoDB](https://mongodb.com/): distributed document database
- [MongoDB Smasher](https://github.com/duckie/mongo_smasher): a small
tool to generate randomized datasets
- [OpenSpace](https://openspaceproject.com/): an open-source
astrovisualization framework
- [PenUltima Online (POL)](https://www.polserver.com/): an MMO server,
compatible with most Ultima Online clients
- [PyTorch](https://github.com/pytorch/pytorch): an open-source
machine learning library
- [quasardb](https://www.quasardb.net/): a distributed,
high-performance, associative database
- [Quill](https://github.com/odygrd/quill): asynchronous low-latency
logging library
- [QKW](https://github.com/ravijanjam/qkw): generalizing aliasing to
simplify navigation, and execute complex multi-line terminal
command sequences
- [redis-cerberus](https://github.com/HunanTV/redis-cerberus): a Redis
cluster proxy
- [redpanda](https://vectorized.io/redpanda): a 10x faster Kafka®
replacement for mission-critical systems written in C++
- [rpclib](http://rpclib.net/): a modern C++ msgpack-RPC server and
client library
- [Salesforce Analytics
Cloud](https://www.salesforce.com/analytics-cloud/overview/):
business intelligence software
- [Scylla](https://www.scylladb.com/): a Cassandra-compatible NoSQL
data store that can handle 1 million transactions per second on a
single server
- [Seastar](http://www.seastar-project.org/): an advanced, open-source
C++ framework for high-performance server applications on modern
hardware
- [spdlog](https://github.com/gabime/spdlog): super fast C++ logging
library
- [Stellar](https://www.stellar.org/): financial platform
- [Touch Surgery](https://www.touchsurgery.com/): surgery simulator
- [TrinityCore](https://github.com/TrinityCore/TrinityCore):
open-source MMORPG framework
- [🐙 userver framework](https://userver.tech/): open-source
asynchronous framework with a rich set of abstractions and database
drivers
- [Windows Terminal](https://github.com/microsoft/terminal): the new
Windows terminal
[More\...](https://github.com/search?q=fmtlib&type=Code)
If you are aware of other projects using this library, please let me
know by [email](mailto:victor.zverovich@gmail.com) or by submitting an
[issue](https://github.com/fmtlib/fmt/issues).
# Motivation
So why yet another formatting library?
There are plenty of methods for doing this task, from standard ones like
the printf family of function and iostreams to Boost Format and
FastFormat libraries. The reason for creating a new library is that
every existing solution that I found either had serious issues or
didn\'t provide all the features I needed.
## printf
The good thing about `printf` is that it is pretty fast and readily
available being a part of the C standard library. The main drawback is
that it doesn\'t support user-defined types. `printf` also has safety
issues although they are somewhat mitigated with [\_\_attribute\_\_
((format (printf,
\...))](https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html) in
GCC. There is a POSIX extension that adds positional arguments required
for
[i18n](https://en.wikipedia.org/wiki/Internationalization_and_localization)
to `printf` but it is not a part of C99 and may not be available on some
platforms.
## iostreams
The main issue with iostreams is best illustrated with an example:
``` c++
std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n";
```
which is a lot of typing compared to printf:
``` c++
printf("%.2f\n", 1.23456);
```
Matthew Wilson, the author of FastFormat, called this \"chevron hell\".
iostreams don\'t support positional arguments by design.
The good part is that iostreams support user-defined types and are safe
although error handling is awkward.
## Boost Format
This is a very powerful library that supports both `printf`-like format
strings and positional arguments. Its main drawback is performance.
According to various benchmarks, it is much slower than other methods
considered here. Boost Format also has excessive build times and severe
code bloat issues (see [Benchmarks](#benchmarks)).
## FastFormat
This is an interesting library that is fast, safe and has positional
arguments. However, it has significant limitations, citing its author:
> Three features that have no hope of being accommodated within the
> current design are:
>
> - Leading zeros (or any other non-space padding)
> - Octal/hexadecimal encoding
> - Runtime width/alignment specification
It is also quite big and has a heavy dependency, on STLSoft, which might be
too restrictive for use in some projects.
## Boost Spirit.Karma
This is not a formatting library but I decided to include it here for
completeness. As iostreams, it suffers from the problem of mixing
verbatim text with arguments. The library is pretty fast, but slower on
integer formatting than `fmt::format_to` with format string compilation
on Karma\'s own benchmark, see [Converting a hundred million integers to
strings per
second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html).
# License
{fmt} is distributed under the MIT
[license](https://github.com/fmtlib/fmt/blob/master/LICENSE).
# Documentation License
The [Format String Syntax](https://fmt.dev/latest/syntax/) section
in the documentation is based on the one from Python [string module
documentation](https://docs.python.org/3/library/string.html#module-string).
For this reason, the documentation is distributed under the Python
Software Foundation license available in
[doc/python-license.txt](https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt).
It only applies if you distribute the documentation of {fmt}.
# Maintainers
The {fmt} library is maintained by Victor Zverovich
([vitaut](https://github.com/vitaut)) with contributions from many other
people. See
[Contributors](https://github.com/fmtlib/fmt/graphs/contributors) and
[Releases](https://github.com/fmtlib/fmt/releases) for some of the
names. Let us know if your contribution is not listed or mentioned
incorrectly and we\'ll make it right.
# Security Policy
To report a security issue, please disclose it at [security
advisory](https://github.com/fmtlib/fmt/security/advisories/new).
This project is maintained by a team of volunteers on a
reasonable-effort basis. As such, please give us at least *90* days to
work on a fix before public exposure.
-524
View File
@@ -1,524 +0,0 @@
{fmt}
=====
.. image:: https://github.com/fmtlib/fmt/workflows/linux/badge.svg
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux
.. image:: https://github.com/fmtlib/fmt/workflows/macos/badge.svg
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos
.. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows
.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v
:target: https://ci.appveyor.com/project/vitaut/fmt
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg
:alt: fmt is continuously fuzzed at oss-fuzz
:target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\
colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\
Summary&q=proj%3Dfmt&can=1
.. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg
:alt: Ask questions at StackOverflow with the tag fmt
:target: https://stackoverflow.com/questions/tagged/fmt
**{fmt}** is an open-source formatting library providing a fast and safe
alternative to C stdio and C++ iostreams.
If you like this project, please consider donating to the BYSOL
Foundation that helps victims of political repressions in Belarus:
https://bysol.org/en/bs/general/.
`Documentation <https://fmt.dev>`__
Q&A: ask questions on `StackOverflow with the tag fmt
<https://stackoverflow.com/questions/tagged/fmt>`_.
Try {fmt} in `Compiler Explorer <https://godbolt.org/z/Eq5763>`_.
Features
--------
* Simple `format API <https://fmt.dev/latest/api.html>`_ with positional arguments
for localization
* Implementation of `C++20 std::format
<https://en.cppreference.com/w/cpp/utility/format>`__
* `Format string syntax <https://fmt.dev/latest/syntax.html>`_ similar to Python's
`format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
* Fast IEEE 754 floating-point formatter with correct rounding, shortness and
round-trip guarantees
* Safe `printf implementation
<https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX
extension for positional arguments
* Extensibility: `support for user-defined types
<https://fmt.dev/latest/api.html#formatting-user-defined-types>`_
* High performance: faster than common standard library implementations of
``(s)printf``, iostreams, ``to_string`` and ``to_chars``, see `Speed tests`_
and `Converting a hundred million integers to strings per second
<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_
* Small code size both in terms of source code with the minimum configuration
consisting of just three files, ``core.h``, ``format.h`` and ``format-inl.h``,
and compiled code; see `Compile time and code bloat`_
* Reliability: the library has an extensive set of `tests
<https://github.com/fmtlib/fmt/tree/master/test>`_ and is `continuously fuzzed
<https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20
Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1>`_
* Safety: the library is fully type safe, errors in format strings can be
reported at compile time, automatic memory management prevents buffer overflow
errors
* Ease of use: small self-contained code base, no external dependencies,
permissive MIT `license
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_
* `Portability <https://fmt.dev/latest/index.html#portability>`_ with
consistent output across platforms and support for older compilers
* Clean warning-free codebase even on high warning levels such as
``-Wall -Wextra -pedantic``
* Locale-independence by default
* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro
See the `documentation <https://fmt.dev>`_ for more details.
Examples
--------
**Print to stdout** (`run <https://godbolt.org/z/Tevcjh>`_)
.. code:: c++
#include <fmt/core.h>
int main() {
fmt::print("Hello, world!\n");
}
**Format a string** (`run <https://godbolt.org/z/oK8h33>`_)
.. code:: c++
std::string s = fmt::format("The answer is {}.", 42);
// s == "The answer is 42."
**Format a string using positional arguments** (`run <https://godbolt.org/z/Yn7Txe>`_)
.. code:: c++
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
// s == "I'd rather be happy than right."
**Print chrono durations** (`run <https://godbolt.org/z/K8s4Mc>`_)
.. code:: c++
#include <fmt/chrono.h>
int main() {
using namespace std::literals::chrono_literals;
fmt::print("Default format: {} {}\n", 42s, 100ms);
fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
}
Output::
Default format: 42s 100ms
strftime-like format: 03:15:30
**Print a container** (`run <https://godbolt.org/z/MjsY7c>`_)
.. code:: c++
#include <vector>
#include <fmt/ranges.h>
int main() {
std::vector<int> v = {1, 2, 3};
fmt::print("{}\n", v);
}
Output::
[1, 2, 3]
**Check a format string at compile time**
.. code:: c++
std::string s = fmt::format(FMT_STRING("{:d}"), "I am not a number");
This gives a compile-time error because ``d`` is an invalid format specifier for
a string.
**Write a file from a single thread**
.. code:: c++
#include <fmt/os.h>
int main() {
auto out = fmt::output_file("guide.txt");
out.print("Don't {}", "Panic");
}
This can be `5 to 9 times faster than fprintf
<http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html>`_.
**Print with colors and text styles**
.. code:: c++
#include <fmt/color.h>
int main() {
fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold,
"Hello, {}!\n", "world");
fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) |
fmt::emphasis::underline, "Hello, {}!\n", "мир");
fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic,
"Hello, {}!\n", "世界");
}
Output on a modern terminal:
.. image:: https://user-images.githubusercontent.com/
576385/88485597-d312f600-cf2b-11ea-9cbe-61f535a86e28.png
Benchmarks
----------
Speed tests
~~~~~~~~~~~
================= ============= ===========
Library Method Run Time, s
================= ============= ===========
libc printf 1.04
libc++ std::ostream 3.05
{fmt} 6.1.1 fmt::print 0.75
Boost Format 1.67 boost::format 7.24
Folly Format folly::format 2.23
================= ============= ===========
{fmt} is the fastest of the benchmarked methods, ~35% faster than ``printf``.
The above results were generated by building ``tinyformat_test.cpp`` on macOS
10.14.6 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the
best of three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"``
or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for
further details refer to the `source
<https://github.com/fmtlib/format-benchmark/blob/master/tinyformat_test.cpp>`_.
{fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on
floating-point formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
and faster than `double-conversion <https://github.com/google/double-conversion>`_ and
`ryu <https://github.com/ulfjack/ryu>`_:
.. image:: https://user-images.githubusercontent.com/576385/
95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png
:target: https://fmt.dev/unknown_mac64_clang12.0.html
Compile time and code bloat
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The script `bloat-test.py
<https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py>`_
from `format-benchmark <https://github.com/fmtlib/format-benchmark>`_
tests compile time and code bloat for nontrivial projects.
It generates 100 translation units and uses ``printf()`` or its alternative
five times in each to simulate a medium sized project. The resulting
executable size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42),
macOS Sierra, best of three) is shown in the following tables.
**Optimized build (-O3)**
============= =============== ==================== ==================
Method Compile Time, s Executable size, KiB Stripped size, KiB
============= =============== ==================== ==================
printf 2.6 29 26
printf+string 16.4 29 26
iostreams 31.1 59 55
{fmt} 19.0 37 34
Boost Format 91.9 226 203
Folly Format 115.7 101 88
============= =============== ==================== ==================
As you can see, {fmt} has 60% less overhead in terms of resulting binary code
size compared to iostreams and comes pretty close to ``printf``. Boost Format
and Folly Format have the largest overheads.
``printf+string`` is the same as ``printf`` but with extra ``<string>``
include to measure the overhead of the latter.
**Non-optimized build**
============= =============== ==================== ==================
Method Compile Time, s Executable size, KiB Stripped size, KiB
============= =============== ==================== ==================
printf 2.2 33 30
printf+string 16.0 33 30
iostreams 28.3 56 52
{fmt} 18.2 59 50
Boost Format 54.1 365 303
Folly Format 79.9 445 430
============= =============== ==================== ==================
``libc``, ``lib(std)c++`` and ``libfmt`` are all linked as shared libraries to
compare formatting function overhead only. Boost Format is a
header-only library so it doesn't provide any linkage options.
Running the tests
~~~~~~~~~~~~~~~~~
Please refer to `Building the library`__ for the instructions on how to build
the library and run the unit tests.
__ https://fmt.dev/latest/usage.html#building-the-library
Benchmarks reside in a separate repository,
`format-benchmarks <https://github.com/fmtlib/format-benchmark>`_,
so to run the benchmarks you first need to clone this repository and
generate Makefiles with CMake::
$ git clone --recursive https://github.com/fmtlib/format-benchmark.git
$ cd format-benchmark
$ cmake .
Then you can run the speed test::
$ make speed-test
or the bloat test::
$ make bloat-test
Migrating code
--------------
`clang-tidy-fmt <https://github.com/mikecrowe/clang-tidy-fmt>`_ provides clang
tidy checks for converting occurrences of ``printf`` and ``fprintf`` to
``fmt::print``.
Projects using this library
---------------------------
* `0 A.D. <https://play0ad.com/>`_: a free, open-source, cross-platform
real-time strategy game
* `2GIS <https://2gis.ru/>`_: free business listings with a city map
* `AMPL/MP <https://github.com/ampl/mp>`_:
an open-source library for mathematical programming
* `Aseprite <https://github.com/aseprite/aseprite>`_:
animated sprite editor & pixel art tool
* `AvioBook <https://www.aviobook.aero/en>`_: a comprehensive aircraft
operations suite
* `Blizzard Battle.net <https://battle.net/>`_: an online gaming platform
* `Celestia <https://celestia.space/>`_: real-time 3D visualization of space
* `Ceph <https://ceph.com/>`_: a scalable distributed storage system
* `ccache <https://ccache.dev/>`_: a compiler cache
* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database
management system
* `CUAUV <https://cuauv.org/>`_: Cornell University's autonomous underwater
vehicle
* `Drake <https://drake.mit.edu/>`_: a planning, control, and analysis toolbox
for nonlinear dynamical systems (MIT)
* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus
(Lyft)
* `FiveM <https://fivem.net/>`_: a modification framework for GTA V
* `fmtlog <https://github.com/MengRao/fmtlog>`_: a performant fmtlib-style
logging library with latency in nanoseconds
* `Folly <https://github.com/facebook/folly>`_: Facebook open-source library
* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
Player vs Player Gaming Network with tweaks
* `KBEngine <https://github.com/kbengine/kbengine>`_: an open-source MMOG server
engine
* `Keypirinha <https://keypirinha.com/>`_: a semantic launcher for Windows
* `Kodi <https://kodi.tv/>`_ (formerly xbmc): home theater software
* `Knuth <https://kth.cash/>`_: high-performance Bitcoin full-node
* `Microsoft Verona <https://github.com/microsoft/verona>`_:
research programming language for concurrent ownership
* `MongoDB <https://mongodb.com/>`_: distributed document database
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: a small tool to
generate randomized datasets
* `OpenSpace <https://openspaceproject.com/>`_: an open-source
astrovisualization framework
* `PenUltima Online (POL) <https://www.polserver.com/>`_:
an MMO server, compatible with most Ultima Online clients
* `PyTorch <https://github.com/pytorch/pytorch>`_: an open-source machine
learning library
* `quasardb <https://www.quasardb.net/>`_: a distributed, high-performance,
associative database
* `Quill <https://github.com/odygrd/quill>`_: asynchronous low-latency logging library
* `QKW <https://github.com/ravijanjam/qkw>`_: generalizing aliasing to simplify
navigation, and executing complex multi-line terminal command sequences
* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: a Redis cluster
proxy
* `redpanda <https://vectorized.io/redpanda>`_: a 10x faster Kafka® replacement
for mission critical systems written in C++
* `rpclib <http://rpclib.net/>`_: a modern C++ msgpack-RPC server and client
library
* `Salesforce Analytics Cloud
<https://www.salesforce.com/analytics-cloud/overview/>`_:
business intelligence software
* `Scylla <https://www.scylladb.com/>`_: a Cassandra-compatible NoSQL data store
that can handle 1 million transactions per second on a single server
* `Seastar <http://www.seastar-project.org/>`_: an advanced, open-source C++
framework for high-performance server applications on modern hardware
* `spdlog <https://github.com/gabime/spdlog>`_: super fast C++ logging library
* `Stellar <https://www.stellar.org/>`_: financial platform
* `Touch Surgery <https://www.touchsurgery.com/>`_: surgery simulator
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source
MMORPG framework
* `Windows Terminal <https://github.com/microsoft/terminal>`_: the new Windows
terminal
`More... <https://github.com/search?q=fmtlib&type=Code>`_
If you are aware of other projects using this library, please let me know
by `email <mailto:victor.zverovich@gmail.com>`_ or by submitting an
`issue <https://github.com/fmtlib/fmt/issues>`_.
Motivation
----------
So why yet another formatting library?
There are plenty of methods for doing this task, from standard ones like
the printf family of function and iostreams to Boost Format and FastFormat
libraries. The reason for creating a new library is that every existing
solution that I found either had serious issues or didn't provide
all the features I needed.
printf
~~~~~~
The good thing about ``printf`` is that it is pretty fast and readily available
being a part of the C standard library. The main drawback is that it
doesn't support user-defined types. ``printf`` also has safety issues although
they are somewhat mitigated with `__attribute__ ((format (printf, ...))
<https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html>`_ in GCC.
There is a POSIX extension that adds positional arguments required for
`i18n <https://en.wikipedia.org/wiki/Internationalization_and_localization>`_
to ``printf`` but it is not a part of C99 and may not be available on some
platforms.
iostreams
~~~~~~~~~
The main issue with iostreams is best illustrated with an example:
.. code:: c++
std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n";
which is a lot of typing compared to printf:
.. code:: c++
printf("%.2f\n", 1.23456);
Matthew Wilson, the author of FastFormat, called this "chevron hell". iostreams
don't support positional arguments by design.
The good part is that iostreams support user-defined types and are safe although
error handling is awkward.
Boost Format
~~~~~~~~~~~~
This is a very powerful library which supports both ``printf``-like format
strings and positional arguments. Its main drawback is performance. According to
various, benchmarks it is much slower than other methods considered here. Boost
Format also has excessive build times and severe code bloat issues (see
`Benchmarks`_).
FastFormat
~~~~~~~~~~
This is an interesting library which is fast, safe and has positional arguments.
However, it has significant limitations, citing its author:
Three features that have no hope of being accommodated within the
current design are:
* Leading zeros (or any other non-space padding)
* Octal/hexadecimal encoding
* Runtime width/alignment specification
It is also quite big and has a heavy dependency, STLSoft, which might be too
restrictive for using it in some projects.
Boost Spirit.Karma
~~~~~~~~~~~~~~~~~~
This is not really a formatting library but I decided to include it here for
completeness. As iostreams, it suffers from the problem of mixing verbatim text
with arguments. The library is pretty fast, but slower on integer formatting
than ``fmt::format_to`` with format string compilation on Karma's own benchmark,
see `Converting a hundred million integers to strings per second
<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_.
License
-------
{fmt} is distributed under the MIT `license
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_.
Documentation License
---------------------
The `Format String Syntax <https://fmt.dev/latest/syntax.html>`_
section in the documentation is based on the one from Python `string module
documentation <https://docs.python.org/3/library/string.html#module-string>`_.
For this reason the documentation is distributed under the Python Software
Foundation license available in `doc/python-license.txt
<https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_.
It only applies if you distribute the documentation of {fmt}.
Maintainers
-----------
The {fmt} library is maintained by Victor Zverovich (`vitaut
<https://github.com/vitaut>`_) and Jonathan Müller (`foonathan
<https://github.com/foonathan>`_) with contributions from many other people.
See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and
`Releases <https://github.com/fmtlib/fmt/releases>`_ for some of the names.
Let us know if your contribution is not listed or mentioned incorrectly and
we'll make it right.
+85 -97
View File
@@ -1,4 +1,4 @@
// Formatting library for C++ - dynamic format arguments // Formatting library for C++ - dynamic argument lists
// //
// Copyright (c) 2012 - present, Victor Zverovich // Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved. // All rights reserved.
@@ -8,34 +8,39 @@
#ifndef FMT_ARGS_H_ #ifndef FMT_ARGS_H_
#define FMT_ARGS_H_ #define FMT_ARGS_H_
#include <functional> // std::reference_wrapper #ifndef FMT_MODULE
#include <memory> // std::unique_ptr # include <functional> // std::reference_wrapper
#include <vector> # include <memory> // std::unique_ptr
# include <vector>
#endif
#include "core.h" #include "format.h" // std_string_view
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
template <typename T> struct is_reference_wrapper : std::false_type {}; template <typename T> struct is_reference_wrapper : std::false_type {};
template <typename T> template <typename T>
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {}; struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
template <typename T> const T& unwrap(const T& v) { return v; } template <typename T> auto unwrap(const T& v) -> const T& { return v; }
template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) { template <typename T>
auto unwrap(const std::reference_wrapper<T>& v) -> const T& {
return static_cast<const T&>(v); return static_cast<const T&>(v);
} }
class dynamic_arg_list { // node is defined outside dynamic_arg_list to workaround a C2504 bug in MSVC
// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for // 2022 (v17.10.0).
// templates it doesn't complain about inability to deduce single translation //
// unit for placing vtable. So storage_node_base is made a fake template. // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
template <typename = void> struct node { // templates it doesn't complain about inability to deduce single translation
virtual ~node() = default; // unit for placing vtable. So node is made a fake template.
std::unique_ptr<node<>> next; template <typename = void> struct node {
}; virtual ~node() = default;
std::unique_ptr<node<>> next;
};
class dynamic_arg_list {
template <typename T> struct typed_node : node<> { template <typename T> struct typed_node : node<> {
T value; T value;
@@ -50,7 +55,7 @@ class dynamic_arg_list {
std::unique_ptr<node<>> head_; std::unique_ptr<node<>> head_;
public: public:
template <typename T, typename Arg> const T& push(const Arg& arg) { template <typename T, typename Arg> auto push(const Arg& arg) -> const T& {
auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg)); auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
auto& value = new_node->value; auto& value = new_node->value;
new_node->next = std::move(head_); new_node->next = std::move(head_);
@@ -61,28 +66,18 @@ class dynamic_arg_list {
} // namespace detail } // namespace detail
/** /**
\rst * A dynamic list of formatting arguments with storage.
A dynamic version of `fmt::format_arg_store`. *
It's equipped with a storage to potentially temporary objects which lifetimes * It can be implicitly converted into `fmt::basic_format_args` for passing
could be shorter than the format arguments object. * into type-erased formatting functions such as `fmt::vformat`.
It can be implicitly converted into `~fmt::basic_format_args` for passing
into type-erased formatting functions such as `~fmt::vformat`.
\endrst
*/ */
template <typename Context> FMT_EXPORT template <typename Context> class dynamic_format_arg_store {
class dynamic_format_arg_store
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround a GCC template argument substitution bug.
: public basic_format_args<Context>
#endif
{
private: private:
using char_type = typename Context::char_type; using char_type = typename Context::char_type;
template <typename T> struct need_copy { template <typename T> struct need_copy {
static constexpr detail::type mapped_type = static constexpr detail::type mapped_type =
detail::mapped_type_constant<T, Context>::value; detail::mapped_type_constant<T, char_type>::value;
enum { enum {
value = !(detail::is_reference_wrapper<T>::value || value = !(detail::is_reference_wrapper<T>::value ||
@@ -95,10 +90,10 @@ class dynamic_format_arg_store
}; };
template <typename T> template <typename T>
using stored_type = conditional_t<detail::is_string<T>::value && using stored_t = conditional_t<
!has_formatter<T, Context>::value && std::is_convertible<T, std::basic_string<char_type>>::value &&
!detail::is_reference_wrapper<T>::value, !detail::is_reference_wrapper<T>::value,
std::basic_string<char_type>, T>; std::basic_string<char_type>, T>;
// Storage of basic_format_arg must be contiguous. // Storage of basic_format_arg must be contiguous.
std::vector<basic_format_arg<Context>> data_; std::vector<basic_format_arg<Context>> data_;
@@ -110,78 +105,72 @@ class dynamic_format_arg_store
friend class basic_format_args<Context>; friend class basic_format_args<Context>;
unsigned long long get_types() const { auto data() const -> const basic_format_arg<Context>* {
return detail::is_unpacked_bit | data_.size() |
(named_info_.empty()
? 0ULL
: static_cast<unsigned long long>(detail::has_named_args_bit));
}
const basic_format_arg<Context>* data() const {
return named_info_.empty() ? data_.data() : data_.data() + 1; return named_info_.empty() ? data_.data() : data_.data() + 1;
} }
template <typename T> void emplace_arg(const T& arg) { template <typename T> void emplace_arg(const T& arg) {
data_.emplace_back(detail::make_arg<Context>(arg)); data_.emplace_back(arg);
} }
template <typename T> template <typename T>
void emplace_arg(const detail::named_arg<char_type, T>& arg) { void emplace_arg(const detail::named_arg<char_type, T>& arg) {
if (named_info_.empty()) { if (named_info_.empty())
constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr}; data_.insert(data_.begin(), basic_format_arg<Context>(nullptr, 0));
data_.insert(data_.begin(), {zero_ptr, 0}); data_.emplace_back(detail::unwrap(arg.value));
}
data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) { auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
data->pop_back(); data->pop_back();
}; };
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)> std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
guard{&data_, pop_one}; guard{&data_, pop_one};
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)}); named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
data_[0].value_.named_args = {named_info_.data(), named_info_.size()}; data_[0] = {named_info_.data(), named_info_.size()};
guard.release(); guard.release();
} }
public: public:
constexpr dynamic_format_arg_store() = default;
operator basic_format_args<Context>() const {
return basic_format_args<Context>(data(), static_cast<int>(data_.size()),
!named_info_.empty());
}
/** /**
\rst * Adds an argument into the dynamic store for later passing to a formatting
Adds an argument into the dynamic store for later passing to a formatting * function.
function. *
* Note that custom types and string types (but not string views) are copied
Note that custom types and string types (but not string views) are copied * into the store dynamically allocating memory if necessary.
into the store dynamically allocating memory if necessary. *
* **Example**:
**Example**:: *
* fmt::dynamic_format_arg_store<fmt::format_context> store;
fmt::dynamic_format_arg_store<fmt::format_context> store; * store.push_back(42);
store.push_back(42); * store.push_back("abc");
store.push_back("abc"); * store.push_back(1.5f);
store.push_back(1.5f); * std::string result = fmt::vformat("{} and {} and {}", store);
std::string result = fmt::vformat("{} and {} and {}", store); */
\endrst
*/
template <typename T> void push_back(const T& arg) { template <typename T> void push_back(const T& arg) {
if (detail::const_check(need_copy<T>::value)) if (detail::const_check(need_copy<T>::value))
emplace_arg(dynamic_args_.push<stored_type<T>>(arg)); emplace_arg(dynamic_args_.push<stored_t<T>>(arg));
else else
emplace_arg(detail::unwrap(arg)); emplace_arg(detail::unwrap(arg));
} }
/** /**
\rst * Adds a reference to the argument into the dynamic store for later passing
Adds a reference to the argument into the dynamic store for later passing to * to a formatting function.
a formatting function. *
* **Example**:
**Example**:: *
* fmt::dynamic_format_arg_store<fmt::format_context> store;
fmt::dynamic_format_arg_store<fmt::format_context> store; * char band[] = "Rolling Stones";
char band[] = "Rolling Stones"; * store.push_back(std::cref(band));
store.push_back(std::cref(band)); * band[9] = 'c'; // Changing str affects the output.
band[9] = 'c'; // Changing str affects the output. * std::string result = fmt::vformat("{}", store);
std::string result = fmt::vformat("{}", store); * // result == "Rolling Scones"
// result == "Rolling Scones" */
\endrst
*/
template <typename T> void push_back(std::reference_wrapper<T> arg) { template <typename T> void push_back(std::reference_wrapper<T> arg) {
static_assert( static_assert(
need_copy<T>::value, need_copy<T>::value,
@@ -190,41 +179,40 @@ class dynamic_format_arg_store
} }
/** /**
Adds named argument into the dynamic store for later passing to a formatting * Adds named argument into the dynamic store for later passing to a
function. ``std::reference_wrapper`` is supported to avoid copying of the * formatting function. `std::reference_wrapper` is supported to avoid
argument. The name is always copied into the store. * copying of the argument. The name is always copied into the store.
*/ */
template <typename T> template <typename T>
void push_back(const detail::named_arg<char_type, T>& arg) { void push_back(const detail::named_arg<char_type, T>& arg) {
const char_type* arg_name = const char_type* arg_name =
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str(); dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
if (detail::const_check(need_copy<T>::value)) { if (detail::const_check(need_copy<T>::value)) {
emplace_arg( emplace_arg(
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value))); fmt::arg(arg_name, dynamic_args_.push<stored_t<T>>(arg.value)));
} else { } else {
emplace_arg(fmt::arg(arg_name, arg.value)); emplace_arg(fmt::arg(arg_name, arg.value));
} }
} }
/** Erase all elements from the store */ /// Erase all elements from the store.
void clear() { void clear() {
data_.clear(); data_.clear();
named_info_.clear(); named_info_.clear();
dynamic_args_ = detail::dynamic_arg_list(); dynamic_args_ = {};
} }
/** /// Reserves space to store at least `new_cap` arguments including
\rst /// `new_cap_named` named arguments.
Reserves space to store at least *new_cap* arguments including
*new_cap_named* named arguments.
\endrst
*/
void reserve(size_t new_cap, size_t new_cap_named) { void reserve(size_t new_cap, size_t new_cap_named) {
FMT_ASSERT(new_cap >= new_cap_named, FMT_ASSERT(new_cap >= new_cap_named,
"Set of arguments includes set of named arguments"); "set of arguments includes set of named arguments");
data_.reserve(new_cap); data_.reserve(new_cap);
named_info_.reserve(new_cap_named); named_info_.reserve(new_cap_named);
} }
/// Returns the number of elements in the store.
auto size() const noexcept -> size_t { return data_.size(); }
}; };
FMT_END_NAMESPACE FMT_END_NAMESPACE
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+311 -301
View File
@@ -10,15 +10,8 @@
#include "format.h" #include "format.h"
// __declspec(deprecated) is broken in some MSVC versions.
#if FMT_MSC_VER
# define FMT_DEPRECATED_NONMSVC
#else
# define FMT_DEPRECATED_NONMSVC FMT_DEPRECATED
#endif
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN FMT_BEGIN_EXPORT
enum class color : uint32_t { enum class color : uint32_t {
alice_blue = 0xF0F8FF, // rgb(240,248,255) alice_blue = 0xF0F8FF, // rgb(240,248,255)
@@ -185,19 +178,23 @@ enum class terminal_color : uint8_t {
enum class emphasis : uint8_t { enum class emphasis : uint8_t {
bold = 1, bold = 1,
italic = 1 << 1, faint = 1 << 1,
underline = 1 << 2, italic = 1 << 2,
strikethrough = 1 << 3 underline = 1 << 3,
blink = 1 << 4,
reverse = 1 << 5,
conceal = 1 << 6,
strikethrough = 1 << 7,
}; };
// rgb is a struct for red, green and blue colors. // rgb is a struct for red, green and blue colors.
// Using the name "rgb" makes some editors show the color in a tooltip. // Using the name "rgb" makes some editors show the color in a tooltip.
struct rgb { struct rgb {
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {} constexpr rgb() : r(0), g(0), b(0) {}
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} constexpr rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
FMT_CONSTEXPR rgb(uint32_t hex) constexpr rgb(uint32_t hex)
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {} : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
FMT_CONSTEXPR rgb(color hex) constexpr rgb(color hex)
: r((uint32_t(hex) >> 16) & 0xFF), : r((uint32_t(hex) >> 16) & 0xFF),
g((uint32_t(hex) >> 8) & 0xFF), g((uint32_t(hex) >> 8) & 0xFF),
b(uint32_t(hex) & 0xFF) {} b(uint32_t(hex) & 0xFF) {}
@@ -206,422 +203,435 @@ struct rgb {
uint8_t b; uint8_t b;
}; };
FMT_BEGIN_DETAIL_NAMESPACE namespace detail {
// color is a struct of either a rgb color or a terminal color. // A bit-packed variant of an RGB color, a terminal color, or unset color.
// see text_style for the bit-packing scheme.
struct color_type { struct color_type {
FMT_CONSTEXPR color_type() FMT_NOEXCEPT : is_rgb(), value{} {} constexpr color_type() noexcept = default;
FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT : is_rgb(true), constexpr color_type(color rgb_color) noexcept
value{} { : value_(static_cast<uint32_t>(rgb_color) | (1 << 24)) {}
value.rgb_color = static_cast<uint32_t>(rgb_color); constexpr color_type(rgb rgb_color) noexcept
: color_type(static_cast<color>(
(static_cast<uint32_t>(rgb_color.r) << 16) |
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b)) {}
constexpr color_type(terminal_color term_color) noexcept
: value_(static_cast<uint32_t>(term_color) | (3 << 24)) {}
constexpr auto is_terminal_color() const noexcept -> bool {
return (value_ & (1 << 25)) != 0;
} }
FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT : is_rgb(true), value{} {
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) | constexpr auto value() const noexcept -> uint32_t {
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b; return value_ & 0xFFFFFF;
} }
FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT : is_rgb(),
value{} { constexpr color_type(uint32_t value) noexcept : value_(value) {}
value.term_color = static_cast<uint8_t>(term_color);
} uint32_t value_ = 0;
bool is_rgb;
union color_union {
uint8_t term_color;
uint32_t rgb_color;
} value;
}; };
} // namespace detail
FMT_END_DETAIL_NAMESPACE /// A text style consisting of foreground and background colors and emphasis.
/** A text style consisting of foreground and background colors and emphasis. */
class text_style { class text_style {
// The information is packed as follows:
// ┌──┐
// │ 0│─┐
// │..│ ├── foreground color value
// │23│─┘
// ├──┤
// │24│─┬── discriminator for the above value. 00 if unset, 01 if it's
// │25│─┘ an RGB color, or 11 if it's a terminal color (10 is unused)
// ├──┤
// │26│──── overflow bit, always zero (see below)
// ├──┤
// │27│─┐
// │..│ │
// │50│ │
// ├──┤ │
// │51│ ├── background color (same format as the foreground color)
// │52│ │
// ├──┤ │
// │53│─┘
// ├──┤
// │54│─┐
// │..│ ├── emphases
// │61│─┘
// ├──┤
// │62│─┬── unused
// │63│─┘
// └──┘
// The overflow bits are there to make operator|= efficient.
// When ORing, we must throw if, for either the foreground or background,
// one style specifies a terminal color and the other specifies any color
// (terminal or RGB); in other words, if one discriminator is 11 and the
// other is 11 or 01.
//
// We do that check by adding the styles. Consider what adding does to each
// possible pair of discriminators:
// 00 + 00 = 000
// 01 + 00 = 001
// 11 + 00 = 011
// 01 + 01 = 010
// 11 + 01 = 100 (!!)
// 11 + 11 = 110 (!!)
// In the last two cases, the ones we want to catch, the third bit——the
// overflow bit——is set. Bingo.
//
// We must take into account the possible carry bit from the bits
// before the discriminator. The only potentially problematic case is
// 11 + 00 = 011 (a carry bit would make it 100, not good!), but a carry
// bit is impossible in that case, because 00 (unset color) means the
// 24 bits that precede the discriminator are all zero.
//
// This test can be applied to both colors simultaneously.
public: public:
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
: set_foreground_color(), : style_(static_cast<uint64_t>(em) << 54) {}
set_background_color(),
ems(em) {}
FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) { FMT_CONSTEXPR auto operator|=(text_style rhs) -> text_style& {
if (!set_foreground_color) { if (((style_ + rhs.style_) & ((1ULL << 26) | (1ULL << 53))) != 0)
set_foreground_color = rhs.set_foreground_color; report_error("can't OR a terminal color");
foreground_color = rhs.foreground_color; style_ |= rhs.style_;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
FMT_THROW(format_error("can't OR a terminal color"));
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
}
if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
FMT_THROW(format_error("can't OR a terminal color"));
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
}
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
static_cast<uint8_t>(rhs.ems));
return *this; return *this;
} }
friend FMT_CONSTEXPR text_style operator|(text_style lhs, friend FMT_CONSTEXPR auto operator|(text_style lhs, text_style rhs)
const text_style& rhs) { -> text_style {
return lhs |= rhs; return lhs |= rhs;
} }
FMT_DEPRECATED_NONMSVC FMT_CONSTEXPR text_style& operator&=( FMT_CONSTEXPR auto operator==(text_style rhs) const noexcept -> bool {
const text_style& rhs) { return style_ == rhs.style_;
return and_assign(rhs);
} }
FMT_DEPRECATED_NONMSVC friend FMT_CONSTEXPR text_style FMT_CONSTEXPR auto operator!=(text_style rhs) const noexcept -> bool {
operator&(text_style lhs, const text_style& rhs) { return !(*this == rhs);
return lhs.and_assign(rhs);
} }
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT { FMT_CONSTEXPR auto has_foreground() const noexcept -> bool {
return set_foreground_color; return (style_ & (1 << 24)) != 0;
} }
FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT { FMT_CONSTEXPR auto has_background() const noexcept -> bool {
return set_background_color; return (style_ & (1ULL << 51)) != 0;
} }
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT { FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {
return static_cast<uint8_t>(ems) != 0; return (style_ >> 54) != 0;
} }
FMT_CONSTEXPR detail::color_type get_foreground() const FMT_NOEXCEPT { FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
FMT_ASSERT(has_foreground(), "no foreground specified for this style"); FMT_ASSERT(has_foreground(), "no foreground specified for this style");
return foreground_color; return style_ & 0x3FFFFFF;
} }
FMT_CONSTEXPR detail::color_type get_background() const FMT_NOEXCEPT { FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
FMT_ASSERT(has_background(), "no background specified for this style"); FMT_ASSERT(has_background(), "no background specified for this style");
return background_color; return (style_ >> 27) & 0x3FFFFFF;
} }
FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT { FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
return ems; return static_cast<emphasis>(style_ >> 54);
} }
private: private:
FMT_CONSTEXPR text_style(bool is_foreground, FMT_CONSTEXPR text_style(uint64_t style) noexcept : style_(style) {}
detail::color_type text_color) FMT_NOEXCEPT
: set_foreground_color(),
set_background_color(),
ems() {
if (is_foreground) {
foreground_color = text_color;
set_foreground_color = true;
} else {
background_color = text_color;
set_background_color = true;
}
}
// DEPRECATED! friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept
FMT_CONSTEXPR text_style& and_assign(const text_style& rhs) { -> text_style;
if (!set_foreground_color) {
set_foreground_color = rhs.set_foreground_color;
foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
FMT_THROW(format_error("can't AND a terminal color"));
foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color;
}
if (!set_background_color) { friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept
set_background_color = rhs.set_background_color; -> text_style;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
FMT_THROW(format_error("can't AND a terminal color"));
background_color.value.rgb_color &= rhs.background_color.value.rgb_color;
}
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) & uint64_t style_ = 0;
static_cast<uint8_t>(rhs.ems));
return *this;
}
friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground)
FMT_NOEXCEPT;
friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background)
FMT_NOEXCEPT;
detail::color_type foreground_color;
detail::color_type background_color;
bool set_foreground_color;
bool set_background_color;
emphasis ems;
}; };
/** Creates a text style from the foreground (text) color. */ /// Creates a text style from the foreground (text) color.
FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) FMT_NOEXCEPT { FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
return text_style(true, foreground); -> text_style {
return foreground.value_;
} }
/** Creates a text style from the background color. */ /// Creates a text style from the background color.
FMT_CONSTEXPR inline text_style bg(detail::color_type background) FMT_NOEXCEPT { FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept
return text_style(false, background); -> text_style {
return static_cast<uint64_t>(background.value_) << 27;
} }
FMT_CONSTEXPR inline text_style operator|(emphasis lhs, FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
emphasis rhs) FMT_NOEXCEPT { -> text_style {
return text_style(lhs) | rhs; return text_style(lhs) | rhs;
} }
FMT_BEGIN_DETAIL_NAMESPACE namespace detail {
template <typename Char> struct ansi_color_escape { template <typename Char> struct ansi_color_escape {
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color, FMT_CONSTEXPR ansi_color_escape(color_type text_color,
const char* esc) FMT_NOEXCEPT { const char* esc) noexcept {
// If we have a terminal color, we need to output another escape code // If we have a terminal color, we need to output another escape code
// sequence. // sequence.
if (!text_color.is_rgb) { if (text_color.is_terminal_color()) {
bool is_background = esc == string_view("\x1b[48;2;"); bool is_background = esc == string_view("\x1b[48;2;");
uint32_t value = text_color.value.term_color; uint32_t value = text_color.value();
// Background ASCII codes are the same as the foreground ones but with // Background ASCII codes are the same as the foreground ones but with
// 10 more. // 10 more.
if (is_background) value += 10u; if (is_background) value += 10u;
size_t index = 0; buffer[size++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('\x1b'); buffer[size++] = static_cast<Char>('[');
buffer[index++] = static_cast<Char>('[');
if (value >= 100u) { if (value >= 100u) {
buffer[index++] = static_cast<Char>('1'); buffer[size++] = static_cast<Char>('1');
value %= 100u; value %= 100u;
} }
buffer[index++] = static_cast<Char>('0' + value / 10u); buffer[size++] = static_cast<Char>('0' + value / 10u);
buffer[index++] = static_cast<Char>('0' + value % 10u); buffer[size++] = static_cast<Char>('0' + value % 10u);
buffer[index++] = static_cast<Char>('m'); buffer[size++] = static_cast<Char>('m');
buffer[index++] = static_cast<Char>('\0');
return; return;
} }
for (int i = 0; i < 7; i++) { for (int i = 0; i < 7; i++) {
buffer[i] = static_cast<Char>(esc[i]); buffer[i] = static_cast<Char>(esc[i]);
} }
rgb color(text_color.value.rgb_color); rgb color(text_color.value());
to_esc(color.r, buffer + 7, ';'); to_esc(color.r, buffer + 7, ';');
to_esc(color.g, buffer + 11, ';'); to_esc(color.g, buffer + 11, ';');
to_esc(color.b, buffer + 15, 'm'); to_esc(color.b, buffer + 15, 'm');
buffer[19] = static_cast<Char>(0); size = 19;
} }
FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT { FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept {
uint8_t em_codes[4] = {}; uint8_t em_codes[num_emphases] = {};
uint8_t em_bits = static_cast<uint8_t>(em); if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1;
if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1; if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2;
if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3; if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3;
if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4; if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4;
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough)) if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5;
em_codes[3] = 9; if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7;
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
size_t index = 0; buffer[size++] = static_cast<Char>('\x1b');
for (int i = 0; i < 4; ++i) { buffer[size++] = static_cast<Char>('[');
for (size_t i = 0; i < num_emphases; ++i) {
if (!em_codes[i]) continue; if (!em_codes[i]) continue;
buffer[index++] = static_cast<Char>('\x1b'); buffer[size++] = static_cast<Char>('0' + em_codes[i]);
buffer[index++] = static_cast<Char>('['); buffer[size++] = static_cast<Char>(';');
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
buffer[index++] = static_cast<Char>('m');
} }
buffer[index++] = static_cast<Char>(0);
}
FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; }
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; } buffer[size - 1] = static_cast<Char>('m');
FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const FMT_NOEXCEPT { }
return buffer + std::char_traits<Char>::length(buffer); FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }
FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }
FMT_CONSTEXPR auto end() const noexcept -> const Char* {
return buffer + size;
} }
private: private:
Char buffer[7u + 3u * 4u + 1u]; static constexpr size_t num_emphases = 8;
Char buffer[7u + 4u * num_emphases] = {};
size_t size = 0;
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
char delimiter) FMT_NOEXCEPT { char delimiter) noexcept {
out[0] = static_cast<Char>('0' + c / 100); out[0] = static_cast<Char>('0' + c / 100);
out[1] = static_cast<Char>('0' + c / 10 % 10); out[1] = static_cast<Char>('0' + c / 10 % 10);
out[2] = static_cast<Char>('0' + c % 10); out[2] = static_cast<Char>('0' + c % 10);
out[3] = static_cast<Char>(delimiter); out[3] = static_cast<Char>(delimiter);
} }
static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept
-> bool {
return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask);
}
}; };
template <typename Char> template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color( FMT_CONSTEXPR auto make_foreground_color(color_type foreground) noexcept
detail::color_type foreground) FMT_NOEXCEPT { -> ansi_color_escape<Char> {
return ansi_color_escape<Char>(foreground, "\x1b[38;2;"); return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
} }
template <typename Char> template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color( FMT_CONSTEXPR auto make_background_color(color_type background) noexcept
detail::color_type background) FMT_NOEXCEPT { -> ansi_color_escape<Char> {
return ansi_color_escape<Char>(background, "\x1b[48;2;"); return ansi_color_escape<Char>(background, "\x1b[48;2;");
} }
template <typename Char> template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) FMT_NOEXCEPT { FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept
-> ansi_color_escape<Char> {
return ansi_color_escape<Char>(em); return ansi_color_escape<Char>(em);
} }
template <typename Char> template <typename Char> inline void reset_color(buffer<Char>& buffer) {
inline void fputs(const Char* chars, FILE* stream) FMT_NOEXCEPT {
std::fputs(chars, stream);
}
template <>
inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
std::fputws(chars, stream);
}
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
fputs("\x1b[0m", stream);
}
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
fputs(L"\x1b[0m", stream);
}
template <typename Char>
inline void reset_color(buffer<Char>& buffer) FMT_NOEXCEPT {
auto reset_color = string_view("\x1b[0m"); auto reset_color = string_view("\x1b[0m");
buffer.append(reset_color.begin(), reset_color.end()); buffer.append(reset_color.begin(), reset_color.end());
} }
template <typename T> struct styled_arg : view {
const T& value;
text_style style;
styled_arg(const T& v, text_style s) : value(v), style(s) {}
};
template <typename Char> template <typename Char>
void vformat_to(buffer<Char>& buf, const text_style& ts, void vformat_to(buffer<Char>& buf, text_style ts, basic_string_view<Char> fmt,
basic_string_view<Char> format_str, basic_format_args<buffered_context<Char>> args) {
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
bool has_style = false;
if (ts.has_emphasis()) { if (ts.has_emphasis()) {
has_style = true; auto emphasis = make_emphasis<Char>(ts.get_emphasis());
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
buf.append(emphasis.begin(), emphasis.end()); buf.append(emphasis.begin(), emphasis.end());
} }
if (ts.has_foreground()) { if (ts.has_foreground()) {
has_style = true; auto foreground = make_foreground_color<Char>(ts.get_foreground());
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
buf.append(foreground.begin(), foreground.end()); buf.append(foreground.begin(), foreground.end());
} }
if (ts.has_background()) { if (ts.has_background()) {
has_style = true; auto background = make_background_color<Char>(ts.get_background());
auto background = detail::make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end()); buf.append(background.begin(), background.end());
} }
detail::vformat_to(buf, format_str, args); vformat_to(buf, fmt, args);
if (has_style) detail::reset_color<Char>(buf); if (ts != text_style()) reset_color<Char>(buf);
} }
} // namespace detail
FMT_END_DETAIL_NAMESPACE inline void vprint(FILE* f, text_style ts, string_view fmt, format_args args) {
auto buf = memory_buffer();
template <typename S, typename Char = char_t<S>> detail::vformat_to(buf, ts, fmt, args);
void vprint(std::FILE* f, const text_style& ts, const S& format, print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size()));
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buf;
detail::vformat_to(buf, ts, to_string_view(format), args);
buf.push_back(Char(0));
detail::fputs(buf.data(), f);
} }
/** /**
\rst * Formats a string and prints it to the specified file stream using ANSI
Formats a string and prints it to the specified file stream using ANSI * escape sequences to specify text formatting.
escape sequences to specify text formatting. *
* **Example**:
**Example**:: *
* fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
fmt::print(fmt::emphasis::bold | fg(fmt::color::red), * "Elapsed time: {0:.2f} seconds", 1.23);
"Elapsed time: {0:.2f} seconds", 1.23);
\endrst
*/ */
template <typename S, typename... Args, template <typename... T>
FMT_ENABLE_IF(detail::is_string<S>::value)> void print(FILE* f, text_style ts, format_string<T...> fmt, T&&... args) {
void print(std::FILE* f, const text_style& ts, const S& format_str, vprint(f, ts, fmt.str, vargs<T...>{{args...}});
const Args&... args) {
vprint(f, ts, format_str,
fmt::make_args_checked<Args...>(format_str, args...));
} }
/** /**
\rst * Formats a string and prints it to stdout using ANSI escape sequences to
Formats a string and prints it to stdout using ANSI escape sequences to * specify text formatting.
specify text formatting. *
* **Example**:
**Example**:: *
* fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
fmt::print(fmt::emphasis::bold | fg(fmt::color::red), * "Elapsed time: {0:.2f} seconds", 1.23);
"Elapsed time: {0:.2f} seconds", 1.23);
\endrst
*/ */
template <typename S, typename... Args, template <typename... T>
FMT_ENABLE_IF(detail::is_string<S>::value)> void print(text_style ts, format_string<T...> fmt, T&&... args) {
void print(const text_style& ts, const S& format_str, const Args&... args) { return print(stdout, ts, fmt, std::forward<T>(args)...);
return print(stdout, ts, format_str, args...);
} }
template <typename S, typename Char = char_t<S>> inline auto vformat(text_style ts, string_view fmt, format_args args)
inline std::basic_string<Char> vformat( -> std::string {
const text_style& ts, const S& format_str, auto buf = memory_buffer();
basic_format_args<buffer_context<type_identity_t<Char>>> args) { detail::vformat_to(buf, ts, fmt, args);
basic_memory_buffer<Char> buf;
detail::vformat_to(buf, ts, to_string_view(format_str), args);
return fmt::to_string(buf); return fmt::to_string(buf);
} }
/** /**
\rst * Formats arguments and returns the result as a string using ANSI escape
Formats arguments and returns the result as a string using ANSI * sequences to specify text formatting.
escape sequences to specify text formatting. *
* **Example**:
**Example**:: *
* ```
#include <fmt/color.h> * #include <fmt/color.h>
std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), * std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),
"The answer is {}", 42); * "The answer is {}", 42);
\endrst * ```
*/
template <typename S, typename... Args, typename Char = char_t<S>>
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
const Args&... args) {
return fmt::vformat(ts, to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str, args...));
}
/**
Formats a string with the given text_style and writes the output to ``out``.
*/ */
template <typename OutputIt, typename Char, template <typename... T>
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)> inline auto format(text_style ts, format_string<T...> fmt, T&&... args)
OutputIt vformat_to( -> std::string {
OutputIt out, const text_style& ts, basic_string_view<Char> format_str, return fmt::vformat(ts, fmt.str, vargs<T...>{{args...}});
basic_format_args<buffer_context<type_identity_t<Char>>> args) { }
auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, ts, format_str, args); /// Formats a string with the given text_style and writes the output to `out`.
return detail::get_iterator(buf); template <typename OutputIt,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
auto vformat_to(OutputIt out, text_style ts, string_view fmt, format_args args)
-> OutputIt {
auto&& buf = detail::get_buffer<char>(out);
detail::vformat_to(buf, ts, fmt, args);
return detail::get_iterator(buf, out);
} }
/** /**
\rst * Formats arguments with the given text style, writes the result to the output
Formats arguments with the given text_style, writes the result to the output * iterator `out` and returns the iterator past the end of the output range.
iterator ``out`` and returns the iterator past the end of the output range. *
* **Example**:
**Example**:: *
* std::vector<char> out;
std::vector<char> out; * fmt::format_to(std::back_inserter(out),
fmt::format_to(std::back_inserter(out), * fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); */
\endrst template <typename OutputIt, typename... T,
*/ FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
template <typename OutputIt, typename S, typename... Args, inline auto format_to(OutputIt out, text_style ts, format_string<T...> fmt,
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&& T&&... args) -> OutputIt {
detail::is_string<S>::value> return vformat_to(out, ts, fmt.str, vargs<T...>{{args...}});
inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
Args&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, ts, to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str, args...));
} }
FMT_MODULE_EXPORT_END template <typename T, typename Char>
struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {
template <typename FormatContext>
auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const
-> decltype(ctx.out()) {
const auto& ts = arg.style;
auto out = ctx.out();
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
out = detail::copy<Char>(emphasis.begin(), emphasis.end(), out);
}
if (ts.has_foreground()) {
has_style = true;
auto foreground =
detail::make_foreground_color<Char>(ts.get_foreground());
out = detail::copy<Char>(foreground.begin(), foreground.end(), out);
}
if (ts.has_background()) {
has_style = true;
auto background =
detail::make_background_color<Char>(ts.get_background());
out = detail::copy<Char>(background.begin(), background.end(), out);
}
out = formatter<T, Char>::format(arg.value, ctx);
if (has_style) {
auto reset_color = string_view("\x1b[0m");
out = detail::copy<Char>(reset_color.begin(), reset_color.end(), out);
}
return out;
}
};
/**
* Returns an argument that will be formatted using ANSI escape sequences,
* to be used in a formatting function.
*
* **Example**:
*
* fmt::print("Elapsed time: {0:.2f} seconds",
* fmt::styled(1.23, fmt::fg(fmt::color::green) |
* fmt::bg(fmt::color::blue)));
*/
template <typename T>
FMT_CONSTEXPR auto styled(const T& value, text_style ts)
-> detail::styled_arg<remove_cvref_t<T>> {
return detail::styled_arg<remove_cvref_t<T>>{value, ts};
}
FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_COLOR_H_ #endif // FMT_COLOR_H_
+250 -300
View File
@@ -8,134 +8,14 @@
#ifndef FMT_COMPILE_H_ #ifndef FMT_COMPILE_H_
#define FMT_COMPILE_H_ #define FMT_COMPILE_H_
#ifndef FMT_MODULE
# include <iterator> // std::back_inserter
#endif
#include "format.h" #include "format.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { FMT_BEGIN_EXPORT
// An output iterator that counts the number of objects written to it and
// discards them.
class counting_iterator {
private:
size_t count_;
public:
using iterator_category = std::output_iterator_tag;
using difference_type = std::ptrdiff_t;
using pointer = void;
using reference = void;
using _Unchecked_type = counting_iterator; // Mark iterator as checked.
struct value_type {
template <typename T> void operator=(const T&) {}
};
counting_iterator() : count_(0) {}
size_t count() const { return count_; }
counting_iterator& operator++() {
++count_;
return *this;
}
counting_iterator operator++(int) {
auto it = *this;
++*this;
return it;
}
friend counting_iterator operator+(counting_iterator it, difference_type n) {
it.count_ += static_cast<size_t>(n);
return it;
}
value_type operator*() const { return {}; }
};
template <typename Char, typename InputIt>
inline counting_iterator copy_str(InputIt begin, InputIt end,
counting_iterator it) {
return it + (end - begin);
}
template <typename OutputIt> class truncating_iterator_base {
protected:
OutputIt out_;
size_t limit_;
size_t count_ = 0;
truncating_iterator_base() : out_(), limit_(0) {}
truncating_iterator_base(OutputIt out, size_t limit)
: out_(out), limit_(limit) {}
public:
using iterator_category = std::output_iterator_tag;
using value_type = typename std::iterator_traits<OutputIt>::value_type;
using difference_type = std::ptrdiff_t;
using pointer = void;
using reference = void;
using _Unchecked_type =
truncating_iterator_base; // Mark iterator as checked.
OutputIt base() const { return out_; }
size_t count() const { return count_; }
};
// An output iterator that truncates the output and counts the number of objects
// written to it.
template <typename OutputIt,
typename Enable = typename std::is_void<
typename std::iterator_traits<OutputIt>::value_type>::type>
class truncating_iterator;
template <typename OutputIt>
class truncating_iterator<OutputIt, std::false_type>
: public truncating_iterator_base<OutputIt> {
mutable typename truncating_iterator_base<OutputIt>::value_type blackhole_;
public:
using value_type = typename truncating_iterator_base<OutputIt>::value_type;
truncating_iterator() = default;
truncating_iterator(OutputIt out, size_t limit)
: truncating_iterator_base<OutputIt>(out, limit) {}
truncating_iterator& operator++() {
if (this->count_++ < this->limit_) ++this->out_;
return *this;
}
truncating_iterator operator++(int) {
auto it = *this;
++*this;
return it;
}
value_type& operator*() const {
return this->count_ < this->limit_ ? *this->out_ : blackhole_;
}
};
template <typename OutputIt>
class truncating_iterator<OutputIt, std::true_type>
: public truncating_iterator_base<OutputIt> {
public:
truncating_iterator() = default;
truncating_iterator(OutputIt out, size_t limit)
: truncating_iterator_base<OutputIt>(out, limit) {}
template <typename T> truncating_iterator& operator=(T val) {
if (this->count_++ < this->limit_) *this->out_++ = val;
return *this;
}
truncating_iterator& operator++() { return *this; }
truncating_iterator& operator++(int) { return *this; }
truncating_iterator& operator*() { return *this; }
};
// A compile-time string which is compiled into fast formatting code. // A compile-time string which is compiled into fast formatting code.
class compiled_string {}; class compiled_string {};
@@ -144,64 +24,99 @@ template <typename S>
struct is_compiled_string : std::is_base_of<compiled_string, S> {}; struct is_compiled_string : std::is_base_of<compiled_string, S> {};
/** /**
\rst * Converts a string literal `s` into a format string that will be parsed at
Converts a string literal *s* into a format string that will be parsed at * compile time and converted into efficient formatting code. Requires C++17
compile time and converted into efficient formatting code. Requires C++17 * `constexpr if` compiler support.
``constexpr if`` compiler support. *
* **Example**:
**Example**:: *
* // Converts 42 into std::string using the most efficient method and no
// Converts 42 into std::string using the most efficient method and no * // runtime format string processing.
// runtime format string processing. * std::string s = fmt::format(FMT_COMPILE("{}"), 42);
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
\endrst
*/ */
#ifdef __cpp_if_constexpr #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
# define FMT_COMPILE(s) \ # define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string)
FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit)
#else #else
# define FMT_COMPILE(s) FMT_STRING(s) # define FMT_COMPILE(s) FMT_STRING(s)
#endif #endif
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS /**
template <typename Char, size_t N, fixed_string<Char, N> Str> * Converts a string literal into a format string that will be parsed at
struct udl_compiled_string : compiled_string { * compile time and converted into efficient formatting code. Requires support
using char_type = Char; * for class types in constant template parameters (a C++20 feature).
constexpr operator basic_string_view<char_type>() const { *
return {Str.data, N - 1}; * **Example**:
} *
}; * // Converts 42 into std::string using the most efficient method and no
* // runtime format string processing.
* using namespace fmt::literals;
* std::string s = fmt::format("{}"_cf, 42);
*/
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals {
template <detail::fixed_string Str> constexpr auto operator""_cf() {
return FMT_COMPILE(Str.data);
}
} // namespace literals
#endif #endif
FMT_END_EXPORT
namespace detail {
template <typename T, typename... Tail> template <typename T, typename... Tail>
const T& first(const T& value, const Tail&...) { constexpr auto first(const T& value, const Tail&...) -> const T& {
return value; return value;
} }
#ifdef __cpp_if_constexpr #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename... Args> struct type_list {}; template <typename... T> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...]. // Returns a reference to the argument at index N from [first, rest...].
template <int N, typename T, typename... Args> template <int N, typename T, typename... Args>
constexpr const auto& get([[maybe_unused]] const T& first, constexpr auto get([[maybe_unused]] const T& first,
[[maybe_unused]] const Args&... rest) { [[maybe_unused]] const Args&... rest) -> const auto& {
static_assert(N < 1 + sizeof...(Args), "index is out of bounds"); static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
if constexpr (N == 0) if constexpr (N == 0)
return first; return first;
else else
return get<N - 1>(rest...); return detail::get<N - 1>(rest...);
}
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
template <int N, typename T, typename... Args, typename Char>
constexpr auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
if constexpr (is_static_named_arg<T>()) {
if (name == T::name) return N;
}
if constexpr (sizeof...(Args) > 0)
return get_arg_index_by_name<N + 1, Args...>(name);
(void)name; // Workaround an MSVC bug about "unused" parameter.
return -1;
}
# endif
template <typename... Args, typename Char>
FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
if constexpr (sizeof...(Args) > 0)
return get_arg_index_by_name<0, Args...>(name);
# endif
(void)name;
return -1;
} }
template <typename Char, typename... Args> template <typename Char, typename... Args>
constexpr int get_arg_index_by_name(basic_string_view<Char> name, constexpr auto get_arg_index_by_name(basic_string_view<Char> name,
type_list<Args...>) { type_list<Args...>) -> int {
return get_arg_index_by_name<Args...>(name); return get_arg_index_by_name<Args...>(name);
} }
template <int N, typename> struct get_type_impl; template <int N, typename> struct get_type_impl;
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> { template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
using type = remove_cvref_t<decltype(get<N>(std::declval<Args>()...))>; using type =
remove_cvref_t<decltype(detail::get<N>(std::declval<Args>()...))>;
}; };
template <int N, typename T> template <int N, typename T>
@@ -213,8 +128,8 @@ template <typename Char> struct text {
basic_string_view<Char> data; basic_string_view<Char> data;
using char_type = Char; using char_type = Char;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&...) const { constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
return write<Char>(out, data); return write<Char>(out, data);
} }
}; };
@@ -223,8 +138,8 @@ template <typename Char>
struct is_compiled_format<text<Char>> : std::true_type {}; struct is_compiled_format<text<Char>> : std::true_type {};
template <typename Char> template <typename Char>
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos, constexpr auto make_text(basic_string_view<Char> s, size_t pos, size_t size)
size_t size) { -> text<Char> {
return {{&s[pos], size}}; return {{&s[pos], size}};
} }
@@ -232,16 +147,17 @@ template <typename Char> struct code_unit {
Char value; Char value;
using char_type = Char; using char_type = Char;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&...) const { constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
return write<Char>(out, value); *out++ = value;
return out;
} }
}; };
// This ensures that the argument type is convertible to `const T&`. // This ensures that the argument type is convertible to `const T&`.
template <typename T, int N, typename... Args> template <typename T, int N, typename... Args>
constexpr const T& get_arg_checked(const Args&... args) { constexpr auto get_arg_checked(const Args&... args) -> const T& {
const auto& arg = get<N>(args...); const auto& arg = detail::get<N>(args...);
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) { if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
return arg.value; return arg.value;
} else { } else {
@@ -253,12 +169,18 @@ template <typename Char>
struct is_compiled_format<code_unit<Char>> : std::true_type {}; struct is_compiled_format<code_unit<Char>> : std::true_type {};
// A replacement field that refers to argument N. // A replacement field that refers to argument N.
template <typename Char, typename T, int N> struct field { template <typename Char, typename V, int N> struct field {
using char_type = Char; using char_type = Char;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&... args) const { constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
return write<Char>(out, get_arg_checked<T, N>(args...)); const V& arg = get_arg_checked<V, N>(args...);
if constexpr (std::is_convertible<V, basic_string_view<Char>>::value) {
auto s = basic_string_view<Char>(arg);
return copy<Char>(s.begin(), s.end(), out);
} else {
return write<Char>(out, arg);
}
} }
}; };
@@ -271,10 +193,10 @@ template <typename Char> struct runtime_named_field {
basic_string_view<Char> name; basic_string_view<Char> name;
template <typename OutputIt, typename T> template <typename OutputIt, typename T>
constexpr static bool try_format_argument( constexpr static auto try_format_argument(
OutputIt& out, OutputIt& out,
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9 // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) { [[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) -> bool {
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) { if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
if (arg_name == arg.name) { if (arg_name == arg.name) {
out = write<Char>(out, arg.value); out = write<Char>(out, arg.value);
@@ -284,11 +206,11 @@ template <typename Char> struct runtime_named_field {
return false; return false;
} }
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&... args) const { constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
bool found = (try_format_argument(out, name, args) || ...); bool found = (try_format_argument(out, name, args) || ...);
if (!found) { if (!found) {
throw format_error("argument with specified name is not found"); FMT_THROW(format_error("argument with specified name is not found"));
} }
return out; return out;
} }
@@ -298,17 +220,17 @@ template <typename Char>
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {}; struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
// A replacement field that refers to argument N and has format specifiers. // A replacement field that refers to argument N and has format specifiers.
template <typename Char, typename T, int N> struct spec_field { template <typename Char, typename V, int N> struct spec_field {
using char_type = Char; using char_type = Char;
formatter<T, Char> fmt; formatter<V, Char> fmt;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr FMT_INLINE OutputIt format(OutputIt out, constexpr FMT_INLINE auto format(OutputIt out, const T&... args) const
const Args&... args) const { -> OutputIt {
const auto& vargs = const auto& vargs =
fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...); fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
basic_format_context<OutputIt, Char> ctx(out, vargs); basic_format_context<OutputIt, Char> ctx(out, vargs);
return fmt.format(get_arg_checked<T, N>(args...), ctx); return fmt.format(get_arg_checked<V, N>(args...), ctx);
} }
}; };
@@ -320,8 +242,8 @@ template <typename L, typename R> struct concat {
R rhs; R rhs;
using char_type = typename L::char_type; using char_type = typename L::char_type;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&... args) const { constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
out = lhs.format(out, args...); out = lhs.format(out, args...);
return rhs.format(out, args...); return rhs.format(out, args...);
} }
@@ -331,14 +253,14 @@ template <typename L, typename R>
struct is_compiled_format<concat<L, R>> : std::true_type {}; struct is_compiled_format<concat<L, R>> : std::true_type {};
template <typename L, typename R> template <typename L, typename R>
constexpr concat<L, R> make_concat(L lhs, R rhs) { constexpr auto make_concat(L lhs, R rhs) -> concat<L, R> {
return {lhs, rhs}; return {lhs, rhs};
} }
struct unknown_format {}; struct unknown_format {};
template <typename Char> template <typename Char>
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) { constexpr auto parse_text(basic_string_view<Char> str, size_t pos) -> size_t {
for (size_t size = str.size(); pos != size; ++pos) { for (size_t size = str.size(); pos != size; ++pos) {
if (str[pos] == '{' || str[pos] == '}') break; if (str[pos] == '{' || str[pos] == '}') break;
} }
@@ -346,13 +268,12 @@ constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
} }
template <typename Args, size_t POS, int ID, typename S> template <typename Args, size_t POS, int ID, typename S>
constexpr auto compile_format_string(S format_str); constexpr auto compile_format_string(S fmt);
template <typename Args, size_t POS, int ID, typename T, typename S> template <typename Args, size_t POS, int ID, typename T, typename S>
constexpr auto parse_tail(T head, S format_str) { constexpr auto parse_tail(T head, S fmt) {
if constexpr (POS != if constexpr (POS != basic_string_view<typename S::char_type>(fmt).size()) {
basic_string_view<typename S::char_type>(format_str).size()) { constexpr auto tail = compile_format_string<Args, POS, ID>(fmt);
constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>, if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
unknown_format>()) unknown_format>())
return tail; return tail;
@@ -369,48 +290,51 @@ template <typename T, typename Char> struct parse_specs_result {
int next_arg_id; int next_arg_id;
}; };
constexpr int manual_indexing_id = -1; enum { manual_indexing_id = -1 };
template <typename T, typename Char> template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str, constexpr auto parse_specs(basic_string_view<Char> str, size_t pos,
size_t pos, int next_arg_id) { int next_arg_id) -> parse_specs_result<T, Char> {
str.remove_prefix(pos); str.remove_prefix(pos);
auto ctx = basic_format_parse_context<Char>(str, {}, next_arg_id); auto ctx =
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
auto f = formatter<T, Char>(); auto f = formatter<T, Char>();
auto end = f.parse(ctx); auto end = f.parse(ctx);
return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1, return {f, pos + fmt::detail::to_unsigned(end - str.data()),
next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()}; next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
} }
template <typename Char> struct arg_id_handler { template <typename Char> struct arg_id_handler {
arg_id_kind kind;
arg_ref<Char> arg_id; arg_ref<Char> arg_id;
constexpr int operator()() { constexpr auto on_auto() -> int {
FMT_ASSERT(false, "handler cannot be used with automatic indexing"); FMT_ASSERT(false, "handler cannot be used with automatic indexing");
return 0; return 0;
} }
constexpr int operator()(int id) { constexpr auto on_index(int id) -> int {
kind = arg_id_kind::index;
arg_id = arg_ref<Char>(id); arg_id = arg_ref<Char>(id);
return 0; return 0;
} }
constexpr int operator()(basic_string_view<Char> id) { constexpr auto on_name(basic_string_view<Char> id) -> int {
kind = arg_id_kind::name;
arg_id = arg_ref<Char>(id); arg_id = arg_ref<Char>(id);
return 0; return 0;
} }
constexpr void on_error(const char* message) { throw format_error(message); }
}; };
template <typename Char> struct parse_arg_id_result { template <typename Char> struct parse_arg_id_result {
arg_id_kind kind;
arg_ref<Char> arg_id; arg_ref<Char> arg_id;
const Char* arg_id_end; const Char* arg_id_end;
}; };
template <int ID, typename Char> template <int ID, typename Char>
constexpr auto parse_arg_id(const Char* begin, const Char* end) { constexpr auto parse_arg_id(const Char* begin, const Char* end) {
auto handler = arg_id_handler<Char>{arg_ref<Char>{}}; auto handler = arg_id_handler<Char>{arg_id_kind::none, arg_ref<Char>{}};
auto arg_id_end = parse_arg_id(begin, end, handler); auto arg_id_end = parse_arg_id(begin, end, handler);
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end}; return parse_arg_id_result<Char>{handler.kind, handler.arg_id, arg_id_end};
} }
template <typename T, typename Enable = void> struct field_type { template <typename T, typename Enable = void> struct field_type {
@@ -424,43 +348,48 @@ struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID, template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
typename S> typename S>
constexpr auto parse_replacement_field_then_tail(S format_str) { constexpr auto parse_replacement_field_then_tail(S fmt) {
using char_type = typename S::char_type; using char_type = typename S::char_type;
constexpr auto str = basic_string_view<char_type>(format_str); constexpr auto str = basic_string_view<char_type>(fmt);
constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type(); constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
if constexpr (c == '}') { if constexpr (c == '}') {
return parse_tail<Args, END_POS + 1, NEXT_ID>( return parse_tail<Args, END_POS + 1, NEXT_ID>(
field<char_type, typename field_type<T>::type, ARG_INDEX>(), field<char_type, typename field_type<T>::type, ARG_INDEX>(), fmt);
format_str); } else if constexpr (c != ':') {
} else if constexpr (c == ':') { FMT_THROW(format_error("expected ':'"));
} else {
constexpr auto result = parse_specs<typename field_type<T>::type>( constexpr auto result = parse_specs<typename field_type<T>::type>(
str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID); str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
return parse_tail<Args, result.end, result.next_arg_id>( if constexpr (result.end >= str.size() || str[result.end] != '}') {
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{ FMT_THROW(format_error("expected '}'"));
result.fmt}, return 0;
format_str); } else {
return parse_tail<Args, result.end + 1, result.next_arg_id>(
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
result.fmt},
fmt);
}
} }
} }
// Compiles a non-empty format string and returns the compiled representation // Compiles a non-empty format string and returns the compiled representation
// or unknown_format() on unrecognized input. // or unknown_format() on unrecognized input.
template <typename Args, size_t POS, int ID, typename S> template <typename Args, size_t POS, int ID, typename S>
constexpr auto compile_format_string(S format_str) { constexpr auto compile_format_string(S fmt) {
using char_type = typename S::char_type; using char_type = typename S::char_type;
constexpr auto str = basic_string_view<char_type>(format_str); constexpr auto str = basic_string_view<char_type>(fmt);
if constexpr (str[POS] == '{') { if constexpr (str[POS] == '{') {
if constexpr (POS + 1 == str.size()) if constexpr (POS + 1 == str.size())
throw format_error("unmatched '{' in format string"); FMT_THROW(format_error("unmatched '{' in format string"));
if constexpr (str[POS + 1] == '{') { if constexpr (str[POS + 1] == '{') {
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str); return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
static_assert(ID != manual_indexing_id, static_assert(ID != manual_indexing_id,
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
constexpr auto next_id = constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id; ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return parse_replacement_field_then_tail<get_type<ID, Args>, Args, return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
POS + 1, ID, next_id>( POS + 1, ID, next_id>(fmt);
format_str);
} else { } else {
constexpr auto arg_id_result = constexpr auto arg_id_result =
parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size()); parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
@@ -468,92 +397,87 @@ constexpr auto compile_format_string(S format_str) {
constexpr char_type c = constexpr char_type c =
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type(); arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
static_assert(c == '}' || c == ':', "missing '}' in format string"); static_assert(c == '}' || c == ':', "missing '}' in format string");
if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) { if constexpr (arg_id_result.kind == arg_id_kind::index) {
static_assert( static_assert(
ID == manual_indexing_id || ID == 0, ID == manual_indexing_id || ID == 0,
"cannot switch from automatic to manual argument indexing"); "cannot switch from automatic to manual argument indexing");
constexpr auto arg_index = arg_id_result.arg_id.val.index; constexpr auto arg_index = arg_id_result.arg_id.index;
return parse_replacement_field_then_tail<get_type<arg_index, Args>, return parse_replacement_field_then_tail<get_type<arg_index, Args>,
Args, arg_id_end_pos, Args, arg_id_end_pos,
arg_index, manual_indexing_id>( arg_index, manual_indexing_id>(
format_str); fmt);
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) { } else if constexpr (arg_id_result.kind == arg_id_kind::name) {
constexpr auto arg_index = constexpr auto arg_index =
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{}); get_arg_index_by_name(arg_id_result.arg_id.name, Args{});
if constexpr (arg_index != invalid_arg_index) { if constexpr (arg_index >= 0) {
constexpr auto next_id = constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id; ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return parse_replacement_field_then_tail< return parse_replacement_field_then_tail<
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos, decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
arg_index, next_id>(format_str); arg_index, next_id>(fmt);
} else { } else if constexpr (c == '}') {
if constexpr (c == '}') { return parse_tail<Args, arg_id_end_pos + 1, ID>(
return parse_tail<Args, arg_id_end_pos + 1, ID>( runtime_named_field<char_type>{arg_id_result.arg_id.name}, fmt);
runtime_named_field<char_type>{arg_id_result.arg_id.val.name}, } else if constexpr (c == ':') {
format_str); return unknown_format(); // no type info for specs parsing
} else if constexpr (c == ':') {
return unknown_format(); // no type info for specs parsing
}
} }
} }
} }
} else if constexpr (str[POS] == '}') { } else if constexpr (str[POS] == '}') {
if constexpr (POS + 1 == str.size()) if constexpr (POS + 1 == str.size())
throw format_error("unmatched '}' in format string"); FMT_THROW(format_error("unmatched '}' in format string"));
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str); return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);
} else { } else {
constexpr auto end = parse_text(str, POS + 1); constexpr auto end = parse_text(str, POS + 1);
if constexpr (end - POS > 1) { if constexpr (end - POS > 1) {
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS), return parse_tail<Args, end, ID>(make_text(str, POS, end - POS), fmt);
format_str);
} else { } else {
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]}, return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]}, fmt);
format_str);
} }
} }
} }
template <typename... Args, typename S, template <typename... Args, typename S,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
constexpr auto compile(S format_str) { constexpr auto compile(S fmt) {
constexpr auto str = basic_string_view<typename S::char_type>(format_str); constexpr auto str = basic_string_view<typename S::char_type>(fmt);
if constexpr (str.size() == 0) { if constexpr (str.size() == 0) {
return detail::make_text(str, 0, 0); return detail::make_text(str, 0, 0);
} else { } else {
constexpr auto result = constexpr auto result =
detail::compile_format_string<detail::type_list<Args...>, 0, 0>( detail::compile_format_string<detail::type_list<Args...>, 0, 0>(fmt);
format_str);
return result; return result;
} }
} }
#endif // __cpp_if_constexpr #endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
} // namespace detail } // namespace detail
FMT_MODULE_EXPORT_BEGIN FMT_BEGIN_EXPORT
#ifdef __cpp_if_constexpr #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename CompiledFormat, typename... Args, template <typename CompiledFormat, typename... T,
typename Char = typename CompiledFormat::char_type, typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)> FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf, FMT_INLINE FMT_CONSTEXPR_STRING auto format(const CompiledFormat& cf,
const Args&... args) { const T&... args)
-> std::basic_string<Char> {
auto s = std::basic_string<Char>(); auto s = std::basic_string<Char>();
cf.format(std::back_inserter(s), args...); cf.format(std::back_inserter(s), args...);
return s; return s;
} }
template <typename OutputIt, typename CompiledFormat, typename... Args, template <typename OutputIt, typename CompiledFormat, typename... T,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)> FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf, constexpr FMT_INLINE auto format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) { const T&... args) -> OutputIt {
return cf.format(out, args...); return cf.format(out, args...);
} }
template <typename S, typename... Args, template <typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_INLINE std::basic_string<typename S::char_type> format(const S&, FMT_INLINE FMT_CONSTEXPR_STRING auto format(const S&, T&&... args)
Args&&... args) { -> std::basic_string<typename S::char_type> {
if constexpr (std::is_same<typename S::char_type, char>::value) { if constexpr (std::is_same<typename S::char_type, char>::value) {
constexpr auto str = basic_string_view<typename S::char_type>(S()); constexpr auto str = basic_string_view<typename S::char_type>(S());
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') { if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
@@ -566,73 +490,99 @@ FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
} }
} }
} }
constexpr auto compiled = detail::compile<Args...>(S()); constexpr auto compiled = detail::compile<T...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>, if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) { detail::unknown_format>()) {
return format(static_cast<basic_string_view<typename S::char_type>>(S()), return fmt::format(
std::forward<Args>(args)...); static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<T>(args)...);
} else { } else {
return format(compiled, std::forward<Args>(args)...); return fmt::format(compiled, std::forward<T>(args)...);
} }
} }
template <typename OutputIt, typename S, typename... Args, template <typename OutputIt, typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { FMT_CONSTEXPR auto format_to(OutputIt out, const S&, T&&... args) -> OutputIt {
constexpr auto compiled = detail::compile<Args...>(S()); constexpr auto compiled = detail::compile<T...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>, if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) { detail::unknown_format>()) {
return format_to(out, return fmt::format_to(
static_cast<basic_string_view<typename S::char_type>>(S()), out, static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...); std::forward<T>(args)...);
} else { } else {
return format_to(out, compiled, std::forward<Args>(args)...); return fmt::format_to(out, compiled, std::forward<T>(args)...);
} }
} }
#endif #endif
template <typename OutputIt, typename S, typename... Args, template <typename OutputIt, typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n, auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
const S& format_str, Args&&... args) { -> format_to_n_result<OutputIt> {
auto it = format_to(detail::truncating_iterator<OutputIt>(out, n), format_str, using traits = detail::fixed_buffer_traits;
std::forward<Args>(args)...); auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
return {it.base(), it.count()}; fmt::format_to(std::back_inserter(buf), fmt, std::forward<T>(args)...);
return {buf.out(), buf.count()};
} }
template <typename S, typename... Args, template <typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
size_t formatted_size(const S& format_str, const Args&... args) { FMT_CONSTEXPR20 auto formatted_size(const S& fmt, T&&... args) -> size_t {
return format_to(detail::counting_iterator(), format_str, args...).count(); auto buf = detail::counting_buffer<>();
fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
return buf.count();
} }
template <typename S, typename... Args, template <typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(std::FILE* f, const S& format_str, const Args&... args) { void print(std::FILE* f, const S& fmt, T&&... args) {
memory_buffer buffer; auto buf = memory_buffer();
format_to(std::back_inserter(buffer), format_str, args...); fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
detail::print(f, {buffer.data(), buffer.size()}); detail::print(f, {buf.data(), buf.size()});
} }
template <typename S, typename... Args, template <typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(const S& format_str, const Args&... args) { void print(const S& fmt, T&&... args) {
print(stdout, format_str, args...); print(stdout, fmt, std::forward<T>(args)...);
} }
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS template <size_t N> class static_format_result {
inline namespace literals { private:
template <detail::fixed_string Str> char data[N];
constexpr detail::udl_compiled_string<
remove_cvref_t<decltype(Str.data[0])>,
sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str>
operator""_cf() {
return {};
}
} // namespace literals
#endif
FMT_MODULE_EXPORT_END public:
template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
explicit FMT_CONSTEXPR static_format_result(const S& fmt, T&&... args) {
*fmt::format_to(data, fmt, std::forward<T>(args)...) = '\0';
}
auto str() const -> fmt::string_view { return {data, N - 1}; }
auto c_str() const -> const char* { return data; }
};
/**
* Formats arguments according to the format string `fmt_str` and produces
* a string of the exact required size at compile time. Both the format string
* and the arguments must be compile-time expressions.
*
* The resulting string can be accessed as a C string via `c_str()` or as
* a `fmt::string_view` via `str()`.
*
* **Example**:
*
* // Produces the static string "42" at compile time.
* static constexpr auto result = FMT_STATIC_FORMAT("{}", 42);
* const char* s = result.c_str();
*/
#define FMT_STATIC_FORMAT(fmt_str, ...) \
fmt::static_format_result< \
fmt::formatted_size(FMT_COMPILE(fmt_str), __VA_ARGS__) + 1>( \
FMT_COMPILE(fmt_str), __VA_ARGS__)
FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_COMPILE_H_ #endif // FMT_COMPILE_H_
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-2
View File
@@ -1,2 +0,0 @@
#include "xchar.h"
#warning fmt/locale.h is deprecated, include fmt/format.h or fmt/xchar.h instead
+177 -265
View File
@@ -8,30 +8,34 @@
#ifndef FMT_OS_H_ #ifndef FMT_OS_H_
#define FMT_OS_H_ #define FMT_OS_H_
#include <cerrno>
#include <clocale> // locale_t
#include <cstddef>
#include <cstdio>
#include <cstdlib> // strtod_l
#include <system_error> // std::system_error
#if defined __APPLE__ || defined(__FreeBSD__)
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
#endif
#include "format.h" #include "format.h"
#ifndef FMT_MODULE
# include <cerrno>
# include <cstddef>
# include <cstdio>
# include <system_error> // std::system_error
# if FMT_HAS_INCLUDE(<xlocale.h>)
# include <xlocale.h> // LC_NUMERIC_MASK on macOS
# endif
#endif // FMT_MODULE
#ifndef FMT_USE_FCNTL
// UWP doesn't provide _pipe. // UWP doesn't provide _pipe.
#if FMT_HAS_INCLUDE("winapifamily.h") # if FMT_HAS_INCLUDE("winapifamily.h")
# include <winapifamily.h> # include <winapifamily.h>
#endif # endif
#if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \ # if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \ defined(__linux__)) && \
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) (!defined(WINAPI_FAMILY) || \
# include <fcntl.h> // for O_RDONLY (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) && \
# define FMT_USE_FCNTL 1 !defined(__wasm__)
#else # include <fcntl.h> // for O_RDONLY
# define FMT_USE_FCNTL 0 # define FMT_USE_FCNTL 1
# else
# define FMT_USE_FCNTL 0
# endif
#endif #endif
#ifndef FMT_POSIX #ifndef FMT_POSIX
@@ -45,6 +49,7 @@
// Calls to system functions are wrapped in FMT_SYSTEM for testability. // Calls to system functions are wrapped in FMT_SYSTEM for testability.
#ifdef FMT_SYSTEM #ifdef FMT_SYSTEM
# define FMT_HAS_SYSTEM
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call) # define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
#else #else
# define FMT_SYSTEM(call) ::call # define FMT_SYSTEM(call) ::call
@@ -70,144 +75,88 @@
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) #define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN FMT_BEGIN_EXPORT
/** /**
\rst * A reference to a null-terminated string. It can be constructed from a C
A reference to a null-terminated string. It can be constructed from a C * string or `std::string`.
string or ``std::string``. *
* You can use one of the following type aliases for common character types:
You can use one of the following type aliases for common character types: *
* +---------------+-----------------------------+
+---------------+-----------------------------+ * | Type | Definition |
| Type | Definition | * +===============+=============================+
+===============+=============================+ * | cstring_view | basic_cstring_view<char> |
| cstring_view | basic_cstring_view<char> | * +---------------+-----------------------------+
+---------------+-----------------------------+ * | wcstring_view | basic_cstring_view<wchar_t> |
| wcstring_view | basic_cstring_view<wchar_t> | * +---------------+-----------------------------+
+---------------+-----------------------------+ *
* This class is most useful as a parameter type for functions that wrap C APIs.
This class is most useful as a parameter type to allow passing
different types of strings to a function, for example::
template <typename... Args>
std::string format(cstring_view format_str, const Args & ... args);
format("{}", 42);
format(std::string("{}"), 42);
\endrst
*/ */
template <typename Char> class basic_cstring_view { template <typename Char> class basic_cstring_view {
private: private:
const Char* data_; const Char* data_;
public: public:
/** Constructs a string reference object from a C string. */ /// Constructs a string reference object from a C string.
basic_cstring_view(const Char* s) : data_(s) {} basic_cstring_view(const Char* s) : data_(s) {}
/** /// Constructs a string reference from an `std::string` object.
\rst
Constructs a string reference from an ``std::string`` object.
\endrst
*/
basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {} basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
/** Returns the pointer to a C string. */ /// Returns the pointer to a C string.
const Char* c_str() const { return data_; } auto c_str() const -> const Char* { return data_; }
}; };
using cstring_view = basic_cstring_view<char>; using cstring_view = basic_cstring_view<char>;
using wcstring_view = basic_cstring_view<wchar_t>; using wcstring_view = basic_cstring_view<wchar_t>;
template <typename Char> struct formatter<std::error_code, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write_bytes(out, ec.category().name(),
basic_format_specs<Char>());
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, ec.value());
return out;
}
};
#ifdef _WIN32 #ifdef _WIN32
FMT_API const std::error_category& system_category() FMT_NOEXCEPT; FMT_API const std::error_category& system_category() noexcept;
FMT_BEGIN_DETAIL_NAMESPACE
// A converter from UTF-16 to UTF-8.
// It is only provided for Windows since other systems support UTF-8 natively.
class utf16_to_utf8 {
private:
memory_buffer buffer_;
public:
utf16_to_utf8() {}
FMT_API explicit utf16_to_utf8(basic_string_view<wchar_t> s);
operator string_view() const { return string_view(&buffer_[0], size()); }
size_t size() const { return buffer_.size() - 1; }
const char* c_str() const { return &buffer_[0]; }
std::string str() const { return std::string(&buffer_[0], size()); }
// Performs conversion returning a system error code instead of
// throwing exception on conversion error. This method may still throw
// in case of memory allocation error.
FMT_API int convert(basic_string_view<wchar_t> s);
};
namespace detail {
FMT_API void format_windows_error(buffer<char>& out, int error_code, FMT_API void format_windows_error(buffer<char>& out, int error_code,
const char* message) FMT_NOEXCEPT; const char* message) noexcept;
FMT_END_DETAIL_NAMESPACE }
FMT_API std::system_error vwindows_error(int error_code, string_view format_str, FMT_API std::system_error vwindows_error(int error_code, string_view fmt,
format_args args); format_args args);
/** /**
\rst * Constructs a `std::system_error` object with the description of the form
Constructs a :class:`std::system_error` object with the description *
of the form * <message>: <system-message>
*
.. parsed-literal:: * where `<message>` is the formatted message and `<system-message>` is the
*<message>*: *<system-message>* * system message corresponding to the error code.
* `error_code` is a Windows error code as given by `GetLastError`.
where *<message>* is the formatted message and *<system-message>* is the * If `error_code` is not a valid error code such as -1, the system message
system message corresponding to the error code. * will look like "error -1".
*error_code* is a Windows error code as given by ``GetLastError``. *
If *error_code* is not a valid error code such as -1, the system message * **Example**:
will look like "error -1". *
* // This throws a system_error with the description
**Example**:: * // cannot open file 'foo': The system cannot find the file specified.
* // or similar (system message may vary) if the file doesn't exist.
// This throws a system_error with the description * const char *filename = "foo";
// cannot open file 'madeup': The system cannot find the file specified. * LPOFSTRUCT of = LPOFSTRUCT();
// or similar (system message may vary). * HFILE file = OpenFile(filename, &of, OF_READ);
const char *filename = "madeup"; * if (file == HFILE_ERROR) {
LPOFSTRUCT of = LPOFSTRUCT(); * throw fmt::windows_error(GetLastError(),
HFILE file = OpenFile(filename, &of, OF_READ); * "cannot open file '{}'", filename);
if (file == HFILE_ERROR) { * }
throw fmt::windows_error(GetLastError(), */
"cannot open file '{}'", filename); template <typename... T>
} auto windows_error(int error_code, string_view message, const T&... args)
\endrst -> std::system_error {
*/ return vwindows_error(error_code, message, vargs<T...>{{args...}});
template <typename... Args>
std::system_error windows_error(int error_code, string_view message,
const Args&... args) {
return vwindows_error(error_code, message, fmt::make_format_args(args...));
} }
// Reports a Windows error without throwing an exception. // Reports a Windows error without throwing an exception.
// Can be used to report errors from destructors. // Can be used to report errors from destructors.
FMT_API void report_windows_error(int error_code, FMT_API void report_windows_error(int error_code, const char* message) noexcept;
const char* message) FMT_NOEXCEPT;
#else #else
inline const std::error_category& system_category() FMT_NOEXCEPT { inline auto system_category() noexcept -> const std::error_category& {
return std::system_category(); return std::system_category();
} }
#endif // _WIN32 #endif // _WIN32
@@ -215,8 +164,8 @@ inline const std::error_category& system_category() FMT_NOEXCEPT {
// std::system is not available on some platforms such as iOS (#2248). // std::system is not available on some platforms such as iOS (#2248).
#ifdef __OSX__ #ifdef __OSX__
template <typename S, typename... Args, typename Char = char_t<S>> template <typename S, typename... Args, typename Char = char_t<S>>
void say(const S& format_str, Args&&... args) { void say(const S& fmt, Args&&... args) {
std::system(format("say \"{}\"", format(format_str, args...)).c_str()); std::system(format("say \"{}\"", format(fmt, args...)).c_str());
} }
#endif #endif
@@ -227,24 +176,24 @@ class buffered_file {
friend class file; friend class file;
explicit buffered_file(FILE* f) : file_(f) {} inline explicit buffered_file(FILE* f) : file_(f) {}
public: public:
buffered_file(const buffered_file&) = delete; buffered_file(const buffered_file&) = delete;
void operator=(const buffered_file&) = delete; void operator=(const buffered_file&) = delete;
// Constructs a buffered_file object which doesn't represent any file. // Constructs a buffered_file object which doesn't represent any file.
buffered_file() FMT_NOEXCEPT : file_(nullptr) {} inline buffered_file() noexcept : file_(nullptr) {}
// Destroys the object closing the file it represents if any. // Destroys the object closing the file it represents if any.
FMT_API ~buffered_file() FMT_NOEXCEPT; FMT_API ~buffered_file() noexcept;
public: public:
buffered_file(buffered_file&& other) FMT_NOEXCEPT : file_(other.file_) { inline buffered_file(buffered_file&& other) noexcept : file_(other.file_) {
other.file_ = nullptr; other.file_ = nullptr;
} }
buffered_file& operator=(buffered_file&& other) { inline auto operator=(buffered_file&& other) -> buffered_file& {
close(); close();
file_ = other.file_; file_ = other.file_;
other.file_ = nullptr; other.file_ = nullptr;
@@ -258,36 +207,35 @@ class buffered_file {
FMT_API void close(); FMT_API void close();
// Returns the pointer to a FILE object representing this file. // Returns the pointer to a FILE object representing this file.
FILE* get() const FMT_NOEXCEPT { return file_; } inline auto get() const noexcept -> FILE* { return file_; }
// We place parentheses around fileno to workaround a bug in some versions FMT_API auto descriptor() const -> int;
// of MinGW that define fileno as a macro.
FMT_API int(fileno)() const;
void vprint(string_view format_str, format_args args) { template <typename... T>
fmt::vprint(file_, format_str, args); inline void print(string_view fmt, const T&... args) {
} fmt::vargs<T...> vargs = {{args...}};
detail::is_locking<T...>() ? fmt::vprint_buffered(file_, fmt, vargs)
template <typename... Args> : fmt::vprint(file_, fmt, vargs);
inline void print(string_view format_str, const Args&... args) {
vprint(format_str, fmt::make_format_args(args...));
} }
}; };
#if FMT_USE_FCNTL #if FMT_USE_FCNTL
// A file. Closed file is represented by a file object with descriptor -1. // A file. Closed file is represented by a file object with descriptor -1.
// Methods that are not declared with FMT_NOEXCEPT may throw // Methods that are not declared with noexcept may throw
// fmt::system_error in case of failure. Note that some errors such as // fmt::system_error in case of failure. Note that some errors such as
// closing the file multiple times will cause a crash on Windows rather // closing the file multiple times will cause a crash on Windows rather
// than an exception. You can get standard behavior by overriding the // than an exception. You can get standard behavior by overriding the
// invalid parameter handler with _set_invalid_parameter_handler. // invalid parameter handler with _set_invalid_parameter_handler.
class file { class FMT_API file {
private: private:
int fd_; // File descriptor. int fd_; // File descriptor.
// Constructs a file object with a given descriptor. // Constructs a file object with a given descriptor.
explicit file(int fd) : fd_(fd) {} explicit file(int fd) : fd_(fd) {}
friend struct pipe;
public: public:
// Possible values for the oflag argument to the constructor. // Possible values for the oflag argument to the constructor.
enum { enum {
@@ -300,19 +248,19 @@ class file {
}; };
// Constructs a file object which doesn't represent any file. // Constructs a file object which doesn't represent any file.
file() FMT_NOEXCEPT : fd_(-1) {} inline file() noexcept : fd_(-1) {}
// Opens a file and constructs a file object representing this file. // Opens a file and constructs a file object representing this file.
FMT_API file(cstring_view path, int oflag); file(cstring_view path, int oflag);
public: public:
file(const file&) = delete; file(const file&) = delete;
void operator=(const file&) = delete; void operator=(const file&) = delete;
file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; } inline file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
// Move assignment is not noexcept because close may throw. // Move assignment is not noexcept because close may throw.
file& operator=(file&& other) { inline auto operator=(file&& other) -> file& {
close(); close();
fd_ = other.fd_; fd_ = other.fd_;
other.fd_ = -1; other.fd_ = -1;
@@ -320,54 +268,65 @@ class file {
} }
// Destroys the object closing the file it represents if any. // Destroys the object closing the file it represents if any.
FMT_API ~file() FMT_NOEXCEPT; ~file() noexcept;
// Returns the file descriptor. // Returns the file descriptor.
int descriptor() const FMT_NOEXCEPT { return fd_; } inline auto descriptor() const noexcept -> int { return fd_; }
// Closes the file. // Closes the file.
FMT_API void close(); void close();
// Returns the file size. The size has signed type for consistency with // Returns the file size. The size has signed type for consistency with
// stat::st_size. // stat::st_size.
FMT_API long long size() const; auto size() const -> long long;
// Attempts to read count bytes from the file into the specified buffer. // Attempts to read count bytes from the file into the specified buffer.
FMT_API size_t read(void* buffer, size_t count); auto read(void* buffer, size_t count) -> size_t;
// Attempts to write count bytes from the specified buffer to the file. // Attempts to write count bytes from the specified buffer to the file.
FMT_API size_t write(const void* buffer, size_t count); auto write(const void* buffer, size_t count) -> size_t;
// Duplicates a file descriptor with the dup function and returns // Duplicates a file descriptor with the dup function and returns
// the duplicate as a file object. // the duplicate as a file object.
FMT_API static file dup(int fd); static auto dup(int fd) -> file;
// Makes fd be the copy of this file descriptor, closing fd first if // Makes fd be the copy of this file descriptor, closing fd first if
// necessary. // necessary.
FMT_API void dup2(int fd); void dup2(int fd);
// Makes fd be the copy of this file descriptor, closing fd first if // Makes fd be the copy of this file descriptor, closing fd first if
// necessary. // necessary.
FMT_API void dup2(int fd, std::error_code& ec) FMT_NOEXCEPT; void dup2(int fd, std::error_code& ec) noexcept;
// Creates a pipe setting up read_end and write_end file objects for reading
// and writing respectively.
FMT_API static void pipe(file& read_end, file& write_end);
// Creates a buffered_file object associated with this file and detaches // Creates a buffered_file object associated with this file and detaches
// this file object from the file. // this file object from the file.
FMT_API buffered_file fdopen(const char* mode); auto fdopen(const char* mode) -> buffered_file;
# if defined(_WIN32) && !defined(__MINGW32__)
// Opens a file and constructs a file object representing this file by
// wcstring_view filename. Windows only.
static file open_windows_file(wcstring_view path, int oflag);
# endif
};
struct FMT_API pipe {
file read_end;
file write_end;
// Creates a pipe setting up read_end and write_end file objects for reading
// and writing respectively.
pipe();
}; };
// Returns the memory page size. // Returns the memory page size.
long getpagesize(); auto getpagesize() -> long;
FMT_BEGIN_DETAIL_NAMESPACE namespace detail {
struct buffer_size { struct buffer_size {
buffer_size() = default; constexpr buffer_size() = default;
size_t value = 0; size_t value = 0;
buffer_size operator=(size_t val) const { FMT_CONSTEXPR auto operator=(size_t val) const -> buffer_size {
auto bs = buffer_size(); auto bs = buffer_size();
bs.value = val; bs.value = val;
return bs; return bs;
@@ -378,7 +337,7 @@ struct ostream_params {
int oflag = file::WRONLY | file::CREATE | file::TRUNC; int oflag = file::WRONLY | file::CREATE | file::TRUNC;
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768; size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
ostream_params() {} constexpr ostream_params() {}
template <typename... T> template <typename... T>
ostream_params(T... params, int new_oflag) : ostream_params(params...) { ostream_params(T... params, int new_oflag) : ostream_params(params...) {
@@ -390,126 +349,79 @@ struct ostream_params {
: ostream_params(params...) { : ostream_params(params...) {
this->buffer_size = bs.value; this->buffer_size = bs.value;
} }
// Intel has a bug that results in failure to deduce a constructor
// for empty parameter packs.
# if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000
ostream_params(int new_oflag) : oflag(new_oflag) {}
ostream_params(detail::buffer_size bs) : buffer_size(bs.value) {}
# endif
}; };
FMT_END_DETAIL_NAMESPACE } // namespace detail
static constexpr detail::buffer_size buffer_size; FMT_INLINE_VARIABLE constexpr auto buffer_size = detail::buffer_size();
/** A fast output stream which is not thread-safe. */ /// A fast buffered output stream for writing from a single thread. Writing from
class FMT_API ostream final : private detail::buffer<char> { /// multiple threads without external synchronization may result in a data race.
class ostream : private detail::buffer<char> {
private: private:
file file_; file file_;
void flush() { FMT_API ostream(cstring_view path, const detail::ostream_params& params);
FMT_API static void grow(buffer<char>& buf, size_t);
public:
FMT_API ostream(ostream&& other) noexcept;
FMT_API ~ostream();
operator writer() {
detail::buffer<char>& buf = *this;
return buf;
}
inline void flush() {
if (size() == 0) return; if (size() == 0) return;
file_.write(data(), size()); file_.write(data(), size() * sizeof(data()[0]));
clear(); clear();
} }
void grow(size_t) override;
ostream(cstring_view path, const detail::ostream_params& params)
: file_(path, params.oflag) {
set(new char[params.buffer_size], params.buffer_size);
}
public:
ostream(ostream&& other)
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
file_(std::move(other.file_)) {
other.clear();
other.set(nullptr, 0);
}
~ostream() {
flush();
delete[] data();
}
template <typename... T> template <typename... T>
friend ostream output_file(cstring_view path, T... params); friend auto output_file(cstring_view path, T... params) -> ostream;
void close() { inline void close() {
flush(); flush();
file_.close(); file_.close();
} }
/** /// Formats `args` according to specifications in `fmt` and writes the
Formats ``args`` according to specifications in ``fmt`` and writes the /// output to the file.
output to the file.
*/
template <typename... T> void print(format_string<T...> fmt, T&&... args) { template <typename... T> void print(format_string<T...> fmt, T&&... args) {
vformat_to(detail::buffer_appender<char>(*this), fmt, vformat_to(appender(*this), fmt.str, vargs<T...>{{args...}});
fmt::make_format_args(args...));
} }
}; };
/** /**
\rst * Opens a file for writing. Supported parameters passed in `params`:
Opens a file for writing. Supported parameters passed in *params*: *
* - `<integer>`: Flags passed to [open](
* ``<integer>``: Flags passed to `open * https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html)
<https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html>`_ * (`file::WRONLY | file::CREATE | file::TRUNC` by default)
(``file::WRONLY | file::CREATE`` by default) * - `buffer_size=<integer>`: Output buffer size
* ``buffer_size=<integer>``: Output buffer size *
* **Example**:
**Example**:: *
* auto out = fmt::output_file("guide.txt");
auto out = fmt::output_file("guide.txt"); * out.print("Don't {}", "Panic");
out.print("Don't {}", "Panic");
\endrst
*/ */
template <typename... T> template <typename... T>
inline ostream output_file(cstring_view path, T... params) { inline auto output_file(cstring_view path, T... params) -> ostream {
return {path, detail::ostream_params(params...)}; return {path, detail::ostream_params(params...)};
} }
#endif // FMT_USE_FCNTL #endif // FMT_USE_FCNTL
#ifdef FMT_LOCALE FMT_END_EXPORT
// A "C" numeric locale.
class locale {
private:
# ifdef _WIN32
using locale_t = _locale_t;
static void freelocale(locale_t loc) { _free_locale(loc); }
static double strtod_l(const char* nptr, char** endptr, _locale_t loc) {
return _strtod_l(nptr, endptr, loc);
}
# endif
locale_t locale_;
public:
using type = locale_t;
locale(const locale&) = delete;
void operator=(const locale&) = delete;
locale() {
# ifndef _WIN32
locale_ = FMT_SYSTEM(newlocale(LC_NUMERIC_MASK, "C", nullptr));
# else
locale_ = _create_locale(LC_NUMERIC, "C");
# endif
if (!locale_) FMT_THROW(system_error(errno, "cannot create locale"));
}
~locale() { freelocale(locale_); }
type get() const { return locale_; }
// Converts string to floating-point number and advances str past the end
// of the parsed input.
double strtod(const char*& str) const {
char* end = nullptr;
double result = strtod_l(str, &end, locale_);
str = end;
return result;
}
};
using Locale FMT_DEPRECATED_ALIAS = locale;
#endif // FMT_LOCALE
FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_OS_H_ #endif // FMT_OS_H_
+122 -136
View File
@@ -8,93 +8,53 @@
#ifndef FMT_OSTREAM_H_ #ifndef FMT_OSTREAM_H_
#define FMT_OSTREAM_H_ #define FMT_OSTREAM_H_
#include <ostream> #ifndef FMT_MODULE
# include <fstream> // std::filebuf
#endif
#include "format.h" #ifdef _WIN32
# ifdef __GLIBCXX__
# include <ext/stdio_filebuf.h>
# include <ext/stdio_sync_filebuf.h>
# endif
# include <io.h>
#endif
#include "chrono.h" // formatbuf
#ifdef _MSVC_STL_UPDATE
# define FMT_MSVC_STL_UPDATE _MSVC_STL_UPDATE
#elif defined(_MSC_VER) && _MSC_VER < 1912 // VS 15.5
# define FMT_MSVC_STL_UPDATE _MSVC_LANG
#else
# define FMT_MSVC_STL_UPDATE 0
#endif
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <typename Char> class basic_printf_parse_context;
template <typename OutputIt, typename Char> class basic_printf_context;
namespace detail { namespace detail {
template <class Char> class formatbuf : public std::basic_streambuf<Char> { // Generate a unique explicit instantiation in every translation unit using a
private: // tag type in an anonymous namespace.
using int_type = typename std::basic_streambuf<Char>::int_type; namespace {
using traits_type = typename std::basic_streambuf<Char>::traits_type; struct file_access_tag {};
} // namespace
buffer<Char>& buffer_; template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
class file_access {
public: friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
formatbuf(buffer<Char>& buf) : buffer_(buf) {}
protected:
// The put-area is actually always empty. This makes the implementation
// simpler and has the advantage that the streambuf and the buffer are always
// in sync and sputc never writes into uninitialized memory. The obvious
// disadvantage is that each call to sputc always results in a (virtual) call
// to overflow. There is no disadvantage here for sputn since this always
// results in a call to xsputn.
int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {
if (!traits_type::eq_int_type(ch, traits_type::eof()))
buffer_.push_back(static_cast<Char>(ch));
return ch;
}
std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE {
buffer_.append(s, s + count);
return count;
}
}; };
struct converter { #if FMT_MSVC_STL_UPDATE
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T); template class file_access<file_access_tag, std::filebuf,
}; &std::filebuf::_Myfile>;
auto get_file(std::filebuf&) -> FILE*;
template <typename Char> struct test_stream : std::basic_ostream<Char> { #endif
private:
void_t<> operator<<(converter);
};
// Hide insertion operators for built-in types.
template <typename Char, typename Traits>
void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char);
template <typename Char, typename Traits>
void_t<> operator<<(std::basic_ostream<Char, Traits>&, char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char);
// Checks if T has a user-defined operator<< (e.g. not a member of
// std::ostream).
template <typename T, typename Char> class is_streamable {
private:
template <typename U>
static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>()
<< std::declval<U>()),
void_t<>>::value>
test(int);
template <typename> static std::false_type test(...);
using result = decltype(test<T>(0));
public:
is_streamable() = default;
static const bool value = result::value;
};
// Write the content of buf to os. // Write the content of buf to os.
// It is a separate function rather than a part of vprint to simplify testing.
template <typename Char> template <typename Char>
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) { void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
const Char* buf_data = buf.data(); const Char* buf_data = buf.data();
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type; using unsigned_streamsize = make_unsigned_t<std::streamsize>;
unsigned_streamsize size = buf.size(); unsigned_streamsize size = buf.size();
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>()); unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
do { do {
@@ -105,77 +65,103 @@ void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
} while (size != 0); } while (size != 0);
} }
template <typename Char, typename T> template <typename T> struct streamed_view {
void format_value(buffer<Char>& buf, const T& value, const T& value;
locale_ref loc = locale_ref()) {
formatbuf<Char> format_buf(buf);
std::basic_ostream<Char> output(&format_buf);
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
if (loc) output.imbue(loc.get<std::locale>());
#endif
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
buf.try_resize(buf.size());
}
// Formats an object of type T that has an overloaded ostream operator<<.
template <typename T, typename Char>
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
: private formatter<basic_string_view<Char>, Char> {
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
return formatter<basic_string_view<Char>, Char>::parse(ctx);
}
template <typename ParseCtx,
FMT_ENABLE_IF(std::is_same<
ParseCtx, basic_printf_parse_context<Char>>::value)>
auto parse(ParseCtx& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename OutputIt>
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx)
-> OutputIt {
basic_memory_buffer<Char> buffer;
format_value(buffer, value, ctx.locale());
basic_string_view<Char> str(buffer.data(), buffer.size());
return formatter<basic_string_view<Char>, Char>::format(str, ctx);
}
template <typename OutputIt>
auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx)
-> OutputIt {
basic_memory_buffer<Char> buffer;
format_value(buffer, value, ctx.locale());
return std::copy(buffer.begin(), buffer.end(), ctx.out());
}
}; };
} // namespace detail } // namespace detail
FMT_MODULE_EXPORT // Formats an object of type T that has an overloaded ostream operator<<.
template <typename Char> template <typename Char>
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str, struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
basic_format_args<buffer_context<type_identity_t<Char>>> args) { void set_debug_format() = delete;
basic_memory_buffer<Char> buffer;
detail::vformat_to(buffer, format_str, args); template <typename T, typename Context>
auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) {
auto buffer = basic_memory_buffer<Char>();
auto&& formatbuf = detail::formatbuf<std::basic_streambuf<Char>>(buffer);
auto&& output = std::basic_ostream<Char>(&formatbuf);
output.imbue(std::locale::classic()); // The default is always unlocalized.
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
return formatter<basic_string_view<Char>, Char>::format(
{buffer.data(), buffer.size()}, ctx);
}
};
using ostream_formatter = basic_ostream_formatter<char>;
template <typename T, typename Char>
struct formatter<detail::streamed_view<T>, Char>
: basic_ostream_formatter<Char> {
template <typename Context>
auto format(detail::streamed_view<T> view, Context& ctx) const
-> decltype(ctx.out()) {
return basic_ostream_formatter<Char>::format(view.value, ctx);
}
};
/**
* Returns a view that formats `value` via an ostream `operator<<`.
*
* **Example**:
*
* fmt::print("Current thread id: {}\n",
* fmt::streamed(std::this_thread::get_id()));
*/
template <typename T>
constexpr auto streamed(const T& value) -> detail::streamed_view<T> {
return {value};
}
inline void vprint(std::ostream& os, string_view fmt, format_args args) {
auto buffer = memory_buffer();
detail::vformat_to(buffer, fmt, args);
FILE* f = nullptr;
#if FMT_MSVC_STL_UPDATE && FMT_USE_RTTI
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
f = detail::get_file(*buf);
#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI
auto* rdbuf = os.rdbuf();
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
f = sfbuf->file();
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
f = fbuf->file();
#endif
#ifdef _WIN32
if (f) {
int fd = _fileno(f);
if (_isatty(fd)) {
os.flush();
if (detail::write_console(fd, {buffer.data(), buffer.size()})) return;
}
}
#endif
detail::ignore_unused(f);
detail::write_buffer(os, buffer); detail::write_buffer(os, buffer);
} }
/** /**
\rst * Prints formatted data to the stream `os`.
Prints formatted data to the stream *os*. *
* **Example**:
**Example**:: *
* fmt::print(cerr, "Don't {}!", "panic");
fmt::print(cerr, "Don't {}!", "panic");
\endrst
*/ */
FMT_MODULE_EXPORT FMT_EXPORT template <typename... T>
template <typename S, typename... Args, void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>> fmt::vargs<T...> vargs = {{args...}};
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) { if (detail::const_check(detail::use_utf8)) return vprint(os, fmt.str, vargs);
vprint(os, to_string_view(format_str), auto buffer = memory_buffer();
fmt::make_args_checked<Args...>(format_str, args...)); detail::vformat_to(buffer, fmt.str, vargs);
detail::write_buffer(os, buffer);
} }
FMT_EXPORT template <typename... T>
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
fmt::print(os, FMT_STRING("{}\n"),
fmt::format(fmt, std::forward<T>(args)...));
}
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_OSTREAM_H_ #endif // FMT_OSTREAM_H_
+330 -414
View File
@@ -8,74 +8,114 @@
#ifndef FMT_PRINTF_H_ #ifndef FMT_PRINTF_H_
#define FMT_PRINTF_H_ #define FMT_PRINTF_H_
#include <algorithm> // std::max #ifndef FMT_MODULE
#include <limits> // std::numeric_limits # include <algorithm> // std::find
#include <ostream> # include <limits> // std::numeric_limits
#endif
#include "format.h" #include "format.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN FMT_BEGIN_EXPORT
template <typename Char> template <typename Char> class basic_printf_context {
class basic_printf_parse_context : public basic_format_parse_context<Char> { private:
using basic_format_parse_context<Char>::basic_format_parse_context; basic_appender<Char> out_;
basic_format_args<basic_printf_context> args_;
static_assert(std::is_same<Char, char>::value ||
std::is_same<Char, wchar_t>::value,
"Unsupported code unit type.");
public:
using char_type = Char;
enum { builtin_types = 1 };
/// Constructs a `printf_context` object. References to the arguments are
/// stored in the context object so make sure they have appropriate lifetimes.
basic_printf_context(basic_appender<Char> out,
basic_format_args<basic_printf_context> args)
: out_(out), args_(args) {}
auto out() -> basic_appender<Char> { return out_; }
void advance_to(basic_appender<Char>) {}
auto locale() -> locale_ref { return {}; }
auto arg(int id) const -> basic_format_arg<basic_printf_context> {
return args_.get(id);
}
}; };
template <typename OutputIt, typename Char> class basic_printf_context;
FMT_BEGIN_DETAIL_NAMESPACE namespace detail {
// Return the result via the out param to workaround gcc bug 77539.
template <bool IS_CONSTEXPR, typename T, typename Ptr = const T*>
FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool {
for (out = first; out != last; ++out) {
if (*out == value) return true;
}
return false;
}
template <>
inline auto find<false, char>(const char* first, const char* last, char value,
const char*& out) -> bool {
out =
static_cast<const char*>(memchr(first, value, to_unsigned(last - first)));
return out != nullptr;
}
// Checks if a value fits in int - used to avoid warnings about comparing // Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers. // signed and unsigned integers.
template <bool IsSigned> struct int_checker { template <bool IS_SIGNED> struct int_checker {
template <typename T> static bool fits_in_int(T value) { template <typename T> static auto fits_in_int(T value) -> bool {
unsigned max = max_value<int>(); return value <= to_unsigned(max_value<int>());
return value <= max;
} }
static bool fits_in_int(bool) { return true; } inline static auto fits_in_int(bool) -> bool { return true; }
}; };
template <> struct int_checker<true> { template <> struct int_checker<true> {
template <typename T> static bool fits_in_int(T value) { template <typename T> static auto fits_in_int(T value) -> bool {
return value >= (std::numeric_limits<int>::min)() && return value >= (std::numeric_limits<int>::min)() &&
value <= max_value<int>(); value <= max_value<int>();
} }
static bool fits_in_int(int) { return true; } inline static auto fits_in_int(int) -> bool { return true; }
}; };
class printf_precision_handler { struct printf_precision_handler {
public:
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
int operator()(T value) { auto operator()(T value) -> int {
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value)) if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
FMT_THROW(format_error("number is too big")); report_error("number is too big");
return (std::max)(static_cast<int>(value), 0); return max_of(static_cast<int>(value), 0);
} }
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
int operator()(T) { auto operator()(T) -> int {
FMT_THROW(format_error("precision is not integer")); report_error("precision is not integer");
return 0; return 0;
} }
}; };
// An argument visitor that returns true iff arg is a zero integer. // An argument visitor that returns true iff arg is a zero integer.
class is_zero_int { struct is_zero_int {
public:
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
bool operator()(T value) { auto operator()(T value) -> bool {
return value == 0; return value == 0;
} }
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
bool operator()(T) { auto operator()(T) -> bool {
return false; return false;
} }
}; };
template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {}; template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {};
template <> struct make_unsigned_or_bool<bool> { using type = bool; }; template <> struct make_unsigned_or_bool<bool> {
using type = bool;
};
template <typename T, typename Context> class arg_converter { template <typename T, typename Context> class arg_converter {
private: private:
@@ -98,24 +138,19 @@ template <typename T, typename Context> class arg_converter {
using target_type = conditional_t<std::is_same<T, void>::value, U, T>; using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
if (const_check(sizeof(target_type) <= sizeof(int))) { if (const_check(sizeof(target_type) <= sizeof(int))) {
// Extra casts are used to silence warnings. // Extra casts are used to silence warnings.
if (is_signed) { using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
arg_ = detail::make_arg<Context>( if (is_signed)
static_cast<int>(static_cast<target_type>(value))); arg_ = static_cast<int>(static_cast<target_type>(value));
} else { else
using unsigned_type = typename make_unsigned_or_bool<target_type>::type; arg_ = static_cast<unsigned>(static_cast<unsigned_type>(value));
arg_ = detail::make_arg<Context>(
static_cast<unsigned>(static_cast<unsigned_type>(value)));
}
} else { } else {
if (is_signed) { // glibc's printf doesn't sign extend arguments of smaller types:
// glibc's printf doesn't sign extend arguments of smaller types: // std::printf("%lld", -42); // prints "4294967254"
// std::printf("%lld", -42); // prints "4294967254" // but we don't have to do the same because it's a UB.
// but we don't have to do the same because it's a UB. if (is_signed)
arg_ = detail::make_arg<Context>(static_cast<long long>(value)); arg_ = static_cast<long long>(value);
} else { else
arg_ = detail::make_arg<Context>( arg_ = static_cast<typename make_unsigned_or_bool<U>::type>(value);
static_cast<typename make_unsigned_or_bool<U>::type>(value));
}
} }
} }
@@ -129,7 +164,7 @@ template <typename T, typename Context> class arg_converter {
// unsigned). // unsigned).
template <typename T, typename Context, typename Char> template <typename T, typename Context, typename Char>
void convert_arg(basic_format_arg<Context>& arg, Char type) { void convert_arg(basic_format_arg<Context>& arg, Char type) {
visit_format_arg(arg_converter<T, Context>(arg, type), arg); arg.visit(arg_converter<T, Context>(arg, type));
} }
// Converts an integer argument to char for printf. // Converts an integer argument to char for printf.
@@ -142,8 +177,7 @@ template <typename Context> class char_converter {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
void operator()(T value) { void operator()(T value) {
arg_ = detail::make_arg<Context>( arg_ = static_cast<typename Context::char_type>(value);
static_cast<typename Context::char_type>(value));
} }
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
@@ -153,261 +187,163 @@ template <typename Context> class char_converter {
// An argument visitor that return a pointer to a C string if argument is a // An argument visitor that return a pointer to a C string if argument is a
// string or null otherwise. // string or null otherwise.
template <typename Char> struct get_cstring { template <typename Char> struct get_cstring {
template <typename T> const Char* operator()(T) { return nullptr; } template <typename T> auto operator()(T) -> const Char* { return nullptr; }
const Char* operator()(const Char* s) { return s; } auto operator()(const Char* s) -> const Char* { return s; }
}; };
// Checks if an argument is a valid printf width specifier and sets // Checks if an argument is a valid printf width specifier and sets
// left alignment if it is negative. // left alignment if it is negative.
template <typename Char> class printf_width_handler { class printf_width_handler {
private: private:
using format_specs = basic_format_specs<Char>;
format_specs& specs_; format_specs& specs_;
public: public:
explicit printf_width_handler(format_specs& specs) : specs_(specs) {} inline explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
unsigned operator()(T value) { auto operator()(T value) -> unsigned {
auto width = static_cast<uint32_or_64_or_128_t<T>>(value); auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
if (detail::is_negative(value)) { if (detail::is_negative(value)) {
specs_.align = align::left; specs_.set_align(align::left);
width = 0 - width; width = 0 - width;
} }
unsigned int_max = max_value<int>(); unsigned int_max = to_unsigned(max_value<int>());
if (width > int_max) FMT_THROW(format_error("number is too big")); if (width > int_max) report_error("number is too big");
return static_cast<unsigned>(width); return static_cast<unsigned>(width);
} }
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
unsigned operator()(T) { auto operator()(T) -> unsigned {
FMT_THROW(format_error("width is not integer")); report_error("width is not integer");
return 0; return 0;
} }
}; };
// The ``printf`` argument formatter. // Workaround for a bug with the XL compiler when initializing
template <typename OutputIt, typename Char> // printf_arg_formatter's base class.
template <typename Char>
auto make_arg_formatter(basic_appender<Char> iter, format_specs& s)
-> arg_formatter<Char> {
return {iter, s, locale_ref()};
}
// The `printf` argument formatter.
template <typename Char>
class printf_arg_formatter : public arg_formatter<Char> { class printf_arg_formatter : public arg_formatter<Char> {
private: private:
using base = arg_formatter<Char>; using base = arg_formatter<Char>;
using context_type = basic_printf_context<OutputIt, Char>; using context_type = basic_printf_context<Char>;
using format_specs = basic_format_specs<Char>;
context_type& context_; context_type& context_;
OutputIt write_null_pointer(bool is_string = false) { void write_null_pointer(bool is_string = false) {
auto s = this->specs; auto s = this->specs;
s.type = 0; s.set_type(presentation_type::none);
return write_bytes(this->out, is_string ? "(null)" : "(nil)", s); write_bytes<Char>(this->out, is_string ? "(null)" : "(nil)", s);
}
template <typename T> void write(T value) {
detail::write<Char>(this->out, value, this->specs, this->locale);
} }
public: public:
printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx) printf_arg_formatter(basic_appender<Char> iter, format_specs& s,
: base{iter, s, locale_ref()}, context_(ctx) {} context_type& ctx)
: base(make_arg_formatter(iter, s)), context_(ctx) {}
OutputIt operator()(monostate value) { return base::operator()(value); } void operator()(monostate value) { write(value); }
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
OutputIt operator()(T value) { void operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and Char so use // MSVC2013 fails to compile separate overloads for bool and Char so use
// std::is_same instead. // std::is_same instead.
if (std::is_same<T, Char>::value) { if (!std::is_same<T, Char>::value) {
format_specs fmt_specs = this->specs; write(value);
if (fmt_specs.type && fmt_specs.type != 'c') return;
return (*this)(static_cast<int>(value));
fmt_specs.sign = sign::none;
fmt_specs.alt = false;
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
// align::numeric needs to be overwritten here since the '0' flag is
// ignored for non-numeric types
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
fmt_specs.align = align::right;
return write<Char>(this->out, static_cast<Char>(value), fmt_specs);
} }
return base::operator()(value); format_specs s = this->specs;
if (s.type() != presentation_type::none &&
s.type() != presentation_type::chr) {
return (*this)(static_cast<int>(value));
}
s.set_sign(sign::none);
s.clear_alt();
s.set_fill(' '); // Ignore '0' flag for char types.
// align::numeric needs to be overwritten here since the '0' flag is
// ignored for non-numeric types
if (s.align() == align::none || s.align() == align::numeric)
s.set_align(align::right);
detail::write<Char>(this->out, static_cast<Char>(value), s);
} }
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
OutputIt operator()(T value) { void operator()(T value) {
return base::operator()(value); write(value);
} }
/** Formats a null-terminated C string. */ void operator()(const char* value) {
OutputIt operator()(const char* value) { if (value)
if (value) return base::operator()(value); write(value);
return write_null_pointer(this->specs.type != 'p'); else
write_null_pointer(this->specs.type() != presentation_type::pointer);
} }
/** Formats a null-terminated wide C string. */ void operator()(const wchar_t* value) {
OutputIt operator()(const wchar_t* value) { if (value)
if (value) return base::operator()(value); write(value);
return write_null_pointer(this->specs.type != 'p'); else
write_null_pointer(this->specs.type() != presentation_type::pointer);
} }
OutputIt operator()(basic_string_view<Char> value) { void operator()(basic_string_view<Char> value) { write(value); }
return base::operator()(value);
void operator()(const void* value) {
if (value)
write(value);
else
write_null_pointer();
} }
/** Formats a pointer. */ void operator()(typename basic_format_arg<context_type>::handle handle) {
OutputIt operator()(const void* value) { auto parse_ctx = parse_context<Char>({});
return value ? base::operator()(value) : write_null_pointer(); handle.format(parse_ctx, context_);
}
/** Formats an argument of a custom (user-defined) type. */
OutputIt operator()(typename basic_format_arg<context_type>::handle handle) {
handle.format(context_.parse_context(), context_);
return this->out;
} }
}; };
template <typename Char, typename Context> template <typename Char>
void vprintf(buffer<Char>& buf, basic_string_view<Char> format, void parse_flags(format_specs& specs, const Char*& it, const Char* end) {
basic_format_args<Context> args) {
Context(buffer_appender<Char>(buf), format, args).format();
}
FMT_END_DETAIL_NAMESPACE
// For printing into memory_buffer.
template <typename Char, typename Context>
FMT_DEPRECATED void printf(detail::buffer<Char>& buf,
basic_string_view<Char> format,
basic_format_args<Context> args) {
return detail::vprintf(buf, format, args);
}
using detail::vprintf;
template <typename T> struct printf_formatter {
printf_formatter() = delete;
template <typename ParseContext>
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const T&, FormatContext& ctx) -> decltype(ctx.out()) {
return ctx.out();
}
};
/**
This template formats data and writes the output through an output iterator.
*/
template <typename OutputIt, typename Char> class basic_printf_context {
public:
/** The character type for the output. */
using char_type = Char;
using iterator = OutputIt;
using format_arg = basic_format_arg<basic_printf_context>;
using parse_context_type = basic_printf_parse_context<Char>;
template <typename T> using formatter_type = printf_formatter<T>;
private:
using format_specs = basic_format_specs<char_type>;
OutputIt out_;
basic_format_args<basic_printf_context> args_;
parse_context_type parse_ctx_;
static void parse_flags(format_specs& specs, const Char*& it,
const Char* end);
// Returns the argument with specified index or, if arg_index is -1, the next
// argument.
format_arg get_arg(int arg_index = -1);
// Parses argument index, flags and width and returns the argument index.
int parse_header(const Char*& it, const Char* end, format_specs& specs);
public:
/**
\rst
Constructs a ``printf_context`` object. References to the arguments are
stored in the context object so make sure they have appropriate lifetimes.
\endrst
*/
basic_printf_context(OutputIt out, basic_string_view<char_type> format_str,
basic_format_args<basic_printf_context> args)
: out_(out), args_(args), parse_ctx_(format_str) {}
OutputIt out() { return out_; }
void advance_to(OutputIt it) { out_ = it; }
detail::locale_ref locale() { return {}; }
format_arg arg(int id) const { return args_.get(id); }
parse_context_type& parse_context() { return parse_ctx_; }
FMT_CONSTEXPR void on_error(const char* message) {
parse_ctx_.on_error(message);
}
/** Formats stored arguments and writes the output to the range. */
OutputIt format();
};
template <typename OutputIt, typename Char>
void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
const Char*& it,
const Char* end) {
for (; it != end; ++it) { for (; it != end; ++it) {
switch (*it) { switch (*it) {
case '-': case '-': specs.set_align(align::left); break;
specs.align = align::left; case '+': specs.set_sign(sign::plus); break;
break; case '0': specs.set_fill('0'); break;
case '+':
specs.sign = sign::plus;
break;
case '0':
specs.fill[0] = '0';
break;
case ' ': case ' ':
if (specs.sign != sign::plus) { if (specs.sign() != sign::plus) specs.set_sign(sign::space);
specs.sign = sign::space;
}
break; break;
case '#': case '#': specs.set_alt(); break;
specs.alt = true; default: return;
break;
default:
return;
} }
} }
} }
template <typename OutputIt, typename Char> template <typename Char, typename GetArg>
typename basic_printf_context<OutputIt, Char>::format_arg auto parse_header(const Char*& it, const Char* end, format_specs& specs,
basic_printf_context<OutputIt, Char>::get_arg(int arg_index) { GetArg get_arg) -> int {
if (arg_index < 0)
arg_index = parse_ctx_.next_arg_id();
else
parse_ctx_.check_arg_id(--arg_index);
return detail::get_arg(*this, arg_index);
}
template <typename OutputIt, typename Char>
int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
const Char* end,
format_specs& specs) {
int arg_index = -1; int arg_index = -1;
char_type c = *it; Char c = *it;
if (c >= '0' && c <= '9') { if (c >= '0' && c <= '9') {
// Parse an argument index (if followed by '$') or a width possibly // Parse an argument index (if followed by '$') or a width possibly
// preceded with '0' flag(s). // preceded with '0' flag(s).
detail::error_handler eh; int value = parse_nonnegative_int(it, end, -1);
int value = parse_nonnegative_int(it, end, eh);
if (it != end && *it == '$') { // value is an argument index if (it != end && *it == '$') { // value is an argument index
++it; ++it;
arg_index = value; arg_index = value != -1 ? value : max_value<int>();
} else { } else {
if (c == '0') specs.fill[0] = '0'; if (c == '0') specs.set_fill('0');
if (value != 0) { if (value != 0) {
// Nonzero value means that we parsed width and don't need to // Nonzero value means that we parsed width and don't need to
// parse it or flags again, so return now. // parse it or flags again, so return now.
if (value == -1) report_error("number is too big");
specs.width = value; specs.width = value;
return arg_index; return arg_index;
} }
@@ -417,89 +353,127 @@ int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
// Parse width. // Parse width.
if (it != end) { if (it != end) {
if (*it >= '0' && *it <= '9') { if (*it >= '0' && *it <= '9') {
detail::error_handler eh; specs.width = parse_nonnegative_int(it, end, -1);
specs.width = parse_nonnegative_int(it, end, eh); if (specs.width == -1) report_error("number is too big");
} else if (*it == '*') { } else if (*it == '*') {
++it; ++it;
specs.width = static_cast<int>(visit_format_arg( specs.width = static_cast<int>(
detail::printf_width_handler<char_type>(specs), get_arg())); get_arg(-1).visit(detail::printf_width_handler(specs)));
} }
} }
return arg_index; return arg_index;
} }
template <typename OutputIt, typename Char> inline auto parse_printf_presentation_type(char c, type t, bool& upper)
OutputIt basic_printf_context<OutputIt, Char>::format() { -> presentation_type {
auto out = this->out(); using pt = presentation_type;
const Char* start = parse_ctx_.begin(); constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
const Char* end = parse_ctx_.end(); switch (c) {
case 'd': return in(t, integral_set) ? pt::dec : pt::none;
case 'o': return in(t, integral_set) ? pt::oct : pt::none;
case 'X': upper = true; FMT_FALLTHROUGH;
case 'x': return in(t, integral_set) ? pt::hex : pt::none;
case 'E': upper = true; FMT_FALLTHROUGH;
case 'e': return in(t, float_set) ? pt::exp : pt::none;
case 'F': upper = true; FMT_FALLTHROUGH;
case 'f': return in(t, float_set) ? pt::fixed : pt::none;
case 'G': upper = true; FMT_FALLTHROUGH;
case 'g': return in(t, float_set) ? pt::general : pt::none;
case 'A': upper = true; FMT_FALLTHROUGH;
case 'a': return in(t, float_set) ? pt::hexfloat : pt::none;
case 'c': return in(t, integral_set) ? pt::chr : pt::none;
case 's': return in(t, string_set | cstring_set) ? pt::string : pt::none;
case 'p': return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
default: return pt::none;
}
}
template <typename Char, typename Context>
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) {
using iterator = basic_appender<Char>;
auto out = iterator(buf);
auto context = basic_printf_context<Char>(out, args);
auto parse_ctx = parse_context<Char>(format);
// Returns the argument with specified index or, if arg_index is -1, the next
// argument.
auto get_arg = [&](int arg_index) {
if (arg_index < 0)
arg_index = parse_ctx.next_arg_id();
else
parse_ctx.check_arg_id(--arg_index);
auto arg = context.arg(arg_index);
if (!arg) report_error("argument not found");
return arg;
};
const Char* start = parse_ctx.begin();
const Char* end = parse_ctx.end();
auto it = start; auto it = start;
while (it != end) { while (it != end) {
if (!detail::find<false, Char>(it, end, '%', it)) { if (!find<false, Char>(it, end, '%', it)) {
it = end; // detail::find leaves it == nullptr if it doesn't find '%' it = end; // find leaves it == nullptr if it doesn't find '%'.
break; break;
} }
char_type c = *it++; Char c = *it++;
if (it != end && *it == c) { if (it != end && *it == c) {
out = detail::write( write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
start = ++it; start = ++it;
continue; continue;
} }
out = detail::write(out, basic_string_view<Char>( write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
start, detail::to_unsigned(it - 1 - start)));
format_specs specs; auto specs = format_specs();
specs.align = align::right; specs.set_align(align::right);
// Parse argument index, flags and width. // Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs); int arg_index = parse_header(it, end, specs, get_arg);
if (arg_index == 0) on_error("argument not found"); if (arg_index == 0) report_error("argument not found");
// Parse precision. // Parse precision.
if (it != end && *it == '.') { if (it != end && *it == '.') {
++it; ++it;
c = it != end ? *it : 0; c = it != end ? *it : 0;
if ('0' <= c && c <= '9') { if ('0' <= c && c <= '9') {
detail::error_handler eh; specs.precision = parse_nonnegative_int(it, end, 0);
specs.precision = parse_nonnegative_int(it, end, eh);
} else if (c == '*') { } else if (c == '*') {
++it; ++it;
specs.precision = static_cast<int>( specs.precision =
visit_format_arg(detail::printf_precision_handler(), get_arg())); static_cast<int>(get_arg(-1).visit(printf_precision_handler()));
} else { } else {
specs.precision = 0; specs.precision = 0;
} }
} }
format_arg arg = get_arg(arg_index); auto arg = get_arg(arg_index);
// For d, i, o, u, x, and X conversion specifiers, if a precision is // For d, i, o, u, x, and X conversion specifiers, if a precision is
// specified, the '0' flag is ignored // specified, the '0' flag is ignored
if (specs.precision >= 0 && arg.is_integral()) if (specs.precision >= 0 && is_integral_type(arg.type())) {
specs.fill[0] = // Ignore '0' for non-numeric types or if '-' present.
' '; // Ignore '0' flag for non-numeric types or if '-' present. specs.set_fill(' ');
if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) { }
auto str = visit_format_arg(detail::get_cstring<Char>(), arg); if (specs.precision >= 0 && arg.type() == type::cstring_type) {
auto str = arg.visit(get_cstring<Char>());
auto str_end = str + specs.precision; auto str_end = str + specs.precision;
auto nul = std::find(str, str_end, Char()); auto nul = std::find(str, str_end, Char());
arg = detail::make_arg<basic_printf_context>(basic_string_view<Char>( auto sv = basic_string_view<Char>(
str, str, to_unsigned(nul != str_end ? nul - str : specs.precision));
detail::to_unsigned(nul != str_end ? nul - str : specs.precision))); arg = sv;
} }
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg)) if (specs.alt() && arg.visit(is_zero_int())) specs.clear_alt();
specs.alt = false; if (specs.fill_unit<Char>() == '0') {
if (specs.fill[0] == '0') { if (is_arithmetic_type(arg.type()) && specs.align() != align::left) {
if (arg.is_arithmetic() && specs.align != align::left) specs.set_align(align::numeric);
specs.align = align::numeric; } else {
else // Ignore '0' flag for non-numeric types or if '-' flag is also present.
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types or if '-' specs.set_fill(' ');
// flag is also present. }
} }
// Parse length and convert the argument to the required type. // Parse length and convert the argument to the required type.
c = it != end ? *it++ : 0; c = it != end ? *it++ : 0;
char_type t = it != end ? *it : 0; Char t = it != end ? *it : 0;
using detail::convert_arg;
switch (c) { switch (c) {
case 'h': case 'h':
if (t == 'h') { if (t == 'h') {
@@ -519,190 +493,132 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
convert_arg<long>(arg, t); convert_arg<long>(arg, t);
} }
break; break;
case 'j': case 'j': convert_arg<intmax_t>(arg, t); break;
convert_arg<intmax_t>(arg, t); case 'z': convert_arg<size_t>(arg, t); break;
break; case 't': convert_arg<std::ptrdiff_t>(arg, t); break;
case 'z':
convert_arg<size_t>(arg, t);
break;
case 't':
convert_arg<std::ptrdiff_t>(arg, t);
break;
case 'L': case 'L':
// printf produces garbage when 'L' is omitted for long double, no // printf produces garbage when 'L' is omitted for long double, no
// need to do the same. // need to do the same.
break; break;
default: default: --it; convert_arg<void>(arg, c);
--it;
convert_arg<void>(arg, c);
} }
// Parse type. // Parse type.
if (it == end) FMT_THROW(format_error("invalid format string")); if (it == end) report_error("invalid format string");
specs.type = static_cast<char>(*it++); char type = static_cast<char>(*it++);
if (arg.is_integral()) { if (is_integral_type(arg.type())) {
// Normalize type. // Normalize type.
switch (specs.type) { switch (type) {
case 'i': case 'i':
case 'u': case 'u': type = 'd'; break;
specs.type = 'd';
break;
case 'c': case 'c':
visit_format_arg(detail::char_converter<basic_printf_context>(arg), arg.visit(char_converter<basic_printf_context<Char>>(arg));
arg);
break; break;
} }
} }
bool upper = false;
specs.set_type(parse_printf_presentation_type(type, arg.type(), upper));
if (specs.type() == presentation_type::none)
report_error("invalid format specifier");
if (upper) specs.set_upper();
start = it; start = it;
// Format argument. // Format argument.
out = visit_format_arg( arg.visit(printf_arg_formatter<Char>(out, specs, context));
detail::printf_arg_formatter<OutputIt, Char>(out, specs, *this), arg);
} }
return detail::write( write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
} }
} // namespace detail
template <typename Char> using printf_context = basic_printf_context<char>;
using basic_printf_context_t = using wprintf_context = basic_printf_context<wchar_t>;
basic_printf_context<detail::buffer_appender<Char>, Char>;
using printf_context = basic_printf_context_t<char>;
using wprintf_context = basic_printf_context_t<wchar_t>;
using printf_args = basic_format_args<printf_context>; using printf_args = basic_format_args<printf_context>;
using wprintf_args = basic_format_args<wprintf_context>; using wprintf_args = basic_format_args<wprintf_context>;
/** /// Constructs an `format_arg_store` object that contains references to
\rst /// arguments and can be implicitly converted to `printf_args`.
Constructs an `~fmt::format_arg_store` object that contains references to template <typename Char = char, typename... T>
arguments and can be implicitly converted to `~fmt::printf_args`. inline auto make_printf_args(T&... args)
\endrst -> decltype(fmt::make_format_args<basic_printf_context<Char>>(args...)) {
*/ return fmt::make_format_args<basic_printf_context<Char>>(args...);
template <typename... T>
inline auto make_printf_args(const T&... args)
-> format_arg_store<printf_context, T...> {
return {args...};
} }
/** template <typename Char> struct vprintf_args {
\rst using type = basic_format_args<basic_printf_context<Char>>;
Constructs an `~fmt::format_arg_store` object that contains references to };
arguments and can be implicitly converted to `~fmt::wprintf_args`.
\endrst
*/
template <typename... T>
inline auto make_wprintf_args(const T&... args)
-> format_arg_store<wprintf_context, T...> {
return {args...};
}
template <typename S, typename Char = char_t<S>> template <typename Char>
inline auto vsprintf( inline auto vsprintf(basic_string_view<Char> fmt,
const S& fmt, typename vprintf_args<Char>::type args)
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
basic_memory_buffer<Char> buffer; auto buf = basic_memory_buffer<Char>();
vprintf(buffer, to_string_view(fmt), args); detail::vprintf(buf, fmt, args);
return to_string(buffer); return {buf.data(), buf.size()};
} }
/** /**
\rst * Formats `args` according to specifications in `fmt` and returns the result
Formats arguments and returns the result as a string. * as as string.
*
**Example**:: * **Example**:
*
std::string message = fmt::sprintf("The answer is %d", 42); * std::string message = fmt::sprintf("The answer is %d", 42);
\endrst */
*/ template <typename... T>
template <typename S, typename... T, inline auto sprintf(string_view fmt, const T&... args) -> std::string {
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>> return vsprintf(fmt, make_printf_args(args...));
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> { }
using context = basic_printf_context_t<Char>; template <typename... T>
return vsprintf(to_string_view(fmt), fmt::make_format_args<context>(args...)); FMT_DEPRECATED auto sprintf(basic_string_view<wchar_t> fmt, const T&... args)
-> std::wstring {
return vsprintf(fmt, make_printf_args<wchar_t>(args...));
} }
template <typename S, typename Char = char_t<S>> template <typename Char>
inline auto vfprintf( auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
std::FILE* f, const S& fmt, typename vprintf_args<Char>::type args) -> int {
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) auto buf = basic_memory_buffer<Char>();
-> int { detail::vprintf(buf, fmt, args);
basic_memory_buffer<Char> buffer; size_t size = buf.size();
vprintf(buffer, to_string_view(fmt), args); return std::fwrite(buf.data(), sizeof(Char), size, f) < size
size_t size = buffer.size();
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
? -1 ? -1
: static_cast<int>(size); : static_cast<int>(size);
} }
/** /**
\rst * Formats `args` according to specifications in `fmt` and writes the output
Prints formatted data to the file *f*. * to `f`.
*
**Example**:: * **Example**:
*
fmt::fprintf(stderr, "Don't %s!", "panic"); * fmt::fprintf(stderr, "Don't %s!", "panic");
\endrst
*/ */
template <typename S, typename... T, typename Char = char_t<S>> template <typename... T>
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int { inline auto fprintf(std::FILE* f, string_view fmt, const T&... args) -> int {
using context = basic_printf_context_t<Char>; return vfprintf(f, fmt, make_printf_args(args...));
return vfprintf(f, to_string_view(fmt), fmt::make_format_args<context>(args...));
} }
template <typename... T>
template <typename S, typename Char = char_t<S>> FMT_DEPRECATED auto fprintf(std::FILE* f, basic_string_view<wchar_t> fmt,
inline auto vprintf( const T&... args) -> int {
const S& fmt, return vfprintf(f, fmt, make_printf_args<wchar_t>(args...));
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
-> int {
return vfprintf(stdout, to_string_view(fmt), args);
} }
/** /**
\rst * Formats `args` according to specifications in `fmt` and writes the output
Prints formatted data to ``stdout``. * to `stdout`.
*
**Example**:: * **Example**:
*
fmt::printf("Elapsed time: %.2f seconds", 1.23); * fmt::printf("Elapsed time: %.2f seconds", 1.23);
\endrst
*/ */
template <typename S, typename... T, FMT_ENABLE_IF(detail::is_string<S>::value)> template <typename... T>
inline auto printf(const S& fmt, const T&... args) -> int { inline auto printf(string_view fmt, const T&... args) -> int {
return vprintf(to_string_view(fmt), return vfprintf(stdout, fmt, make_printf_args(args...));
fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...));
} }
template <typename S, typename Char = char_t<S>> FMT_END_EXPORT
inline auto vfprintf(
std::basic_ostream<Char>& os, const S& fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
-> int {
basic_memory_buffer<Char> buffer;
vprintf(buffer, to_string_view(fmt), args);
os.write(buffer.data(), static_cast<std::streamsize>(buffer.size()));
return static_cast<int>(buffer.size());
}
/**
\rst
Prints formatted data to the stream *os*.
**Example**::
fmt::fprintf(cerr, "Don't %s!", "panic");
\endrst
*/
template <typename S, typename... T, typename Char = char_t<S>>
inline auto fprintf(std::basic_ostream<Char>& os, const S& fmt,
const T&... args) -> int {
return vfprintf(os, to_string_view(fmt),
fmt::make_format_args<basic_printf_context_t<Char>>(args...));
}
FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_PRINTF_H_ #endif // FMT_PRINTF_H_
File diff suppressed because it is too large Load Diff
+727
View File
@@ -0,0 +1,727 @@
// Formatting library for C++ - formatters for standard library types
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_STD_H_
#define FMT_STD_H_
#include "format.h"
#include "ostream.h"
#ifndef FMT_MODULE
# include <atomic>
# include <bitset>
# include <complex>
# include <exception>
# include <functional> // std::reference_wrapper
# include <memory>
# include <thread>
# include <type_traits>
# include <typeinfo> // std::type_info
# include <utility> // std::make_index_sequence
// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC.
# if FMT_CPLUSPLUS >= 201703L
# if FMT_HAS_INCLUDE(<filesystem>) && \
(!defined(FMT_CPP_LIB_FILESYSTEM) || FMT_CPP_LIB_FILESYSTEM != 0)
# include <filesystem>
# endif
# if FMT_HAS_INCLUDE(<variant>)
# include <variant>
# endif
# if FMT_HAS_INCLUDE(<optional>)
# include <optional>
# endif
# endif
// Use > instead of >= in the version check because <source_location> may be
// available after C++17 but before C++20 is marked as implemented.
# if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE(<source_location>)
# include <source_location>
# endif
# if FMT_CPLUSPLUS > 202002L && FMT_HAS_INCLUDE(<expected>)
# include <expected>
# endif
#endif // FMT_MODULE
#if FMT_HAS_INCLUDE(<version>)
# include <version>
#endif
// GCC 4 does not support FMT_HAS_INCLUDE.
#if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)
# include <cxxabi.h>
// Android NDK with gabi++ library on some architectures does not implement
// abi::__cxa_demangle().
# ifndef __GABIXX_CXXABI_H__
# define FMT_HAS_ABI_CXA_DEMANGLE
# endif
#endif
#ifdef FMT_CPP_LIB_FILESYSTEM
// Use the provided definition.
#elif defined(__cpp_lib_filesystem)
# define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem
#else
# define FMT_CPP_LIB_FILESYSTEM 0
#endif
#ifdef FMT_CPP_LIB_VARIANT
// Use the provided definition.
#elif defined(__cpp_lib_variant)
# define FMT_CPP_LIB_VARIANT __cpp_lib_variant
#else
# define FMT_CPP_LIB_VARIANT 0
#endif
FMT_BEGIN_NAMESPACE
namespace detail {
#if FMT_CPP_LIB_FILESYSTEM
template <typename Char, typename PathChar>
auto get_path_string(const std::filesystem::path& p,
const std::basic_string<PathChar>& native) {
if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>)
return to_utf8<wchar_t>(native, to_utf8_error_policy::replace);
else
return p.string<Char>();
}
template <typename Char, typename PathChar>
void write_escaped_path(basic_memory_buffer<Char>& quoted,
const std::filesystem::path& p,
const std::basic_string<PathChar>& native) {
if constexpr (std::is_same_v<Char, char> &&
std::is_same_v<PathChar, wchar_t>) {
auto buf = basic_memory_buffer<wchar_t>();
write_escaped_string<wchar_t>(std::back_inserter(buf), native);
bool valid = to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()});
FMT_ASSERT(valid, "invalid utf16");
} else if constexpr (std::is_same_v<Char, PathChar>) {
write_escaped_string<std::filesystem::path::value_type>(
std::back_inserter(quoted), native);
} else {
write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());
}
}
#endif // FMT_CPP_LIB_FILESYSTEM
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
template <typename Char, typename OutputIt, typename T, typename FormatContext>
auto write_escaped_alternative(OutputIt out, const T& v, FormatContext& ctx)
-> OutputIt {
if constexpr (has_to_string_view<T>::value)
return write_escaped_string<Char>(out, detail::to_string_view(v));
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
formatter<std::remove_cv_t<T>, Char> underlying;
maybe_set_debug_format(underlying, true);
return underlying.format(v, ctx);
}
#endif
#if FMT_CPP_LIB_VARIANT
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
template <typename Variant, typename Char> class is_variant_formattable {
template <size_t... Is>
static auto check(std::index_sequence<Is...>) -> std::conjunction<
is_formattable<std::variant_alternative_t<Is, Variant>, Char>...>;
public:
static constexpr bool value = decltype(check(
std::make_index_sequence<std::variant_size<Variant>::value>()))::value;
};
#endif // FMT_CPP_LIB_VARIANT
#if FMT_USE_RTTI
inline auto normalize_libcxx_inline_namespaces(string_view demangled_name_view,
char* begin) -> string_view {
// Normalization of stdlib inline namespace names.
// libc++ inline namespaces.
// std::__1::* -> std::*
// std::__1::__fs::* -> std::*
// libstdc++ inline namespaces.
// std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) {
char* to = begin + 5; // std::
for (const char *from = to, *end = begin + demangled_name_view.size();
from < end;) {
// This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') {
const char* next = from + 1;
while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') {
from = next + 2;
continue;
}
}
*to++ = *from++;
}
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
}
return demangled_name_view;
}
template <class OutputIt>
auto normalize_msvc_abi_name(string_view abi_name_view, OutputIt out)
-> OutputIt {
const string_view demangled_name(abi_name_view);
for (size_t i = 0; i < demangled_name.size(); ++i) {
auto sub = demangled_name;
sub.remove_prefix(i);
if (sub.starts_with("enum ")) {
i += 4;
continue;
}
if (sub.starts_with("class ") || sub.starts_with("union ")) {
i += 5;
continue;
}
if (sub.starts_with("struct ")) {
i += 6;
continue;
}
if (*sub.begin() != ' ') *out++ = *sub.begin();
}
return out;
}
template <typename OutputIt>
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
size_t size = 0;
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = normalize_libcxx_inline_namespaces(
demangled_name_ptr.get(), demangled_name_ptr.get());
} else {
demangled_name_view = string_view(ti.name());
}
return detail::write_bytes<char>(out, demangled_name_view);
# elif FMT_MSC_VERSION && defined(_MSVC_STL_UPDATE)
return normalize_msvc_abi_name(ti.name(), out);
# elif FMT_MSC_VERSION && defined(_LIBCPP_VERSION)
const string_view demangled_name = ti.name();
std::string name_copy(demangled_name.size(), '\0');
// normalize_msvc_abi_name removes class, struct, union etc that MSVC has in
// front of types
name_copy.erase(normalize_msvc_abi_name(demangled_name, name_copy.begin()),
name_copy.end());
// normalize_libcxx_inline_namespaces removes the inline __1, __2, etc
// namespaces libc++ uses for ABI versioning On MSVC ABI + libc++
// environments, we need to eliminate both of them.
const string_view normalized_name =
normalize_libcxx_inline_namespaces(name_copy, name_copy.data());
return detail::write_bytes<char>(out, normalized_name);
# else
return detail::write_bytes<char>(out, string_view(ti.name()));
# endif
}
#endif // FMT_USE_RTTI
template <typename T, typename Enable = void>
struct has_flip : std::false_type {};
template <typename T>
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
: std::true_type {};
template <typename T> struct is_bit_reference_like {
static constexpr bool value = std::is_convertible<T, bool>::value &&
std::is_nothrow_assignable<T, bool>::value &&
has_flip<T>::value;
};
// Workaround for libc++ incompatibility with C++ standard.
// According to the Standard, `bitset::operator[] const` returns bool.
#if defined(_LIBCPP_VERSION) && !defined(FMT_IMPORT_STD)
template <typename C>
struct is_bit_reference_like<std::__bit_const_reference<C>> {
static constexpr bool value = true;
};
#endif
template <typename T, typename Enable = void>
struct has_format_as : std::false_type {};
template <typename T>
struct has_format_as<T, void_t<decltype(format_as(std::declval<const T&>()))>>
: std::true_type {};
template <typename T, typename Enable = void>
struct has_format_as_member : std::false_type {};
template <typename T>
struct has_format_as_member<
T, void_t<decltype(formatter<T>::format_as(std::declval<const T&>()))>>
: std::true_type {};
} // namespace detail
template <typename T, typename Deleter>
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
return p.get();
}
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
return p.get();
}
#if FMT_CPP_LIB_FILESYSTEM
template <typename Char> struct formatter<std::filesystem::path, Char> {
private:
format_specs specs_;
detail::arg_ref<Char> width_ref_;
bool debug_ = false;
char path_type_ = 0;
public:
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {
auto it = ctx.begin(), end = ctx.end();
if (it == end) return it;
it = detail::parse_align(it, end, specs_);
if (it == end) return it;
Char c = *it;
if ((c >= '0' && c <= '9') || c == '{')
it = detail::parse_width(it, end, specs_, width_ref_, ctx);
if (it != end && *it == '?') {
debug_ = true;
++it;
}
if (it != end && (*it == 'g')) path_type_ = detail::to_ascii(*it++);
return it;
}
template <typename FormatContext>
auto format(const std::filesystem::path& p, FormatContext& ctx) const {
auto specs = specs_;
auto path_string =
!path_type_ ? p.native()
: p.generic_string<std::filesystem::path::value_type>();
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx);
if (!debug_) {
auto s = detail::get_path_string<Char>(p, path_string);
return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
}
auto quoted = basic_memory_buffer<Char>();
detail::write_escaped_path(quoted, p, path_string);
return detail::write(ctx.out(),
basic_string_view<Char>(quoted.data(), quoted.size()),
specs);
}
};
class path : public std::filesystem::path {
public:
auto display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{}"), base);
}
auto system_string() const -> std::string { return string(); }
auto generic_display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{:g}"), base);
}
auto generic_system_string() const -> std::string { return generic_string(); }
};
#endif // FMT_CPP_LIB_FILESYSTEM
template <size_t N, typename Char>
struct formatter<std::bitset<N>, Char>
: nested_formatter<basic_string_view<Char>, Char> {
private:
// This is a functor because C++11 doesn't support generic lambdas.
struct writer {
const std::bitset<N>& bs;
template <typename OutputIt>
FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
for (auto pos = N; pos > 0; --pos)
out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));
return out;
}
};
public:
template <typename FormatContext>
auto format(const std::bitset<N>& bs, FormatContext& ctx) const
-> decltype(ctx.out()) {
return this->write_padded(ctx, writer{bs});
}
};
template <typename Char>
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
#ifdef __cpp_lib_optional
template <typename T, typename Char>
struct formatter<std::optional<T>, Char,
std::enable_if_t<is_formattable<T, Char>::value>> {
private:
formatter<std::remove_cv_t<T>, Char> underlying_;
static constexpr basic_string_view<Char> optional =
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
'('>{};
static constexpr basic_string_view<Char> none =
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {
detail::maybe_set_debug_format(underlying_, true);
return underlying_.parse(ctx);
}
template <typename FormatContext>
auto format(const std::optional<T>& opt, FormatContext& ctx) const
-> decltype(ctx.out()) {
if (!opt) return detail::write<Char>(ctx.out(), none);
auto out = ctx.out();
out = detail::write<Char>(out, optional);
ctx.advance_to(out);
out = underlying_.format(*opt, ctx);
return detail::write(out, ')');
}
};
#endif // __cpp_lib_optional
#ifdef __cpp_lib_expected
template <typename T, typename E, typename Char>
struct formatter<std::expected<T, E>, Char,
std::enable_if_t<(std::is_void<T>::value ||
is_formattable<T, Char>::value) &&
is_formattable<E, Char>::value>> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return ctx.begin();
}
template <typename FormatContext>
auto format(const std::expected<T, E>& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
if (value.has_value()) {
out = detail::write<Char>(out, "expected(");
if constexpr (!std::is_void<T>::value)
out = detail::write_escaped_alternative<Char>(out, *value, ctx);
} else {
out = detail::write<Char>(out, "unexpected(");
out = detail::write_escaped_alternative<Char>(out, value.error(), ctx);
}
*out++ = ')';
return out;
}
};
#endif // __cpp_lib_expected
#ifdef __cpp_lib_source_location
template <> struct formatter<std::source_location> {
FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const std::source_location& loc, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write(out, loc.file_name());
out = detail::write(out, ':');
out = detail::write<char>(out, loc.line());
out = detail::write(out, ':');
out = detail::write<char>(out, loc.column());
out = detail::write(out, ": ");
out = detail::write(out, loc.function_name());
return out;
}
};
#endif
#if FMT_CPP_LIB_VARIANT
template <typename T> struct is_variant_like {
static constexpr bool value = detail::is_variant_like_<T>::value;
};
template <typename Char> struct formatter<std::monostate, Char> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return ctx.begin();
}
template <typename FormatContext>
auto format(const std::monostate&, FormatContext& ctx) const
-> decltype(ctx.out()) {
return detail::write<Char>(ctx.out(), "monostate");
}
};
template <typename Variant, typename Char>
struct formatter<Variant, Char,
std::enable_if_t<std::conjunction_v<
is_variant_like<Variant>,
detail::is_variant_formattable<Variant, Char>>>> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return ctx.begin();
}
template <typename FormatContext>
auto format(const Variant& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write<Char>(out, "variant(");
FMT_TRY {
std::visit(
[&](const auto& v) {
out = detail::write_escaped_alternative<Char>(out, v, ctx);
},
value);
}
FMT_CATCH(const std::bad_variant_access&) {
detail::write<Char>(out, "valueless by exception");
}
*out++ = ')';
return out;
}
};
#endif // FMT_CPP_LIB_VARIANT
template <> struct formatter<std::error_code> {
private:
format_specs specs_;
detail::arg_ref<char> width_ref_;
bool debug_ = false;
public:
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
auto it = ctx.begin(), end = ctx.end();
if (it == end) return it;
it = detail::parse_align(it, end, specs_);
char c = *it;
if (it != end && ((c >= '0' && c <= '9') || c == '{'))
it = detail::parse_width(it, end, specs_, width_ref_, ctx);
if (it != end && *it == '?') {
debug_ = true;
++it;
}
if (it != end && *it == 's') {
specs_.set_type(presentation_type::string);
++it;
}
return it;
}
template <typename FormatContext>
FMT_CONSTEXPR20 auto format(const std::error_code& ec,
FormatContext& ctx) const -> decltype(ctx.out()) {
auto specs = specs_;
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx);
auto buf = memory_buffer();
if (specs_.type() == presentation_type::string) {
buf.append(ec.message());
} else {
buf.append(string_view(ec.category().name()));
buf.push_back(':');
detail::write<char>(appender(buf), ec.value());
}
auto quoted = memory_buffer();
auto str = string_view(buf.data(), buf.size());
if (debug_) {
detail::write_escaped_string<char>(std::back_inserter(quoted), str);
str = string_view(quoted.data(), quoted.size());
}
return detail::write<char>(ctx.out(), str, specs);
}
};
#if FMT_USE_RTTI
template <> struct formatter<std::type_info> {
public:
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
return ctx.begin();
}
template <typename Context>
auto format(const std::type_info& ti, Context& ctx) const
-> decltype(ctx.out()) {
return detail::write_demangled_name(ctx.out(), ti);
}
};
#endif // FMT_USE_RTTI
template <typename T>
struct formatter<
T, char,
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
private:
bool with_typename_ = false;
public:
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
auto it = ctx.begin();
auto end = ctx.end();
if (it == end || *it == '}') return it;
if (*it == 't') {
++it;
with_typename_ = FMT_USE_RTTI != 0;
}
return it;
}
template <typename Context>
auto format(const std::exception& ex, Context& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
#if FMT_USE_RTTI
if (with_typename_) {
out = detail::write_demangled_name(out, typeid(ex));
*out++ = ':';
*out++ = ' ';
}
#endif
return detail::write_bytes<char>(out, string_view(ex.what()));
}
};
// We can't use std::vector<bool, Allocator>::reference and
// std::bitset<N>::reference because the compiler can't deduce Allocator and N
// in partial specialization.
template <typename BitRef, typename Char>
struct formatter<BitRef, Char,
enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
: formatter<bool, Char> {
template <typename FormatContext>
FMT_CONSTEXPR auto format(const BitRef& v, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<bool, Char>::format(v, ctx);
}
};
template <typename T, typename Char>
struct formatter<std::atomic<T>, Char,
enable_if_t<is_formattable<T, Char>::value>>
: formatter<T, Char> {
template <typename FormatContext>
auto format(const std::atomic<T>& v, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<T, Char>::format(v.load(), ctx);
}
};
#ifdef __cpp_lib_atomic_flag_test
template <typename Char>
struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
template <typename FormatContext>
auto format(const std::atomic_flag& v, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<bool, Char>::format(v.test(), ctx);
}
};
#endif // __cpp_lib_atomic_flag_test
template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
private:
detail::dynamic_format_specs<Char> specs_;
template <typename FormatContext, typename OutputIt>
FMT_CONSTEXPR auto do_format(const std::complex<T>& c,
detail::dynamic_format_specs<Char>& specs,
FormatContext& ctx, OutputIt out) const
-> OutputIt {
if (c.real() != 0) {
*out++ = Char('(');
out = detail::write<Char>(out, c.real(), specs, ctx.locale());
specs.set_sign(sign::plus);
out = detail::write<Char>(out, c.imag(), specs, ctx.locale());
if (!detail::isfinite(c.imag())) *out++ = Char(' ');
*out++ = Char('i');
*out++ = Char(')');
return out;
}
out = detail::write<Char>(out, c.imag(), specs, ctx.locale());
if (!detail::isfinite(c.imag())) *out++ = Char(' ');
*out++ = Char('i');
return out;
}
public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin();
return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
detail::type_constant<T, Char>::value);
}
template <typename FormatContext>
auto format(const std::complex<T>& c, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto specs = specs_;
if (specs.dynamic()) {
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width,
specs.width_ref, ctx);
detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision,
specs.precision_ref, ctx);
}
if (specs.width == 0) return do_format(c, specs, ctx, ctx.out());
auto buf = basic_memory_buffer<Char>();
auto outer_specs = format_specs();
outer_specs.width = specs.width;
outer_specs.copy_fill_from(specs);
outer_specs.set_align(specs.align());
specs.width = 0;
specs.set_fill({});
specs.set_align(align::none);
do_format(c, specs, ctx, basic_appender<Char>(buf));
return detail::write<Char>(ctx.out(),
basic_string_view<Char>(buf.data(), buf.size()),
outer_specs);
}
};
template <typename T, typename Char>
struct formatter<std::reference_wrapper<T>, Char,
// Guard against format_as because reference_wrapper is
// implicitly convertible to T&.
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value &&
!detail::has_format_as<T>::value &&
!detail::has_format_as_member<T>::value>>
: formatter<remove_cvref_t<T>, Char> {
template <typename FormatContext>
auto format(std::reference_wrapper<T> ref, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<remove_cvref_t<T>, Char>::format(ref.get(), ctx);
}
};
FMT_END_NAMESPACE
#endif // FMT_STD_H_
+265 -120
View File
@@ -5,53 +5,136 @@
// //
// For the license information refer to format.h. // For the license information refer to format.h.
#ifndef FMT_WCHAR_H_ #ifndef FMT_XCHAR_H_
#define FMT_WCHAR_H_ #define FMT_XCHAR_H_
#include <cwchar>
#include "color.h"
#include "format.h" #include "format.h"
#include "ostream.h"
#include "ranges.h"
#ifndef FMT_MODULE
# include <cwchar>
# if FMT_USE_LOCALE
# include <locale>
# endif
#endif
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
template <typename T> template <typename T>
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>; using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
template <typename S, typename = void> struct format_string_char {};
template <typename S>
struct format_string_char<
S, void_t<decltype(sizeof(detail::to_string_view(std::declval<S>())))>> {
using type = char_t<S>;
};
template <typename S>
struct format_string_char<
S, enable_if_t<std::is_base_of<detail::compile_string, S>::value>> {
using type = typename S::char_type;
};
template <typename S>
using format_string_char_t = typename format_string_char<S>::type;
inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
const format_specs& specs, locale_ref loc) -> bool {
#if FMT_USE_LOCALE
auto& numpunct =
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
auto separator = std::wstring();
auto grouping = numpunct.grouping();
if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep());
return value.visit(loc_writer<wchar_t>{out, specs, separator, grouping, {}});
#endif
return false;
} }
FMT_MODULE_EXPORT_BEGIN template <typename Char>
void vformat_to(buffer<Char>& buf, basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args,
locale_ref loc = {}) {
static_assert(!std::is_same<Char, char>::value, "");
auto out = basic_appender<Char>(buf);
parse_format_string(
fmt, format_handler<Char>{parse_context<Char>(fmt), {out, args, loc}});
}
} // namespace detail
FMT_BEGIN_EXPORT
using wstring_view = basic_string_view<wchar_t>; using wstring_view = basic_string_view<wchar_t>;
using wformat_parse_context = basic_format_parse_context<wchar_t>; using wformat_parse_context = parse_context<wchar_t>;
using wformat_context = buffer_context<wchar_t>; using wformat_context = buffered_context<wchar_t>;
using wformat_args = basic_format_args<wformat_context>; using wformat_args = basic_format_args<wformat_context>;
using wmemory_buffer = basic_memory_buffer<wchar_t>; using wmemory_buffer = basic_memory_buffer<wchar_t>;
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 template <typename Char, typename... T> struct basic_fstring {
// Workaround broken conversion on older gcc. private:
template <typename... Args> using wformat_string = wstring_view; basic_string_view<Char> str_;
#else
template <typename... Args>
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
#endif
template <typename... Args> static constexpr int num_static_named_args =
constexpr format_arg_store<wformat_context, Args...> make_wformat_args( detail::count_static_named_args<T...>();
const Args&... args) {
return {args...}; using checker = detail::format_string_checker<
Char, static_cast<int>(sizeof...(T)), num_static_named_args,
num_static_named_args != detail::count_named_args<T...>()>;
using arg_pack = detail::arg_pack<T...>;
public:
using t = basic_fstring;
template <typename S,
FMT_ENABLE_IF(
std::is_convertible<const S&, basic_string_view<Char>>::value)>
FMT_CONSTEVAL FMT_ALWAYS_INLINE basic_fstring(const S& s) : str_(s) {
if (FMT_USE_CONSTEVAL)
detail::parse_format_string<Char>(s, checker(s, arg_pack()));
}
template <typename S,
FMT_ENABLE_IF(std::is_base_of<detail::compile_string, S>::value&&
std::is_same<typename S::char_type, Char>::value)>
FMT_ALWAYS_INLINE basic_fstring(const S&) : str_(S()) {
FMT_CONSTEXPR auto sv = basic_string_view<Char>(S());
FMT_CONSTEXPR int ignore =
(parse_format_string(sv, checker(sv, arg_pack())), 0);
detail::ignore_unused(ignore);
}
basic_fstring(runtime_format_string<Char> fmt) : str_(fmt.str) {}
operator basic_string_view<Char>() const { return str_; }
auto get() const -> basic_string_view<Char> { return str_; }
};
template <typename Char, typename... T>
using basic_format_string = basic_fstring<Char, T...>;
template <typename... T>
using wformat_string = typename basic_format_string<wchar_t, T...>::t;
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
return {{s}};
} }
template <typename... T>
constexpr auto make_wformat_args(T&... args)
-> decltype(fmt::make_format_args<wformat_context>(args...)) {
return fmt::make_format_args<wformat_context>(args...);
}
#if !FMT_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals { inline namespace literals {
constexpr auto operator"" _format(const wchar_t* s, size_t n) inline auto operator""_a(const wchar_t* s, size_t) -> detail::udl_arg<wchar_t> {
-> detail::udl_formatter<wchar_t> {
return {{s, n}};
}
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
constexpr detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) {
return {s}; return {s};
} }
#endif
} // namespace literals } // namespace literals
#endif
template <typename It, typename Sentinel> template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, wstring_view sep) auto join(It begin, Sentinel end, wstring_view sep)
@@ -59,9 +142,9 @@ auto join(It begin, Sentinel end, wstring_view sep)
return {begin, end, sep}; return {begin, end, sep};
} }
template <typename Range> template <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>
auto join(Range&& range, wstring_view sep) auto join(Range&& range, wstring_view sep)
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>, -> join_view<decltype(std::begin(range)), decltype(std::end(range)),
wchar_t> { wchar_t> {
return join(std::begin(range), std::end(range), sep); return join(std::begin(range), std::end(range), sep);
} }
@@ -72,118 +155,144 @@ auto join(std::initializer_list<T> list, wstring_view sep)
return join(std::begin(list), std::end(list), sep); return join(std::begin(list), std::end(list), sep);
} }
template <typename Locale, typename S, typename Char = char_t<S>, template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
FMT_ENABLE_IF(detail::is_locale<Locale>::value&& auto join(const Tuple& tuple, basic_string_view<wchar_t> sep)
detail::is_exotic_char<Char>::value)> -> tuple_join_view<Tuple, wchar_t> {
inline std::basic_string<Char> vformat( return {tuple, sep};
const Locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
return detail::vformat(loc, to_string_view(format_str), args);
} }
template <typename Locale, typename S, typename... Args, template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
typename Char = char_t<S>, auto vformat(basic_string_view<Char> fmt,
FMT_ENABLE_IF(detail::is_locale<Locale>::value&& basic_format_args<buffered_context<Char>> args)
detail::is_exotic_char<Char>::value)> -> std::basic_string<Char> {
inline std::basic_string<Char> format(const Locale& loc, const S& format_str, auto buf = basic_memory_buffer<Char>();
Args&&... args) { detail::vformat_to(buf, fmt, args);
return detail::vformat(loc, to_string_view(format_str), return {buf.data(), buf.size()};
fmt::make_args_checked<Args...>(format_str, args...));
} }
template <typename OutputIt, typename S, typename Char = char_t<S>, template <typename... T>
auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring {
return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...));
}
template <typename OutputIt, typename... T>
auto format_to(OutputIt out, wformat_string<T...> fmt, T&&... args)
-> OutputIt {
return vformat_to(out, fmt::wstring_view(fmt),
fmt::make_wformat_args(args...));
}
// Pass char_t as a default template parameter instead of using
// std::basic_string<char_t<S>> to reduce the symbol size.
template <typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
!std::is_same<Char, wchar_t>::value)>
auto format(const S& fmt, T&&... args) -> std::basic_string<Char> {
return vformat(detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...));
}
template <typename S, typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
inline auto vformat(locale_ref loc, const S& fmt,
basic_format_args<buffered_context<Char>> args)
-> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, detail::to_string_view(fmt), args, loc);
return {buf.data(), buf.size()};
}
template <typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
inline auto format(locale_ref loc, const S& fmt, T&&... args)
-> std::basic_string<Char> {
return vformat(loc, detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...));
}
template <typename OutputIt, typename S,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
auto vformat_to(OutputIt out, const S& format_str, auto vformat_to(OutputIt out, const S& fmt,
basic_format_args<buffer_context<type_identity_t<Char>>> args) basic_format_args<buffered_context<Char>> args) -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, detail::to_string_view(fmt), args);
return detail::get_iterator(buf, out);
}
template <typename OutputIt, typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value &&
!std::is_same<Char, char>::value &&
!std::is_same<Char, wchar_t>::value)>
inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {
return vformat_to(out, detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...));
}
template <typename S, typename OutputIt, typename... Args,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to(OutputIt out, locale_ref loc, const S& fmt,
basic_format_args<buffered_context<Char>> args)
-> OutputIt { -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out); auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, to_string_view(format_str), args); vformat_to(buf, detail::to_string_view(fmt), args, loc);
return detail::get_iterator(buf); return detail::get_iterator(buf, out);
} }
template <typename OutputIt, typename S, typename... Args, template <typename OutputIt, typename S, typename... T,
typename Char = char_t<S>, typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& bool enable = detail::is_output_iterator<OutputIt, Char>::value &&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value>
inline auto format_to(OutputIt out, const S& fmt, Args&&... args) -> OutputIt { inline auto format_to(OutputIt out, locale_ref loc, const S& fmt, T&&... args)
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...); -> typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, to_string_view(fmt), vargs); return vformat_to(out, loc, detail::to_string_view(fmt),
} fmt::make_format_args<buffered_context<Char>>(args...));
template <typename S, typename... Args, typename Char, size_t SIZE,
typename Allocator, FMT_ENABLE_IF(detail::is_string<S>::value)>
FMT_DEPRECATED auto format_to(basic_memory_buffer<Char, SIZE, Allocator>& buf,
const S& format_str, Args&&... args) ->
typename buffer_context<Char>::iterator {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
detail::vformat_to(buf, to_string_view(format_str), vargs);
return detail::buffer_appender<Char>(buf);
}
template <typename Locale, typename S, typename OutputIt, typename... Args,
typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline OutputIt vformat_to(
OutputIt out, const Locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
auto&& buf = detail::get_buffer<Char>(out);
vformat_to(buf, to_string_view(format_str), args, detail::locale_ref(loc));
return detail::get_iterator(buf);
}
template <
typename OutputIt, typename Locale, typename S, typename... Args,
typename Char = char_t<S>,
bool enable = detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value>
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
Args&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
return vformat_to(out, loc, to_string_view(format_str), vargs);
} }
template <typename OutputIt, typename Char, typename... Args, template <typename OutputIt, typename Char, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
inline auto vformat_to_n( inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view<Char> fmt,
OutputIt out, size_t n, basic_string_view<Char> format_str, basic_format_args<buffered_context<Char>> args)
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> format_to_n_result<OutputIt> { -> format_to_n_result<OutputIt> {
detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out, using traits = detail::fixed_buffer_traits;
n); auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
detail::vformat_to(buf, format_str, args); detail::vformat_to(buf, fmt, args);
return {buf.out(), buf.count()}; return {buf.out(), buf.count()};
} }
template <typename OutputIt, typename S, typename... Args, template <typename OutputIt, typename S, typename... T,
typename Char = char_t<S>, typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
inline auto format_to_n(OutputIt out, size_t n, const S& fmt, inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
const Args&... args) -> format_to_n_result<OutputIt> { -> format_to_n_result<OutputIt> {
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...); return vformat_to_n(out, n, fmt::basic_string_view<Char>(fmt),
return vformat_to_n(out, n, to_string_view(fmt), vargs); fmt::make_format_args<buffered_context<Char>>(args...));
} }
template <typename S, typename... Args, typename Char = char_t<S>, template <typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)> FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
inline auto formatted_size(const S& fmt, Args&&... args) -> size_t { inline auto formatted_size(const S& fmt, T&&... args) -> size_t {
detail::counting_buffer<Char> buf; auto buf = detail::counting_buffer<Char>();
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...); detail::vformat_to(buf, detail::to_string_view(fmt),
detail::vformat_to(buf, to_string_view(fmt), vargs); fmt::make_format_args<buffered_context<Char>>(args...));
return buf.count(); return buf.count();
} }
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) { inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
wmemory_buffer buffer; auto buf = wmemory_buffer();
detail::vformat_to(buffer, fmt, args); detail::vformat_to(buf, fmt, args);
buffer.push_back(L'\0'); buf.push_back(L'\0');
if (std::fputws(buffer.data(), f) == -1) if (std::fputws(buf.data(), f) == -1)
FMT_THROW(system_error(errno, "cannot write to file")); FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
} }
inline void vprint(wstring_view fmt, wformat_args args) { inline void vprint(wstring_view fmt, wformat_args args) {
@@ -192,20 +301,56 @@ inline void vprint(wstring_view fmt, wformat_args args) {
template <typename... T> template <typename... T>
void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) { void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
return vprint(f, wstring_view(fmt), make_wformat_args(args...)); return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...));
} }
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) { template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
return vprint(wstring_view(fmt), make_wformat_args(args...)); return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));
} }
/** template <typename... T>
Converts *value* to ``std::wstring`` using the default format for type *T*. void println(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
*/ return print(f, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
template <typename T> inline std::wstring to_wstring(const T& value) { }
template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
inline auto vformat(text_style ts, wstring_view fmt, wformat_args args)
-> std::wstring {
auto buf = wmemory_buffer();
detail::vformat_to(buf, ts, fmt, args);
return {buf.data(), buf.size()};
}
template <typename... T>
inline auto format(text_style ts, wformat_string<T...> fmt, T&&... args)
-> std::wstring {
return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...));
}
inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) {
auto buffer = basic_memory_buffer<wchar_t>();
detail::vformat_to(buffer, fmt, args);
detail::write_buffer(os, buffer);
}
template <typename... T>
void print(std::wostream& os, wformat_string<T...> fmt, T&&... args) {
vprint(os, fmt, fmt::make_format_args<buffered_context<wchar_t>>(args...));
}
template <typename... T>
void println(std::wostream& os, wformat_string<T...> fmt, T&&... args) {
print(os, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
/// Converts `value` to `std::wstring` using the default format for type `T`.
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
return format(FMT_STRING(L"{}"), value); return format(FMT_STRING(L"{}"), value);
} }
FMT_MODULE_EXPORT_END FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_WCHAR_H_ #endif // FMT_XCHAR_H_
+109 -54
View File
@@ -1,45 +1,67 @@
module; module;
// put all implementation-provided headers into the global module fragment
// to prevent attachment to this module #define FMT_MODULE
#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
# define _CRT_SECURE_NO_WARNINGS #ifdef _MSVC_LANG
#endif # define FMT_CPLUSPLUS _MSVC_LANG
#if !defined(WIN32_LEAN_AND_MEAN) && defined(_WIN32) #else
# define WIN32_LEAN_AND_MEAN # define FMT_CPLUSPLUS __cplusplus
#endif #endif
#include <algorithm> // Put all implementation-provided headers into the global module fragment
#include <cctype> // to prevent attachment to this module.
#ifndef FMT_IMPORT_STD
# include <algorithm>
# include <bitset>
# include <chrono>
# include <cmath>
# include <complex>
# include <cstddef>
# include <cstdint>
# include <cstdio>
# include <cstdlib>
# include <cstring>
# include <ctime>
# include <exception>
# if FMT_CPLUSPLUS > 202002L
# include <expected>
# endif
# include <filesystem>
# include <fstream>
# include <functional>
# include <iterator>
# include <limits>
# include <locale>
# include <memory>
# include <optional>
# include <ostream>
# include <source_location>
# include <stdexcept>
# include <string>
# include <string_view>
# include <system_error>
# include <thread>
# include <type_traits>
# include <typeinfo>
# include <utility>
# include <variant>
# include <vector>
#else
# include <limits.h>
# include <stdint.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <time.h>
#endif
#include <cerrno> #include <cerrno>
#include <chrono>
#include <climits> #include <climits>
#include <clocale> #include <version>
#include <cmath>
#include <cstdarg>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cwchar>
#include <exception>
#include <functional>
#include <iterator>
#include <limits>
#include <locale>
#include <memory>
#include <ostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <type_traits>
#include <utility>
#include <vector>
#if _MSC_VER #if __has_include(<cxxabi.h>)
# include <cxxabi.h>
#endif
#if defined(_MSC_VER) || defined(__MINGW32__)
# include <intrin.h> # include <intrin.h>
#endif #endif
#if defined __APPLE__ || defined(__FreeBSD__) #if defined __APPLE__ || defined(__FreeBSD__)
@@ -61,40 +83,73 @@ module;
# endif # endif
#endif #endif
#ifdef _WIN32 #ifdef _WIN32
# if defined(__GLIBCXX__)
# include <ext/stdio_filebuf.h>
# include <ext/stdio_sync_filebuf.h>
# endif
# define WIN32_LEAN_AND_MEAN
# include <windows.h> # include <windows.h>
#endif #endif
export module fmt; export module fmt;
#define FMT_MODULE_EXPORT export #ifdef FMT_IMPORT_STD
#define FMT_MODULE_EXPORT_BEGIN export { import std;
#define FMT_MODULE_EXPORT_END }
#define FMT_BEGIN_DETAIL_NAMESPACE \
} \
namespace detail {
#define FMT_END_DETAIL_NAMESPACE \
} \
export {
#if defined(_MSC_FULL_VER) && _MSC_FULL_VER > 192930036
#define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 0
#endif #endif
// all library-provided declarations and definitions #define FMT_EXPORT export
// must be in the module purview to be exported #define FMT_BEGIN_EXPORT export {
#define FMT_END_EXPORT }
// If you define FMT_ATTACH_TO_GLOBAL_MODULE
// - all declarations are detached from module 'fmt'
// - the module behaves like a traditional static library, too
// - all library symbols are mangled traditionally
// - you can mix TUs with either importing or #including the {fmt} API
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
extern "C++" {
#endif
#ifndef FMT_OS
# define FMT_OS 1
#endif
// All library-provided declarations and definitions must be in the module
// purview to be exported.
#include "fmt/args.h" #include "fmt/args.h"
#include "fmt/chrono.h" #include "fmt/chrono.h"
#include "fmt/color.h" #include "fmt/color.h"
#include "fmt/compile.h" #include "fmt/compile.h"
#include "fmt/format.h" #include "fmt/format.h"
#include "fmt/os.h" #if FMT_OS
# include "fmt/os.h"
#endif
#include "fmt/ostream.h"
#include "fmt/printf.h" #include "fmt/printf.h"
#include "fmt/ranges.h"
#include "fmt/std.h"
#include "fmt/xchar.h" #include "fmt/xchar.h"
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
}
#endif
// gcc doesn't yet implement private module fragments // gcc doesn't yet implement private module fragments
#if !FMT_GCC_VERSION #if !FMT_GCC_VERSION
module : private; module :private;
#endif #endif
#include "format.cc" #ifdef FMT_ATTACH_TO_GLOBAL_MODULE
#include "os.cc" extern "C++" {
#endif
#if FMT_HAS_INCLUDE("format.cc")
# include "format.cc"
#endif
#if FMT_OS && FMT_HAS_INCLUDE("os.cc")
# include "os.cc"
#endif
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
}
#endif
+21 -53
View File
@@ -8,68 +8,36 @@
#include "fmt/format-inl.h" #include "fmt/format-inl.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
#if FMT_USE_LOCALE
template FMT_API locale_ref::locale_ref(const std::locale& loc); // DEPRECATED!
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
#endif
namespace detail { namespace detail {
template <typename T> template FMT_API auto dragonbox::to_decimal(float x) noexcept
int format_float(char* buf, std::size_t size, const char* format, int precision, -> dragonbox::decimal_fp<float>;
T value) { template FMT_API auto dragonbox::to_decimal(double x) noexcept
#ifdef FMT_FUZZ -> dragonbox::decimal_fp<double>;
if (precision > 100000)
throw std::runtime_error(
"fuzz mode - avoid large allocation inside snprintf");
#endif
// Suppress the warning about nonliteral format string.
int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF;
return precision < 0 ? snprintf_ptr(buf, size, format, value)
: snprintf_ptr(buf, size, format, precision, value);
}
template FMT_API dragonbox::decimal_fp<float> dragonbox::to_decimal(float x)
FMT_NOEXCEPT;
template FMT_API dragonbox::decimal_fp<double> dragonbox::to_decimal(double x)
FMT_NOEXCEPT;
} // namespace detail
// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
int (*instantiate_format_float)(double, int, detail::float_specs,
detail::buffer<char>&) = detail::format_float;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template FMT_API detail::locale_ref::locale_ref(const std::locale& loc);
template FMT_API std::locale detail::locale_ref::get<std::locale>() const;
#endif
// Explicit instantiations for char. // Explicit instantiations for char.
template FMT_API std::string detail::grouping_impl<char>(locale_ref); template FMT_API auto thousands_sep_impl(locale_ref)
template FMT_API char detail::thousands_sep_impl(locale_ref); -> thousands_sep_result<char>;
template FMT_API char detail::decimal_point_impl(locale_ref); template FMT_API auto decimal_point_impl(locale_ref) -> char;
template FMT_API void detail::buffer<char>::append(const char*, const char*); // DEPRECATED!
template FMT_API void buffer<char>::append(const char*, const char*);
template FMT_API void detail::vformat_to(
detail::buffer<char>&, string_view,
basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);
template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
detail::buffer<char>&);
template FMT_API int detail::snprintf_float(long double, int,
detail::float_specs,
detail::buffer<char>&);
template FMT_API int detail::format_float(double, int, detail::float_specs,
detail::buffer<char>&);
template FMT_API int detail::format_float(long double, int, detail::float_specs,
detail::buffer<char>&);
// Explicit instantiations for wchar_t. // Explicit instantiations for wchar_t.
template FMT_API std::string detail::grouping_impl<wchar_t>(locale_ref); template FMT_API auto thousands_sep_impl(locale_ref)
template FMT_API wchar_t detail::thousands_sep_impl(locale_ref); -> thousands_sep_result<wchar_t>;
template FMT_API wchar_t detail::decimal_point_impl(locale_ref); template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;
template FMT_API void detail::buffer<wchar_t>::append(const wchar_t*, // DEPRECATED!
const wchar_t*); template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*);
template struct detail::basic_data<void>;
} // namespace detail
FMT_END_NAMESPACE FMT_END_NAMESPACE
+170 -132
View File
@@ -12,43 +12,51 @@
#include "fmt/os.h" #include "fmt/os.h"
#include <climits> #ifndef FMT_MODULE
# include <climits>
#if FMT_USE_FCNTL # if FMT_USE_FCNTL
# include <sys/stat.h> # include <sys/stat.h>
# include <sys/types.h> # include <sys/types.h>
# ifndef _WIN32 # ifdef _WRS_KERNEL // VxWorks7 kernel
# include <unistd.h> # include <ioLib.h> // getpagesize
# else
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <io.h>
# define O_CREAT _O_CREAT
# define O_TRUNC _O_TRUNC
# ifndef S_IRUSR
# define S_IRUSR _S_IREAD
# endif # endif
# ifndef S_IWUSR # ifndef _WIN32
# define S_IWUSR _S_IWRITE # include <unistd.h>
# endif # else
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <io.h>
# endif // _WIN32
# endif // FMT_USE_FCNTL
# ifdef __MINGW32__ # ifdef _WIN32
# define _SH_DENYNO 0x40 # include <windows.h>
# endif # endif
# endif // _WIN32
#endif // FMT_USE_FCNTL
#ifdef _WIN32
# include <windows.h>
#endif #endif
#ifdef fileno #ifdef _WIN32
# undef fileno # ifndef S_IRUSR
# define S_IRUSR _S_IREAD
# endif
# ifndef S_IWUSR
# define S_IWUSR _S_IWRITE
# endif
# ifndef S_IRGRP
# define S_IRGRP 0
# endif
# ifndef S_IWGRP
# define S_IWGRP 0
# endif
# ifndef S_IROTH
# define S_IROTH 0
# endif
# ifndef S_IWOTH
# define S_IWOTH 0
# endif
#endif #endif
namespace { namespace {
@@ -58,48 +66,20 @@ using rwresult = int;
// On Windows the count argument to read and write is unsigned, so convert // On Windows the count argument to read and write is unsigned, so convert
// it from size_t preventing integer overflow. // it from size_t preventing integer overflow.
inline unsigned convert_rwcount(std::size_t count) { inline unsigned convert_rwcount(size_t count) {
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX; return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
} }
#elif FMT_USE_FCNTL #elif FMT_USE_FCNTL
// Return type of read and write functions. // Return type of read and write functions.
using rwresult = ssize_t; using rwresult = ssize_t;
inline std::size_t convert_rwcount(std::size_t count) { return count; } inline auto convert_rwcount(size_t count) -> size_t { return count; }
#endif #endif
} // namespace } // namespace
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
#ifdef _WIN32 #ifdef _WIN32
detail::utf16_to_utf8::utf16_to_utf8(basic_string_view<wchar_t> s) {
if (int error_code = convert(s)) {
FMT_THROW(windows_error(error_code,
"cannot convert string from UTF-16 to UTF-8"));
}
}
int detail::utf16_to_utf8::convert(basic_string_view<wchar_t> s) {
if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
int s_size = static_cast<int>(s.size());
if (s_size == 0) {
// WideCharToMultiByte does not support zero length, handle separately.
buffer_.resize(1);
buffer_[0] = 0;
return 0;
}
int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, nullptr, 0,
nullptr, nullptr);
if (length == 0) return GetLastError();
buffer_.resize(length + 1);
length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, &buffer_[0],
length, nullptr, nullptr);
if (length == 0) return GetLastError();
buffer_[length] = 0;
return 0;
}
namespace detail { namespace detail {
class system_message { class system_message {
@@ -109,7 +89,7 @@ class system_message {
unsigned long result_; unsigned long result_;
wchar_t* message_; wchar_t* message_;
static bool is_whitespace(wchar_t c) FMT_NOEXCEPT { static bool is_whitespace(wchar_t c) noexcept {
return c == L' ' || c == L'\n' || c == L'\r' || c == L'\t' || c == L'\0'; return c == L' ' || c == L'\n' || c == L'\r' || c == L'\t' || c == L'\0';
} }
@@ -128,20 +108,20 @@ class system_message {
} }
} }
~system_message() { LocalFree(message_); } ~system_message() { LocalFree(message_); }
explicit operator bool() const FMT_NOEXCEPT { return result_ != 0; } explicit operator bool() const noexcept { return result_ != 0; }
operator basic_string_view<wchar_t>() const FMT_NOEXCEPT { operator basic_string_view<wchar_t>() const noexcept {
return basic_string_view<wchar_t>(message_, result_); return basic_string_view<wchar_t>(message_, result_);
} }
}; };
class utf8_system_category final : public std::error_category { class utf8_system_category final : public std::error_category {
public: public:
const char* name() const FMT_NOEXCEPT override { return "system"; } const char* name() const noexcept override { return "system"; }
std::string message(int error_code) const override { std::string message(int error_code) const override {
system_message msg(error_code); auto&& msg = system_message(error_code);
if (msg) { if (msg) {
utf16_to_utf8 utf8_message; auto utf8_message = to_utf8<wchar_t>();
if (utf8_message.convert(msg) == ERROR_SUCCESS) { if (utf8_message.convert(msg)) {
return utf8_message.str(); return utf8_message.str();
} }
} }
@@ -151,7 +131,7 @@ class utf8_system_category final : public std::error_category {
} // namespace detail } // namespace detail
FMT_API const std::error_category& system_category() FMT_NOEXCEPT { FMT_API const std::error_category& system_category() noexcept {
static const detail::utf8_system_category category; static const detail::utf8_system_category category;
return category; return category;
} }
@@ -163,13 +143,14 @@ std::system_error vwindows_error(int err_code, string_view format_str,
} }
void detail::format_windows_error(detail::buffer<char>& out, int error_code, void detail::format_windows_error(detail::buffer<char>& out, int error_code,
const char* message) FMT_NOEXCEPT { const char* message) noexcept {
FMT_TRY { FMT_TRY {
system_message msg(error_code); auto&& msg = system_message(error_code);
if (msg) { if (msg) {
utf16_to_utf8 utf8_message; auto utf8_message = to_utf8<wchar_t>();
if (utf8_message.convert(msg) == ERROR_SUCCESS) { if (utf8_message.convert(msg)) {
format_to(buffer_appender<char>(out), "{}: {}", message, utf8_message); fmt::format_to(appender(out), FMT_STRING("{}: {}"), message,
string_view(utf8_message));
return; return;
} }
} }
@@ -178,12 +159,12 @@ void detail::format_windows_error(detail::buffer<char>& out, int error_code,
format_error_code(out, error_code, message); format_error_code(out, error_code, message);
} }
void report_windows_error(int error_code, const char* message) FMT_NOEXCEPT { void report_windows_error(int error_code, const char* message) noexcept {
report_error(detail::format_windows_error, error_code, message); do_report_error(detail::format_windows_error, error_code, message);
} }
#endif // _WIN32 #endif // _WIN32
buffered_file::~buffered_file() FMT_NOEXCEPT { buffered_file::~buffered_file() noexcept {
if (file_ && FMT_SYSTEM(fclose(file_)) != 0) if (file_ && FMT_SYSTEM(fclose(file_)) != 0)
report_system_error(errno, "cannot close file"); report_system_error(errno, "cannot close file");
} }
@@ -192,39 +173,57 @@ buffered_file::buffered_file(cstring_view filename, cstring_view mode) {
FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())), FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())),
nullptr); nullptr);
if (!file_) if (!file_)
FMT_THROW(system_error(errno, "cannot open file {}", filename.c_str())); FMT_THROW(system_error(errno, FMT_STRING("cannot open file {}"),
filename.c_str()));
} }
void buffered_file::close() { void buffered_file::close() {
if (!file_) return; if (!file_) return;
int result = FMT_SYSTEM(fclose(file_)); int result = FMT_SYSTEM(fclose(file_));
file_ = nullptr; file_ = nullptr;
if (result != 0) FMT_THROW(system_error(errno, "cannot close file")); if (result != 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
} }
// A macro used to prevent expansion of fileno on broken versions of MinGW. auto buffered_file::descriptor() const -> int {
#define FMT_ARGS #ifdef FMT_HAS_SYSTEM
// fileno is a macro on OpenBSD.
int buffered_file::fileno() const { # ifdef fileno
int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_)); # undef fileno
if (fd == -1) FMT_THROW(system_error(errno, "cannot get file descriptor")); # endif
int fd = FMT_POSIX_CALL(fileno(file_));
#elif defined(_WIN32)
int fd = _fileno(file_);
#else
int fd = fileno(file_);
#endif
if (fd == -1)
FMT_THROW(system_error(errno, FMT_STRING("cannot get file descriptor")));
return fd; return fd;
} }
#if FMT_USE_FCNTL #if FMT_USE_FCNTL
# ifdef _WIN32
using mode_t = int;
# endif
constexpr mode_t default_open_mode =
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
file::file(cstring_view path, int oflag) { file::file(cstring_view path, int oflag) {
int mode = S_IRUSR | S_IWUSR;
# if defined(_WIN32) && !defined(__MINGW32__) # if defined(_WIN32) && !defined(__MINGW32__)
fd_ = -1; fd_ = -1;
FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode)); auto converted = detail::utf8_to_utf16(string_view(path.c_str()));
*this = file::open_windows_file(converted.c_str(), oflag);
# else # else
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode))); FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, default_open_mode)));
# endif
if (fd_ == -1) if (fd_ == -1)
FMT_THROW(system_error(errno, "cannot open file {}", path.c_str())); FMT_THROW(
system_error(errno, FMT_STRING("cannot open file {}"), path.c_str()));
# endif
} }
file::~file() FMT_NOEXCEPT { file::~file() noexcept {
// Don't retry close in case of EINTR! // Don't retry close in case of EINTR!
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0) if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0)
@@ -237,10 +236,11 @@ void file::close() {
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
int result = FMT_POSIX_CALL(close(fd_)); int result = FMT_POSIX_CALL(close(fd_));
fd_ = -1; fd_ = -1;
if (result != 0) FMT_THROW(system_error(errno, "cannot close file")); if (result != 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
} }
long long file::size() const { auto file::size() const -> long long {
# ifdef _WIN32 # ifdef _WIN32
// Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT // Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
// is less than 0x0500 as is the case with some default MinGW builds. // is less than 0x0500 as is the case with some default MinGW builds.
@@ -251,7 +251,7 @@ long long file::size() const {
if (size_lower == INVALID_FILE_SIZE) { if (size_lower == INVALID_FILE_SIZE) {
DWORD error = GetLastError(); DWORD error = GetLastError();
if (error != NO_ERROR) if (error != NO_ERROR)
FMT_THROW(windows_error(GetLastError(), "cannot get file size")); FMT_THROW(windows_error(error, "cannot get file size"));
} }
unsigned long long long_size = size_upper; unsigned long long long_size = size_upper;
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower; return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
@@ -259,33 +259,36 @@ long long file::size() const {
using Stat = struct stat; using Stat = struct stat;
Stat file_stat = Stat(); Stat file_stat = Stat();
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1) if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
FMT_THROW(system_error(errno, "cannot get file attributes")); FMT_THROW(system_error(errno, FMT_STRING("cannot get file attributes")));
static_assert(sizeof(long long) >= sizeof(file_stat.st_size), static_assert(sizeof(long long) >= sizeof(file_stat.st_size),
"return type of file::size is not large enough"); "return type of file::size is not large enough");
return file_stat.st_size; return file_stat.st_size;
# endif # endif
} }
std::size_t file::read(void* buffer, std::size_t count) { auto file::read(void* buffer, size_t count) -> size_t {
rwresult result = 0; rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count)))); FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
if (result < 0) FMT_THROW(system_error(errno, "cannot read from file")); if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot read from file")));
return detail::to_unsigned(result); return detail::to_unsigned(result);
} }
std::size_t file::write(const void* buffer, std::size_t count) { auto file::write(const void* buffer, size_t count) -> size_t {
rwresult result = 0; rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count)))); FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
if (result < 0) FMT_THROW(system_error(errno, "cannot write to file")); if (result < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
return detail::to_unsigned(result); return detail::to_unsigned(result);
} }
file file::dup(int fd) { auto file::dup(int fd) -> file {
// Don't retry as dup doesn't return EINTR. // Don't retry as dup doesn't return EINTR.
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html // http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
int new_fd = FMT_POSIX_CALL(dup(fd)); int new_fd = FMT_POSIX_CALL(dup(fd));
if (new_fd == -1) if (new_fd == -1)
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {}", fd)); FMT_THROW(system_error(
errno, FMT_STRING("cannot duplicate file descriptor {}"), fd));
return file(new_fd); return file(new_fd);
} }
@@ -293,22 +296,47 @@ void file::dup2(int fd) {
int result = 0; int result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
if (result == -1) { if (result == -1) {
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {} to {}", FMT_THROW(system_error(
fd_, fd)); errno, FMT_STRING("cannot duplicate file descriptor {} to {}"), fd_,
fd));
} }
} }
void file::dup2(int fd, std::error_code& ec) FMT_NOEXCEPT { void file::dup2(int fd, std::error_code& ec) noexcept {
int result = 0; int result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
if (result == -1) ec = std::error_code(errno, std::generic_category()); if (result == -1) ec = std::error_code(errno, std::generic_category());
} }
void file::pipe(file& read_end, file& write_end) { auto file::fdopen(const char* mode) -> buffered_file {
// Close the descriptors first to make sure that assignments don't throw // Don't retry as fdopen doesn't return EINTR.
// and there are no leaks. # if defined(__MINGW32__) && defined(_POSIX_)
read_end.close(); FILE* f = ::fdopen(fd_, mode);
write_end.close(); # else
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
# endif
if (!f) {
FMT_THROW(system_error(
errno, FMT_STRING("cannot associate stream with file descriptor")));
}
buffered_file bf(f);
fd_ = -1;
return bf;
}
# if defined(_WIN32) && !defined(__MINGW32__)
file file::open_windows_file(wcstring_view path, int oflag) {
int fd = -1;
auto err = _wsopen_s(&fd, path.c_str(), oflag, _SH_DENYNO, default_open_mode);
if (fd == -1) {
FMT_THROW(system_error(err, FMT_STRING("cannot open file {}"),
detail::to_utf8<wchar_t>(path.c_str()).c_str()));
}
return file(fd);
}
# endif
pipe::pipe() {
int fds[2] = {}; int fds[2] = {};
# ifdef _WIN32 # ifdef _WIN32
// Make the default pipe capacity same as on Linux 2.6.11+. // Make the default pipe capacity same as on Linux 2.6.11+.
@@ -319,42 +347,52 @@ void file::pipe(file& read_end, file& write_end) {
// http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html // http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
int result = FMT_POSIX_CALL(pipe(fds)); int result = FMT_POSIX_CALL(pipe(fds));
# endif # endif
if (result != 0) FMT_THROW(system_error(errno, "cannot create pipe")); if (result != 0)
// The following assignments don't throw because read_fd and write_fd FMT_THROW(system_error(errno, FMT_STRING("cannot create pipe")));
// are closed. // The following assignments don't throw.
read_end = file(fds[0]); read_end = file(fds[0]);
write_end = file(fds[1]); write_end = file(fds[1]);
} }
buffered_file file::fdopen(const char* mode) { # if !defined(__MSDOS__)
// Don't retry as fdopen doesn't return EINTR. auto getpagesize() -> long {
# if defined(__MINGW32__) && defined(_POSIX_) # ifdef _WIN32
FILE* f = ::fdopen(fd_, mode);
# else
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
# endif
if (!f)
FMT_THROW(
system_error(errno, "cannot associate stream with file descriptor"));
buffered_file bf(f);
fd_ = -1;
return bf;
}
long getpagesize() {
# ifdef _WIN32
SYSTEM_INFO si; SYSTEM_INFO si;
GetSystemInfo(&si); GetSystemInfo(&si);
return si.dwPageSize; return si.dwPageSize;
# else # else
# ifdef _WRS_KERNEL
long size = FMT_POSIX_CALL(getpagesize());
# else
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE)); long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
if (size < 0) FMT_THROW(system_error(errno, "cannot get memory page size")); # endif
if (size < 0)
FMT_THROW(system_error(errno, FMT_STRING("cannot get memory page size")));
return size; return size;
# endif
}
# endif # endif
void ostream::grow(buffer<char>& buf, size_t) {
if (buf.size() == buf.capacity()) static_cast<ostream&>(buf).flush();
} }
FMT_API void ostream::grow(size_t) { ostream::ostream(cstring_view path, const detail::ostream_params& params)
if (this->size() == this->capacity()) flush(); : buffer<char>(grow), file_(path, params.oflag) {
set(new char[params.buffer_size], params.buffer_size);
}
ostream::ostream(ostream&& other) noexcept
: buffer<char>(grow, other.data(), other.size(), other.capacity()),
file_(std::move(other.file_)) {
other.clear();
other.set(nullptr, 0);
}
ostream::~ostream() {
flush();
delete[] data();
} }
#endif // FMT_USE_FCNTL #endif // FMT_USE_FCNTL
FMT_END_NAMESPACE FMT_END_NAMESPACE
+1 -1
View File
@@ -1 +1 @@
<manifest package="net.fmtlib" /> <manifest package="dev.fmt" />
+5 -6
View File
@@ -3,18 +3,17 @@
# A vagrant config for testing against gcc-4.8. # A vagrant config for testing against gcc-4.8.
Vagrant.configure("2") do |config| Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/xenial64" config.vm.box = "bento/ubuntu-22.04-arm64"
config.disksize.size = '15GB'
config.vm.provider "virtualbox" do |vb| config.vm.provider "vmware_desktop" do |vb|
vb.memory = "4096" vb.memory = "4096"
end end
config.vm.provision "shell", inline: <<-SHELL config.vm.provision "shell", inline: <<-SHELL
apt-get update apt-get update
apt-get install -y g++ make wget git apt-get install -y g++ make wget git
wget -q https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4-Linux-x86_64.tar.gz wget -q https://github.com/Kitware/CMake/releases/download/v3.26.0/cmake-3.26.0-Linux-x86_64.tar.gz
tar xzf cmake-3.14.4-Linux-x86_64.tar.gz tar xzf cmake-3.26.0-Linux-x86_64.tar.gz
ln -s `pwd`/cmake-3.14.4-Linux-x86_64/bin/cmake /usr/local/bin ln -s `pwd`/cmake-3.26.0-Linux-x86_64/bin/cmake /usr/local/bin
SHELL SHELL
end end
-43
View File
@@ -1,43 +0,0 @@
#!/usr/bin/env python
# Build the project on AppVeyor.
import os
from subprocess import check_call
build = os.environ['BUILD']
config = os.environ['CONFIGURATION']
platform = os.environ['PLATFORM']
path = os.environ['PATH']
image = os.environ['APPVEYOR_BUILD_WORKER_IMAGE']
jobid = os.environ['APPVEYOR_JOB_ID']
cmake_command = ['cmake', '-DFMT_PEDANTIC=ON', '-DCMAKE_BUILD_TYPE=' + config, '..']
if build == 'mingw':
cmake_command.append('-GMinGW Makefiles')
build_command = ['mingw32-make', '-j4']
test_command = ['mingw32-make', 'test']
# Remove the path to Git bin directory from $PATH because it breaks
# MinGW config.
path = path.replace(r'C:\Program Files (x86)\Git\bin', '')
os.environ['PATH'] = r'C:\MinGW\bin;' + path
else:
# Add MSBuild 14.0 to PATH as described in
# http://help.appveyor.com/discussions/problems/2229-v140-not-found-on-vs2105rc.
os.environ['PATH'] = r'C:\Program Files (x86)\MSBuild\15.0\Bin;' + path
if image == 'Visual Studio 2019':
generator = 'Visual Studio 16 2019'
if platform == 'x64':
cmake_command.extend(['-A', 'x64'])
else:
if image == 'Visual Studio 2015':
generator = 'Visual Studio 14 2015'
elif image == 'Visual Studio 2017':
generator = 'Visual Studio 15 2017'
if platform == 'x64':
generator += ' Win64'
cmake_command.append('-G' + generator)
build_command = ['cmake', '--build', '.', '--config', config, '--', '/m:4']
test_command = ['ctest', '-C', config]
check_call(cmake_command)
check_call(build_command)
check_call(test_command)
-31
View File
@@ -1,31 +0,0 @@
configuration:
- Debug
- Release
clone_depth: 1
image:
- Visual Studio 2015
platform:
- x64
environment:
CTEST_OUTPUT_ON_FAILURE: 1
MSVC_DEFAULT_OPTIONS: ON
BUILD: msvc
before_build:
- mkdir build
- cd build
build_script:
- python ../support/appveyor-build.py
on_failure:
- appveyor PushArtifact Testing/Temporary/LastTest.log
- appveyor AddTest test
# Uncomment this to debug AppVeyor failures.
#on_finish:
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
-58
View File
@@ -1,58 +0,0 @@
#!/usr/bin/env python
# Build the documentation in CI.
from __future__ import print_function
import errno, os, shutil, subprocess, sys, urllib
from subprocess import call, check_call, Popen, PIPE, STDOUT
def rmtree_if_exists(dir):
try:
shutil.rmtree(dir)
except OSError as e:
if e.errno == errno.ENOENT:
pass
# Build the docs.
fmt_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.insert(0, os.path.join(fmt_dir, 'doc'))
import build
build.create_build_env()
html_dir = build.build_docs()
repo = 'fmtlib.github.io'
branch = os.environ['GITHUB_REF']
is_ci = 'CI' in os.environ
if is_ci and branch != 'refs/heads/master':
print('Branch: ' + branch)
exit(0) # Ignore non-master branches
if is_ci and 'KEY' not in os.environ:
# Don't update the repo if building in CI from an account that doesn't have
# push access.
print('Skipping update of ' + repo)
exit(0)
# Clone the fmtlib.github.io repo.
rmtree_if_exists(repo)
git_url = 'https://github.com/' if is_ci else 'git@github.com:'
check_call(['git', 'clone', git_url + 'fmtlib/{}.git'.format(repo)])
# Copy docs to the repo.
target_dir = os.path.join(repo, 'dev')
rmtree_if_exists(target_dir)
shutil.copytree(html_dir, target_dir, ignore=shutil.ignore_patterns('.*'))
if is_ci:
check_call(['git', 'config', '--global', 'user.name', 'fmtbot'])
check_call(['git', 'config', '--global', 'user.email', 'viz@fmt.dev'])
# Push docs to GitHub pages.
check_call(['git', 'add', '--all'], cwd=repo)
if call(['git', 'diff-index', '--quiet', 'HEAD'], cwd=repo):
check_call(['git', 'commit', '-m', 'Update documentation'], cwd=repo)
cmd = 'git push'
if is_ci:
cmd += ' https://$KEY@github.com/fmtlib/fmtlib.github.io.git master'
p = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT, cwd=repo)
# Print the output without the key.
print(p.communicate()[0].replace(os.environ['KEY'], '$KEY'))
if p.returncode != 0:
raise subprocess.CalledProcessError(p.returncode, cmd)
+1 -1
View File
@@ -11,7 +11,7 @@ buildscript {
// https://developer.android.com/studio/releases/gradle-plugin#updating-gradle // https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
// //
// Notice that 4.0.0 here is the version of [Android Gradle Plugin] // Notice that 4.0.0 here is the version of [Android Gradle Plugin]
// Accroding to URL above you will need Gradle 6.1 or higher // According to URL above you will need Gradle 6.1 or higher
// //
classpath "com.android.tools.build:gradle:4.1.1" classpath "com.android.tools.build:gradle:4.1.1"
} }
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env python3
"""Compile source on a range of commits
Usage:
check-commits <start> <source>
"""
import docopt, os, sys, tempfile
from subprocess import check_call, check_output, run
args = docopt.docopt(__doc__)
start = args.get('<start>')
source = args.get('<source>')
cwd = os.getcwd()
with tempfile.TemporaryDirectory() as work_dir:
check_call(['git', 'clone', 'https://github.com/fmtlib/fmt.git'],
cwd=work_dir)
repo_dir = os.path.join(work_dir, 'fmt')
commits = check_output(
['git', 'rev-list', f'{start}..HEAD', '--abbrev-commit',
'--', 'include', 'src'],
text=True, cwd=repo_dir).rstrip().split('\n')
commits.reverse()
print('Time\tCommit')
for commit in commits:
check_call(['git', '-c', 'advice.detachedHead=false', 'checkout', commit],
cwd=repo_dir)
returncode = run(
['c++', '-std=c++11', '-O3', '-DNDEBUG', '-I', 'include',
'src/format.cc', os.path.join(cwd, source)], cwd=repo_dir).returncode
if returncode != 0:
continue
times = []
for i in range(5):
output = check_output([os.path.join(repo_dir, 'a.out')], text=True)
times.append(float(output))
message = check_output(['git', 'log', '-1', '--pretty=format:%s', commit],
cwd=repo_dir, text=True)
print(f'{min(times)}\t{commit} {message[:40]}')
sys.stdout.flush()
-70
View File
@@ -1,70 +0,0 @@
# C++14 feature support detection
include(CheckCXXSourceCompiles)
include(CheckCXXCompilerFlag)
if (NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 11)
endif()
message(STATUS "CXX_STANDARD: ${CMAKE_CXX_STANDARD}")
if (CMAKE_CXX_STANDARD EQUAL 20)
check_cxx_compiler_flag(-std=c++20 has_std_20_flag)
check_cxx_compiler_flag(-std=c++2a has_std_2a_flag)
if (has_std_20_flag)
set(CXX_STANDARD_FLAG -std=c++20)
elseif (has_std_2a_flag)
set(CXX_STANDARD_FLAG -std=c++2a)
endif ()
elseif (CMAKE_CXX_STANDARD EQUAL 17)
check_cxx_compiler_flag(-std=c++17 has_std_17_flag)
check_cxx_compiler_flag(-std=c++1z has_std_1z_flag)
if (has_std_17_flag)
set(CXX_STANDARD_FLAG -std=c++17)
elseif (has_std_1z_flag)
set(CXX_STANDARD_FLAG -std=c++1z)
endif ()
elseif (CMAKE_CXX_STANDARD EQUAL 14)
check_cxx_compiler_flag(-std=c++14 has_std_14_flag)
check_cxx_compiler_flag(-std=c++1y has_std_1y_flag)
if (has_std_14_flag)
set(CXX_STANDARD_FLAG -std=c++14)
elseif (has_std_1y_flag)
set(CXX_STANDARD_FLAG -std=c++1y)
endif ()
elseif (CMAKE_CXX_STANDARD EQUAL 11)
check_cxx_compiler_flag(-std=c++11 has_std_11_flag)
check_cxx_compiler_flag(-std=c++0x has_std_0x_flag)
if (has_std_11_flag)
set(CXX_STANDARD_FLAG -std=c++11)
elseif (has_std_0x_flag)
set(CXX_STANDARD_FLAG -std=c++0x)
endif ()
endif ()
set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG})
# Check if user-defined literals are available
check_cxx_source_compiles("
void operator\"\" _udl(long double);
int main() {}"
SUPPORTS_USER_DEFINED_LITERALS)
if (NOT SUPPORTS_USER_DEFINED_LITERALS)
set (SUPPORTS_USER_DEFINED_LITERALS OFF)
endif ()
# Check if <variant> is available
set(CMAKE_REQUIRED_FLAGS -std=c++1z)
check_cxx_source_compiles("
#include <variant>
int main() {}"
FMT_HAS_VARIANT)
if (NOT FMT_HAS_VARIANT)
set (FMT_HAS_VARIANT OFF)
endif ()
set(CMAKE_REQUIRED_FLAGS )
+4 -1
View File
@@ -1,4 +1,7 @@
@PACKAGE_INIT@ @PACKAGE_INIT@
include(${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake) if (NOT TARGET fmt::fmt)
include(${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake)
endif ()
check_required_components(fmt) check_required_components(fmt)
-53
View File
@@ -1,53 +0,0 @@
#!/usr/bin/env python
# Compute 10 ** exp with exp in the range [min_exponent, max_exponent] and print
# normalized (with most-significant bit equal to 1) significands in hexadecimal.
from __future__ import print_function
min_exponent = -348
max_exponent = 340
step = 8
significand_size = 64
exp_offset = 2000
class fp:
pass
powers = []
for i, exp in enumerate(range(min_exponent, max_exponent + 1, step)):
result = fp()
n = 10 ** exp if exp >= 0 else 2 ** exp_offset / 10 ** -exp
k = significand_size + 1
# Convert to binary and round.
binary = '{:b}'.format(n)
result.f = (int('{:0<{}}'.format(binary[:k], k), 2) + 1) / 2
result.e = len(binary) - (exp_offset if exp < 0 else 0) - significand_size
powers.append(result)
# Sanity check.
exp_offset10 = 400
actual = result.f * 10 ** exp_offset10
if result.e > 0:
actual *= 2 ** result.e
else:
for j in range(-result.e):
actual /= 2
expected = 10 ** (exp_offset10 + exp)
precision = len('{}'.format(expected)) - len('{}'.format(actual - expected))
if precision < 19:
print('low precision:', precision)
exit(1)
print('Significands:', end='')
for i, fp in enumerate(powers):
if i % 3 == 0:
print(end='\n ')
print(' {:0<#16x}'.format(fp.f, ), end=',')
print('\n\nExponents:', end='')
for i, fp in enumerate(powers):
if i % 11 == 0:
print(end='\n ')
print(' {:5}'.format(fp.e), end=',')
print('\n\nMax exponent difference:',
max([x.e - powers[i - 1].e for i, x in enumerate(powers)][1:]))
-290
View File
@@ -1,290 +0,0 @@
#!/usr/bin/env python
"""Manage site and releases.
Usage:
manage.py release [<branch>]
manage.py site
For the release command $FMT_TOKEN should contain a GitHub personal access token
obtained from https://github.com/settings/tokens.
"""
from __future__ import print_function
import datetime, docopt, errno, fileinput, json, os
import re, requests, shutil, sys, tempfile
from contextlib import contextmanager
from distutils.version import LooseVersion
from subprocess import check_call
class Git:
def __init__(self, dir):
self.dir = dir
def call(self, method, args, **kwargs):
return check_call(['git', method] + list(args), **kwargs)
def add(self, *args):
return self.call('add', args, cwd=self.dir)
def checkout(self, *args):
return self.call('checkout', args, cwd=self.dir)
def clean(self, *args):
return self.call('clean', args, cwd=self.dir)
def clone(self, *args):
return self.call('clone', list(args) + [self.dir])
def commit(self, *args):
return self.call('commit', args, cwd=self.dir)
def pull(self, *args):
return self.call('pull', args, cwd=self.dir)
def push(self, *args):
return self.call('push', args, cwd=self.dir)
def reset(self, *args):
return self.call('reset', args, cwd=self.dir)
def update(self, *args):
clone = not os.path.exists(self.dir)
if clone:
self.clone(*args)
return clone
def clean_checkout(repo, branch):
repo.clean('-f', '-d')
repo.reset('--hard')
repo.checkout(branch)
class Runner:
def __init__(self, cwd):
self.cwd = cwd
def __call__(self, *args, **kwargs):
kwargs['cwd'] = kwargs.get('cwd', self.cwd)
check_call(args, **kwargs)
def create_build_env():
"""Create a build environment."""
class Env:
pass
env = Env()
# Import the documentation build module.
env.fmt_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(env.fmt_dir, 'doc'))
import build
env.build_dir = 'build'
env.versions = build.versions
# Virtualenv and repos are cached to speed up builds.
build.create_build_env(os.path.join(env.build_dir, 'virtualenv'))
env.fmt_repo = Git(os.path.join(env.build_dir, 'fmt'))
return env
@contextmanager
def rewrite(filename):
class Buffer:
pass
buffer = Buffer()
if not os.path.exists(filename):
buffer.data = ''
yield buffer
return
with open(filename) as f:
buffer.data = f.read()
yield buffer
with open(filename, 'w') as f:
f.write(buffer.data)
fmt_repo_url = 'git@github.com:fmtlib/fmt'
def update_site(env):
env.fmt_repo.update(fmt_repo_url)
doc_repo = Git(os.path.join(env.build_dir, 'fmtlib.github.io'))
doc_repo.update('git@github.com:fmtlib/fmtlib.github.io')
for version in env.versions:
clean_checkout(env.fmt_repo, version)
target_doc_dir = os.path.join(env.fmt_repo.dir, 'doc')
# Remove the old theme.
for entry in os.listdir(target_doc_dir):
path = os.path.join(target_doc_dir, entry)
if os.path.isdir(path):
shutil.rmtree(path)
# Copy the new theme.
for entry in ['_static', '_templates', 'basic-bootstrap', 'bootstrap',
'conf.py', 'fmt.less']:
src = os.path.join(env.fmt_dir, 'doc', entry)
dst = os.path.join(target_doc_dir, entry)
copy = shutil.copytree if os.path.isdir(src) else shutil.copyfile
copy(src, dst)
# Rename index to contents.
contents = os.path.join(target_doc_dir, 'contents.rst')
if not os.path.exists(contents):
os.rename(os.path.join(target_doc_dir, 'index.rst'), contents)
# Fix issues in reference.rst/api.rst.
for filename in ['reference.rst', 'api.rst', 'index.rst']:
pattern = re.compile('doxygenfunction.. (bin|oct|hexu|hex)$', re.M)
with rewrite(os.path.join(target_doc_dir, filename)) as b:
b.data = b.data.replace('std::ostream &', 'std::ostream&')
b.data = re.sub(pattern, r'doxygenfunction:: \1(int)', b.data)
b.data = b.data.replace('std::FILE*', 'std::FILE *')
b.data = b.data.replace('unsigned int', 'unsigned')
#b.data = b.data.replace('operator""_', 'operator"" _')
b.data = b.data.replace(
'format_to_n(OutputIt, size_t, string_view, Args&&',
'format_to_n(OutputIt, size_t, const S&, const Args&')
b.data = b.data.replace(
'format_to_n(OutputIt, std::size_t, string_view, Args&&',
'format_to_n(OutputIt, std::size_t, const S&, const Args&')
if version == ('3.0.2'):
b.data = b.data.replace(
'fprintf(std::ostream&', 'fprintf(std::ostream &')
if version == ('5.3.0'):
b.data = b.data.replace(
'format_to(OutputIt, const S&, const Args&...)',
'format_to(OutputIt, const S &, const Args &...)')
if version.startswith('5.') or version.startswith('6.'):
b.data = b.data.replace(', size_t', ', std::size_t')
if version.startswith('7.'):
b.data = b.data.replace(', std::size_t', ', size_t')
b.data = b.data.replace('join(It, It', 'join(It, Sentinel')
b.data = b.data.replace('aa long', 'a long')
b.data = b.data.replace('serveral', 'several')
if version.startswith('6.2.'):
b.data = b.data.replace(
'vformat(const S&, basic_format_args<' +
'buffer_context<Char>>)',
'vformat(const S&, basic_format_args<' +
'buffer_context<type_identity_t<Char>>>)')
# Fix a broken link in index.rst.
index = os.path.join(target_doc_dir, 'index.rst')
with rewrite(index) as b:
b.data = b.data.replace(
'doc/latest/index.html#format-string-syntax', 'syntax.html')
# Build the docs.
html_dir = os.path.join(env.build_dir, 'html')
if os.path.exists(html_dir):
shutil.rmtree(html_dir)
include_dir = env.fmt_repo.dir
if LooseVersion(version) >= LooseVersion('5.0.0'):
include_dir = os.path.join(include_dir, 'include', 'fmt')
elif LooseVersion(version) >= LooseVersion('3.0.0'):
include_dir = os.path.join(include_dir, 'fmt')
import build
build.build_docs(version, doc_dir=target_doc_dir,
include_dir=include_dir, work_dir=env.build_dir)
shutil.rmtree(os.path.join(html_dir, '.doctrees'))
# Create symlinks for older versions.
for link, target in {'index': 'contents', 'api': 'reference'}.items():
link = os.path.join(html_dir, link) + '.html'
target += '.html'
if os.path.exists(os.path.join(html_dir, target)) and \
not os.path.exists(link):
os.symlink(target, link)
# Copy docs to the website.
version_doc_dir = os.path.join(doc_repo.dir, version)
try:
shutil.rmtree(version_doc_dir)
except OSError as e:
if e.errno != errno.ENOENT:
raise
shutil.move(html_dir, version_doc_dir)
def release(args):
env = create_build_env()
fmt_repo = env.fmt_repo
branch = args.get('<branch>')
if branch is None:
branch = 'master'
if not fmt_repo.update('-b', branch, fmt_repo_url):
clean_checkout(fmt_repo, branch)
# Convert changelog from RST to GitHub-flavored Markdown and get the
# version.
changelog = 'ChangeLog.rst'
changelog_path = os.path.join(fmt_repo.dir, changelog)
import rst2md
changes, version = rst2md.convert(changelog_path)
cmakelists = 'CMakeLists.txt'
for line in fileinput.input(os.path.join(fmt_repo.dir, cmakelists),
inplace=True):
prefix = 'set(FMT_VERSION '
if line.startswith(prefix):
line = prefix + version + ')\n'
sys.stdout.write(line)
# Update the version in the changelog.
title_len = 0
for line in fileinput.input(changelog_path, inplace=True):
if line.decode('utf-8').startswith(version + ' - TBD'):
line = version + ' - ' + datetime.date.today().isoformat()
title_len = len(line)
line += '\n'
elif title_len:
line = '-' * title_len + '\n'
title_len = 0
sys.stdout.write(line)
# Add the version to the build script.
script = os.path.join('doc', 'build.py')
script_path = os.path.join(fmt_repo.dir, script)
for line in fileinput.input(script_path, inplace=True):
m = re.match(r'( *versions = )\[(.+)\]', line)
if m:
line = '{}[{}, \'{}\']\n'.format(m.group(1), m.group(2), version)
sys.stdout.write(line)
fmt_repo.checkout('-B', 'release')
fmt_repo.add(changelog, cmakelists, script)
fmt_repo.commit('-m', 'Update version')
# Build the docs and package.
run = Runner(fmt_repo.dir)
run('cmake', '.')
run('make', 'doc', 'package_source')
update_site(env)
# Create a release on GitHub.
fmt_repo.push('origin', 'release')
params = {'access_token': os.getenv('FMT_TOKEN')}
r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases',
params=params,
data=json.dumps({'tag_name': version,
'target_commitish': 'release',
'body': changes, 'draft': True}))
if r.status_code != 201:
raise Exception('Failed to create a release ' + str(r))
id = r.json()['id']
uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases'
package = 'fmt-{}.zip'.format(version)
r = requests.post(
'{}/{}/assets?name={}'.format(uploads_url, id, package),
headers={'Content-Type': 'application/zip'},
params=params, data=open('build/fmt/' + package, 'rb'))
if r.status_code != 201:
raise Exception('Failed to upload an asset ' + str(r))
if __name__ == '__main__':
args = docopt.docopt(__doc__)
if args.get('release'):
release(args)
elif args.get('site'):
update_site(create_build_env())
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env python3
# A script to invoke mkdocs with the correct environment.
# Additionally supports deploying via mike:
# ./mkdocs deploy [mike-deploy-options]
# For example:
# ./mkdocs deploy <version>
# This will checkout the website to fmt/build/fmt.dev and deploy documentation
# <version> there.
import errno, os, shutil, sys
from subprocess import call
support_dir = os.path.dirname(os.path.normpath(__file__))
build_dir = os.path.join(os.path.dirname(support_dir), 'build')
# Set PYTHONPATH for the mkdocstrings handler.
env = os.environ.copy()
path = env.get('PYTHONPATH')
env['PYTHONPATH'] = \
(path + ':' if path else '') + os.path.join(support_dir, 'python')
redirect_page = \
'''<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Redirecting</title>
<noscript>
<meta http-equiv="refresh" content="1; url=11.0/" />
</noscript>
<script>
window.location.replace(
"api/" + window.location.search + window.location.hash
);
</script>
</head>
<body>
Redirecting to <a href="api/">api</a>...
</body>
</html>
'''
config_path = os.path.join(support_dir, 'mkdocs.yml')
args = sys.argv[1:]
if len(args) > 0:
command = args[0]
if command == 'deploy' or command == 'set-default':
git_url = 'https://github.com/' if 'CI' in os.environ else 'git@github.com:'
site_repo = git_url + 'fmtlib/fmt.dev.git'
site_dir = os.path.join(build_dir, 'fmt.dev')
try:
shutil.rmtree(site_dir)
except OSError as e:
if e.errno == errno.ENOENT:
pass
ret = call(['git', 'clone', '--depth=1', site_repo, site_dir])
if ret != 0:
sys.exit(ret)
# Copy the config to the build dir because the site is built relative to it.
config_build_path = os.path.join(build_dir, 'mkdocs.yml')
shutil.copyfile(config_path, config_build_path)
version = args[1]
ret = call(['mike'] + args + ['--config-file', config_build_path,
'--branch', 'master'], cwd=site_dir, env=env)
if ret != 0 or version == 'dev':
sys.exit(ret)
current_doc_path = os.path.join(site_dir, version)
# mike stages files added by deploy for deletion for unclear reason,
# undo it.
ret = call(['git', 'reset', '--hard'], cwd=site_dir)
if False:
os.makedirs(current_doc_path, exist_ok=True)
redirect_page_path = os.path.join(current_doc_path, 'api.html')
with open(redirect_page_path, "w") as file:
file.write(redirect_page)
ret = call(['git', 'add', redirect_page_path], cwd=site_dir)
if ret != 0:
sys.exit(ret)
ret = call(['git', 'commit', '--amend', '--no-edit'], cwd=site_dir)
sys.exit(ret)
elif not command.startswith('-'):
args += ['-f', config_path]
sys.exit(call(['mkdocs'] + args, env=env))
+48
View File
@@ -0,0 +1,48 @@
site_name: '{fmt}'
docs_dir: ../doc
repo_url: https://github.com/fmtlib/fmt
theme:
name: material
features:
- navigation.tabs
- navigation.top
- toc.integrate
extra_javascript:
- https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/highlight.min.js
- fmt.js
extra_css:
- https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/styles/default.min.css
- fmt.css
markdown_extensions:
- pymdownx.highlight:
# Use JavaScript syntax highlighter instead of Pygments because it
# automatically applies to code blocks extracted through Doxygen.
use_pygments: false
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
- pymdownx.inlinehilite
- pymdownx.snippets
plugins:
- search
- mkdocstrings:
default_handler: cxx
nav:
- Home: index.md
- Get Started: get-started.md
- API: api.md
- Syntax: syntax.md
exclude_docs: ChangeLog-old.md
extra:
version:
provider: mike
generator: false
+201
View File
@@ -0,0 +1,201 @@
#!/usr/bin/env python3
# This script is based on
# https://github.com/rust-lang/rust/blob/master/library/core/src/unicode/printable.py
# distributed under https://github.com/rust-lang/rust/blob/master/LICENSE-MIT.
# This script uses the following Unicode tables:
# - UnicodeData.txt
from collections import namedtuple
import csv
import os
import subprocess
NUM_CODEPOINTS=0x110000
def to_ranges(iter):
current = None
for i in iter:
if current is None or i != current[1] or i in (0x10000, 0x20000):
if current is not None:
yield tuple(current)
current = [i, i + 1]
else:
current[1] += 1
if current is not None:
yield tuple(current)
def get_escaped(codepoints):
for c in codepoints:
if (c.class_ or "Cn") in "Cc Cf Cs Co Cn Zl Zp Zs".split() and c.value != ord(' '):
yield c.value
def get_file(f):
try:
return open(os.path.basename(f))
except FileNotFoundError:
subprocess.run(["curl", "-O", f], check=True)
return open(os.path.basename(f))
Codepoint = namedtuple('Codepoint', 'value class_')
def get_codepoints(f):
r = csv.reader(f, delimiter=";")
prev_codepoint = 0
class_first = None
for row in r:
codepoint = int(row[0], 16)
name = row[1]
class_ = row[2]
if class_first is not None:
if not name.endswith("Last>"):
raise ValueError("Missing Last after First")
for c in range(prev_codepoint + 1, codepoint):
yield Codepoint(c, class_first)
class_first = None
if name.endswith("First>"):
class_first = class_
yield Codepoint(codepoint, class_)
prev_codepoint = codepoint
if class_first is not None:
raise ValueError("Missing Last after First")
for c in range(prev_codepoint + 1, NUM_CODEPOINTS):
yield Codepoint(c, None)
def compress_singletons(singletons):
uppers = [] # (upper, # items in lowers)
lowers = []
for i in singletons:
upper = i >> 8
lower = i & 0xff
if len(uppers) == 0 or uppers[-1][0] != upper:
uppers.append((upper, 1))
else:
upper, count = uppers[-1]
uppers[-1] = upper, count + 1
lowers.append(lower)
return uppers, lowers
def compress_normal(normal):
# lengths 0x00..0x7f are encoded as 00, 01, ..., 7e, 7f
# lengths 0x80..0x7fff are encoded as 80 80, 80 81, ..., ff fe, ff ff
compressed = [] # [truelen, (truelenaux), falselen, (falselenaux)]
prev_start = 0
for start, count in normal:
truelen = start - prev_start
falselen = count
prev_start = start + count
assert truelen < 0x8000 and falselen < 0x8000
entry = []
if truelen > 0x7f:
entry.append(0x80 | (truelen >> 8))
entry.append(truelen & 0xff)
else:
entry.append(truelen & 0x7f)
if falselen > 0x7f:
entry.append(0x80 | (falselen >> 8))
entry.append(falselen & 0xff)
else:
entry.append(falselen & 0x7f)
compressed.append(entry)
return compressed
def print_singletons(uppers, lowers, uppersname, lowersname):
print(" static constexpr singleton {}[] = {{".format(uppersname))
for u, c in uppers:
print(" {{{:#04x}, {}}},".format(u, c))
print(" };")
print(" static constexpr unsigned char {}[] = {{".format(lowersname))
for i in range(0, len(lowers), 8):
print(" {}".format(" ".join("{:#04x},".format(l) for l in lowers[i:i+8])))
print(" };")
def print_normal(normal, normalname):
print(" static constexpr unsigned char {}[] = {{".format(normalname))
for v in normal:
print(" {}".format(" ".join("{:#04x},".format(i) for i in v)))
print(" };")
def main():
file = get_file("https://www.unicode.org/Public/UNIDATA/UnicodeData.txt")
codepoints = get_codepoints(file)
CUTOFF=0x10000
singletons0 = []
singletons1 = []
normal0 = []
normal1 = []
extra = []
for a, b in to_ranges(get_escaped(codepoints)):
if a > 2 * CUTOFF:
extra.append((a, b - a))
elif a == b - 1:
if a & CUTOFF:
singletons1.append(a & ~CUTOFF)
else:
singletons0.append(a)
elif a == b - 2:
if a & CUTOFF:
singletons1.append(a & ~CUTOFF)
singletons1.append((a + 1) & ~CUTOFF)
else:
singletons0.append(a)
singletons0.append(a + 1)
else:
if a >= 2 * CUTOFF:
extra.append((a, b - a))
elif a & CUTOFF:
normal1.append((a & ~CUTOFF, b - a))
else:
normal0.append((a, b - a))
singletons0u, singletons0l = compress_singletons(singletons0)
singletons1u, singletons1l = compress_singletons(singletons1)
normal0 = compress_normal(normal0)
normal1 = compress_normal(normal1)
print("""\
FMT_FUNC auto is_printable(uint32_t cp) -> bool {\
""")
print_singletons(singletons0u, singletons0l, 'singletons0', 'singletons0_lower')
print_singletons(singletons1u, singletons1l, 'singletons1', 'singletons1_lower')
print_normal(normal0, 'normal0')
print_normal(normal1, 'normal1')
print("""\
auto lower = static_cast<uint16_t>(cp);
if (cp < 0x10000) {
return is_printable(lower, singletons0,
sizeof(singletons0) / sizeof(*singletons0),
singletons0_lower, normal0, sizeof(normal0));
}
if (cp < 0x20000) {
return is_printable(lower, singletons1,
sizeof(singletons1) / sizeof(*singletons1),
singletons1_lower, normal1, sizeof(normal1));
}\
""")
for a, b in extra:
print(" if (0x{:x} <= cp && cp < 0x{:x}) return false;".format(a, a + b))
print("""\
return cp < 0x{:x};
}}\
""".format(NUM_CODEPOINTS))
if __name__ == '__main__':
main()
@@ -0,0 +1,338 @@
# A basic mkdocstrings handler for {fmt}.
# Copyright (c) 2012 - present, Victor Zverovich
# https://github.com/fmtlib/fmt/blob/master/LICENSE
import os
import xml.etree.ElementTree as ElementTree
from pathlib import Path
from subprocess import PIPE, STDOUT, CalledProcessError, Popen
from typing import Any, List, Mapping, Optional
from mkdocstrings.handlers.base import BaseHandler
class Definition:
"""A definition extracted by Doxygen."""
def __init__(self, name: str, kind: Optional[str] = None,
node: Optional[ElementTree.Element] = None,
is_member: bool = False):
self.name = name
self.kind = kind if kind is not None else node.get('kind')
self.desc = None
self.id = name if not is_member else None
self.members = None
self.params = None
self.template_params = None
self.trailing_return_type = None
self.type = None
# A map from Doxygen to HTML tags.
tag_map = {
'bold': 'b',
'emphasis': 'em',
'computeroutput': 'code',
'para': 'p',
'programlisting': 'pre',
'verbatim': 'pre'
}
# A map from Doxygen tags to text.
tag_text_map = {
'codeline': '',
'highlight': '',
'sp': ' '
}
def escape_html(s: str) -> str:
return s.replace("<", "&lt;")
def doxyxml2html(nodes: List[ElementTree.Element]):
out = ''
for n in nodes:
tag = tag_map.get(n.tag)
if not tag:
out += tag_text_map[n.tag]
out += '<' + tag + '>' if tag else ''
out += '<code class="language-cpp">' if tag == 'pre' else ''
if n.text:
out += escape_html(n.text)
out += doxyxml2html(list(n))
out += '</code>' if tag == 'pre' else ''
out += '</' + tag + '>' if tag else ''
if n.tail:
out += n.tail
return out
def convert_template_params(node: ElementTree.Element) -> Optional[List[Definition]]:
template_param_list = node.find('templateparamlist')
if template_param_list is None:
return None
params = []
for param_node in template_param_list.findall('param'):
name = param_node.find('declname')
param = Definition(name.text if name is not None else '', 'param')
param.type = param_node.find('type').text
params.append(param)
return params
def get_description(node: ElementTree.Element) -> List[ElementTree.Element]:
return node.findall('briefdescription/para') + \
node.findall('detaileddescription/para')
def normalize_type(type_: str) -> str:
type_ = type_.replace('< ', '<').replace(' >', '>')
return type_.replace(' &', '&').replace(' *', '*')
def convert_type(type_: ElementTree.Element) -> Optional[str]:
if type_ is None:
return None
result = type_.text if type_.text else ''
for ref in type_:
result += ref.text
if ref.tail:
result += ref.tail
result += type_.tail.strip()
return normalize_type(result)
def convert_params(func: ElementTree.Element) -> List[Definition]:
params = []
for p in func.findall('param'):
d = Definition(p.find('declname').text, 'param')
d.type = convert_type(p.find('type'))
params.append(d)
return params
def convert_return_type(d: Definition, node: ElementTree.Element) -> None:
d.trailing_return_type = None
if d.type == 'auto' or d.type == 'constexpr auto':
parts = node.find('argsstring').text.split(' -> ')
if len(parts) > 1:
d.trailing_return_type = normalize_type(parts[1])
def render_param(param: Definition) -> str:
return param.type + (f'&nbsp;{param.name}' if len(param.name) > 0 else '')
def render_decl(d: Definition) -> str:
text = ''
if d.id is not None:
text += f'<a id="{d.id}">\n'
text += '<pre><code class="language-cpp decl">'
text += '<div>'
if d.template_params is not None:
text += 'template &lt;'
text += ', '.join([render_param(p) for p in d.template_params])
text += '&gt;\n'
text += '</div>'
text += '<div>'
end = ';'
if d.kind == 'function' or d.kind == 'variable':
text += d.type + ' ' if len(d.type) > 0 else ''
elif d.kind == 'typedef':
text += 'using '
elif d.kind == 'define':
end = ''
else:
text += d.kind + ' '
text += d.name
if d.params is not None:
params = ', '.join([
(p.type + ' ' if p.type else '') + p.name for p in d.params])
text += '(' + escape_html(params) + ')'
if d.trailing_return_type:
text += ' -&NoBreak;>&nbsp;' + escape_html(d.trailing_return_type)
elif d.kind == 'typedef':
text += ' = ' + escape_html(d.type)
text += end
text += '</div>'
text += '</code></pre>\n'
if d.id is not None:
text += f'</a>\n'
return text
class CxxHandler(BaseHandler):
def __init__(self, **kwargs: Any) -> None:
super().__init__(handler='cxx', **kwargs)
headers = [
'args.h', 'base.h', 'chrono.h', 'color.h', 'compile.h', 'format.h',
'os.h', 'ostream.h', 'printf.h', 'ranges.h', 'std.h', 'xchar.h'
]
# Run doxygen.
cmd = ['doxygen', '-']
support_dir = Path(__file__).parents[3]
top_dir = os.path.dirname(support_dir)
include_dir = os.path.join(top_dir, 'include', 'fmt')
self._ns2doxyxml = {}
build_dir = os.path.join(top_dir, 'build')
os.makedirs(build_dir, exist_ok=True)
self._doxyxml_dir = os.path.join(build_dir, 'doxyxml')
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
_, _ = p.communicate(input=r'''
PROJECT_NAME = fmt
GENERATE_XML = YES
GENERATE_LATEX = NO
GENERATE_HTML = NO
INPUT = {0}
XML_OUTPUT = {1}
QUIET = YES
AUTOLINK_SUPPORT = NO
MACRO_EXPANSION = YES
PREDEFINED = _WIN32=1 \
__linux__=1 \
FMT_ENABLE_IF(...)= \
FMT_USE_USER_LITERALS=1 \
FMT_USE_ALIAS_TEMPLATES=1 \
FMT_USE_NONTYPE_TEMPLATE_ARGS=1 \
FMT_API= \
"FMT_BEGIN_NAMESPACE=namespace fmt {{" \
"FMT_END_NAMESPACE=}}" \
"FMT_DOC=1"
'''.format(
' '.join([os.path.join(include_dir, h) for h in headers]),
self._doxyxml_dir).encode('utf-8'))
if p.returncode != 0:
raise CalledProcessError(p.returncode, cmd)
# Merge all file-level XMLs into one to simplify search.
self._file_doxyxml = None
for h in headers:
filename = h.replace(".h", "_8h.xml")
with open(os.path.join(self._doxyxml_dir, filename)) as f:
doxyxml = ElementTree.parse(f)
if self._file_doxyxml is None:
self._file_doxyxml = doxyxml
continue
root = self._file_doxyxml.getroot()
for node in doxyxml.getroot():
root.append(node)
def collect_compound(self, identifier: str,
cls: List[ElementTree.Element]) -> Definition:
"""Collect a compound definition such as a struct."""
path = os.path.join(self._doxyxml_dir, cls[0].get('refid') + '.xml')
with open(path) as f:
xml = ElementTree.parse(f)
node = xml.find('compounddef')
d = Definition(identifier, node=node)
d.template_params = convert_template_params(node)
d.desc = get_description(node)
d.members = []
for m in \
node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \
node.findall('sectiondef[@kind="public-func"]/memberdef'):
name = m.find('name').text
# Doxygen incorrectly classifies members of private unnamed unions as
# public members of the containing class.
if name.endswith('_'):
continue
desc = get_description(m)
if len(desc) == 0:
continue
kind = m.get('kind')
member = Definition(name if name else '', kind=kind, is_member=True)
type_text = m.find('type').text
member.type = type_text if type_text else ''
if kind == 'function':
member.params = convert_params(m)
convert_return_type(member, m)
member.template_params = None
member.desc = desc
d.members.append(member)
return d
def collect(self, identifier: str, _config: Mapping[str, Any]) -> Definition:
qual_name = 'fmt::' + identifier
param_str = None
paren = qual_name.find('(')
if paren > 0:
qual_name, param_str = qual_name[:paren], qual_name[paren + 1:-1]
colons = qual_name.rfind('::')
namespace, name = qual_name[:colons], qual_name[colons + 2:]
# Load XML.
doxyxml = self._ns2doxyxml.get(namespace)
if doxyxml is None:
path = f'namespace{namespace.replace("::", "_1_1")}.xml'
with open(os.path.join(self._doxyxml_dir, path)) as f:
doxyxml = ElementTree.parse(f)
self._ns2doxyxml[namespace] = doxyxml
nodes = doxyxml.findall(
f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
if len(nodes) == 0:
nodes = self._file_doxyxml.findall(
f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
candidates = []
for node in nodes:
# Process a function or a typedef.
params = None
d = Definition(name, node=node)
if d.kind == 'function':
params = convert_params(node)
node_param_str = ', '.join([p.type for p in params])
if param_str and param_str != node_param_str:
candidates.append(f'{name}({node_param_str})')
continue
elif d.kind == 'define':
params = []
for p in node.findall('param'):
param = Definition(p.find('defname').text, kind='param')
param.type = None
params.append(param)
d.type = convert_type(node.find('type'))
d.template_params = convert_template_params(node)
d.params = params
convert_return_type(d, node)
d.desc = get_description(node)
return d
cls = doxyxml.findall(f"compounddef/innerclass[.='{qual_name}']")
if not cls:
raise Exception(f'Cannot find {identifier}. Candidates: {candidates}')
return self.collect_compound(identifier, cls)
def render(self, d: Definition, config: dict) -> str:
if d.id is not None:
self.do_heading('', 0, id=d.id)
text = '<div class="docblock">\n'
text += render_decl(d)
text += '<div class="docblock-desc">\n'
text += doxyxml2html(d.desc)
if d.members is not None:
for m in d.members:
text += self.render(m, config)
text += '</div>\n'
text += '</div>\n'
return text
def get_handler(theme: str, custom_templates: Optional[str] = None,
**_config: Any) -> CxxHandler:
"""Return an instance of `CxxHandler`.
Arguments:
theme: The theme to use when rendering contents.
custom_templates: Directory containing custom templates.
**_config: Configuration passed to the handler.
"""
return CxxHandler(theme=theme, custom_templates=custom_templates)
@@ -0,0 +1 @@
mkdocsstrings requires a handler to have a templates directory.
+188
View File
@@ -0,0 +1,188 @@
#!/usr/bin/env python3
"""Make a release.
Usage:
release.py [<branch>]
For the release command $FMT_TOKEN should contain a GitHub personal access token
obtained from https://github.com/settings/tokens.
"""
from __future__ import print_function
import datetime, docopt, errno, fileinput, json, os
import re, shutil, sys
from subprocess import check_call
import urllib.request
class Git:
def __init__(self, dir):
self.dir = dir
def call(self, method, args, **kwargs):
return check_call(['git', method] + list(args), **kwargs)
def add(self, *args):
return self.call('add', args, cwd=self.dir)
def checkout(self, *args):
return self.call('checkout', args, cwd=self.dir)
def clean(self, *args):
return self.call('clean', args, cwd=self.dir)
def clone(self, *args):
return self.call('clone', list(args) + [self.dir])
def commit(self, *args):
return self.call('commit', args, cwd=self.dir)
def pull(self, *args):
return self.call('pull', args, cwd=self.dir)
def push(self, *args):
return self.call('push', args, cwd=self.dir)
def reset(self, *args):
return self.call('reset', args, cwd=self.dir)
def update(self, *args):
clone = not os.path.exists(self.dir)
if clone:
self.clone(*args)
return clone
def clean_checkout(repo, branch):
repo.clean('-f', '-d')
repo.reset('--hard')
repo.checkout(branch)
class Runner:
def __init__(self, cwd):
self.cwd = cwd
def __call__(self, *args, **kwargs):
kwargs['cwd'] = kwargs.get('cwd', self.cwd)
check_call(args, **kwargs)
def create_build_env():
"""Create a build environment."""
class Env:
pass
env = Env()
env.fmt_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
env.build_dir = 'build'
env.fmt_repo = Git(os.path.join(env.build_dir, 'fmt'))
return env
if __name__ == '__main__':
args = docopt.docopt(__doc__)
env = create_build_env()
fmt_repo = env.fmt_repo
branch = args.get('<branch>')
if branch is None:
branch = 'master'
if not fmt_repo.update('-b', branch, 'git@github.com:fmtlib/fmt'):
clean_checkout(fmt_repo, branch)
# Update the date in the changelog and extract the version and the first
# section content.
changelog = 'ChangeLog.md'
changelog_path = os.path.join(fmt_repo.dir, changelog)
is_first_section = True
first_section = []
for i, line in enumerate(fileinput.input(changelog_path, inplace=True)):
if i == 0:
version = re.match(r'# (.*) - TBD', line).group(1)
line = '# {} - {}\n'.format(
version, datetime.date.today().isoformat())
elif not is_first_section:
pass
elif line.startswith('#'):
is_first_section = False
else:
first_section.append(line)
sys.stdout.write(line)
if first_section[0] == '\n':
first_section.pop(0)
ns_version = None
base_h_path = os.path.join(fmt_repo.dir, 'include', 'fmt', 'base.h')
for line in fileinput.input(base_h_path):
m = re.match(r'\s*inline namespace v(.*) .*', line)
if m:
ns_version = m.group(1)
break
major_version = version.split('.')[0]
if not ns_version or ns_version != major_version:
raise Exception(f'Version mismatch {ns_version} != {major_version}')
# Workaround GitHub-flavored Markdown treating newlines as <br>.
changes = ''
code_block = False
stripped = False
for line in first_section:
if re.match(r'^\s*```', line):
code_block = not code_block
changes += line
stripped = False
continue
if code_block:
changes += line
continue
if line == '\n' or re.match(r'^\s*\|.*', line):
if stripped:
changes += '\n'
stripped = False
changes += line
continue
if stripped:
line = ' ' + line.lstrip()
changes += line.rstrip()
stripped = True
fmt_repo.checkout('-B', 'release')
fmt_repo.add(changelog)
fmt_repo.commit('-m', 'Update version')
# Build the docs and package.
run = Runner(fmt_repo.dir)
run('cmake', '.')
run('make', 'doc', 'package_source')
# Create a release on GitHub.
fmt_repo.push('origin', 'release')
auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')}
req = urllib.request.Request(
'https://api.github.com/repos/fmtlib/fmt/releases',
data=json.dumps({'tag_name': version,
'target_commitish': 'release',
'body': changes, 'draft': True}).encode('utf-8'),
headers=auth_headers, method='POST')
with urllib.request.urlopen(req) as response:
if response.status != 201:
raise Exception(f'Failed to create a release ' +
'{response.status} {response.reason}')
response_data = json.loads(response.read().decode('utf-8'))
id = response_data['id']
# Upload the package.
uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases'
package = 'fmt-{}.zip'.format(version)
req = urllib.request.Request(
f'{uploads_url}/{id}/assets?name={package}',
headers={'Content-Type': 'application/zip'} | auth_headers,
data=open('build/fmt/' + package, 'rb').read(), method='POST')
with urllib.request.urlopen(req) as response:
if response.status != 201:
raise Exception(f'Failed to upload an asset '
'{response.status} {response.reason}')
short_version = '.'.join(version.split('.')[:-1])
check_call(['./mkdocs', 'deploy', short_version])
-159
View File
@@ -1,159 +0,0 @@
#!/usr/bin/env python
# reStructuredText (RST) to GitHub-flavored Markdown converter
import re, sys
from docutils import core, nodes, writers
def is_github_ref(node):
return re.match('https://github.com/.*/(issues|pull)/.*', node['refuri'])
class Translator(nodes.NodeVisitor):
def __init__(self, document):
nodes.NodeVisitor.__init__(self, document)
self.output = ''
self.indent = 0
self.preserve_newlines = False
def write(self, text):
self.output += text.replace('\n', '\n' + ' ' * self.indent)
def visit_document(self, node):
pass
def depart_document(self, node):
pass
def visit_section(self, node):
pass
def depart_section(self, node):
# Skip all sections except the first one.
raise nodes.StopTraversal
def visit_title(self, node):
self.version = re.match(r'(\d+\.\d+\.\d+).*', node.children[0]).group(1)
raise nodes.SkipChildren
def visit_title_reference(self, node):
raise Exception(node)
def depart_title(self, node):
pass
def visit_Text(self, node):
if not self.preserve_newlines:
node = node.replace('\n', ' ')
self.write(node)
def depart_Text(self, node):
pass
def visit_bullet_list(self, node):
pass
def depart_bullet_list(self, node):
pass
def visit_list_item(self, node):
self.write('* ')
self.indent += 2
def depart_list_item(self, node):
self.indent -= 2
self.write('\n\n')
def visit_paragraph(self, node):
pass
def depart_paragraph(self, node):
pass
def visit_reference(self, node):
if not is_github_ref(node):
self.write('[')
def depart_reference(self, node):
if not is_github_ref(node):
self.write('](' + node['refuri'] + ')')
def visit_target(self, node):
pass
def depart_target(self, node):
pass
def visit_literal(self, node):
self.write('`')
def depart_literal(self, node):
self.write('`')
def visit_literal_block(self, node):
self.write('\n\n```')
if 'c++' in node['classes']:
self.write('c++')
self.write('\n')
self.preserve_newlines = True
def depart_literal_block(self, node):
self.write('\n```\n')
self.preserve_newlines = False
def visit_inline(self, node):
pass
def depart_inline(self, node):
pass
def visit_image(self, node):
self.write('![](' + node['uri'] + ')')
def depart_image(self, node):
pass
def write_row(self, row, widths):
for i, entry in enumerate(row):
text = entry[0][0] if len(entry) > 0 else ''
if i != 0:
self.write('|')
self.write('{:{}}'.format(text, widths[i]))
self.write('\n')
def visit_table(self, node):
table = node.children[0]
colspecs = table[:-2]
thead = table[-2]
tbody = table[-1]
widths = [int(cs['colwidth']) for cs in colspecs]
sep = '|'.join(['-' * w for w in widths]) + '\n'
self.write('\n\n')
self.write_row(thead[0], widths)
self.write(sep)
for row in tbody:
self.write_row(row, widths)
raise nodes.SkipChildren
def depart_table(self, node):
pass
class MDWriter(writers.Writer):
"""GitHub-flavored markdown writer"""
supported = ('md',)
"""Formats this writer supports."""
def translate(self):
translator = Translator(self.document)
self.document.walkabout(translator)
self.output = (translator.output, translator.version)
def convert(rst_path):
"""Converts RST file to Markdown."""
return core.publish_file(source_path=rst_path, writer=MDWriter())
if __name__ == '__main__':
convert(sys.argv[1])
-7
View File
@@ -1,7 +0,0 @@
# Sphinx configuration for readthedocs.
import os, sys
master_doc = 'index'
html_theme = 'theme'
html_theme_path = ["."]
-2
View File
@@ -1,2 +0,0 @@
If you are not redirected automatically, follow the
`link to the fmt documentation <https://fmt.dev/latest/>`_.
-17
View File
@@ -1,17 +0,0 @@
{% extends "basic/layout.html" %}
{% block extrahead %}
<meta charset="UTF-8">
<meta http-equiv="refresh" content="1;url=https://fmt.dev/latest/">
<script type="text/javascript">
window.location.href = "https://fmt.dev/latest/"
</script>
<title>Page Redirection</title>
{% endblock %}
{% block document %}
If you are not redirected automatically, follow the <a href='https://fmt.dev/latest/'>link to the fmt documentation</a>.
{% endblock %}
{% block footer %}
{% endblock %}
-2
View File
@@ -1,2 +0,0 @@
[theme]
inherit = basic
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2014-2023 Omar Cornut Copyright (c) 2014-2026 Omar Cornut
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+645 -269
View File
@@ -1,16 +1,37 @@
// dear imgui: Renderer for DirectX9 // dear imgui: Renderer Backend for DirectX9
// This needs to be used along with a Platform Binding (e.g. Win32) // This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. // [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9.
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// https://github.com/ocornut/imgui // Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2026-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2026-03-19: Fixed issue in ImGui_ImplDX9_UpdateTexture() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310)
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
// 2025-06-11: DirectX9: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
// 2024-10-07: DirectX9: Changed default texture sampler to Clamp instead of Repeat/Wrap.
// 2024-02-12: DirectX9: Using RGBA format when supported by the driver to avoid CPU side conversion. (#6575)
// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
// 2021-06-25: DirectX9: Explicitly disable texture state stages after >= 1.
// 2021-05-19: DirectX9: Replaced direct access to ImDrawCmd::TextureId with a call to ImDrawCmd::GetTexID(). (will become a requirement)
// 2021-04-23: DirectX9: Explicitly setting up more graphics states to increase compatibility with unusual non-default states.
// 2021-03-18: DirectX9: Calling IDirect3DStateBlock9::Capture() after CreateStateBlock() as a workaround for state restoring issues (see #3857).
// 2021-03-03: DirectX9: Added support for IMGUI_USE_BGRA_PACKED_COLOR in user's imconfig file.
// 2021-02-18: DirectX9: Change blending equation to preserve alpha in output buffer.
// 2019-05-29: DirectX9: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. // 2019-05-29: DirectX9: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag.
// 2019-04-30: DirectX9: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. // 2019-04-30: DirectX9: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
// 2019-03-29: Misc: Fixed erroneous assert in ImGui_ImplDX9_InvalidateDeviceObjects(). // 2019-03-29: Misc: Fixed erroneous assert in ImGui_ImplDX9_InvalidateDeviceObjects().
@@ -22,160 +43,154 @@
// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX9_RenderDrawData() in the .h file so you can call it yourself. // 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX9_RenderDrawData() in the .h file so you can call it yourself.
// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. // 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
#include "imgui_impl_dx9.h"
#include <algorithm> #include <algorithm>
// DirectX
#include <d3d9.h>
#include "imgui.h" #include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_impl_dx9.h"
// allow std::min use // allow std::min use
#ifdef min #ifdef min
#undef min #undef min
#endif #endif
// DirectX data // DirectX
static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; #include <d3d9.h>
static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
static LPDIRECT3DINDEXBUFFER9 g_pIB = NULL;
static LPDIRECT3DTEXTURE9 g_FontTexture = NULL;
static int g_VertexBufferSize = 5000, g_IndexBufferSize = 10000;
// Clang/GCC warnings with -Weverything
#if defined(__clang__)
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#endif
// DirectX data
struct ImGui_ImplDX9_Data
{
LPDIRECT3DDEVICE9 pd3dDevice;
LPDIRECT3DVERTEXBUFFER9 pVB;
LPDIRECT3DINDEXBUFFER9 pIB;
int VertexBufferSize;
int IndexBufferSize;
bool HasRgbaSupport;
ImGui_ImplDX9_Data() { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; }
};
struct CUSTOMVERTEX
{
float pos[3];
D3DCOLOR col;
float uv[2];
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1) #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1)
// Render function. #ifdef IMGUI_USE_BGRA_PACKED_COLOR
// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) #define IMGUI_COL_TO_DX9_ARGB(_COL) (_COL)
void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data) { #else
#define IMGUI_COL_TO_DX9_ARGB(_COL) (((_COL) & 0xFF00FF00) | (((_COL) & 0xFF0000) >> 16) | (((_COL) & 0xFF) << 16))
#endif
// Avoid rendering when minimized // Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) // It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
return; static ImGui_ImplDX9_Data* ImGui_ImplDX9_GetBackendData()
{
return ImGui::GetCurrentContext() ? (ImGui_ImplDX9_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
}
// Create and grow buffers if needed // Forward Declarations
if (!g_pVB || g_VertexBufferSize < draw_data->TotalVtxCount) { static void ImGui_ImplDX9_InitMultiViewportSupport();
if (g_pVB) { static void ImGui_ImplDX9_ShutdownMultiViewportSupport();
g_pVB->Release(); static void ImGui_ImplDX9_CreateDeviceObjectsForPlatformWindows();
g_pVB = NULL; static void ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows();
}
g_VertexBufferSize = draw_data->TotalVtxCount + 5000;
if (g_pd3dDevice->CreateVertexBuffer(g_VertexBufferSize * sizeof(ImDrawVert),
D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT, &g_pVB, NULL) < 0)
return;
}
if (!g_pIB || g_IndexBufferSize < draw_data->TotalIdxCount) {
if (g_pIB) {
g_pIB->Release();
g_pIB = NULL;
}
g_IndexBufferSize = draw_data->TotalIdxCount + 10000;
if (g_pd3dDevice->CreateIndexBuffer(g_IndexBufferSize * sizeof(ImDrawIdx),
D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY,
sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32,
D3DPOOL_DEFAULT, &g_pIB, NULL) < 0)
return;
}
// Backup the DX9 state struct render_state_t {
IDirect3DStateBlock9 *d3d9_state_block = NULL; IDirect3DSurface9 *back_buffer;
if (g_pd3dDevice->CreateStateBlock(D3DSBT_ALL, &d3d9_state_block) < 0) IDirect3DSurface9 *depth_stencil;
return; IDirect3DSurface9 *render_targets[8];
D3DCAPS9 caps;
};
// Backup the DX9 transform (DX9 documentation suggests that it is included in the StateBlock but it doesn't appear to) // Functions
D3DMATRIX last_world, last_view, last_projection; static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data, struct render_state_t *render_state)
g_pd3dDevice->GetTransform(D3DTS_WORLD, &last_world); {
g_pd3dDevice->GetTransform(D3DTS_VIEW, &last_view); ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &last_projection);
// Copy all vertices into a single contiguous buffer
ImDrawVert *vtx_dst;
ImDrawIdx *idx_dst;
if (g_pVB->Lock(0, (UINT) (draw_data->TotalVtxCount * sizeof(ImDrawVert)), (void **) &vtx_dst,
D3DLOCK_DISCARD) < 0)
return;
if (g_pIB->Lock(0, (UINT) (draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void **) &idx_dst,
D3DLOCK_DISCARD) < 0)
return;
for (int n = 0; n < draw_data->CmdListsCount; n++) {
const ImDrawList *cmd_list = draw_data->CmdLists[n];
memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
vtx_dst += cmd_list->VtxBuffer.Size;
idx_dst += cmd_list->IdxBuffer.Size;
}
g_pVB->Unlock();
g_pIB->Unlock();
g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(ImDrawVert));
g_pd3dDevice->SetIndices(g_pIB);
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
// Setup viewport // Setup viewport
D3DVIEWPORT9 vp; D3DVIEWPORT9 vp;
vp.X = vp.Y = 0; vp.X = vp.Y = 0;
vp.Width = (DWORD) draw_data->DisplaySize.x; vp.Width = (DWORD)draw_data->DisplaySize.x;
vp.Height = (DWORD) draw_data->DisplaySize.y; vp.Height = (DWORD)draw_data->DisplaySize.y;
vp.MinZ = 0.0f; vp.MinZ = 0.0f;
vp.MaxZ = 1.0f; vp.MaxZ = 1.0f;
g_pd3dDevice->SetViewport(&vp);
g_pd3dDevice->SetPixelShader(nullptr); LPDIRECT3DDEVICE9 device = bd->pd3dDevice;
g_pd3dDevice->SetVertexShader(nullptr); device->SetViewport(&vp);
D3DCAPS9 caps {}; // spice ldj
if (FAILED(g_pd3dDevice->GetDeviceCaps(&caps))) { {
caps.NumSimultaneousRTs = 0UL; if (FAILED(device->GetDeviceCaps(&render_state->caps))) {
} render_state->caps.NumSimultaneousRTs = 0UL;
}
IDirect3DSurface9 *back_buffer = nullptr; // save all previous render target state
IDirect3DSurface9 *depth_stencil = nullptr; for (size_t target = 0; target < std::min(8UL, render_state->caps.NumSimultaneousRTs); target++) {
IDirect3DSurface9 *render_targets[8]; if (FAILED(device->GetRenderTarget(target, &render_state->render_targets[target]))) {
render_state->render_targets[target] = nullptr;
}
}
// save all previous render target state // get the previous depth stencil
for (size_t target = 0; target < std::min(8UL, caps.NumSimultaneousRTs); target++) { if (FAILED(device->GetDepthStencilSurface(&render_state->depth_stencil))) {
if (FAILED(g_pd3dDevice->GetRenderTarget(target, &render_targets[target]))) { render_state->depth_stencil = nullptr;
render_targets[target] = nullptr; }
// set the back buffer as the current render target
if (SUCCEEDED(device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &render_state->back_buffer))) {
device->SetRenderTarget(0, render_state->back_buffer);
device->SetDepthStencilSurface(nullptr);
for (size_t target = 1; target < std::min(8UL, render_state->caps.NumSimultaneousRTs); target++) {
device->SetRenderTarget(target, nullptr);
}
} else {
render_state->back_buffer = nullptr;
} }
} }
// get the previous depth stencil // Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient), bilinear sampling.
if (FAILED(g_pd3dDevice->GetDepthStencilSurface(&depth_stencil))) { device->SetPixelShader(nullptr);
depth_stencil = nullptr; device->SetVertexShader(nullptr);
} device->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
// set the back buffer as the current render target device->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
if (SUCCEEDED(g_pd3dDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &back_buffer))) { device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
g_pd3dDevice->SetRenderTarget(0, back_buffer); device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
g_pd3dDevice->SetDepthStencilSurface(nullptr); device->SetRenderState(D3DRS_ZENABLE, FALSE);
device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
for (size_t target = 1; target < std::min(8UL, caps.NumSimultaneousRTs); target++) { device->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
g_pd3dDevice->SetRenderTarget(target, nullptr); device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
} device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
} else { device->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, TRUE);
back_buffer = nullptr; device->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);
} device->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA);
device->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
// Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient) device->SetRenderState(D3DRS_FOGENABLE, FALSE);
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); device->SetRenderState(D3DRS_RANGEFOGENABLE, FALSE);
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, false); device->SetRenderState(D3DRS_SPECULARENABLE, FALSE);
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); device->SetRenderState(D3DRS_STENCILENABLE, FALSE);
g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true); device->SetRenderState(D3DRS_CLIPPING, TRUE);
g_pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, false); device->SetRenderState(D3DRS_LIGHTING, FALSE);
g_pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); device->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, true); device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
g_pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
g_pd3dDevice->SetRenderState(D3DRS_FOGENABLE, false); device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); device->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); device->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
// Setup orthographic projection matrix // Setup orthographic projection matrix
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
@@ -185,176 +200,537 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data) {
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x + 0.5f; float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x + 0.5f;
float T = draw_data->DisplayPos.y + 0.5f; float T = draw_data->DisplayPos.y + 0.5f;
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y + 0.5f; float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y + 0.5f;
D3DMATRIX mat_identity = {{{1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}}; D3DMATRIX mat_identity = { { { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f } } };
D3DMATRIX mat_projection = D3DMATRIX mat_projection =
{{{ { { {
2.0f / (R - L), 0.0f, 0.0f, 0.0f, 2.0f/(R-L), 0.0f, 0.0f, 0.0f,
0.0f, 2.0f / (T - B), 0.0f, 0.0f, 0.0f, 2.0f/(T-B), 0.0f, 0.0f,
0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f,
(L + R) / (L - R), (T + B) / (B - T), 0.5f, 1.0f (L+R)/(L-R), (T+B)/(B-T), 0.5f, 1.0f
}}}; } } };
g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity); device->SetTransform(D3DTS_WORLD, &mat_identity);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity); device->SetTransform(D3DTS_VIEW, &mat_identity);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection); device->SetTransform(D3DTS_PROJECTION, &mat_projection);
} }
}
// Render function.
void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
{
// Avoid rendering when minimized
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
return;
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
LPDIRECT3DDEVICE9 device = bd->pd3dDevice;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplDX9_UpdateTexture(tex);
// Create and grow buffers if needed
if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
{
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
bd->VertexBufferSize = draw_data->TotalVtxCount + 5000;
if (device->CreateVertexBuffer(bd->VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &bd->pVB, nullptr) < 0)
return;
}
if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
{
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
bd->IndexBufferSize = draw_data->TotalIdxCount + 10000;
if (device->CreateIndexBuffer(bd->IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &bd->pIB, nullptr) < 0)
return;
}
// Backup the DX9 state
IDirect3DStateBlock9* state_block = nullptr;
if (device->CreateStateBlock(D3DSBT_ALL, &state_block) < 0)
return;
if (state_block->Capture() < 0)
{
state_block->Release();
return;
}
// Backup the DX9 transform (DX9 documentation suggests that it is included in the StateBlock but it doesn't appear to)
D3DMATRIX last_world, last_view, last_projection;
device->GetTransform(D3DTS_WORLD, &last_world);
device->GetTransform(D3DTS_VIEW, &last_view);
device->GetTransform(D3DTS_PROJECTION, &last_projection);
// Allocate buffers
CUSTOMVERTEX* vtx_dst;
ImDrawIdx* idx_dst;
if (bd->pVB->Lock(0, (UINT)(draw_data->TotalVtxCount * sizeof(CUSTOMVERTEX)), (void**)&vtx_dst, D3DLOCK_DISCARD) < 0)
{
state_block->Release();
return;
}
if (bd->pIB->Lock(0, (UINT)(draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void**)&idx_dst, D3DLOCK_DISCARD) < 0)
{
bd->pVB->Unlock();
state_block->Release();
return;
}
// Copy and convert all vertices into a single contiguous buffer, convert colors to DX9 default format.
// FIXME-OPT: This is a minor waste of resource, the ideal is to use imconfig.h and
// 1) to avoid repacking colors: #define IMGUI_USE_BGRA_PACKED_COLOR
// 2) to avoid repacking vertices: #define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; }
for (const ImDrawList* draw_list : draw_data->CmdLists)
{
const ImDrawVert* vtx_src = draw_list->VtxBuffer.Data;
for (int i = 0; i < draw_list->VtxBuffer.Size; i++)
{
vtx_dst->pos[0] = vtx_src->pos.x;
vtx_dst->pos[1] = vtx_src->pos.y;
vtx_dst->pos[2] = 0.0f;
vtx_dst->col = IMGUI_COL_TO_DX9_ARGB(vtx_src->col);
vtx_dst->uv[0] = vtx_src->uv.x;
vtx_dst->uv[1] = vtx_src->uv.y;
vtx_dst++;
vtx_src++;
}
memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx));
idx_dst += draw_list->IdxBuffer.Size;
}
bd->pVB->Unlock();
bd->pIB->Unlock();
device->SetStreamSource(0, bd->pVB, 0, sizeof(CUSTOMVERTEX));
device->SetIndices(bd->pIB);
device->SetFVF(D3DFVF_CUSTOMVERTEX);
// Setup desired DX state
struct render_state_t render_state = {};
ImGui_ImplDX9_SetupRenderState(draw_data, &render_state);
// Render command lists // Render command lists
// (Because we merged all buffers into a single one, we maintain our own offset into them) // (Because we merged all buffers into a single one, we maintain our own offset into them)
int global_vtx_offset = 0; int global_vtx_offset = 0;
int global_idx_offset = 0; int global_idx_offset = 0;
ImVec2 clip_off = draw_data->DisplayPos; ImVec2 clip_off = draw_data->DisplayPos;
for (int n = 0; n < draw_data->CmdListsCount; n++) { for (const ImDrawList* draw_list : draw_data->CmdLists)
const ImDrawList *cmd_list = draw_data->CmdLists[n]; {
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++)
const ImDrawCmd *pcmd = &cmd_list->CmdBuffer[cmd_i]; {
if (pcmd->UserCallback != NULL) { const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
pcmd->UserCallback(cmd_list, pcmd); if (pcmd->UserCallback != nullptr)
} else { {
const RECT r = {(LONG) (pcmd->ClipRect.x - clip_off.x), (LONG) (pcmd->ClipRect.y - clip_off.y), // User callback, registered via ImDrawList::AddCallback()
(LONG) (pcmd->ClipRect.z - clip_off.x), (LONG) (pcmd->ClipRect.w - clip_off.y)}; // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
auto texture = reinterpret_cast<IDirect3DBaseTexture9 *>(pcmd->TextureId); if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
g_pd3dDevice->SetTexture(0, texture); ImGui_ImplDX9_SetupRenderState(draw_data, &render_state);
g_pd3dDevice->SetScissorRect(&r); else
g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->UserCallback(draw_list, pcmd);
pcmd->VtxOffset + global_vtx_offset, 0, }
(UINT) cmd_list->VtxBuffer.Size, else
pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3); {
// Project scissor/clipping rectangles into framebuffer space
ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
continue;
// Apply scissor/clipping rectangle
const RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
device->SetScissorRect(&r);
// Bind texture, Draw
const LPDIRECT3DTEXTURE9 texture = (LPDIRECT3DTEXTURE9)pcmd->GetTexID();
device->SetTexture(0, texture);
device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)draw_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3);
} }
} }
global_idx_offset += cmd_list->IdxBuffer.Size; global_idx_offset += draw_list->IdxBuffer.Size;
global_vtx_offset += cmd_list->VtxBuffer.Size; global_vtx_offset += draw_list->VtxBuffer.Size;
} }
if (back_buffer) { // When using multi-viewports, it appears that there's an odd logic in DirectX9 which prevent subsequent windows
back_buffer->Release(); // from rendering until the first window submits at least one draw call, even once. That's our workaround. (see #2560)
back_buffer = nullptr; if (global_vtx_offset == 0)
} bd->pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 0, 0, 0);
// restore previous depth stencil // spice ldj
if (depth_stencil) { {
g_pd3dDevice->SetDepthStencilSurface(depth_stencil); if (render_state.back_buffer) {
depth_stencil->Release(); render_state.back_buffer->Release();
depth_stencil = nullptr; render_state.back_buffer = nullptr;
} }
// restore previous depth stencil
if (render_state.depth_stencil) {
device->SetDepthStencilSurface(render_state.depth_stencil);
render_state.depth_stencil->Release();
render_state.depth_stencil = nullptr;
}
// restore all render target state
for (size_t target = 0; target < std::min(8UL, render_state.caps.NumSimultaneousRTs); target++) {
auto render_target = render_state.render_targets[target];
// restore all render target state if (render_target) {
for (size_t target = 0; target < std::min(8UL, caps.NumSimultaneousRTs); target++) { device->SetRenderTarget(target, render_target);
auto render_target = render_targets[target]; render_target->Release();
}
if (render_target) {
g_pd3dDevice->SetRenderTarget(target, render_target);
render_target->Release();
} }
} }
// restore the DX9 transform // Restore the DX9 transform
g_pd3dDevice->SetTransform(D3DTS_WORLD, &last_world); device->SetTransform(D3DTS_WORLD, &last_world);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &last_view); device->SetTransform(D3DTS_VIEW, &last_view);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &last_projection); device->SetTransform(D3DTS_PROJECTION, &last_projection);
// restore the DX9 state // Restore the DX9 state
d3d9_state_block->Apply(); state_block->Apply();
d3d9_state_block->Release(); state_block->Release();
} }
bool ImGui_ImplDX9_Init(IDirect3DDevice9 *device) { static bool ImGui_ImplDX9_CheckFormatSupport(LPDIRECT3DDEVICE9 pDevice, D3DFORMAT format)
{
LPDIRECT3D9 pd3d = nullptr;
if (pDevice->GetDirect3D(&pd3d) != D3D_OK)
return false;
D3DDEVICE_CREATION_PARAMETERS param = {};
D3DDISPLAYMODE mode = {};
if (pDevice->GetCreationParameters(&param) != D3D_OK || pDevice->GetDisplayMode(0, &mode) != D3D_OK)
{
pd3d->Release();
return false;
}
// Font texture should support linear filter, color blend and write to render-target
bool support = (pd3d->CheckDeviceFormat(param.AdapterOrdinal, param.DeviceType, mode.Format, D3DUSAGE_DYNAMIC | D3DUSAGE_QUERY_FILTER | D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING, D3DRTYPE_TEXTURE, format)) == D3D_OK;
pd3d->Release();
return support;
}
// Setup back-end capabilities flags bool ImGui_ImplDX9_Init(IDirect3DDevice9* device)
auto &io = ImGui::GetIO(); {
ImGuiIO& io = ImGui::GetIO();
IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
// Setup backend capabilities flags
ImGui_ImplDX9_Data* bd = IM_NEW(ImGui_ImplDX9_Data)();
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_dx9"; io.BackendRendererName = "imgui_impl_dx9";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
// We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = 4096;
bd->pd3dDevice = device;
bd->pd3dDevice->AddRef();
bd->HasRgbaSupport = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8);
ImGui_ImplDX9_InitMultiViewportSupport();
g_pd3dDevice = device;
g_pd3dDevice->AddRef();
return true; return true;
} }
void ImGui_ImplDX9_Shutdown() { void ImGui_ImplDX9_Shutdown()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
ImGui_ImplDX9_ShutdownMultiViewportSupport();
ImGui_ImplDX9_InvalidateDeviceObjects(); ImGui_ImplDX9_InvalidateDeviceObjects();
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
if (g_pd3dDevice) { io.BackendRendererName = nullptr;
g_pd3dDevice->Release(); io.BackendRendererUserData = nullptr;
g_pd3dDevice = NULL; io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports);
platform_io.ClearRendererHandlers();
IM_DELETE(bd);
}
// Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices)
static void ImGui_ImplDX9_CopyTextureRegion(bool tex_use_colors, const ImU32* src, int src_pitch, ImU32* dst, int dst_pitch, int w, int h)
{
#ifndef IMGUI_USE_BGRA_PACKED_COLOR
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
const bool convert_rgba_to_bgra = (!bd->HasRgbaSupport && tex_use_colors);
#else
const bool convert_rgba_to_bgra = false;
IM_UNUSED(tex_use_colors);
#endif
for (int y = 0; y < h; y++)
{
const ImU32* src_p = (const ImU32*)(const void*)((const unsigned char*)src + src_pitch * y);
ImU32* dst_p = (ImU32*)(void*)((unsigned char*)dst + dst_pitch * y);
if (convert_rgba_to_bgra)
for (int x = w; x > 0; x--, src_p++, dst_p++) // Convert copy
*dst_p = IMGUI_COL_TO_DX9_ARGB(*src_p);
else
memcpy(dst_p, src_p, w * 4); // Raw copy
} }
} }
static bool ImGui_ImplDX9_CreateFontsTexture() { void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex)
// Build texture atlas {
ImGuiIO &io = ImGui::GetIO(); ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
unsigned char *pixels;
int width, height, bytes_per_pixel;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel);
// Upload texture to graphics system if (tex->Status == ImTextureStatus_WantCreate)
g_FontTexture = NULL; {
if (g_pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, // Create and upload new texture to graphics system
D3DPOOL_DEFAULT, &g_FontTexture, NULL) < 0) //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
return false; IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
D3DLOCKED_RECT tex_locked_rect; IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
if (g_FontTexture->LockRect(0, &tex_locked_rect, NULL, 0) != D3D_OK) LPDIRECT3DTEXTURE9 dx_tex = nullptr;
return false; HRESULT hr = bd->pd3dDevice->CreateTexture(tex->Width, tex->Height, 1, D3DUSAGE_DYNAMIC, bd->HasRgbaSupport ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &dx_tex, nullptr);
for (int y = 0; y < height; y++) if (hr < 0)
memcpy((unsigned char *) tex_locked_rect.pBits + tex_locked_rect.Pitch * y, {
pixels + (width * bytes_per_pixel) * y, (width * bytes_per_pixel)); IM_ASSERT(hr >= 0 && "Backend failed to create texture!");
g_FontTexture->UnlockRect(0); return;
// Store our identifier
io.Fonts->TexID = (ImTextureID) g_FontTexture;
return true;
}
bool ImGui_ImplDX9_CreateDeviceObjects() {
if (!g_pd3dDevice) {
return false;
}
return ImGui_ImplDX9_CreateFontsTexture();
}
void ImGui_ImplDX9_InvalidateDeviceObjects() {
if (!g_pd3dDevice)
return;
if (g_pVB) {
g_pVB->Release();
g_pVB = NULL;
}
if (g_pIB) {
g_pIB->Release();
g_pIB = NULL;
}
if (g_FontTexture) {
g_FontTexture->Release();
g_FontTexture = NULL;
ImGui::GetIO().Fonts->TexID = 0;
} // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well.
}
void ImGui_ImplDX9_NewFrame() {
if (!g_FontTexture) {
ImGui_ImplDX9_CreateDeviceObjects();
}
IDirect3DSwapChain9 *swap_chain = nullptr;
if (SUCCEEDED(g_pd3dDevice->GetSwapChain(0, &swap_chain))) {
auto &io = ImGui::GetIO();
D3DPRESENT_PARAMETERS present_params {};
if (SUCCEEDED(swap_chain->GetPresentParameters(&present_params))) {
if (present_params.BackBufferWidth != 0 && present_params.BackBufferHeight != 0) {
io.DisplaySize.x = static_cast<float>(present_params.BackBufferWidth);
io.DisplaySize.y = static_cast<float>(present_params.BackBufferHeight);
} else {
RECT rect {};
GetClientRect(present_params.hDeviceWindow, &rect);
io.DisplaySize.x = static_cast<float>(rect.right - rect.left);
io.DisplaySize.y = static_cast<float>(rect.bottom - rect.top);
}
} }
swap_chain->Release(); D3DLOCKED_RECT locked_rect;
if (dx_tex->LockRect(0, &locked_rect, nullptr, 0) == D3D_OK)
{
ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixels(), tex->Width * 4, (ImU32*)locked_rect.pBits, (ImU32)locked_rect.Pitch, tex->Width, tex->Height);
dx_tex->UnlockRect(0);
}
// Store identifiers
tex->SetTexID((ImTextureID)(intptr_t)dx_tex);
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantUpdates)
{
// Update selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)(intptr_t)tex->TexID;
RECT update_rect = { (LONG)tex->UpdateRect.x, (LONG)tex->UpdateRect.y, (LONG)(tex->UpdateRect.x + tex->UpdateRect.w), (LONG)(tex->UpdateRect.y + tex->UpdateRect.h) };
D3DLOCKED_RECT locked_rect;
if (backend_tex->LockRect(0, &locked_rect, &update_rect, 0) == D3D_OK)
for (ImTextureRect& r : tex->Updates)
ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixelsAt(r.x, r.y), tex->Width * 4,
(ImU32*)locked_rect.pBits + (r.x - update_rect.left) + (r.y - update_rect.top) * (locked_rect.Pitch / 4), (int)locked_rect.Pitch, r.w, r.h);
backend_tex->UnlockRect(0);
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantDestroy)
{
if (tex->TexID != ImTextureID_Invalid)
if (LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)tex->TexID)
{
IM_ASSERT(tex->TexID == (ImTextureID)(intptr_t)backend_tex);
backend_tex->Release();
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
}
tex->SetStatus(ImTextureStatus_Destroyed);
} }
} }
bool ImGui_ImplDX9_CreateDeviceObjects()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (!bd || !bd->pd3dDevice)
return false;
ImGui_ImplDX9_CreateDeviceObjectsForPlatformWindows();
return true;
}
void ImGui_ImplDX9_InvalidateDeviceObjects()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (!bd || !bd->pd3dDevice)
return;
// Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
{
tex->SetStatus(ImTextureStatus_WantDestroy);
ImGui_ImplDX9_UpdateTexture(tex);
}
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows();
}
void ImGui_ImplDX9_NewFrame()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX9_Init()?");
// IM_UNUSED(bd);
// spice ldj
{
IDirect3DSwapChain9 *swap_chain = nullptr;
if (SUCCEEDED(bd->pd3dDevice->GetSwapChain(0, &swap_chain))) {
auto &io = ImGui::GetIO();
D3DPRESENT_PARAMETERS present_params {};
if (SUCCEEDED(swap_chain->GetPresentParameters(&present_params))) {
if (present_params.BackBufferWidth != 0 && present_params.BackBufferHeight != 0) {
io.DisplaySize.x = static_cast<float>(present_params.BackBufferWidth);
io.DisplaySize.y = static_cast<float>(present_params.BackBufferHeight);
} else {
RECT rect {};
GetClientRect(present_params.hDeviceWindow, &rect);
io.DisplaySize.x = static_cast<float>(rect.right - rect.left);
io.DisplaySize.y = static_cast<float>(rect.bottom - rect.top);
}
}
swap_chain->Release();
}
}
}
//--------------------------------------------------------------------------------------------------------
// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT
// This is an _advanced_ and _optional_ feature, allowing the backend to create and handle multiple viewports simultaneously.
// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first..
//--------------------------------------------------------------------------------------------------------
// Helper structure we store in the void* RendererUserData field of each ImGuiViewport to easily retrieve our backend data.
struct ImGui_ImplDX9_ViewportData
{
IDirect3DSwapChain9* SwapChain;
D3DPRESENT_PARAMETERS d3dpp;
ImGui_ImplDX9_ViewportData() { SwapChain = nullptr; ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS)); }
~ImGui_ImplDX9_ViewportData() { IM_ASSERT(SwapChain == nullptr); }
};
static void ImGui_ImplDX9_CreateWindow(ImGuiViewport* viewport)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
ImGui_ImplDX9_ViewportData* vd = IM_NEW(ImGui_ImplDX9_ViewportData)();
viewport->RendererUserData = vd;
// PlatformHandleRaw should always be a HWND, whereas PlatformHandle might be a higher-level handle (e.g. GLFWWindow*, SDL's WindowID).
// Some backends will leave PlatformHandleRaw == 0, in which case we assume PlatformHandle will contain the HWND.
HWND hwnd = viewport->PlatformHandleRaw ? (HWND)viewport->PlatformHandleRaw : (HWND)viewport->PlatformHandle;
IM_ASSERT(hwnd != 0);
ZeroMemory(&vd->d3dpp, sizeof(D3DPRESENT_PARAMETERS));
vd->d3dpp.Windowed = TRUE;
vd->d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
vd->d3dpp.BackBufferWidth = (UINT)viewport->Size.x;
vd->d3dpp.BackBufferHeight = (UINT)viewport->Size.y;
vd->d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
vd->d3dpp.hDeviceWindow = hwnd;
vd->d3dpp.EnableAutoDepthStencil = FALSE;
vd->d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
vd->d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; // Present without vsync
HRESULT hr = bd->pd3dDevice->CreateAdditionalSwapChain(&vd->d3dpp, &vd->SwapChain); IM_UNUSED(hr);
IM_ASSERT(hr == D3D_OK);
IM_ASSERT(vd->SwapChain != nullptr);
}
static void ImGui_ImplDX9_DestroyWindow(ImGuiViewport* viewport)
{
// The main viewport (owned by the application) will always have RendererUserData == 0 since we didn't create the data for it.
if (ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData)
{
if (vd->SwapChain)
vd->SwapChain->Release();
vd->SwapChain = nullptr;
ZeroMemory(&vd->d3dpp, sizeof(D3DPRESENT_PARAMETERS));
IM_DELETE(vd);
}
viewport->RendererUserData = nullptr;
}
static void ImGui_ImplDX9_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData;
if (vd->SwapChain)
{
vd->SwapChain->Release();
vd->SwapChain = nullptr;
vd->d3dpp.BackBufferWidth = (UINT)size.x;
vd->d3dpp.BackBufferHeight = (UINT)size.y;
HRESULT hr = bd->pd3dDevice->CreateAdditionalSwapChain(&vd->d3dpp, &vd->SwapChain); IM_UNUSED(hr);
IM_ASSERT(hr == D3D_OK);
}
}
static void ImGui_ImplDX9_RenderWindow(ImGuiViewport* viewport, void*)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData;
ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
LPDIRECT3DSURFACE9 render_target = nullptr;
LPDIRECT3DSURFACE9 last_render_target = nullptr;
LPDIRECT3DSURFACE9 last_depth_stencil = nullptr;
vd->SwapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &render_target);
bd->pd3dDevice->GetRenderTarget(0, &last_render_target);
bd->pd3dDevice->GetDepthStencilSurface(&last_depth_stencil);
bd->pd3dDevice->SetRenderTarget(0, render_target);
bd->pd3dDevice->SetDepthStencilSurface(nullptr);
if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear))
{
D3DCOLOR clear_col_dx = D3DCOLOR_RGBA((int)(clear_color.x*255.0f), (int)(clear_color.y*255.0f), (int)(clear_color.z*255.0f), (int)(clear_color.w*255.0f));
bd->pd3dDevice->Clear(0, nullptr, D3DCLEAR_TARGET, clear_col_dx, 1.0f, 0);
}
ImGui_ImplDX9_RenderDrawData(viewport->DrawData);
// Restore render target
bd->pd3dDevice->SetRenderTarget(0, last_render_target);
bd->pd3dDevice->SetDepthStencilSurface(last_depth_stencil);
render_target->Release();
last_render_target->Release();
if (last_depth_stencil) last_depth_stencil->Release();
}
static void ImGui_ImplDX9_SwapBuffers(ImGuiViewport* viewport, void*)
{
ImGui_ImplDX9_ViewportData* vd = (ImGui_ImplDX9_ViewportData*)viewport->RendererUserData;
HRESULT hr = vd->SwapChain->Present(nullptr, nullptr, vd->d3dpp.hDeviceWindow, nullptr, 0);
// Let main application handle D3DERR_DEVICELOST by resetting the device.
IM_ASSERT(SUCCEEDED(hr) || hr == D3DERR_DEVICELOST);
}
static void ImGui_ImplDX9_InitMultiViewportSupport()
{
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Renderer_CreateWindow = ImGui_ImplDX9_CreateWindow;
platform_io.Renderer_DestroyWindow = ImGui_ImplDX9_DestroyWindow;
platform_io.Renderer_SetWindowSize = ImGui_ImplDX9_SetWindowSize;
platform_io.Renderer_RenderWindow = ImGui_ImplDX9_RenderWindow;
platform_io.Renderer_SwapBuffers = ImGui_ImplDX9_SwapBuffers;
}
static void ImGui_ImplDX9_ShutdownMultiViewportSupport()
{
ImGui::DestroyPlatformWindows();
}
static void ImGui_ImplDX9_CreateDeviceObjectsForPlatformWindows()
{
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
for (int i = 1; i < platform_io.Viewports.Size; i++)
if (!platform_io.Viewports[i]->RendererUserData)
ImGui_ImplDX9_CreateWindow(platform_io.Viewports[i]);
}
static void ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows()
{
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
for (int i = 1; i < platform_io.Viewports.Size; i++)
if (platform_io.Viewports[i]->RendererUserData)
ImGui_ImplDX9_DestroyWindow(platform_io.Viewports[i]);
}
//-----------------------------------------------------------------------------
#endif // #ifndef IMGUI_DISABLE
+25 -12
View File
@@ -1,25 +1,38 @@
// dear imgui: Renderer for DirectX9 // dear imgui: Renderer Backend for DirectX9
// This needs to be used along with a Platform Binding (e.g. Win32) // This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. // [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bits indices. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9.
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// https://github.com/ocornut/imgui // Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
#pragma once #pragma once
#include "imgui.h" // IMGUI_IMPL_API
#include "imgui.h" #ifndef IMGUI_DISABLE
struct IDirect3DDevice9; struct IDirect3DDevice9;
IMGUI_IMPL_API bool ImGui_ImplDX9_Init(IDirect3DDevice9 *device); // Follow "Getting Started" link and check examples/ folder to learn about using backends!
IMGUI_IMPL_API bool ImGui_ImplDX9_Init(IDirect3DDevice9* device);
IMGUI_IMPL_API void ImGui_ImplDX9_Shutdown(); IMGUI_IMPL_API void ImGui_ImplDX9_Shutdown();
IMGUI_IMPL_API void ImGui_ImplDX9_NewFrame(); IMGUI_IMPL_API void ImGui_ImplDX9_NewFrame();
IMGUI_IMPL_API void ImGui_ImplDX9_RenderDrawData(ImDrawData *draw_data); IMGUI_IMPL_API void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data);
// Use if you want to reset your rendering device without losing ImGui state. // Use if you want to reset your rendering device without losing Dear ImGui state.
IMGUI_IMPL_API bool ImGui_ImplDX9_CreateDeviceObjects(); IMGUI_IMPL_API bool ImGui_ImplDX9_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = nullptr to handle this manually.
IMGUI_IMPL_API void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex);
#endif // #ifndef IMGUI_DISABLE
+13 -5
View File
@@ -15,7 +15,8 @@
#pragma once #pragma once
//---- Define assertion handler. Defaults to calling assert(). //---- Define assertion handler. Defaults to calling assert().
// If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. // - If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.
// - Compiling with NDEBUG will usually strip out assert() to nothing, which is NOT recommended because we use asserts to notify of programmer mistakes.
//#define IM_ASSERT(_EXPR) MyAssert(_EXPR) //#define IM_ASSERT(_EXPR) MyAssert(_EXPR)
#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts #define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts
@@ -29,7 +30,6 @@
//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names. //---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names.
//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
//#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87+ disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This is automatically done by IMGUI_DISABLE_OBSOLETE_FUNCTIONS.
//---- Disable all of Dear ImGui or don't implement standard windows/tools. //---- Disable all of Dear ImGui or don't implement standard windows/tools.
// It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp. // It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp.
@@ -49,6 +49,7 @@
//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies) //#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. //#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.
#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). #define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean/ProggyForever), remove ~9 KB + ~14 KB from output binary. AddFontDefaultXXX() functions will assert.
//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available //#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available
//---- Enable Test Engine / Automation features. //---- Enable Test Engine / Automation features.
@@ -59,12 +60,15 @@
//#define IMGUI_INCLUDE_IMGUI_USER_H //#define IMGUI_INCLUDE_IMGUI_USER_H
//#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h" //#define IMGUI_USER_H_FILENAME "my_folder/my_imgui_user.h"
//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) //---- Pack vertex colors as BGRA8 instead of RGBA8 (to avoid converting from one to another). Need dedicated backend support.
#define IMGUI_USE_BGRA_PACKED_COLOR #define IMGUI_USE_BGRA_PACKED_COLOR
//---- Performance optimization for DX9 //---- Performance optimization for DX9
#define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; } #define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; }
//---- Use legacy CRC32-adler tables (used before 1.91.6), in order to preserve old .ini data that you cannot afford to invalidate.
//#define IMGUI_USE_LEGACY_CRC32_ADLER
//---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...) //---- Use 32-bit for ImWchar (default is 16-bit) to support Unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)
//#define IMGUI_USE_WCHAR32 //#define IMGUI_USE_WCHAR32
@@ -83,13 +87,13 @@
//---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui) //---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui)
// Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided). // Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided).
// Note that imgui_freetype.cpp may be used _without_ this define, if you manually call ImFontAtlas::SetFontLoader(). The define is simply a convenience.
// On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'. // On Windows you may use vcpkg with 'vcpkg install freetype --triplet=x64-windows' + 'vcpkg integrate install'.
//#define IMGUI_ENABLE_FREETYPE //#define IMGUI_ENABLE_FREETYPE
//---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT) //---- Use FreeType + plutosvg or lunasvg to render OpenType SVG fonts (SVGinOT)
// Only works in combination with IMGUI_ENABLE_FREETYPE. // Only works in combination with IMGUI_ENABLE_FREETYPE.
// - lunasvg is currently easier to acquire/install, as e.g. it is part of vcpkg. // - plutosvg is currently easier to install, as e.g. it is part of vcpkg. It will support more fonts and may load them faster. See misc/freetype/README for instructions.
// - plutosvg will support more fonts and may load them faster. It currently requires to be built manually but it is fairly easy. See misc/freetype/README for instructions.
// - Both require headers to be available in the include path + program to be linked with the library code (not provided). // - Both require headers to be available in the include path + program to be linked with the library code (not provided).
// - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement) // - (note: lunasvg implementation is based on Freetype's rsvg-port.c which is licensed under CeCILL-C Free Software License Agreement)
//#define IMGUI_ENABLE_FREETYPE_PLUTOSVG //#define IMGUI_ENABLE_FREETYPE_PLUTOSVG
@@ -130,6 +134,10 @@
//#define IM_DEBUG_BREAK IM_ASSERT(0) //#define IM_DEBUG_BREAK IM_ASSERT(0)
//#define IM_DEBUG_BREAK __debugbreak() //#define IM_DEBUG_BREAK __debugbreak()
//---- Debug Tools: Enable highlight ID conflicts _before_ hovering items. When io.ConfigDebugHighlightIdConflicts is set.
// (THIS WILL SLOW DOWN DEAR IMGUI. Only use occasionally and disable after use)
//#define IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS
//---- Debug Tools: Enable slower asserts //---- Debug Tools: Enable slower asserts
//#define IMGUI_DEBUG_PARANOID //#define IMGUI_DEBUG_PARANOID
+3439 -1953
View File
File diff suppressed because it is too large Load Diff
+1022 -516
View File
File diff suppressed because it is too large Load Diff
+2825 -2079
View File
File diff suppressed because it is too large Load Diff
+3216 -1153
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+258 -164
View File
@@ -1,6 +1,7 @@
// Mini memory editor for Dear ImGui (to embed in your game/tools) // Mini memory editor for Dear ImGui (to embed in your game/tools)
// Get latest version at http://www.github.com/ocornut/imgui_club // Get latest version at http://www.github.com/ocornut/imgui_club
// // Licensed under The MIT License (MIT)
// Right-click anywhere to access the Options menu! // Right-click anywhere to access the Options menu!
// You can adjust the keyboard repeat delay/rate in ImGuiIO. // You can adjust the keyboard repeat delay/rate in ImGuiIO.
// The code assume a mono-space font for simplicity! // The code assume a mono-space font for simplicity!
@@ -39,10 +40,22 @@
// - v0.43 (2021/03/12): added OptFooterExtraHeight to allow for custom drawing at the bottom of the editor [@leiradel] // - v0.43 (2021/03/12): added OptFooterExtraHeight to allow for custom drawing at the bottom of the editor [@leiradel]
// - v0.44 (2021/03/12): use ImGuiInputTextFlags_AlwaysOverwrite in 1.82 + fix hardcoded width. // - v0.44 (2021/03/12): use ImGuiInputTextFlags_AlwaysOverwrite in 1.82 + fix hardcoded width.
// - v0.50 (2021/11/12): various fixes for recent dear imgui versions (fixed misuse of clipper, relying on SetKeyboardFocusHere() handling scrolling from 1.85). added default size. // - v0.50 (2021/11/12): various fixes for recent dear imgui versions (fixed misuse of clipper, relying on SetKeyboardFocusHere() handling scrolling from 1.85). added default size.
// - v0.51 (2024/02/22): fix for layout change in 1.89 when using IMGUI_DISABLE_OBSOLETE_FUNCTIONS. (#34)
// - v0.52 (2024/03/08): removed unnecessary GetKeyIndex() calls, they are a no-op since 1.87.
// - v0.53 (2024/05/27): fixed right-click popup from not appearing when using DrawContents(). warning fixes. (#35)
// - v0.54 (2024/07/29): allow ReadOnly mode to still select and preview data. (#46) [@DeltaGW2])
// - v0.55 (2024/08/19): added BgColorFn to allow setting background colors independently from highlighted selection. (#27) [@StrikerX3]
// added MouseHoveredAddr public readable field. (#47, #27) [@StrikerX3]
// fixed a data preview crash with 1.91.0 WIP. fixed contiguous highlight color when using data preview.
// *BREAKING* added UserData field passed to all optional function handlers: ReadFn, WriteFn, HighlightFn, BgColorFn. (#50) [@silverweed]
// - v0.56 (2024/11/04): fixed MouseHovered, MouseHoveredAddr not being set when hovering a byte being edited. (#54)
// - v0.57 (2025/03/26): fixed warnings. using ImGui's ImSXX/ImUXX types instead of e.g. int32_t/uint32_t. (#56)
// - v0.58 (2025/03/31): fixed extraneous footer spacing (added in 0.51) breaking vertical auto-resize. (#53)
// - v0.59 (2025/04/08): fixed GotoAddrAndHighlight() not working if OptShowOptions is disabled.
// //
// Todo/Bugs: // TODO:
// - This is generally old/crappy code, it should work but isn't very good.. to be rewritten some day. // - This is generally old/crappy code, it should work but isn't very good.. to be rewritten some day.
// - PageUp/PageDown are supported because we use _NoNav. This is a good test scenario for working out idioms of how to mix natural nav and our own... // - PageUp/PageDown are not supported because we use _NoNav. This is a good test scenario for working out idioms of how to mix natural nav and our own...
// - Arrows are being sent to the InputText() about to disappear which for LeftArrow makes the text cursor appear at position 1 for one frame. // - Arrows are being sent to the InputText() about to disappear which for LeftArrow makes the text cursor appear at position 1 for one frame.
// - Using InputText() is awkward and maybe overkill here, consider implementing something custom. // - Using InputText() is awkward and maybe overkill here, consider implementing something custom.
@@ -51,15 +64,18 @@
#include <stdio.h> // sprintf, scanf #include <stdio.h> // sprintf, scanf
#include <stdint.h> // uint8_t, etc. #include <stdint.h> // uint8_t, etc.
#ifdef _MSC_VER #if defined(_MSC_VER) && !defined(snprintf)
#define _PRISizeT "I"
#define ImSnprintf _snprintf #define ImSnprintf _snprintf
#else #else
#define _PRISizeT "z"
#define ImSnprintf snprintf #define ImSnprintf snprintf
#endif #endif
#if defined(_MSC_VER) && !defined(__clang__)
#define _PRISizeT "I"
#else
#define _PRISizeT "z"
#endif
#ifdef _MSC_VER #if defined(_MSC_VER) || defined(_UCRT)
#pragma warning (push) #pragma warning (push)
#pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe. #pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe.
#endif #endif
@@ -88,9 +104,17 @@ struct MemoryEditor
int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr). int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr).
float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add custom widgets float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add custom widgets
ImU32 HighlightColor; // // background color of highlighted bytes. ImU32 HighlightColor; // // background color of highlighted bytes.
ImU8 (*ReadFn)(const ImU8* data, size_t off); // = 0 // optional handler to read bytes.
void (*WriteFn)(ImU8* data, size_t off, ImU8 d); // = 0 // optional handler to write bytes. // Function handlers
bool (*HighlightFn)(const ImU8* data, size_t off);//= 0 // optional handler to return Highlight property (to support non-contiguous highlighting). ImU8 (*ReadFn)(const ImU8* mem, size_t off, void* user_data); // = 0 // optional handler to read bytes.
void (*WriteFn)(ImU8* mem, size_t off, ImU8 d, void* user_data); // = 0 // optional handler to write bytes.
bool (*HighlightFn)(const ImU8* mem, size_t off, void* user_data); // = 0 // optional handler to return Highlight property (to support non-contiguous highlighting).
ImU32 (*BgColorFn)(const ImU8* mem, size_t off, void* user_data); // = 0 // optional handler to return custom background color of individual bytes.
void* UserData; // = NULL // user data forwarded to the function handlers
// Public read-only data
bool MouseHovered; // set when mouse is hovering a value.
size_t MouseHoveredAddr; // the address currently being hovered if MouseHovered is set.
// [Internal State] // [Internal State]
bool ContentsWidthChanged; bool ContentsWidthChanged;
@@ -101,7 +125,7 @@ struct MemoryEditor
char AddrInputBuf[32]; char AddrInputBuf[32];
size_t GotoAddr; size_t GotoAddr;
size_t HighlightMin, HighlightMax; size_t HighlightMin, HighlightMax;
int PreviewEndianess; int PreviewEndianness;
ImGuiDataType PreviewDataType; ImGuiDataType PreviewDataType;
MemoryEditor() MemoryEditor()
@@ -120,9 +144,11 @@ struct MemoryEditor
OptAddrDigitsCount = 0; OptAddrDigitsCount = 0;
OptFooterExtraHeight = 0.0f; OptFooterExtraHeight = 0.0f;
HighlightColor = IM_COL32(255, 255, 255, 50); HighlightColor = IM_COL32(255, 255, 255, 50);
ReadFn = NULL; ReadFn = nullptr;
WriteFn = NULL; WriteFn = nullptr;
HighlightFn = NULL; HighlightFn = nullptr;
BgColorFn = nullptr;
UserData = nullptr;
// State/Internals // State/Internals
ContentsWidthChanged = false; ContentsWidthChanged = false;
@@ -131,8 +157,10 @@ struct MemoryEditor
memset(DataInputBuf, 0, sizeof(DataInputBuf)); memset(DataInputBuf, 0, sizeof(DataInputBuf));
memset(AddrInputBuf, 0, sizeof(AddrInputBuf)); memset(AddrInputBuf, 0, sizeof(AddrInputBuf));
GotoAddr = (size_t)-1; GotoAddr = (size_t)-1;
MouseHovered = false;
MouseHoveredAddr = 0;
HighlightMin = HighlightMax = (size_t)-1; HighlightMin = HighlightMax = (size_t)-1;
PreviewEndianess = 0; PreviewEndianness = 0;
PreviewDataType = ImGuiDataType_S32; PreviewDataType = ImGuiDataType_S32;
} }
@@ -145,16 +173,16 @@ struct MemoryEditor
struct Sizes struct Sizes
{ {
int AddrDigitsCount; int AddrDigitsCount; // Number of digits required to represent maximum address.
float LineHeight; float LineHeight; // Height of each line (no spacing).
float GlyphWidth; float GlyphWidth; // Glyph width (assume mono-space).
float HexCellWidth; float HexCellWidth; // Width of a hex edit cell ~2.5f * GlypHWidth.
float SpacingBetweenMidCols; float SpacingBetweenMidCols; // Spacing between each columns section (OptMidColsCount).
float PosHexStart; float OffsetHexMinX;
float PosHexEnd; float OffsetHexMaxX;
float PosAsciiStart; float OffsetAsciiMinX;
float PosAsciiEnd; float OffsetAsciiMaxX;
float WindowWidth; float WindowWidth; // Ideal window width.
Sizes() { memset(this, 0, sizeof(*this)); } Sizes() { memset(this, 0, sizeof(*this)); }
}; };
@@ -170,17 +198,17 @@ struct MemoryEditor
s.GlyphWidth = ImGui::CalcTextSize("F").x + 1; // We assume the font is mono-space s.GlyphWidth = ImGui::CalcTextSize("F").x + 1; // We assume the font is mono-space
s.HexCellWidth = (float)(int)(s.GlyphWidth * 2.5f); // "FF " we include trailing space in the width to easily catch clicks everywhere s.HexCellWidth = (float)(int)(s.GlyphWidth * 2.5f); // "FF " we include trailing space in the width to easily catch clicks everywhere
s.SpacingBetweenMidCols = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidColsCount columns we add a bit of extra spacing s.SpacingBetweenMidCols = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidColsCount columns we add a bit of extra spacing
s.PosHexStart = (s.AddrDigitsCount + 2) * s.GlyphWidth; s.OffsetHexMinX = (s.AddrDigitsCount + 2) * s.GlyphWidth;
s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * Cols); s.OffsetHexMaxX = s.OffsetHexMinX + (s.HexCellWidth * Cols);
s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd; s.OffsetAsciiMinX = s.OffsetAsciiMaxX = s.OffsetHexMaxX;
if (OptShowAscii) if (OptShowAscii)
{ {
s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1; s.OffsetAsciiMinX = s.OffsetHexMaxX + s.GlyphWidth * 1;
if (OptMidColsCount > 0) if (OptMidColsCount > 0)
s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols; s.OffsetAsciiMinX += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth; s.OffsetAsciiMaxX = s.OffsetAsciiMinX + Cols * s.GlyphWidth;
} }
s.WindowWidth = s.PosAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth; s.WindowWidth = s.OffsetAsciiMaxX + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth;
} }
// Standalone Memory Editor window // Standalone Memory Editor window
@@ -194,8 +222,6 @@ struct MemoryEditor
Open = true; Open = true;
if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar)) if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar))
{ {
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right))
ImGui::OpenPopup("context");
DrawContents(mem_data, mem_size, base_display_addr); DrawContents(mem_data, mem_size, base_display_addr);
if (ContentsWidthChanged) if (ContentsWidthChanged)
{ {
@@ -217,6 +243,8 @@ struct MemoryEditor
CalcSizes(s, mem_size, base_display_addr); CalcSizes(s, mem_size, base_display_addr);
ImGuiStyle& style = ImGui::GetStyle(); ImGuiStyle& style = ImGui::GetStyle();
const ImVec2 contents_pos_start = ImGui::GetCursorScreenPos();
// We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window. // We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window.
// This is used as a facility since our main click detection code doesn't assign an ActiveId so the click would normally be caught as a window-move. // This is used as a facility since our main click detection code doesn't assign an ActiveId so the click would normally be caught as a window-move.
const float height_separator = style.ItemSpacing.y; const float height_separator = style.ItemSpacing.y;
@@ -225,20 +253,21 @@ struct MemoryEditor
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1; footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1;
if (OptShowDataPreview) if (OptShowDataPreview)
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 + ImGui::GetTextLineHeightWithSpacing() * 3; footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 + ImGui::GetTextLineHeightWithSpacing() * 3;
ImGui::BeginChild("##scrolling", ImVec2(0, -footer_height), false, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav); ImGui::BeginChild("##scrolling", ImVec2(-FLT_MIN, -footer_height), ImGuiChildFlags_None, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav);
ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
// We are not really using the clipper API correctly here, because we rely on visible_start_addr/visible_end_addr for our scrolling function. // We are not really using the clipper API correctly here, because we rely on visible_start_addr/visible_end_addr for our scrolling function.
const ImVec2 avail_size = ImGui::GetContentRegionAvail();
const int line_total_count = (int)((mem_size + Cols - 1) / Cols); const int line_total_count = (int)((mem_size + Cols - 1) / Cols);
ImGuiListClipper clipper; ImGuiListClipper clipper;
clipper.Begin(line_total_count, s.LineHeight); clipper.Begin(line_total_count, s.LineHeight);
bool data_next = false; bool data_next = false;
if (ReadOnly || DataEditingAddr >= mem_size) if (DataEditingAddr >= mem_size)
DataEditingAddr = (size_t)-1; DataEditingAddr = (size_t)-1;
if (DataPreviewAddr >= mem_size) if (DataPreviewAddr >= mem_size)
DataPreviewAddr = (size_t)-1; DataPreviewAddr = (size_t)-1;
@@ -249,16 +278,16 @@ struct MemoryEditor
if (DataEditingAddr != (size_t)-1) if (DataEditingAddr != (size_t)-1)
{ {
// Move cursor but only apply on next frame so scrolling with be synchronized (because currently we can't change the scrolling while the window is being rendered) // Move cursor but only apply on next frame so scrolling with be synchronized (because currently we can't change the scrolling while the window is being rendered)
if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)) && (ptrdiff_t)DataEditingAddr >= (ptrdiff_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; } if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) && (ptrdiff_t)DataEditingAddr >= (ptrdiff_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - Cols) { data_editing_addr_next = DataEditingAddr + Cols; } else if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - Cols){ data_editing_addr_next = DataEditingAddr + Cols; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)) && (ptrdiff_t)DataEditingAddr > (ptrdiff_t)0) { data_editing_addr_next = DataEditingAddr - 1; } else if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow) && (ptrdiff_t)DataEditingAddr > (ptrdiff_t)0) { data_editing_addr_next = DataEditingAddr - 1; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; } else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; }
} }
// Draw vertical separator // Draw vertical separator
ImVec2 window_pos = ImGui::GetWindowPos(); ImVec2 window_pos = ImGui::GetWindowPos();
if (OptShowAscii) if (OptShowAscii)
draw_list->AddLine(ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border)); draw_list->AddLine(ImVec2(window_pos.x + s.OffsetAsciiMinX - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.OffsetAsciiMinX - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border));
const ImU32 color_text = ImGui::GetColorU32(ImGuiCol_Text); const ImU32 color_text = ImGui::GetColorU32(ImGuiCol_Text);
const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text; const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text;
@@ -268,36 +297,51 @@ struct MemoryEditor
const char* format_byte = OptUpperCaseHex ? "%02X" : "%02x"; const char* format_byte = OptUpperCaseHex ? "%02X" : "%02x";
const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x "; const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x ";
MouseHovered = false;
MouseHoveredAddr = 0;
while (clipper.Step()) while (clipper.Step())
for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines
{ {
size_t addr = (size_t)(line_i * Cols); size_t addr = (size_t)line_i * Cols;
ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr); ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr);
// Draw Hexadecimal // Draw Hexadecimal
for (int n = 0; n < Cols && addr < mem_size; n++, addr++) for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
{ {
float byte_pos_x = s.PosHexStart + s.HexCellWidth * n; float byte_pos_x = s.OffsetHexMinX + s.HexCellWidth * n;
if (OptMidColsCount > 0) if (OptMidColsCount > 0)
byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols; byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols;
ImGui::SameLine(byte_pos_x); ImGui::SameLine(byte_pos_x);
// Draw highlight // Draw highlight or custom background color
bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax); const bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax);
bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr)); const bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr, UserData));
bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size); const bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size);
ImU32 bg_color = 0;
bool is_next_byte_highlighted = false;
if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview) if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview)
{ {
ImVec2 pos = ImGui::GetCursorScreenPos(); is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1, UserData)) || (addr + 1 < DataPreviewAddr + preview_data_type_size));
float highlight_width = s.GlyphWidth * 2; bg_color = HighlightColor;
bool is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1))); }
else if (BgColorFn != nullptr)
{
is_next_byte_highlighted = (addr + 1 < mem_size) && ((BgColorFn(mem_data, addr + 1, UserData) & IM_COL32_A_MASK) != 0);
bg_color = BgColorFn(mem_data, addr, UserData);
}
if (bg_color != 0)
{
float bg_width = s.GlyphWidth * 2;
if (is_next_byte_highlighted || (n + 1 == Cols)) if (is_next_byte_highlighted || (n + 1 == Cols))
{ {
highlight_width = s.HexCellWidth; bg_width = s.HexCellWidth;
if (OptMidColsCount > 0 && n > 0 && (n + 1) < Cols && ((n + 1) % OptMidColsCount) == 0) if (OptMidColsCount > 0 && n > 0 && (n + 1) < Cols && ((n + 1) % OptMidColsCount) == 0)
highlight_width += s.SpacingBetweenMidCols; bg_width += s.SpacingBetweenMidCols;
} }
draw_list->AddRectFilled(pos, ImVec2(pos.x + highlight_width, pos.y + s.LineHeight), HighlightColor); ImVec2 pos = ImGui::GetCursorScreenPos();
draw_list->AddRectFilled(pos, ImVec2(pos.x + bg_width, pos.y + s.LineHeight), bg_color);
} }
if (DataEditingAddr == addr) if (DataEditingAddr == addr)
@@ -308,17 +352,21 @@ struct MemoryEditor
if (DataEditingTakeFocus) if (DataEditingTakeFocus)
{ {
ImGui::SetKeyboardFocusHere(0); ImGui::SetKeyboardFocusHere(0);
sprintf(AddrInputBuf, format_data, s.AddrDigitsCount, base_display_addr + addr); ImSnprintf(AddrInputBuf, 32, format_data, s.AddrDigitsCount, base_display_addr + addr);
sprintf(DataInputBuf, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); ImSnprintf(DataInputBuf, 32, format_byte, ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr]);
} }
struct UserData struct InputTextUserData
{ {
// FIXME: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious. This is such a ugly mess we may be better off not using InputText() at all here. // FIXME: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious. This is such a ugly mess we may be better off not using InputText() at all here.
static int Callback(ImGuiInputTextCallbackData* data) static int Callback(ImGuiInputTextCallbackData* data)
{ {
UserData* user_data = (UserData*)data->UserData; InputTextUserData* user_data = (InputTextUserData*)data->UserData;
if (!data->HasSelection()) if (!data->HasSelection())
user_data->CursorPos = data->CursorPos; user_data->CursorPos = data->CursorPos;
#if IMGUI_VERSION_NUM < 19102
if (data->Flags & ImGuiInputTextFlags_ReadOnly)
return 0;
#endif
if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen) if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen)
{ {
// When not editing a byte, always refresh its InputText content pulled from underlying memory data // When not editing a byte, always refresh its InputText content pulled from underlying memory data
@@ -334,39 +382,42 @@ struct MemoryEditor
char CurrentBufOverwrite[3]; // Input char CurrentBufOverwrite[3]; // Input
int CursorPos; // Output int CursorPos; // Output
}; };
UserData user_data; InputTextUserData input_text_user_data;
user_data.CursorPos = -1; input_text_user_data.CursorPos = -1;
sprintf(user_data.CurrentBufOverwrite, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); ImSnprintf(input_text_user_data.CurrentBufOverwrite, 3, format_byte, ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr]);
ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways; ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways;
#if IMGUI_VERSION_NUM >= 18104 if (ReadOnly)
flags |= ImGuiInputTextFlags_AlwaysOverwrite; flags |= ImGuiInputTextFlags_ReadOnly;
#else flags |= ImGuiInputTextFlags_AlwaysOverwrite; // was ImGuiInputTextFlags_AlwaysInsertMode
flags |= ImGuiInputTextFlags_AlwaysInsertMode;
#endif
ImGui::SetNextItemWidth(s.GlyphWidth * 2); ImGui::SetNextItemWidth(s.GlyphWidth * 2);
if (ImGui::InputText("##data", DataInputBuf, IM_ARRAYSIZE(DataInputBuf), flags, UserData::Callback, &user_data)) if (ImGui::InputText("##data", DataInputBuf, IM_ARRAYSIZE(DataInputBuf), flags, InputTextUserData::Callback, &input_text_user_data))
data_write = data_next = true; data_write = data_next = true;
else if (!DataEditingTakeFocus && !ImGui::IsItemActive()) else if (!DataEditingTakeFocus && !ImGui::IsItemActive())
DataEditingAddr = data_editing_addr_next = (size_t)-1; DataEditingAddr = data_editing_addr_next = (size_t)-1;
DataEditingTakeFocus = false; DataEditingTakeFocus = false;
if (user_data.CursorPos >= 2) if (input_text_user_data.CursorPos >= 2)
data_write = data_next = true; data_write = data_next = true;
if (data_editing_addr_next != (size_t)-1) if (data_editing_addr_next != (size_t)-1)
data_write = data_next = false; data_write = data_next = false;
unsigned int data_input_value = 0; unsigned int data_input_value = 0;
if (data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1) if (!ReadOnly && data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1)
{ {
if (WriteFn) if (WriteFn)
WriteFn(mem_data, addr, (ImU8)data_input_value); WriteFn(mem_data, addr, (ImU8)data_input_value, UserData);
else else
mem_data[addr] = (ImU8)data_input_value; mem_data[addr] = (ImU8)data_input_value;
} }
if (ImGui::IsItemHovered())
{
MouseHovered = true;
MouseHoveredAddr = addr;
}
ImGui::PopID(); ImGui::PopID();
} }
else else
{ {
// NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on. // NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on.
ImU8 b = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; ImU8 b = ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr];
if (OptShowHexII) if (OptShowHexII)
{ {
@@ -386,10 +437,15 @@ struct MemoryEditor
else else
ImGui::Text(format_byte_space, b); ImGui::Text(format_byte_space, b);
} }
if (!ReadOnly && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) if (ImGui::IsItemHovered())
{ {
DataEditingTakeFocus = true; MouseHovered = true;
data_editing_addr_next = addr; MouseHoveredAddr = addr;
if (ImGui::IsMouseClicked(0))
{
DataEditingTakeFocus = true;
data_editing_addr_next = addr;
}
} }
} }
} }
@@ -397,15 +453,24 @@ struct MemoryEditor
if (OptShowAscii) if (OptShowAscii)
{ {
// Draw ASCII values // Draw ASCII values
ImGui::SameLine(s.PosAsciiStart); ImGui::SameLine(s.OffsetAsciiMinX);
ImVec2 pos = ImGui::GetCursorScreenPos(); ImVec2 pos = ImGui::GetCursorScreenPos();
addr = line_i * Cols; addr = (size_t)line_i * Cols;
const float mouse_off_x = ImGui::GetIO().MousePos.x - pos.x;
const size_t mouse_addr = (mouse_off_x >= 0.0f && mouse_off_x < s.OffsetAsciiMaxX - s.OffsetAsciiMinX) ? addr + (size_t)(mouse_off_x / s.GlyphWidth) : (size_t)-1;
ImGui::PushID(line_i); ImGui::PushID(line_i);
if (ImGui::InvisibleButton("ascii", ImVec2(s.PosAsciiEnd - s.PosAsciiStart, s.LineHeight))) if (ImGui::InvisibleButton("ascii", ImVec2(s.OffsetAsciiMaxX - s.OffsetAsciiMinX, s.LineHeight)))
{ {
DataEditingAddr = DataPreviewAddr = addr + (size_t)((ImGui::GetIO().MousePos.x - pos.x) / s.GlyphWidth); DataEditingAddr = DataPreviewAddr = mouse_addr;
DataEditingTakeFocus = true; DataEditingTakeFocus = true;
} }
if (ImGui::IsItemHovered())
{
MouseHovered = true;
MouseHoveredAddr = mouse_addr;
}
ImGui::PopID(); ImGui::PopID();
for (int n = 0; n < Cols && addr < mem_size; n++, addr++) for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
{ {
@@ -414,7 +479,11 @@ struct MemoryEditor
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg)); draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg));
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg)); draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg));
} }
unsigned char c = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; else if (BgColorFn)
{
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), BgColorFn(mem_data, addr, UserData));
}
unsigned char c = ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr];
char display_c = (c < 32 || c >= 128) ? '.' : c; char display_c = (c < 32 || c >= 128) ? '.' : c;
draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1); draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1);
pos.x += s.GlyphWidth; pos.x += s.GlyphWidth;
@@ -422,10 +491,14 @@ struct MemoryEditor
} }
} }
ImGui::PopStyleVar(2); ImGui::PopStyleVar(2);
const float child_width = ImGui::GetWindowSize().x;
ImGui::EndChild(); ImGui::EndChild();
// Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child) // Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child)
ImVec2 backup_pos = ImGui::GetCursorScreenPos();
ImGui::SetCursorPosX(s.WindowWidth); ImGui::SetCursorPosX(s.WindowWidth);
ImGui::Dummy(ImVec2(0.0f, 0.0f));
ImGui::SetCursorScreenPos(backup_pos);
if (data_next && DataEditingAddr + 1 < mem_size) if (data_next && DataEditingAddr + 1 < mem_size)
{ {
@@ -450,18 +523,28 @@ struct MemoryEditor
ImGui::Separator(); ImGui::Separator();
DrawPreviewLine(s, mem_data, mem_size, base_display_addr); DrawPreviewLine(s, mem_data, mem_size, base_display_addr);
} }
}
void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr) if (GotoAddr != (size_t)-1)
{ {
IM_UNUSED(mem_data); if (GotoAddr < mem_size)
ImGuiStyle& style = ImGui::GetStyle(); {
const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x"; ImGui::BeginChild("##scrolling");
ImGui::SetScrollY((GotoAddr / Cols) * ImGui::GetTextLineHeight() - avail_size.y * 0.5f);
ImGui::EndChild();
DataEditingAddr = DataPreviewAddr = GotoAddr;
DataEditingTakeFocus = true;
}
GotoAddr = (size_t)-1;
}
// Options menu const ImVec2 contents_pos_end(contents_pos_start.x + child_width, ImGui::GetCursorScreenPos().y);
if (ImGui::Button("Options")) //ImGui::GetForegroundDrawList()->AddRect(contents_pos_start, contents_pos_end, IM_COL32(255, 0, 0, 255));
ImGui::OpenPopup("context"); if (OptShowOptions)
if (ImGui::BeginPopup("context")) if (ImGui::IsMouseHoveringRect(contents_pos_start, contents_pos_end))
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right))
ImGui::OpenPopup("OptionsPopup");
if (ImGui::BeginPopup("OptionsPopup"))
{ {
ImGui::SetNextItemWidth(s.GlyphWidth * 7 + style.FramePadding.x * 2.0f); ImGui::SetNextItemWidth(s.GlyphWidth * 7 + style.FramePadding.x * 2.0f);
if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) { ContentsWidthChanged = true; if (Cols < 1) Cols = 1; } if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) { ContentsWidthChanged = true; if (Cols < 1) Cols = 1; }
@@ -473,6 +556,17 @@ struct MemoryEditor
ImGui::EndPopup(); ImGui::EndPopup();
} }
}
void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr)
{
IM_UNUSED(mem_data);
ImGuiStyle& style = ImGui::GetStyle();
const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x";
// Options menu
if (ImGui::Button("Options"))
ImGui::OpenPopup("OptionsPopup");
ImGui::SameLine(); ImGui::SameLine();
ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1); ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1);
@@ -488,18 +582,11 @@ struct MemoryEditor
} }
} }
if (GotoAddr != (size_t)-1) //if (MouseHovered)
{ //{
if (GotoAddr < mem_size) // ImGui::SameLine();
{ // ImGui::Text("Hovered: %p", MouseHoveredAddr);
ImGui::BeginChild("##scrolling"); //}
ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + (GotoAddr / Cols) * ImGui::GetTextLineHeight());
ImGui::EndChild();
DataEditingAddr = DataPreviewAddr = GotoAddr;
DataEditingTakeFocus = true;
}
GotoAddr = (size_t)-1;
}
} }
void DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size, size_t base_display_addr) void DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size, size_t base_display_addr)
@@ -511,16 +598,21 @@ struct MemoryEditor
ImGui::Text("Preview as:"); ImGui::Text("Preview as:");
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x); ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
static const ImGuiDataType supported_data_types[] = { ImGuiDataType_S8, ImGuiDataType_U8, ImGuiDataType_S16, ImGuiDataType_U16, ImGuiDataType_S32, ImGuiDataType_U32, ImGuiDataType_S64, ImGuiDataType_U64, ImGuiDataType_Float, ImGuiDataType_Double };
if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest)) if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest))
{ {
for (int n = 0; n < ImGuiDataType_COUNT; n++) for (int n = 0; n < IM_ARRAYSIZE(supported_data_types); n++)
if (ImGui::Selectable(DataTypeGetDesc((ImGuiDataType)n), PreviewDataType == n)) {
PreviewDataType = (ImGuiDataType)n; ImGuiDataType data_type = supported_data_types[n];
if (ImGui::Selectable(DataTypeGetDesc(data_type), PreviewDataType == data_type))
PreviewDataType = data_type;
}
ImGui::EndCombo(); ImGui::EndCombo();
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x); ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
ImGui::Combo("##combo_endianess", &PreviewEndianess, "LE\0BE\0\0"); ImGui::Combo("##combo_endianness", &PreviewEndianness, "LE\0BE\0\0");
char buf[128] = ""; char buf[128] = "";
float x = s.GlyphWidth * 6.0f; float x = s.GlyphWidth * 6.0f;
@@ -537,18 +629,19 @@ struct MemoryEditor
ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
} }
// Utilities for Data Preview // Utilities for Data Preview (since we don't access imgui_internal.h)
// FIXME: This technically depends on ImGuiDataType order.
const char* DataTypeGetDesc(ImGuiDataType data_type) const const char* DataTypeGetDesc(ImGuiDataType data_type) const
{ {
const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double" }; const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double" };
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(descs));
return descs[data_type]; return descs[data_type];
} }
size_t DataTypeGetSize(ImGuiDataType data_type) const size_t DataTypeGetSize(ImGuiDataType data_type) const
{ {
const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double) }; const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double) };
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(sizes));
return sizes[data_type]; return sizes[data_type];
} }
@@ -561,18 +654,18 @@ struct MemoryEditor
bool IsBigEndian() const bool IsBigEndian() const
{ {
uint16_t x = 1; ImU16 x = 1;
char c[2]; char c[2];
memcpy(c, &x, 2); memcpy(c, &x, 2);
return c[0] != 0; return c[0] != 0;
} }
static void* EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian) static void* EndiannessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian)
{ {
if (is_little_endian) if (is_little_endian)
{ {
uint8_t* dst = (uint8_t*)_dst; ImU8* dst = (ImU8*)_dst;
uint8_t* src = (uint8_t*)_src + s - 1; ImU8* src = (ImU8*)_src + s - 1;
for (int i = 0, n = (int)s; i < n; ++i) for (int i = 0, n = (int)s; i < n; ++i)
memcpy(dst++, src--, 1); memcpy(dst++, src--, 1);
return _dst; return _dst;
@@ -583,7 +676,7 @@ struct MemoryEditor
} }
} }
static void* EndianessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian) static void* EndiannessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian)
{ {
if (is_little_endian) if (is_little_endian)
{ {
@@ -591,23 +684,23 @@ struct MemoryEditor
} }
else else
{ {
uint8_t* dst = (uint8_t*)_dst; ImU8* dst = (ImU8*)_dst;
uint8_t* src = (uint8_t*)_src + s - 1; ImU8* src = (ImU8*)_src + s - 1;
for (int i = 0, n = (int)s; i < n; ++i) for (int i = 0, n = (int)s; i < n; ++i)
memcpy(dst++, src--, 1); memcpy(dst++, src--, 1);
return _dst; return _dst;
} }
} }
void* EndianessCopy(void* dst, void* src, size_t size) const void* EndiannessCopy(void* dst, void* src, size_t size) const
{ {
static void* (*fp)(void*, void*, size_t, int) = NULL; static void* (*fp)(void*, void*, size_t, int) = nullptr;
if (fp == NULL) if (fp == nullptr)
fp = IsBigEndian() ? EndianessCopyBigEndian : EndianessCopyLittleEndian; fp = IsBigEndian() ? EndiannessCopyBigEndian : EndiannessCopyLittleEndian;
return fp(dst, src, size, PreviewEndianess); return fp(dst, src, size, PreviewEndianness);
} }
const char* FormatBinary(const uint8_t* buf, int width) const const char* FormatBinary(const ImU8* buf, int width) const
{ {
IM_ASSERT(width <= 64); IM_ASSERT(width <= 64);
size_t out_n = 0; size_t out_n = 0;
@@ -627,19 +720,19 @@ struct MemoryEditor
// [Internal] // [Internal]
void DrawPreviewData(size_t addr, const ImU8* mem_data, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const void DrawPreviewData(size_t addr, const ImU8* mem_data, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const
{ {
uint8_t buf[8]; ImU8 buf[8];
size_t elem_size = DataTypeGetSize(data_type); size_t elem_size = DataTypeGetSize(data_type);
size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size; size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size;
if (ReadFn) if (ReadFn)
for (int i = 0, n = (int)size; i < n; ++i) for (int i = 0, n = (int)size; i < n; ++i)
buf[i] = ReadFn(mem_data, addr + i); buf[i] = ReadFn(mem_data, addr + i, UserData);
else else
memcpy(buf, mem_data + addr, size); memcpy(buf, mem_data + addr, size);
if (data_format == DataFormat_Bin) if (data_format == DataFormat_Bin)
{ {
uint8_t binbuf[8]; ImU8 binbuf[8];
EndianessCopy(binbuf, buf, size); EndiannessCopy(binbuf, buf, size);
ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8)); ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8));
return; return;
} }
@@ -649,84 +742,85 @@ struct MemoryEditor
{ {
case ImGuiDataType_S8: case ImGuiDataType_S8:
{ {
int8_t int8 = 0; ImS8 data = 0;
EndianessCopy(&int8, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", int8); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", int8 & 0xFF); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0xFF); return; }
break; break;
} }
case ImGuiDataType_U8: case ImGuiDataType_U8:
{ {
uint8_t uint8 = 0; ImU8 data = 0;
EndianessCopy(&uint8, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", uint8); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", uint8 & 0XFF); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0XFF); return; }
break; break;
} }
case ImGuiDataType_S16: case ImGuiDataType_S16:
{ {
int16_t int16 = 0; ImS16 data = 0;
EndianessCopy(&int16, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", int16); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", int16 & 0xFFFF); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
break; break;
} }
case ImGuiDataType_U16: case ImGuiDataType_U16:
{ {
uint16_t uint16 = 0; ImU16 data = 0;
EndianessCopy(&uint16, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", uint16); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", uint16 & 0xFFFF); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; }
break; break;
} }
case ImGuiDataType_S32: case ImGuiDataType_S32:
{ {
int32_t int32 = 0; ImS32 data = 0;
EndianessCopy(&int32, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", int32); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", int32); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
break; break;
} }
case ImGuiDataType_U32: case ImGuiDataType_U32:
{ {
uint32_t uint32 = 0; ImU32 data = 0;
EndianessCopy(&uint32, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", uint32); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", uint32); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; }
break; break;
} }
case ImGuiDataType_S64: case ImGuiDataType_S64:
{ {
int64_t int64 = 0; ImS64 data = 0;
EndianessCopy(&int64, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)int64); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)int64); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
break; break;
} }
case ImGuiDataType_U64: case ImGuiDataType_U64:
{ {
uint64_t uint64 = 0; ImU64 data = 0;
EndianessCopy(&uint64, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)uint64); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)uint64); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; }
break; break;
} }
case ImGuiDataType_Float: case ImGuiDataType_Float:
{ {
float float32 = 0.0f; float data = 0.0f;
EndianessCopy(&float32, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float32); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float32); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
break; break;
} }
case ImGuiDataType_Double: case ImGuiDataType_Double:
{ {
double float64 = 0.0; double data = 0.0;
EndianessCopy(&float64, buf, size); EndiannessCopy(&data, buf, size);
if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float64); return; } if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", data); return; }
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float64); return; } if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; }
break; break;
} }
default:
case ImGuiDataType_COUNT: case ImGuiDataType_COUNT:
break; break;
} // Switch } // Switch
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -315,7 +315,7 @@ static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0
if (node->y > min_y) { if (node->y > min_y) {
// raise min_y higher. // raise min_y higher.
// we've accounted for all waste up to min_y, // we've accounted for all waste up to min_y,
// but we'll now add more waste for everything we've visted // but we'll now add more waste for everything we've visited
waste_area += visited_width * (node->y - min_y); waste_area += visited_width * (node->y - min_y);
min_y = node->y; min_y = node->y;
// the first time through, visited_width might be reduced // the first time through, visited_width might be reduced
@@ -470,7 +470,7 @@ static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, i
// insert the new node into the right starting point, and // insert the new node into the right starting point, and
// let 'cur' point to the remaining nodes needing to be // let 'cur' point to the remaining nodes needing to be
// stiched back in // stitched back in
cur = *res.prev_link; cur = *res.prev_link;
if (cur->x < res.x) { if (cur->x < res.x) {

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