I'm writing this simple program to learn how to work with audio with SDL. The program opens a window with a button that says go and when you hit the button it should play a WAV of a drum sample. There are no errors and the program doesn't crash it's just that instead of playing the drum sound it makes a weird buzzing noise that doesn't stop until you exit the program. I think it's something to do with the audio callback function but I am very new to this so I'm not sure exactly how it works.
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <SDL2/SDL_audio.h>
TTF_Font* font;
SDL_Color white = {255, 255, 255};
SDL_AudioSpec frmt;
bool run = true;
bool playing = false;
struct sample
{
Uint8 *data;
Uint32 dpos;
Uint32 dlen;
} sound;
bool buttonPress(int x, int y, SDL_Rect r)
{
if (x < (r.x + r.w) && x > r.x)
{
if (y < (r.y + r.h) && y > r.y)
{
return true;
}
}
return false;
}
void mixing(void *unused, Uint8 *stream, int len)
{
Uint32 amount;
if (playing)
{
amount = sound.dlen - sound.dpos;
if ( amount > len ) {
amount = len;
}
SDL_MixAudio(stream, &sound.data[sound.dpos], amount, SDL_MIX_MAXVOLUME);
sound.dpos += amount;
}
}
int main(int argc, char *argv[])
{
SDL_Init(SDL_INIT_EVERYTHING);
TTF_Init();
frmt.channels = 1;
frmt.format = AUDIO_S16;
frmt.freq = 22050;
frmt.samples = 512;
frmt.userdata = NULL;
frmt.callback = mixing;
int mX, mY;
Uint32 fpsTimer = SDL_GetTicks();
font = TTF_OpenFont("octaFont.TTF", 20);
SDL_Window* window = SDL_CreateWindow("Test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 400, 200, 0);
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
SDL_Surface* surface = TTF_RenderText_Solid(font, "Go!", white);
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_Rect button;
button.w = 50;
button.h = 35;
button.x = 10;
button.y = 10;
SDL_Rect bg;
bg.w = 400;
bg.h = 200;
bg.x = 0;
bg.y = 0;
SDL_Event e;
if (SDL_OpenAudio(&frmt, NULL) < 0)
{
fprintf(stderr, "Unable to open audio: %s\n", SDL_GetError());
return 1;
}
SDL_PauseAudio(0);
SDL_AudioSpec wave;
SDL_AudioCVT cvt;
Uint8 *data;
Uint32 dlen;
if (SDL_LoadWAV("sound.wav", &wave, &data, &dlen) == NULL)
{
fprintf(stderr, "Unable to load wave: %s\n", SDL_GetError());
return 1;
}
SDL_BuildAudioCVT(&cvt, wave.format, wave.channels, wave.freq, AUDIO_S16, 1, 22050);
cvt.buf = malloc(dlen*cvt.len_mult);
memcpy(cvt.buf, data, dlen);
cvt.len = dlen;
SDL_ConvertAudio(&cvt);
SDL_FreeWAV(data);
SDL_LockAudio();
free(sound.data);
sound.data = cvt.buf;
sound.dlen = cvt.len_cvt;
sound.dpos = 0;
SDL_UnlockAudio();
while (run)
{
while (SDL_PollEvent(&e))
{
switch (e.type)
{
case SDL_QUIT:
run = false;
break;
case SDL_KEYDOWN:
switch (e.key.keysym.sym)
{
case SDLK_SPACE:
playing = false;
break;
default:
break;
}
break;
case SDL_MOUSEBUTTONDOWN:
SDL_GetMouseState(&mX, &mY);
if (buttonPress(mX, mY, button))
{
playing = true;
}
break;
default:
break;
}
}
if (SDL_GetTicks() - fpsTimer >= 16)
{
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderFillRect(renderer, &bg);
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderDrawRect(renderer, &button);
SDL_RenderCopy(renderer, texture, NULL, &button);
SDL_RenderPresent(renderer);
fpsTimer = SDL_GetTicks();
}
}
SDL_CloseAudio();
SDL_DestroyWindow(window);
SDL_DestroyRenderer(renderer);
TTF_Quit();
SDL_Quit();
return 0;
}
The issue may be that you're using SDL_MixAudio.
Your code mixes in the data from the .wav file, but mixes it into what?
What is in stream before calling SDL_MixAudio?
I get the same buzz, but when I use memset to zero out the stream buffer before calling SDL_MixAudio, I get the correct sound from the .wav file.
I don't know for sure but I believe that on the second and subsequent calls to your mixing function, the data from the prior callback is still in the stream buffer.
Side note: When the .wav data has finished playing, we can reset sound.dpos to replay the file again.
Here's the refactored code:
void
mixing(void *unused, Uint8 *stream, int len)
{
Uint32 amount;
// NOTE/DEBUG: force sound on
#if 1
playing = 1;
#endif
// NOTE/FIX: zero out stream data so we don't mix in random data (from the
// prior round)
#if 1
memset(stream,0,len);
#endif
if (playing) {
amount = sound.dlen - sound.dpos;
if (amount > len) {
amount = len;
}
#if 1
SDL_MixAudio(stream, &sound.data[sound.dpos], amount, SDL_MIX_MAXVOLUME);
#else
memcpy(stream,&sound.data[sound.dpos],amount);
#endif
sound.dpos += amount;
// NOTE/FIX(?) -- start wav at beginning after all data has been output
#if 1
if (sound.dpos >= sound.dlen)
sound.dpos = 0;
#endif
}
}
Related
I'm using the genann library https://github.com/codeplea/genann for neural networks, I have used this library mainly because it is very easy to integrate with my c/c++ projects in dev-c++ as it is just an .h file without dependencies and it is done with "c" which I think is easier for me to port to other systems.
I am trying to do an OCR so that the neural network tells me if a image is a specific letter, but I can't make the neural network understand what I want to do, could you give me a simple example of how pass the pixel data of the images to the network with this library.
update:
use this library to load the images https://github.com/nothings/stb i am using the dev-c++ IDE image 0.jpg is a letter "A" drawn with paint, image 1.jpg AND 3.jpg is the letter "B" but a little different from each other.
this is what i tried, excuse some parts of my code are in spanish:
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "genann.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include <string.h>
#include <string>
#include <map>
#include <vector>
using namespace std;
struct IMAGEN {
int ancho;
int alto;
int canales; // r,g,b,a
vector<int> pixeles;
};
IMAGEN letraA;
IMAGEN letraB;
IMAGEN letraB2;
bool cargarImagen(char *ruta,IMAGEN &v) {
int x,y,n;
unsigned char *data = stbi_load(ruta, &x, &y, &n, 0);
if (data) {
v.ancho = x;
v.alto = y;
v.canales = n;
int total = x*y*n;
for(int i = 0; i < total; i++) {
v.pixeles.push_back(data[i]);
}
stbi_image_free(data);
return true;
}else{
stbi_image_free(data);
return false;
}
}
void pintarImagen(HWND &hwnd,IMAGEN &v,float x,float y) {
PAINTSTRUCT ps;
RECT r;
HDC hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &r);
for(int yy = 0; yy < v.alto; yy++) {
for(int xx = 0; xx < v.ancho; xx++) {
int r = v.pixeles[yy*v.ancho*v.canales+xx*v.canales+0];
int g = v.pixeles[yy*v.ancho*v.canales+xx*v.canales+1];
int b = v.pixeles[yy*v.ancho*v.canales+xx*v.canales+2];
SetPixel(hdc, x+xx, y+yy, RGB(r, g, b));
}
}
EndPaint(hwnd, &ps);
}
int prepararInputs(double* &inputs,IMAGEN &letraA) {
inputs = new double [letraA.alto*letraA.ancho*letraA.canales];
for(int yy = 0; yy < letraA.alto; yy++) {
for(int xx = 0; xx < letraA.ancho; xx++) {
double r = letraA.pixeles[yy*letraA.ancho*letraA.canales+xx*letraA.canales+0] / 255;
double g = letraA.pixeles[yy*letraA.ancho*letraA.canales+xx*letraA.canales+1] / 255;
double b = letraA.pixeles[yy*letraA.ancho*letraA.canales+xx*letraA.canales+2] / 255;
inputs[yy*letraA.ancho*letraA.canales+xx*letraA.canales+0] = r;
inputs[yy*letraA.ancho*letraA.canales+xx*letraA.canales+1] = g;
inputs[yy*letraA.ancho*letraA.canales+xx*letraA.canales+2] = b;
}
}
return letraA.ancho;
}
/* This is where all the input to the window goes to */
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {
switch(Message) {
case WM_PAINT:
{
//pintarImagen(hwnd,letraA,50,50);
pintarImagen(hwnd,letraB2,100,250);
}
break;
case WM_CREATE:
{
if( cargarImagen((char*)"0.jpg",letraA) == false ) {
printf("0.jpg no cargo.\n");
}
if( cargarImagen((char*)"1.jpg",letraB) == false ) {
printf("1.jpg no cargo.\n");
}
if( cargarImagen((char*)"3.jpg",letraB2) == false ) {
printf("3.jpg no cargo.\n");
}
printf("GENANN example 1.\n");
printf("Train a small ANN to the XOR function using backpropagation.\n");
/* This will make the neural network initialize differently each run. */
/* If you don't get a good result, try again for a different result. */
srand(time(0));
int CapasOcultas = letraA.ancho*2;
int Neuronas = 100;
const double output[][2] = { // a,b
{1,0},
{0,1}
};
int i;
double *LETRA_A_INPUT = NULL;
prepararInputs(LETRA_A_INPUT,letraA);
double *LETRA_B_INPUT = NULL;
prepararInputs(LETRA_B_INPUT,letraB);
double *LETRA_B2_INPUT = NULL;
prepararInputs(LETRA_B2_INPUT,letraB2);
/* New network with 2 inputs,
* 1 hidden layer of 2 neurons,
* and 1 output. */
genann *ann = genann_init(letraA.pixeles.size(), CapasOcultas, Neuronas, 2);
/* Train on the four labeled data points many times. */
for (i = 0; i < 500; ++i) {
genann_train(ann, LETRA_A_INPUT, output[0], 3);
genann_train(ann, LETRA_B_INPUT, output[1], 3);
}
/* Run the network and see what it predicts. */
double *r = NULL;
prepararInputs(r,letraB2);
double *resultado = (double*)genann_run(ann, r);
printf("Output for Letra A is [%1.f,%1.f].\n", resultado[0], resultado[1] );
genann_free(ann);
if(r!=NULL) {
delete[] r;
r = NULL;
}
if(LETRA_A_INPUT!=NULL) {
delete[] LETRA_A_INPUT;
LETRA_A_INPUT = NULL;
}
if(LETRA_B_INPUT!=NULL) {
delete[] LETRA_B_INPUT;
LETRA_B_INPUT = NULL;
}
if(LETRA_B2_INPUT!=NULL) {
delete[] LETRA_B2_INPUT;
LETRA_B2_INPUT = NULL;
}
}
break;
case WM_DESTROY: {
PostQuitMessage(0);
break;
}
default:
return DefWindowProc(hwnd, Message, wParam, lParam);
}
return 0;
}
/* The 'main' function of Win32 GUI programs: this is where execution starts */
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
WNDCLASSEX wc; /* A properties struct of our window */
HWND hwnd; /* A 'HANDLE', hence the H, or a pointer to our window */
MSG msg; /* A temporary location for all messages */
/* zero out the struct and set the stuff we want to modify */
memset(&wc,0,sizeof(wc));
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = WndProc; /* This is where we will send messages to */
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
/* White, COLOR_WINDOW is just a #define for a system color, try Ctrl+Clicking it */
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wc.lpszClassName = "WindowClass";
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); /* Load a standard icon */
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); /* use the name "A" to use the project icon */
if(!RegisterClassEx(&wc)) {
MessageBox(NULL, "Window Registration Failed!","Error!",MB_ICONEXCLAMATION|MB_OK);
return 0;
}
hwnd = CreateWindowEx(WS_EX_CLIENTEDGE,"WindowClass","Caption",WS_VISIBLE|WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, /* x */
CW_USEDEFAULT, /* y */
640, /* width */
480, /* height */
NULL,NULL,hInstance,NULL);
if(hwnd == NULL) {
MessageBox(NULL, "Window Creation Failed!","Error!",MB_ICONEXCLAMATION|MB_OK);
return 0;
}
/*
This is the heart of our program where all input is processed and
sent to WndProc. Note that GetMessage blocks code flow until it receives something, so
this loop will not produce unreasonably high CPU usage
*/
while(GetMessage(&msg, NULL, 0, 0) > 0) { /* If no error is received... */
TranslateMessage(&msg); /* Translate key codes to chars if present */
DispatchMessage(&msg); /* Send it to WndProc */
}
return msg.wParam;
}
I have tried to use tensorflow for "C" but it has been very difficult to install even using Microsoft Visual Studio, and I can't find anything prebuilt to use tensorflow with dev-c++, the genann library is my solution for the moment but I'm I stopped at this part and I need help, thank you very much for the help :D
This piece of SDL2 code draws some white pixels on-screen using OpenGL, then grabs the pixels field of the window's SDL_Surface and loops through it, printing out the values of the contents. Even though I just drew a white triangle, the loop shows that there's nothing but zeros in that buffer (the code just prints 0 to standard out over and over).
How can I actually get at the modified pixel buffer, in something like RGB or ARGB or RGBA format?
#include <SDL2/SDL.h>
#include <SDL2/SDL_opengl.h>
#include <GL/glu.h>
int main()
{
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
const int WINDOW_WIDTH = 100;
const int WINDOW_HEIGHT = 100;
SDL_Window *window = SDL_CreateWindow("OpenGL Test", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_OPENGL);
SDL_GLContext gl_context = SDL_GL_CreateContext(window);
SDL_GL_SetSwapInterval(1);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
GLenum error = glGetError();
if( error != GL_NO_ERROR )
{
printf( "Error initializing OpenGL! %s\n", gluErrorString(error));
}
glClearColor(0, 0, 0, 1);
int quit = 0;
SDL_Event event;
while (!quit)
{
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
quit = 1;
break;
}
}
glBegin(GL_TRIANGLES);
glColor3f(255, 255, 255);
glVertex2f(0, 0);
glVertex2f(0, 1);
glVertex2f(1, 0);
glEnd();
SDL_GL_SwapWindow(window);
SDL_Surface *surface = SDL_GetWindowSurface(window);
int pixel_depth = SDL_BYTESPERPIXEL(surface->format->format);
char *pixels = (char*) surface->pixels;
int max_value = 0;
for (int i = 0; i < WINDOW_WIDTH * WINDOW_HEIGHT * pixel_depth; i++)
{
if (pixels[i] > max_value)
{
max_value = pixels[i];
}
}
SDL_FreeSurface(surface);
SDL_Log("%d", max_value);
}
SDL_Quit();
return 0;
}
SDL_GetWindowSurface() doesn't work with OpenGL:
You may not combine this with 3D or the rendering API on this window.
Use glReadPixels() instead.
Use PBO to read data from from the pixel buffer.
glReadBuffer(GL_COLOR_ATTACHMENT0);
writeIndex = (writeIndex + 1) % 2;
readIndex = (readIndex + 1) % 2;
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[writeIndex]);
// copy from framebuffer to PBO asynchronously. it will be ready in the NEXT frame
glReadPixels(0, 0, SCR_WIDTH, SCR_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
// now read other PBO which should be already in CPU memory
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[readIndex]);
unsigned char* Data = (unsigned char *)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
I'm using Vs Code as my IDE.
I've figured out how to add breakpoints and have the debugger hit them, but only if they're added before compiling. In other game development environments I'm used to being able to add a breakpoint whenever I want to halt the game and check things out.
Is there any way to add breakpoints while my game is running?
This is the command I'm running to build my game.
gcc -std=c17 main.c -g -I "C:\Program Files\clib\SDL2\include" -L "C:\Program Files\clib\SDL2\lib" -Wall -lmingw32 -lSDL2main -lSDL2 -o game
This is my little "game" that I copy-pasted from the internet, in case that helps provide context. It's a little red square that can move and jump around.
#include <stdio.h>
#include <stdbool.h>
#include <SDL2/SDL.h>
#define WIDTH 640
#define HEIGHT 480
#define SIZE 200
#define SPEED 600
#define GRAVITY 60
#define FPS 60
#define JUMP -1200
int main(int argc, char *argv[])
{
/* Initializes the timer, audio, video, joystick,
haptic, gamecontroller and events subsystems */
if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
{
printf("Error initializing SDL: %s\n", SDL_GetError());
return 0;
}
/* Create a window */
SDL_Window *wind = SDL_CreateWindow(
"Hello Platformer!",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
WIDTH,
HEIGHT,
0
);
if (!wind)
{
printf("Error creating window: %s\n", SDL_GetError());
SDL_Quit();
return 0;
}
/* Create a renderer */
Uint32 render_flags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC;
SDL_Renderer *rend = SDL_CreateRenderer(wind, -1, render_flags);
if (!rend)
{
printf("Error creating renderer: %s\n", SDL_GetError());
SDL_DestroyWindow(wind);
SDL_Quit();
return 0;
}
/* Main loop */
bool running = true,
jump_pressed = false,
can_jump = true,
left_pressed = false,
right_pressed = false;
float x_pos = (WIDTH - SIZE) / 2, y_pos = (HEIGHT - SIZE) / 2, x_vel = 0, y_vel = 0;
SDL_Rect rect = {(int)x_pos, (int)y_pos, SIZE, SIZE};
SDL_Event event;
while (running)
{
/* Process events */
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
running = false;
break;
case SDL_KEYDOWN:
switch (event.key.keysym.scancode)
{
case SDL_SCANCODE_SPACE:
jump_pressed = true;
break;
case SDL_SCANCODE_A:
case SDL_SCANCODE_LEFT:
left_pressed = true;
break;
case SDL_SCANCODE_D:
case SDL_SCANCODE_RIGHT:
right_pressed = true;
break;
default:
break;
}
break;
case SDL_KEYUP:
switch (event.key.keysym.scancode)
{
case SDL_SCANCODE_SPACE:
jump_pressed = false;
break;
case SDL_SCANCODE_A:
case SDL_SCANCODE_LEFT:
left_pressed = false;
break;
case SDL_SCANCODE_D:
case SDL_SCANCODE_RIGHT:
right_pressed = false;
break;
default:
break;
}
break;
default:
break;
}
}
/* Clear screen */
SDL_SetRenderDrawColor(rend, 0, 0, 0, 255);
SDL_RenderClear(rend);
/* Move the rectangle */
x_vel = (right_pressed - left_pressed) * SPEED;
y_vel += GRAVITY;
if (jump_pressed && can_jump)
{
can_jump = false;
y_vel = JUMP;
}
x_pos += x_vel / 60;
y_pos += y_vel / 60;
if (x_pos <= 0) x_pos = 0;
if (x_pos >= WIDTH - rect.w) x_pos = WIDTH - rect.w;
if (y_pos <= 0) y_pos = 0;
if (y_pos >= HEIGHT - rect.h)
{
y_vel = 0;
y_pos = HEIGHT - rect.h;
if (!jump_pressed)
can_jump = true;
}
rect.x = (int)x_pos;
rect.y = (int)y_pos;
/* Draw the rectangle */
SDL_SetRenderDrawColor(rend, 255, 0, 0, 255);
SDL_RenderFillRect(rend, &rect);
/* Draw to window and loop */
SDL_RenderPresent(rend);
SDL_Delay(1000 / FPS);
}
/* Release resources */
SDL_DestroyRenderer(rend);
SDL_DestroyWindow(wind);
SDL_Quit();
return 0;
}
After adding a checkpoint, you need to send SIGINT to your program by pressing CtrlC in its terminal.
This will pause the program. After you resume it with the debugger, any previously set checkpoints will work.
I don't have experience with the stock C++ extension, but at least in the Native Debug extension you need to add "windows": {"terminal": ""}, to the current configuration in launch.json, to get a dedicated terminal for your app.
I need to create a Tray Icon for my application using purely the Xlib. After some searching, reading the XEmbed documentation and some stuff on SO, this is what the code looks like so far (mostly copy & paste). I understand what it does, but not why. It is creating a tray icon appearantly, but only 1px wide in my mate-panels area. What am I doing wrong? Why is it not the size of a normal tray icon?
Here is my code:
#include <X11/Xutil.h>
#include <string.h>
#define MIN(A, B) ((A) < (B) ? (A) : (B))
/* --------- XEMBED and systray stuff */
#define SYSTEM_TRAY_REQUEST_DOCK 0
#define SYSTEM_TRAY_BEGIN_MESSAGE 1
#define SYSTEM_TRAY_CANCEL_MESSAGE 2
static int trapped_error_code = 0;
static int (*old_error_handler) (Display *, XErrorEvent *);
static int
error_handler(Display *display, XErrorEvent *error) {
trapped_error_code = error->error_code;
return 0;
}
void
trap_errors(void) {
trapped_error_code = 0;
old_error_handler = XSetErrorHandler(error_handler);
}
int
untrap_errors(void) {
XSetErrorHandler(old_error_handler);
return trapped_error_code;
}
void
send_systray_message(Display* dpy, long message, long data1, long data2, long data3) {
XEvent ev;
Atom selection_atom = XInternAtom (dpy,"_NET_SYSTEM_TRAY_S0",False);
Window tray = XGetSelectionOwner (dpy,selection_atom);
if ( tray != None)
XSelectInput (dpy,tray,StructureNotifyMask);
memset(&ev, 0, sizeof(ev));
ev.xclient.type = ClientMessage;
ev.xclient.window = tray;
ev.xclient.message_type = XInternAtom (dpy, "_NET_SYSTEM_TRAY_OPCODE", False );
ev.xclient.format = 32;
ev.xclient.data.l[0] = CurrentTime;
ev.xclient.data.l[1] = message;
ev.xclient.data.l[2] = data1; // <--- your window is only here
ev.xclient.data.l[3] = data2;
ev.xclient.data.l[4] = data3;
trap_errors();
XSendEvent(dpy, tray, False, NoEventMask, &ev);
XSync(dpy, False);
usleep(10000);
if (untrap_errors()) {
/* Handle errors */
}
}
/* ------------ Regular X stuff */
int
main(int argc, char **argv) {
int width, height;
XWindowAttributes wa;
XEvent ev;
Display *dpy;
int screen;
Window root, win;
/* init */
if (!(dpy=XOpenDisplay(NULL)))
return 1;
screen = DefaultScreen(dpy);
root = RootWindow(dpy, screen);
if(!XGetWindowAttributes(dpy, root, &wa))
return 1;
width = height = MIN(wa.width, wa.height);
/* create window */
win = XCreateSimpleWindow(dpy, root, 0, 0, width, height, 0, 0, 0x7F7F997F);
send_systray_message(dpy, SYSTEM_TRAY_REQUEST_DOCK, win, 0, 0); // pass win only once
XMapWindow(dpy, win);
XSync(dpy, False);
/* run */
while(1) {
while(XPending(dpy)) {
XNextEvent(dpy, &ev); /* just waiting until we error because window closed */
}
}
}
I am thankful for everything that will point me in the right direction, even if its just some documentation that I have not found so far.
I'm building a custom listview control as the standard comctl32.dll one does not adequately fit my needs. Things seem to be going well for what I have so far, except for scrolling.
At first, things look like this (under wine, but real Windows is affected too);
The form of corruption changes depending on the type of scrolling used. If I scroll using the mouse wheel, every few rows is corrupted:
And if I scroll by dragging the thumb, this happens:
In both cases, clicking somewhere will redraw everything, and the corruption goes away.
I'm not really sure what I'm doing wrong here.
Here's the code; I will explain how it works below:
// 19 october 2014
#define UNICODE
#define _UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
// get Windows version right; right now Windows XP
#define WINVER 0x0501
#define _WIN32_WINNT 0x0501
#define _WIN32_WINDOWS 0x0501 /* according to Microsoft's winperf.h */
#define _WIN32_IE 0x0600 /* according to Microsoft's sdkddkver.h */
#define NTDDI_VERSION 0x05010000 /* according to Microsoft's sdkddkver.h */
#include <windows.h>
#include <commctrl.h>
#include <stdint.h>
#include <uxtheme.h>
#include <string.h>
#include <wchar.h>
#include <windowsx.h>
#include <vsstyle.h>
#include <vssym32.h>
// #qo LIBS: user32 kernel32 gdi32 comctl32
// TODO
// - http://blogs.msdn.com/b/oldnewthing/archive/2003/09/09/54826.aspx (relies on the integrality parts? IDK)
// - might want to http://blogs.msdn.com/b/oldnewthing/archive/2003/09/17/54944.aspx instead
// - http://msdn.microsoft.com/en-us/library/windows/desktop/bb775574%28v=vs.85%29.aspx
// - hscroll (harder)
// - keyboard navigation
// - mousewheel navigation
#define tableWindowClass L"gouitable"
struct table {
HWND hwnd;
HFONT defaultFont;
HFONT font;
intptr_t selected;
intptr_t count;
intptr_t firstVisible;
intptr_t pagesize; // in rows
int wheelCarry;
HWND header;
int headerHeight;
intptr_t nColumns;
HIMAGELIST imagelist;
int imagelistHeight;
};
static LONG rowHeight(struct table *t)
{
HFONT thisfont, prevfont;
TEXTMETRICW tm;
HDC dc;
LONG ret;
dc = GetDC(t->hwnd);
if (dc == NULL)
abort();
thisfont = t->font; // in case WM_SETFONT happens before we return
prevfont = (HFONT) SelectObject(dc, thisfont);
if (prevfont == NULL)
abort();
if (GetTextMetricsW(dc, &tm) == 0)
abort();
if (SelectObject(dc, prevfont) != (HGDIOBJ) (thisfont))
abort();
if (ReleaseDC(t->hwnd, dc) == 0)
abort();
ret = tm.tmHeight;
if (ret < t->imagelistHeight)
ret = t->imagelistHeight;
return ret;
}
static void redrawAll(struct table *t)
{
if (InvalidateRect(t->hwnd, NULL, TRUE) == 0)
abort();
if (UpdateWindow(t->hwnd) == 0)
abort();
}
static RECT realClientRect(struct table *t)
{
RECT r;
if (GetClientRect(t->hwnd, &r) == 0)
abort();
r.top += t->headerHeight;
return r;
}
static void recomputeHScroll(struct table *t)
{
HDITEMW item;
intptr_t i;
int width = 0;
RECT r;
SCROLLINFO si;
// TODO count dividers
for (i = 0; i < t->nColumns; i++) {
ZeroMemory(&item, sizeof (HDITEMW));
item.mask = HDI_WIDTH;
if (SendMessageW(t->header, HDM_GETITEM, (WPARAM) i, (LPARAM) (&item)) == FALSE)
abort();
width += item.cxy;
}
if (GetClientRect(t->hwnd, &r) == 0)
abort();
ZeroMemory(&si, sizeof (SCROLLINFO));
si.cbSize = sizeof (SCROLLINFO);
si.fMask = SIF_PAGE | SIF_RANGE;
si.nPage = r.right - r.left;
si.nMin = 0;
si.nMax = width - 1; // - 1 because endpoints inclusive
SetScrollInfo(t->hwnd, SB_HORZ, &si, TRUE);
}
static void finishSelect(struct table *t)
{
if (t->selected < 0)
t->selected = 0;
if (t->selected >= t->count)
t->selected = t->count - 1;
// TODO update only the old and new selected items
redrawAll(t);
// TODO scroll to the selected item if it's not entirely visible
}
static void keySelect(struct table *t, WPARAM wParam, LPARAM lParam)
{
// TODO figure out correct behavior with nothing selected
if (t->count == 0) // don't try to do anything if there's nothing to do
return;
switch (wParam) {
case VK_UP:
t->selected--;
break;
case VK_DOWN:
t->selected++;
break;
case VK_PRIOR:
t->selected -= t->pagesize;
break;
case VK_NEXT:
t->selected += t->pagesize;
break;
case VK_HOME:
t->selected = 0;
break;
case VK_END:
t->selected = t->count - 1;
break;
default:
// don't touch anything
return;
}
finishSelect(t);
}
static void selectItem(struct table *t, WPARAM wParam, LPARAM lParam)
{
int x, y;
LONG h;
x = GET_X_LPARAM(lParam);
y = GET_Y_LPARAM(lParam);
h = rowHeight(t);
y += t->firstVisible * h;
y -= t->headerHeight;
y /= h;
t->selected = y;
if (t->selected >= t->count)
t->selected = -1;
finishSelect(t);
}
// TODO on initial show the items are not arranged properly
// TODO the lowest visible row does not redraw properly after scrolling
// TODO the row behind the header bar does not redraw properly after scrolling
static void vscrollto(struct table *t, intptr_t newpos)
{
SCROLLINFO si;
RECT scrollArea;
if (newpos < 0)
newpos = 0;
if (newpos > (t->count - t->pagesize))
newpos = (t->count - t->pagesize);
scrollArea = realClientRect(t);
// negative because ScrollWindowEx() is "backwards"
if (ScrollWindowEx(t->hwnd, 0, (-(newpos - t->firstVisible)) * rowHeight(t),
&scrollArea, &scrollArea, NULL, NULL,
SW_ERASE | SW_INVALIDATE) == ERROR)
abort();
t->firstVisible = newpos;
ZeroMemory(&si, sizeof (SCROLLINFO));
si.cbSize = sizeof (SCROLLINFO);
si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
si.nPage = t->pagesize;
si.nMin = 0;
si.nMax = t->count - 1; // nMax is inclusive
si.nPos = t->firstVisible;
SetScrollInfo(t->hwnd, SB_VERT, &si, TRUE);
}
static void vscrollby(struct table *t, intptr_t n)
{
vscrollto(t, t->firstVisible + n);
}
static void wheelscroll(struct table *t, WPARAM wParam)
{
int delta;
int lines;
UINT scrollAmount;
delta = GET_WHEEL_DELTA_WPARAM(wParam);
if (SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &scrollAmount, 0) == 0)
abort();
if (scrollAmount == WHEEL_PAGESCROLL)
scrollAmount = t->pagesize;
if (scrollAmount == 0) // no mouse wheel scrolling (or t->pagesize == 0)
return;
// the rest of this is basically http://blogs.msdn.com/b/oldnewthing/archive/2003/08/07/54615.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2003/08/11/54624.aspx
// see those pages for information on subtleties
delta += t->wheelCarry;
lines = delta * ((int) scrollAmount) / WHEEL_DELTA;
t->wheelCarry = delta - lines * WHEEL_DELTA / ((int) scrollAmount);
vscrollby(t, -lines);
}
static void vscroll(struct table *t, WPARAM wParam)
{
SCROLLINFO si;
intptr_t newpos;
ZeroMemory(&si, sizeof (SCROLLINFO));
si.cbSize = sizeof (SCROLLINFO);
si.fMask = SIF_POS | SIF_TRACKPOS;
if (GetScrollInfo(t->hwnd, SB_VERT, &si) == 0)
abort();
newpos = t->firstVisible;
switch (LOWORD(wParam)) {
case SB_TOP:
newpos = 0;
break;
case SB_BOTTOM:
newpos = t->count - t->pagesize;
break;
case SB_LINEUP:
newpos--;
break;
case SB_LINEDOWN:
newpos++;
break;
case SB_PAGEUP:
newpos -= t->pagesize;
break;
case SB_PAGEDOWN:
newpos += t->pagesize;
break;
case SB_THUMBPOSITION:
newpos = (intptr_t) (si.nPos);
break;
case SB_THUMBTRACK:
newpos = (intptr_t) (si.nTrackPos);
}
vscrollto(t, newpos);
}
static void resize(struct table *t)
{
RECT r;
SCROLLINFO si;
HDLAYOUT headerlayout;
WINDOWPOS headerpos;
// do this first so our scrollbar calculations can be correct
if (GetClientRect(t->hwnd, &r) == 0) // use the whole client rect
abort();
headerlayout.prc = &r;
headerlayout.pwpos = &headerpos;
if (SendMessageW(t->header, HDM_LAYOUT, 0, (LPARAM) (&headerlayout)) == FALSE)
abort();
if (SetWindowPos(t->header, headerpos.hwndInsertAfter, headerpos.x, headerpos.y, headerpos.cx, headerpos.cy, headerpos.flags | SWP_SHOWWINDOW) == 0)
abort();
t->headerHeight = headerpos.cy;
// now adjust the scrollbars
r = realClientRect(t);
t->pagesize = (r.bottom - r.top) / rowHeight(t);
ZeroMemory(&si, sizeof (SCROLLINFO));
si.cbSize = sizeof (SCROLLINFO);
si.fMask = SIF_RANGE | SIF_PAGE;
si.nMin = 0;
si.nMax = t->count - 1;
si.nPage = t->pagesize;
SetScrollInfo(t->hwnd, SB_VERT, &si, TRUE);
recomputeHScroll(t);
}
static void drawItems(struct table *t, HDC dc, RECT cliprect)
{
HFONT thisfont, prevfont;
LONG height;
LONG y;
intptr_t i;
RECT controlSize; // for filling the entire selected row
intptr_t first, last;
POINT prevOrigin, prevViewportOrigin;
if (GetClientRect(t->hwnd, &controlSize) == 0)
abort();
height = rowHeight(t);
thisfont = t->font; // in case WM_SETFONT happens before we return
prevfont = (HFONT) SelectObject(dc, thisfont);
if (prevfont == NULL)
abort();
// adjust the clip rect and the window so that (0, 0) is always the first item
// adjust the viewport so that everything is shifted down t->headerHeight pixels
if (OffsetRect(&cliprect, 0, t->firstVisible * height) == 0)
abort();
if (GetWindowOrgEx(dc, &prevOrigin) == 0)
abort();
if (SetWindowOrgEx(dc, prevOrigin.x, prevOrigin.y + (t->firstVisible * height), NULL) == 0)
abort();
if (SetViewportOrgEx(dc, 0, t->headerHeight, &prevViewportOrigin) == 0)
abort();
// see http://blogs.msdn.com/b/oldnewthing/archive/2003/07/29/54591.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2003/07/30/54600.aspx
first = cliprect.top / height;
if (first < 0)
first = 0;
last = (cliprect.bottom + height - 1) / height;
if (last >= t->count)
last = t->count;
y = first * height;
for (i = first; i < last; i++) {
RECT rsel;
HBRUSH background;
int textColor;
WCHAR msg[100];
RECT headeritem;
intptr_t j;
LRESULT xoff;
// TODO verify these two
background = (HBRUSH) (COLOR_WINDOW + 1);
textColor = COLOR_WINDOWTEXT;
if (t->selected == i) {
// these are the colors wine uses (http://source.winehq.org/source/dlls/comctl32/listview.c)
// the two for unfocused are also suggested by http://stackoverflow.com/questions/10428710/windows-forms-inactive-highlight-color
background = (HBRUSH) (COLOR_HIGHLIGHT + 1);
textColor = COLOR_HIGHLIGHTTEXT;
if (GetFocus() != t->hwnd) {
background = (HBRUSH) (COLOR_BTNFACE + 1);
textColor = COLOR_BTNTEXT;
}
}
// first fill the selection rect
rsel.left = controlSize.left;
rsel.top = y;
rsel.right = controlSize.right - controlSize.left;
rsel.bottom = y + height;
if (FillRect(dc, &rsel, background) == 0)
abort();
xoff = SendMessageW(t->header, HDM_GETBITMAPMARGIN, 0, 0);
// now draw the cells
if (SetTextColor(dc, GetSysColor(textColor)) == CLR_INVALID)
abort();
if (SetBkMode(dc, TRANSPARENT) == 0)
abort();
for (j = 0; j < t->nColumns; j++) {
if (SendMessageW(t->header, HDM_GETITEMRECT, (WPARAM) j, (LPARAM) (&headeritem)) == 0)
abort();
if (j == 1) { // TODO
IMAGELISTDRAWPARAMS ip;
ZeroMemory(&ip, sizeof (IMAGELISTDRAWPARAMS));
ip.cbSize = sizeof (IMAGELISTDRAWPARAMS);
ip.himl = t->imagelist;
ip.i = 0;
ip.hdcDst = dc;
ip.x = headeritem.left + xoff;
ip.y = y;
ip.cx = 0; // draw whole image
ip.cy = 0;
ip.xBitmap = 0;
ip.yBitmap = 0;
ip.rgbBk = CLR_NONE;
ip.fStyle = ILD_NORMAL | ILD_SCALE; // TODO alpha-blend; ILD_DPISCALE?
// TODO ILS_ALPHA?
if (ImageList_DrawIndirect(&ip) == 0)
abort();
continue;
}
rsel.left = headeritem.left + xoff;
rsel.top = y;
rsel.right = headeritem.right;
rsel.bottom = y + height;
// TODO vertical center in case the height is less than the icon height?
if (DrawTextExW(dc, msg, wsprintf(msg, L"Item %d", i), &rsel, DT_END_ELLIPSIS | DT_LEFT | DT_NOPREFIX | DT_SINGLELINE, NULL) == 0)
abort();
}
y += height;
}
// reset everything
if (SetViewportOrgEx(dc, prevViewportOrigin.x, prevViewportOrigin.y, NULL) == 0)
abort();
if (SetWindowOrgEx(dc, prevOrigin.x, prevOrigin.y, NULL) == 0)
abort();
if (SelectObject(dc, prevfont) != (HGDIOBJ) (thisfont))
abort();
}
static LRESULT CALLBACK tableWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
struct table *t;
HDC dc;
PAINTSTRUCT ps;
NMHDR *nmhdr = (NMHDR *) lParam;
NMHEADERW *nm = (NMHEADERW *) lParam;
t = (struct table *) GetWindowLongPtrW(hwnd, GWLP_USERDATA);
if (t == NULL) {
// we have to do things this way because creating the header control will fail mysteriously if we create it first thing
// (which is fine; we can get the parent hInstance this way too)
if (uMsg == WM_NCCREATE) {
CREATESTRUCTW *cs = (CREATESTRUCTW *) lParam;
t = (struct table *) malloc(sizeof (struct table));
if (t == NULL)
abort();
ZeroMemory(t, sizeof (struct table));
t->hwnd = hwnd;
// TODO this should be a global
t->defaultFont = (HFONT) GetStockObject(SYSTEM_FONT);
if (t->defaultFont == NULL)
abort();
t->font = t->defaultFont;
t->selected = 5;t->count=100;//TODO
t->header = CreateWindowExW(0,
WC_HEADERW, L"",
// TODO is HOTTRACK needed?
WS_CHILD | HDS_FULLDRAG | HDS_HORZ | HDS_HOTTRACK,
0, 0, 0, 0,
t->hwnd, (HMENU) 100, cs->hInstance, NULL);
if (t->header == NULL)
abort();
{HDITEMW item;
ZeroMemory(&item, sizeof (HDITEMW));
item.mask = HDI_WIDTH | HDI_TEXT | HDI_FORMAT;
item.cxy = 200;
item.pszText = L"Column";
item.fmt = HDF_LEFT | HDF_STRING;
if (SendMessage(t->header, HDM_INSERTITEM, 0, (LPARAM) (&item)) == (LRESULT) (-1))
abort();
ZeroMemory(&item, sizeof (HDITEMW));
item.mask = HDI_WIDTH | HDI_TEXT | HDI_FORMAT;
item.cxy = 150;
item.pszText = L"Column 2";
item.fmt = HDF_LEFT | HDF_STRING;
if (SendMessage(t->header, HDM_INSERTITEM, 1, (LPARAM) (&item)) == (LRESULT) (-1))
abort();
t->nColumns=2;
t->imagelist = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32, 1, 1);
if(t->imagelist==NULL)abort();
{
HICON icon;
int unused;
icon = LoadIconW(NULL, IDI_ERROR);
if(icon == NULL)abort();
if (ImageList_AddIcon(t->imagelist, icon) == -1)abort();
if (ImageList_GetIconSize(t->imagelist, &unused, &(t->imagelistHeight)) == 0)abort();
}
}
SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) t);
}
// even if we did the above, fall through
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
switch (uMsg) {
case WM_PAINT:
dc = BeginPaint(hwnd, &ps);
if (dc == NULL)
abort();
drawItems(t, dc, ps.rcPaint);
EndPaint(hwnd, &ps);
return 0;
case WM_SETFONT:
t->font = (HFONT) wParam;
if (t->font == NULL)
t->font = t->defaultFont;
// also set the header font
SendMessageW(t->header, WM_SETFONT, wParam, lParam);
if (LOWORD(lParam) != FALSE) {
// the scrollbar page size will change so redraw that too
// also recalculate the header height
// TODO do that when this is FALSE too somehow
resize(t);
redrawAll(t);
}
return 0;
case WM_GETFONT:
return (LRESULT) t->font;
case WM_VSCROLL:
vscroll(t, wParam);
return 0;
case WM_MOUSEWHEEL:
wheelscroll(t, wParam);
return 0;
case WM_SIZE:
resize(t);
return 0;
case WM_LBUTTONDOWN:
selectItem(t, wParam, lParam);
return 0;
case WM_SETFOCUS:
case WM_KILLFOCUS:
// all we need to do here is redraw the highlight
// TODO localize to just the selected item
// TODO ensure giving focus works right
redrawAll(t);
return 0;
case WM_KEYDOWN:
keySelect(t, wParam, lParam);
return 0;
// TODO header double-click
case WM_NOTIFY:
if (nmhdr->hwndFrom == t->header)
switch (nmhdr->code) {
// I could use HDN_TRACK but wine doesn't emit that
case HDN_ITEMCHANGING:
case HDN_ITEMCHANGED: // TODO needed?
recomputeHScroll(t);
redrawAll(t);
return FALSE;
}
// otherwise fall through
default:
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
abort();
return 0; // unreached
}
void makeTableWindowClass(void)
{
WNDCLASSW wc;
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpszClassName = tableWindowClass;
wc.lpfnWndProc = tableWndProc;
wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
wc.hIcon = LoadIconW(NULL, IDI_APPLICATION);
wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); // TODO correct?
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.hInstance = GetModuleHandle(NULL);
if (RegisterClassW(&wc) == 0)
abort();
}
int main(void)
{
HWND mainwin;
MSG msg;
INITCOMMONCONTROLSEX icc;
ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX));
icc.dwSize = sizeof (INITCOMMONCONTROLSEX);
icc.dwICC = ICC_LISTVIEW_CLASSES;
if (InitCommonControlsEx(&icc) == 0)
abort();
makeTableWindowClass();
mainwin = CreateWindowExW(0,
tableWindowClass, L"Main Window",
WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT,
400, 400,
NULL, NULL, GetModuleHandle(NULL), NULL);
if (mainwin == NULL)
abort();
ShowWindow(mainwin, SW_SHOWDEFAULT);
if (UpdateWindow(mainwin) == 0)
abort();
while (GetMessageW(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return 0;
}
t->firstVisible is the first visible row; t->pagesize is the number of rows per vscroll page, and t->headerHeight is the height of the header control in pixels. t->wheelcarry is the mouse wheel carryover, from this.
The actual height of a row is calculated by the rowHeight() function, which vscrollto(), the routine that actually calls ScrolLWindow(), multiplies the new t->firstVisible by to figure out how many pixels to scroll.
The realClientRect() logic in vscrollto() is to make sure that only the content area gets scrolled, and not the header control.
For drawing, I change the window origin to make (0, 0) be the first item (row 0) no matter what and then I change the viewport origin to account for the header. Readers of MSDN will note that this sounds strange (the documentation for both SetWindowOrgEx() and SetViewportOrgEx() say you generally don't use both), but my attempts at accounting for the header height manually have proven disastrous.
Again, this happens on both real Windows (did this earlier today on Windows 7) and wine.
What did I do wrong? Thanks.
I've had a play around with your code and I think the problem is definitely caused by your modifying the viewport and window origins. The issue is that adjusting the origins like that changes where new drawing will appear in the window, but it doesn't cause the update region to be shifted as well.
Therefore when there's only a partially invalidated area, as you get when scrolling, some of your drawing is clipped out by the update region.
It's actually not specific to scrolling - you can also see the same effect by dragging your window off the bottom of the screen, and then slowly dragging it back into view. The window will end up completely blank. If you comment out your call to SetViewportOrgEx the problem goes away completely.
You could probably solve this by adjusting the clipping region as well - you'd have to get the current update region, shift it by the same offset, and reselect it into the DC. Alternatively, you could redesign your painting code to take the height of the header into account when painting rather than shifting the viewport offset. You mentioned you had other issues when doing this - I suspect it will be easier to solve those than you think (i.e. it seems like a bit of an XY problem).