View Issue Details

IDProjectCategoryView StatusLast Update
0012636Dwarf FortressTechnical -- Input/Keybinding/Macrospublic2024-05-20 12:38
ReporterKonig Assigned To 
PrioritynormalSeverityminorReproducibilityalways
Status newResolutionopen 
PlatformSteamOSWindowsOS Version11
Product Version50.11 
Summary0012636: Strange behaviour of lists when scrolling down with ThinkPad touchpad gesture
DescriptionOn a brand-new ThinkPad laptop, when I "two-finger swipe" up on the touchpad (a normal scroll movement) it scrolls up menus/lists in DF fine, but when I swipe down it kind of scrolls down but then scrolls up a bit. This is on default keybindings.
Steps To Reproduce1) Get a ThinkPad laptop
2) Download DF
3) Launch DF
4) Try to scroll down on a menu with the touchpad
Additional Information1) Swipe speed affects it - if I swipe down slow, the up-movement is more pronounced, to such an extent that I'll often end up higher in the list than where I started if I go real slow, and if I swipe down fast it's just a little tick back up at the end of the scroll movement.

2) The touchpad scrolls as normal in all other programs. Changing the scroll behaviour in Windows (so that swiping down scrolls me up instead, like a Macbook) doesn't change the behaviour in DF; scrolling up in a menu (by swiping down now) behaves as normal, and scrolling down has the janky movement.

3) Plugging in a traditional mouse solves the problem - menus scroll as normal.

4) Reversing the DF keybindings for anything default bound to Mwheel Down/Up seems to have the effect of disabling scrolling down in DF menus entirely (again, just the "mouse wheel scroll down" DF input seems to be affected, whether I'm swiping down for scroll down or swiping up for scroll down at the OS-level).

5) Rebinding scrolling to anything else (e.g. 1 and 2) fixes the problem and I can scroll up or down menus like normal. It only happens with Mwheel Down.

6) This bug happens in both the main menu options and in fortress mode - so probably everywhere.

7) Windowed/Fullscreen has no effect.

8) Locking Graphical FPS to something lower (tried 30) doesn't seem to have an effect.

System Specs
ThinkPad E14 Gen 5 Intel
Processor: 13th Gen Intel Core i5-1335U 1.30 GHz
RAM: 16GB
Graphics: Integrated Intel Iris Xe Graphics
OS: Windows 11 Home 64
Tagsinterface, menu, mouse

Activities

Konig

2023-12-06 14:31

reporter   ~0041943

Looks like this might be related to/the same as another bug https://dwarffortressbugtracker.com/view.php?id=12635

safflower

2024-05-20 11:55

reporter   ~0042239

I can reproduce this bug on my MX Master 3 mouse on Linux. I suspect this has something to do with hi-resolution scrolling, and the bug here is how dwarf fortress handles scroll events. Here's some code to show what's going on:

```
#include <SDL2/SDL.h>
#include <iostream>

int main(int argc, char* argv[]) {
    if (SDL_Init(SDL_INIT_VIDEO) != 0) {
        std::cerr << "SDL_Init Error: " << SDL_GetError() << std::endl;
        return 1;
    }
    std::cout << "SDL initialized successfully." << std::endl;

    SDL_Window* window = SDL_CreateWindow("Mouse Scroll Logger",
                                          SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
                                          640, 480, SDL_WINDOW_SHOWN);
    if (window == nullptr) {
        std::cerr << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl;
        SDL_Quit();
        return 1;
    }

    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    if (renderer == nullptr) {
        std::cerr << "SDL_CreateRenderer Error: " << SDL_GetError() << std::endl;
        SDL_DestroyWindow(window);
        SDL_Quit();
        return 1;
    }

    SDL_Event event;
    bool running = true;
    int scrollPosition = 0;

    while (running) {
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                running = false;
            } else if (event.type == SDL_MOUSEWHEEL) {
                scrollPosition += event.wheel.y;
                std::cout << "Mouse Scroll: "
                          << (event.wheel.y > 0 ? "Up" : event.wheel.x < 0 ? "Dn" : "00")
                          << ", Position: " << scrollPosition << std::endl;
            }
        }

        SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255);
        SDL_RenderClear(renderer);
        SDL_RenderPresent(renderer);
    }

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}
```

And here's how I reproduced it on Linux. After starting the program, I only scrolled up:

```
$ g++ -o mouse_scroll_logger main.cpp $(sdl2-config --cflags --libs)
$ ./mouse_scroll_logger
SDL initialized successfully.
Mouse Scroll: 00, Position: 0
Mouse Scroll: 00, Position: 0
Mouse Scroll: Up, Position: 1
Mouse Scroll: 00, Position: 1
Mouse Scroll: Up, Position: 2
Mouse Scroll: 00, Position: 2
Mouse Scroll: Up, Position: 3
Mouse Scroll: 00, Position: 3
Mouse Scroll: Up, Position: 4
Mouse Scroll: 00, Position: 4
Mouse Scroll: Up, Position: 5
Mouse Scroll: 00, Position: 5
Mouse Scroll: 00, Position: 5
Mouse Scroll: Up, Position: 6
Mouse Scroll: 00, Position: 6
Mouse Scroll: Up, Position: 7
Mouse Scroll: 00, Position: 7
Mouse Scroll: 00, Position: 7
Mouse Scroll: 00, Position: 7
Mouse Scroll: Up, Position: 8
```

Note the number of "00" events. These are printed when event.wheel.x == 0. I suspect what's going on is that Dwarf Fortress has buggy code that looks like this:

```
void onScrollEvent(event) {
  if event.wheel.x > 0 {
    scrollUp()
  } else {
    scrollDown()
  }
}
```

This code needs to be changed to something like this to handle scroll events where the wheel position change is not 1:

```
void onScrollEvent(event) {
  if event.wheel.x > 0 {
    for(int i = 0; i < event.wheel.x; i++) { scrollUp() }
  } else {
    for(int i = 0; i > event.wheel.x; i--) { scrollDown() }
  }
}
```

If it makes the developers feel better, the first version of my test program had the exact same bug :)

safflower

2024-05-20 12:38

reporter   ~0042240

1. every time I said `wheel.x` in the above comment, I should have said `wheel.y`.
2. The following LD_PRELOAD patch fixes the bug on linux:

```
#include <SDL2/SDL.h>
#include <dlfcn.h>
#include <stdio.h>

// Function pointer to hold the original SDL_PollEvent
int (*original_SDL_PollEvent)(SDL_Event *) = NULL;

// Function to print event details in a human-readable format
void print_event(SDL_Event *event) {
    switch (event->type) {
        case SDL_QUIT:
            printf("Event: SDL_QUIT\n");
            break;
        case SDL_KEYDOWN:
            printf("Event: SDL_KEYDOWN, Key: %s\n", SDL_GetKeyName(event->key.keysym.sym));
            break;
        case SDL_KEYUP:
            printf("Event: SDL_KEYUP, Key: %s\n", SDL_GetKeyName(event->key.keysym.sym));
            break;
        case SDL_MOUSEBUTTONDOWN:
            printf("Event: SDL_MOUSEBUTTONDOWN, Button: %d\n", event->button.button);
            break;
        case SDL_MOUSEBUTTONUP:
            printf("Event: SDL_MOUSEBUTTONUP, Button: %d\n", event->button.button);
            break;
        case SDL_MOUSEMOTION:
            printf("Event: SDL_MOUSEMOTION, X: %d, Y: %d, Xrel: %d, Yrel: %d\n", event->motion.x, event->motion.y, event->motion.xrel, event->motion.yrel);
            break;
        case SDL_MOUSEWHEEL:
            printf("Event: SDL_MOUSEWHEEL, X: %d, Y: %d, Direction: %d\n", event->wheel.x, event->wheel.y, event->wheel.direction);
            break;
        case SDL_WINDOWEVENT:
            printf("Event: SDL_WINDOWEVENT, WindowEvent: %d\n", event->window.event);
            break;
        case SDL_SYSWMEVENT:
            printf("Event: SDL_SYSWMEVENT\n");
            break;
        case SDL_TEXTEDITING:
            printf("Event: SDL_TEXTEDITING, Text: %s, Start: %d, Length: %d\n", event->edit.text, event->edit.start, event->edit.length);
            break;
        case SDL_USEREVENT:
            printf("Event: SDL_USEREVENT, Code: %d\n", event->user.code);
            break;
        default:
            printf("Event: %d (unknown)\n", event->type);
            break;
    }
}

// Our replacement for SDL_PollEvent
int SDL_PollEvent(SDL_Event *event) {
    if (!original_SDL_PollEvent) {
        // Get the original SDL_PollEvent function
        original_SDL_PollEvent = dlsym(RTLD_NEXT, "SDL_PollEvent");
        if (!original_SDL_PollEvent) {
            fprintf(stderr, "Error: could not find original SDL_PollEvent\n");
            return 0;
        } else {
            printf("Successfully hooked SDL_PollEvent.\n");
        }
    }

    // Call the original SDL_PollEvent
    int result = original_SDL_PollEvent(event);
    if (result == 0) {
        return result;
    }

    // Print the event details
    // print_event(event);

    if (event->type == SDL_MOUSEWHEEL && event->wheel.y == 0) {
        return SDL_PollEvent(event); // Recursively call to get the next event
    }

    // Return the event if it's not ignored
    return result;
}
```

compile with `gcc -fPIC -shared -o sdl_patch.so sdl_patch.c -ldl -lSDL2`, copy to the `~/.local/share/Steam/steamapps/common/Dwarf Fortress` folder, and run using `LD_PRELOAD=./sdl_patch.so ./dwarfort`

Add Note

Note

Issue History

Date Modified Username Field Change
2023-12-06 14:26 Konig New Issue
2023-12-06 14:26 Konig Tag Attached: interface
2023-12-06 14:26 Konig Tag Attached: menu
2023-12-06 14:26 Konig Tag Attached: mouse
2023-12-06 14:31 Konig Note Added: 0041943
2024-05-20 11:55 safflower Note Added: 0042239
2024-05-20 12:38 safflower Note Added: 0042240