Can someone tell me what I am doing wrong in the following snippet?
The problem is that this snippet, which is supposed to print "Hello, World!" with colors works exactly as expected when ran in cmder with cmd.exe as the shell, but is is completly broken when used in the native cmd.exe terminal emulator or the native PowerShell emulator.
Specifically, the background color is changed, and the reset doesn't work.
#include <windows.h>
#include <io.h>
#include <conio.h>
#include <fileapi.h>
#include <assert.h>
#include <stdio.h>
typedef struct {
HANDLE h;
HANDLE hin;
WORD savedAttr;
} Terminal;
#define FOREGROUND_RGB (FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN)
#define BACKGROUND_RGB (BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_GREEN)
#define TRY_OS(expr) if ((expr) == 0) { osError(); }
#define TRY_OR(expr) if ((expr) == 0)
static WORD get_text_attributes(Terminal term)
{
CONSOLE_SCREEN_BUFFER_INFO c;
TRY_OR(GetConsoleScreenBufferInfo(term.h, &c)) {
return 0x70;
}
return c.wAttributes;
}
Terminal getHandle(FILE* in, FILE* out)
{
Terminal term;
if ((in == stdin) && (out == stdout)) {
// grab the current console even if stdin/stdout have been redirected
term.h =
CreateFile("CON", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
0, 0);
term.hin =
CreateFile("CON", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0,
0);
// term.h = GetStdHandle(STD_OUTPUT_HANDLE);
// term.hin = GetStdHandle(STD_INPUT_HANDLE);
} else {
term.h = (HANDLE) _get_osfhandle(_fileno(out));
term.hin = (HANDLE) _get_osfhandle(_fileno(in));
}
term.savedAttr = get_text_attributes(term);
return term;
}
void setFG(Terminal term, int color)
{
WORD attr = get_text_attributes(term);
attr &= ~FOREGROUND_RGB; // clear FG color
attr &= ~FOREGROUND_INTENSITY; // clear FG intensity
attr |= color;
SetConsoleTextAttribute(term.h, attr);
}
void setBG(Terminal term, int color)
{
WORD attr = get_text_attributes(term);
attr &= ~BACKGROUND_RGB; // clear BG color
attr &= ~BACKGROUND_INTENSITY; // clear BG intensity
attr |= color;
SetConsoleTextAttribute(term.h, attr);
}
int main()
{
Terminal term = getHandle(stdin, stdout);
setFG(term, FOREGROUND_RED);
WriteConsole(term.h, "Hello, ", 7, NULL, NULL);
setFG(term, FOREGROUND_BLUE);
WriteConsole(term.h, "World !\r\n", 9, NULL, NULL);
// reset style
SetConsoleTextAttribute(term.h, term.savedAttr);
return 0;
}
I guess I am doing something wrong in my use of the API and cmder is looser about it, but I wrote this whole code by reading the official Microsoft doc, so I am a bit confused.
I suggest you could try to use Multi-Byte Character Set in stead of Unicode Character Set.
Or you could try to use wide-character literal, like:
term.h =
CreateFile(L"CON", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
0, 0);
term.hin =
CreateFile(L"CON", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0,
.
WriteConsole(term.h, L"Hello, ", 8, NULL, NULL);
.
WriteConsole(term.h, L"World !\n\r", 9, NULL, NULL);
EDIT:
Results of running with cmd:
Results of running with visual studio:
Related
I made a program that outputs a click-sound whenever a key on the keyboard has been pressed.
When I tried the program on my pc (with Ryzen 3900x) and on one of my laptops (with i7 8850h) it outputted the sound flawlessly without any delay, even when typing fast.
But on a slower laptop I use for school, it is not outputting the sound right, especially when typing fast. (The sound is either delayed or it only plays a little part before stopping)
I cant really find an issue other than that PlaySound might be too slow or something. It hasn't anything to do with loading the sound-file, because I am playing the sound from memory. It is also not the recognition of a key-press, because the console output (which key gets pressed) is almost instantly.
Here is my code:
#ifndef UNICODE
#define UNICODE
#endif // UNICODE
#include <stdio.h>
#include <stdlib.h>
#include <w32api.h>
#define WINVER WindowsXP
#include <windows.h>
#include <winuser.h>
BYTE* byteAudio = NULL;
LRESULT CALLBACK WinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if(uMsg == WM_INPUT)
{
HRAWINPUT hRawInput = (HRAWINPUT)lParam;
RAWINPUT input = { 0 };
UINT size = sizeof(input);
GetRawInputData(hRawInput, RID_INPUT,&input,&size,sizeof(RAWINPUTHEADER));
if(input.data.keyboard.Flags == 0)
{
//PlaySoundW(TEXT("res/click.wav"), NULL, SND_ASYNC | SND_FILENAME);
PlaySound(byteAudio, SND_MEMORY, SND_ASYNC | SND_MEMORY);
printf("vkey: %x, flag: %d\n",input.data.keyboard.VKey, input.data.keyboard.Flags);
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpComLine, int iShowCmd)
{
const char* CLASS_NAME = "RawInputClass";
FILE* fPtr;
long lFileSize;
//Gets file size
fPtr = fopen("res/click.wav", "rb");
if(!fPtr)
{
printf("Error while opening file!");
return -1;
}
fseek(fPtr, 0, SEEK_END);
lFileSize = ftell(fPtr);
rewind(fPtr);
//Reads file into byte-Array
byteAudio = (BYTE*)malloc(lFileSize * sizeof(BYTE));
if(!byteAudio)
{
printf("byteAudio; OUT_OF_MEMORY"); //If that happens, it is probably time for an upgrade
return -1;
}
fread(byteAudio, lFileSize, 1, fPtr);
fclose(fPtr);
//Create Message-Only window
WNDCLASS wnd = { 0 };
wnd.hInstance = GetModuleHandle(NULL);
wnd.lpfnWndProc = WinProc;
wnd.lpszClassName = CLASS_NAME;
RegisterClass(&wnd);
HWND hWnd = CreateWindowExW(0, CLASS_NAME, TEXT("THE WINDOW IS INVISIBLE SO WHO CARES!"),
0, 0, 0, 0, 0, HWND_MESSAGE, NULL, GetModuleHandle(NULL), NULL);
RAWINPUTDEVICE rid = { 0 };
rid.usUsagePage = 0x01;
rid.usUsage = 0x06; //keyboard
rid.dwFlags = RIDEV_INPUTSINK | RIDEV_NOLEGACY;
rid.hwndTarget = hWnd;
RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE));
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
You are using a polling method, which is slow.
while(GetMessage(&msg, NULL, 0, 0))
{
/* stuff */
}
Read about what interrupts are and for what those interrupts are used. These will solve your problem.
This is a working example of X11 code that handles Ctrl-q event to quit application:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
void exitOnCondition(char cond, const char *msg, int exitCode, Display *dpy, Window *w, GC *gc) {
if(cond) {
printf("%s\n", msg);
if(dpy && gc) XFreeGC(dpy, *gc);
if(dpy && w) XDestroyWindow(dpy, *w);
if(dpy) XCloseDisplay(dpy);
exit(exitCode);
}
}
int main(int argc, char **argv) {
Display *dpy = XOpenDisplay(0);
exitOnCondition(dpy == 0, "Error: XOpenDisplay failed", -1, dpy, 0, 0);
int blackColor = BlackPixel(dpy, DefaultScreen(dpy));
int whiteColor = WhitePixel(dpy, DefaultScreen(dpy));
Window w = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0,
200, 100, 0, blackColor, blackColor);
//Tell X Server to send MapNotify events
XSelectInput(dpy, w, StructureNotifyMask | KeyPressMask);
//Make window appear
XMapWindow(dpy, w);
//Graphics Context
GC gc = XCreateGC(dpy, w, 0, 0);
//Set white color for drawing
XSetForeground(dpy, gc, whiteColor);
//Wait for the MapNotify event
for(;;) {
XEvent e;
XNextEvent(dpy, &e);
if (e.type == MapNotify) {
break;
}
}
//Draw the line
XDrawLine(dpy, w, gc, 10, 60, 180, 20);
//Send the "DrawLine" request to the server
XFlush(dpy);
char text[255];
XEvent e;
KeySym key;
int numKeys = 0;
for(;;) {
XNextEvent(dpy, &e);
if(e.type == KeyPress) {
//With modifier XLookupString will return garbage(?) in text[0] and key as latin1
if((numKeys = XLookupString(&e.xkey, text, 255, &key, 0))) {
printf("lookup returned:\n");
for(int i = 0; i < numKeys; i++) {
printf("text[%d]=%x\n", i, text[i]);
}
if(e.xkey.state == ControlMask && key == XK_q) {
exitOnCondition(1, "C-Q pressed", 0, dpy, &w, &gc);
}
}
}
}
XFreeGC(dpy, gc);
XDestroyWindow(dpy,w);
XCloseDisplay(dpy);
}
Will this code correctly handle Ctrl-q event on any system?
Can I use e.xkey.state to check for Ctrl modifier after XLookupString for any keyboard layout even when Ctrl is rebound to CAPS Lock(or anything else)?
Why does XLookupString return one symbol text[0]==0x11 for Ctrl-q event and not text[0]==CtrlModifierCode text[1]=='q'?
XLookupString returns a sequence of ISO-8859-1 characters (at least according to the manual I have; I do not know how up-to-date it is). There is no character code in ISO-8859-1 for the "ctrl" key by itself. Strictly speaking, there isn't one for the ctrl-q combination either, but tradition dictates that ctrl + (A...Z) map to the 1...26 range.
I'm trying to autosize some date-time picker controls with DTM_GETIDEALSIZE, but I don't quite get how to use this message properly. While widths returned seem to be fine if treated as pixels, heights seem to be not only way off, but sometimes change over time!
The sample program below creates three date-time pickers. The first shows both date and time with a custom format, the second shows dates only with DTS_SHORTDATECENTURYFORMAT, and the third shows times only with DTS_TIMEFORMAT. Resizing the window resizes all three pickers using DTM_GETIDEALSIZE. The widths seem to be fine. However, the heights of the first two are always 100, and the height of the last (the time picker) seems to start at 98 or so and decreases by two with each resize until it reaches 16, at which point it stays at 16.
This is tested both with Windows Vista and with Windows 7. Error checking in the below program is inconsistent (but some errors are not checked for the purposes of keeping this a simple test example). However, I will note that MSDN explicitly documents DTM_GETIDEALSIZE as only ever returning TRUE with no error condition specified.
Thanks.
// 3 june 2015
#define UNICODE
#define _UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
#define CINTERFACE
#define WINVER 0x0600
#define _WIN32_WINNT 0x0600
#define _WIN32_WINDOWS 0x0600
#define _WIN32_IE 0x0700
#define NTDDI_VERSION 0x06000000
#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>
#include <stdarg.h>
#include <oleacc.h>
#include <stdio.h>
void die(char *s)
{
// TODO
}
HWND mainwin;
HWND dtp1, dtp2, dtp3;
void idealsize(HWND dtp, char *n, int *x, int *y)
{
SIZE s;
s.cx = 0;
s.cy = 0;
printf("%s | %I32d ", n, SendMessageW(dtp, DTM_GETIDEALSIZE, 0, (LPARAM) (&s)));
printf("%I32d %I32d\n", s.cx, s.cy);
MoveWindow(dtp, *x, *y, s.cx, s.cy, TRUE);
*y += s.cy;
}
LRESULT CALLBACK wndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
int x, y;
SIZE s;
switch (uMsg) {
case WM_CLOSE:
PostQuitMessage(0);
return 0;
case WM_SIZE:
x = 10;
y = 10;
idealsize(dtp1, "1", &x, &y);
idealsize(dtp2, "2", &x, &y);
idealsize(dtp3, "3", &x, &y);
break;
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
static HWND makedtp(DWORD style, WCHAR *format)
{
HWND hwnd;
hwnd = CreateWindowExW(WS_EX_CLIENTEDGE,
DATETIMEPICK_CLASSW, L"",
style | WS_TABSTOP | WS_CHILD | WS_VISIBLE,
0, 0, 100, 100,
mainwin, NULL, GetModuleHandle(NULL), NULL);
if (format != NULL)
if (SendMessageW(hwnd, DTM_SETFORMAT, 0, (LPARAM) format) == 0)
die("error applying format string to date/time picker in finishNewDateTimePicker()");
return hwnd;
}
#define GLI(what, buf, n) GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, what, buf, n)
HWND makeDateTimePicker(void)
{
WCHAR *date, *time, *datetime;
int ndate, ntime;
int n;
HWND hwnd;
// TODO verify that this always returns a century year
ndate = GLI(LOCALE_SSHORTDATE, NULL, 0);
if (ndate == 0)
die("error getting date string length in uiNewDateTimePicker()");
date = (WCHAR *) malloc(ndate * sizeof (WCHAR));
if (GLI(LOCALE_SSHORTDATE, date, ndate) == 0)
die("error geting date string in uiNewDateTimePicker()");
ntime = GLI(LOCALE_STIMEFORMAT, NULL, 0);
if (ndate == 0)
die("error getting time string length in uiNewDateTimePicker()");
time = (WCHAR *) malloc(ntime * sizeof (WCHAR));
if (GLI(LOCALE_STIMEFORMAT, time, ntime) == 0)
die("error geting time string in uiNewDateTimePicker()");
n = _scwprintf(L"%s %s", date, time);
datetime = (WCHAR *) malloc((n + 1) * sizeof (WCHAR));
snwprintf(datetime, n + 1, L"%s %s", date, time);
hwnd = makedtp(0, datetime);
free(datetime);
free(time);
free(date);
return hwnd;
}
HWND makeDatePicker(void)
{
return makedtp(DTS_SHORTDATECENTURYFORMAT, NULL);
}
HWND makeTimePicker(void)
{
return makedtp(DTS_TIMEFORMAT, NULL);
}
static void makeWindows(void)
{
mainwin = CreateWindowExW(0,
L"mainwin", L"Full Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
500, 500,
NULL, NULL, GetModuleHandle(NULL), NULL);
dtp1 = makeDateTimePicker();
dtp2 = makeDatePicker();
dtp3 = makeTimePicker();
}
void initCommonControls(BOOL);
int main(int argc, char *argv[])
{
WNDCLASSW wc;
MSG msg;
HBRUSH b;
initCommonControls(TRUE);
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpszClassName = L"mainwin";
wc.lpfnWndProc = wndProc;
wc.hInstance = GetModuleHandle(NULL);
wc.hIcon = LoadIconW(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
RegisterClassW(&wc);
makeWindows();
ShowWindow(mainwin, SW_SHOWDEFAULT);
UpdateWindow(mainwin);
while (GetMessageW(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
static const char manifest[] = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n<assemblyIdentity\n version=\"1.0.0.0\"\n processorArchitecture=\"*\"\n name=\"CompanyName.ProductName.YourApplication\"\n type=\"win32\"\n/>\n<description>Your application description here.</description>\n<dependency>\n <dependentAssembly>\n <assemblyIdentity\n type=\"win32\"\n name=\"Microsoft.Windows.Common-Controls\"\n version=\"6.0.0.0\"\n processorArchitecture=\"*\"\n publicKeyToken=\"6595b64144ccf1df\"\n language=\"*\"\n />\n </dependentAssembly>\n</dependency>\n</assembly>\n";
static ULONG_PTR comctlManifestCookie;
static HMODULE comctl32;
void initCommonControls(BOOL comctl6)
{
WCHAR temppath[MAX_PATH + 1];
WCHAR filename[MAX_PATH + 1];
HANDLE file;
DWORD nExpected, nGot;
ACTCTX actctx;
HANDLE ac;
INITCOMMONCONTROLSEX icc;
FARPROC f;
// this is listed as WINAPI in both Microsoft's and MinGW's headers, but not on MSDN for some reason
BOOL (*WINAPI ficc)(const LPINITCOMMONCONTROLSEX);
if (comctl6) {
if (GetTempPathW(MAX_PATH + 1, temppath) == 0)
die("getting temporary path for writing manifest file");
if (GetTempFileNameW(temppath, L"manifest", 0, filename) == 0)
die("getting temporary filename for writing manifest file");
file = CreateFileW(filename, GENERIC_WRITE,
0, // don't share while writing
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (file == NULL)
die("creating manifest file");
nExpected = (sizeof manifest / sizeof manifest[0]) - 1; // - 1 to omit the terminating null character)
if (WriteFile(file, manifest, nExpected, &nGot, NULL) == 0)
die("writing manifest file");
if (nGot != nExpected)
die("short write to manifest file");
if (CloseHandle(file) == 0)
die("closing manifest file (this IS an error here because not doing so will prevent Windows from being able to use the manifest file in an activation context)");
ZeroMemory(&actctx, sizeof (ACTCTX));
actctx.cbSize = sizeof (ACTCTX);
actctx.dwFlags = ACTCTX_FLAG_SET_PROCESS_DEFAULT;
actctx.lpSource = filename;
ac = CreateActCtx(&actctx);
if (ac == INVALID_HANDLE_VALUE)
die("creating activation context for synthesized manifest file");
if (ActivateActCtx(ac, &comctlManifestCookie) == FALSE)
die("activating activation context for synthesized manifest file");
}
ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX));
icc.dwSize = sizeof (INITCOMMONCONTROLSEX);
icc.dwICC = ICC_STANDARD_CLASSES | ICC_PROGRESS_CLASS | ICC_TAB_CLASSES | ICC_LISTVIEW_CLASSES | ICC_UPDOWN_CLASS | ICC_BAR_CLASSES | ICC_DATE_CLASSES;
comctl32 = LoadLibraryW(L"comctl32.dll");
if (comctl32 == NULL)
die("loading comctl32.dll");
f = GetProcAddress(comctl32, "InitCommonControlsEx");
if (f == NULL)
die("loading InitCommonControlsEx()");
ficc = (BOOL (*WINAPI)(const LPINITCOMMONCONTROLSEX)) f;
if ((*ficc)(&icc) == FALSE)
die("initializing Common Controls (comctl32.dll)");
}
I've decided that th easiest way to handle tooltips in my table control is to handle the initial delay myself, create the tooltip dynamically, and then destroy it when it's not needed anymore. My problem is that when I use TTM_POPUP to show the tooltip, it fades out instantly and comes back again some time later. I'd rather have it fade in and stay there once.
My hypothesis is that the tooltip is handling the tooltip delay itself, and fading the existing tip out and back in. I'm not sure what to do about that. Should I just set the delay time to 0 and expect it to work (without worrying about TTM_POPUP at all)? Or is there a better way? Or am I wrong?
The program below demonstrates what's going on. Simply hover the mouse over the window and you should see it.
Tested on Windows XP and Windows 7. This is strictly with Common Controls 6.
EDIT Okay, I tried adding a TTM_SETDELAYTIME to set the initial time to 0 (not reflected in the program below). It didn't work. If I keep the TTM_POPUP afterward, it just fades out even faster than usual (which proves my hypothesis, maybe). If I remove the TTM_POPUP, it doesn't show up until I move the mouse anyway. So is there something else I can do to just get my tooltip to pop up?
Thanks!
// 5 april 2015
// based on wintooltipsubclasstest.c 31 march-2 april 2015
#define UNICODE
#define _UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
#define CINTERFACE
// 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 <windowsx.h>
#include <stdio.h>
#include <stdlib.h>
void die(char *why)
{
fprintf(stderr, "error %s: %I32u\n", why, GetLastError());
abort();
}
HWND tooltip = NULL;
HINSTANCE hInstance;
void makeTooltip(HWND hwnd)
{
TOOLINFOW ti;
tooltip = CreateWindowExW(WS_EX_TOOLWINDOW,
TOOLTIPS_CLASSW, L"",
WS_POPUP | TTS_NOPREFIX,
0, 0,
0, 0,
hwnd, NULL, hInstance, NULL);
if (tooltip == NULL)
die("creating tooltip");
ZeroMemory(&ti, sizeof (TOOLINFOW));
ti.cbSize = TTTOOLINFOW_V2_SIZE;
ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS | TTF_TRANSPARENT;
ti.hwnd = hwnd;
ti.uId = (UINT_PTR) hwnd;
ti.hinst = hInstance;
ti.lpszText = L"this is a tooltip! wow!";
if (SendMessageW(tooltip, TTM_ADDTOOL, 0, (LPARAM) (&ti)) == FALSE)
die("setting up tooltip");
}
LPARAM last = 0;
LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_CLOSE:
PostQuitMessage(0);
break;
case WM_MOUSEMOVE:
// cheap way of testing for hovers
if (lParam != last)
SetTimer(hwnd, 1, GetDoubleClickTime(), NULL);
last = lParam;
break;
case WM_TIMER:
if (wParam != 1)
break;
KillTimer(hwnd, 1);
makeTooltip(hwnd);
SendMessage(tooltip, TTM_POPUP, 0, 0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
void initCommonControls(BOOL);
int main(int argc, char *argv[])
{
BOOL comctl5;
WNDCLASSW wc;
HWND mainwin;
MSG msg;
hInstance = GetModuleHandle(NULL);
comctl5 = FALSE;
if (argc > 1)
comctl5 = strcmp(argv[1], "comctl5") == 0;
initCommonControls(comctl5);
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpszClassName = L"mainwin";
wc.lpfnWndProc = wndproc;
wc.hIcon = LoadIconW(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
wc.hInstance = GetModuleHandle(NULL);
if (RegisterClassW(&wc) == 0)
die("registering main window class");
mainwin = CreateWindowExW(0,
L"mainwin", L"Main Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, GetModuleHandle(NULL), NULL);
if (mainwin == NULL)
die("creating main window");
ShowWindow(mainwin, SW_SHOWDEFAULT);
UpdateWindow(mainwin);
while (GetMessage(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
// MSDN doesn't list a constant that only includes tooltips but says this and a few others do
#define wantedICCClasses (ICC_BAR_CLASSES)
static ULONG_PTR comctlManifestCookie;
static HMODULE comctl32;
// note that this is an 8-bit character string we're writing; see the encoding clause
static const char manifest[] = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n<assemblyIdentity\n version=\"1.0.0.0\"\n processorArchitecture=\"*\"\n name=\"CompanyName.ProductName.YourApplication\"\n type=\"win32\"\n/>\n<description>Your application description here.</description>\n<dependency>\n <dependentAssembly>\n <assemblyIdentity\n type=\"win32\"\n name=\"Microsoft.Windows.Common-Controls\"\n version=\"6.0.0.0\"\n processorArchitecture=\"*\"\n publicKeyToken=\"6595b64144ccf1df\"\n language=\"*\"\n />\n </dependentAssembly>\n</dependency>\n</assembly>\n";
void initCommonControls(BOOL comctl5)
{
WCHAR temppath[MAX_PATH + 1];
WCHAR filename[MAX_PATH + 1];
HANDLE file;
DWORD nExpected, nGot;
ACTCTX actctx;
HANDLE ac;
INITCOMMONCONTROLSEX icc;
FARPROC f;
// this is listed as WINAPI in both Microsoft's and MinGW's headers, but not on MSDN for some reason
BOOL (*WINAPI ficc)(const LPINITCOMMONCONTROLSEX);
if (!comctl5) {
if (GetTempPathW(MAX_PATH + 1, temppath) == 0)
die("getting temporary path for writing manifest file");
if (GetTempFileNameW(temppath, L"manifest", 0, filename) == 0)
die("getting temporary filename for writing manifest file");
file = CreateFileW(filename, GENERIC_WRITE,
0, // don't share while writing
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (file == NULL)
die("creating manifest file");
nExpected = (sizeof manifest / sizeof manifest[0]) - 1; // - 1 to omit the terminating null character)
if (WriteFile(file, manifest, nExpected, &nGot, NULL) == 0)
die("writing manifest file");
if (nGot != nExpected)
die("short write to manifest file");
if (CloseHandle(file) == 0)
die("closing manifest file (this IS an error here because not doing so will prevent Windows from being able to use the manifest file in an activation context)");
ZeroMemory(&actctx, sizeof (ACTCTX));
actctx.cbSize = sizeof (ACTCTX);
actctx.dwFlags = ACTCTX_FLAG_SET_PROCESS_DEFAULT;
actctx.lpSource = filename;
ac = CreateActCtx(&actctx);
if (ac == INVALID_HANDLE_VALUE)
die("creating activation context for synthesized manifest file");
if (ActivateActCtx(ac, &comctlManifestCookie) == FALSE)
die("activating activation context for synthesized manifest file");
}
ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX));
icc.dwSize = sizeof (INITCOMMONCONTROLSEX);
icc.dwICC = wantedICCClasses;
comctl32 = LoadLibraryW(L"comctl32.dll");
if (comctl32 == NULL)
die("loading comctl32.dll");
f = GetProcAddress(comctl32, "InitCommonControlsEx");
if (f == NULL)
die("loading InitCommonControlsEx()");
ficc = (BOOL (*WINAPI)(const LPINITCOMMONCONTROLSEX)) f;
if ((*ficc)(&icc) == FALSE)
die("initializing Common Controls (comctl32.dll)");
}
The program below creates a window with a Common Controls tooltip that subclasses the window. When I get a WM_DESTROY, I instinctively decide to destroy the tooltip, since WM_DESTROY comes before child (and I assume owned as well) windows are destroyed, but DestroyWindow() fails, returning last error 1400 (ERROR_INVALID_WINDOW_HANDLE). (On wine, it returns 5 (ERROR_ACCESS_DENIED) instead. I'm assuming this is a bug with wine either setting the last error incorrectly or not at all; I can file one later.)
I have tested on Windows XP 32-bit andWindows 7 64-bit, with both Common Controls 5 and 6 (a command line "flag" is used below), and wine on Ubuntu 14.10 and 15.04 beta.
If the tooltip is destroying itself, what can I do to handle it? Should I just rely on that behavior? Or should I subclass the tooltip and handle WM_DESTROY myself? (Will two windows subclassing each other cause problems?)
Thanks.
// 31 march-2 april 2015
#define UNICODE
#define _UNICODE
#define STRICT
#define STRICT_TYPED_ITEMIDS
#define CINTERFACE
// 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 <windowsx.h>
#include <stdio.h>
#include <stdlib.h>
void die(char *why)
{
fprintf(stderr, "error %s: %I32u\n", why, GetLastError());
abort();
}
HWND tooltip = NULL;
LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CREATESTRUCT *cs = (CREATESTRUCT *) lParam;
TOOLINFOW ti;
switch (uMsg) {
case WM_CREATE:
tooltip = CreateWindowExW(WS_EX_TOOLWINDOW,
TOOLTIPS_CLASSW, L"",
WS_POPUP | TTS_NOPREFIX,
0, 0,
0, 0,
hwnd, NULL, cs->hInstance, NULL);
if (tooltip == NULL)
die("creating tooltip");
ZeroMemory(&ti, sizeof (TOOLINFOW));
ti.cbSize = TTTOOLINFOW_V2_SIZE;
ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS | TTF_TRANSPARENT;
ti.hwnd = hwnd;
ti.uId = (UINT_PTR) hwnd;
ti.hinst = cs->hInstance;
ti.lpszText = L"this is a tooltip! wow!";
if (SendMessageW(tooltip, TTM_ADDTOOL, 0, (LPARAM) (&ti)) == FALSE)
die("setting up tooltip");
break;
case WM_CLOSE:
PostQuitMessage(0);
break;
case WM_DESTROY:
if (DestroyWindow(tooltip) == 0)
die("deleting tooltips");
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
void initCommonControls(BOOL);
int main(int argc, char *argv[])
{
BOOL comctl5;
WNDCLASSW wc;
HWND mainwin;
MSG msg;
comctl5 = FALSE;
if (argc > 1)
comctl5 = strcmp(argv[1], "comctl5") == 0;
initCommonControls(comctl5);
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpszClassName = L"mainwin";
wc.lpfnWndProc = wndproc;
wc.hIcon = LoadIconW(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
wc.hInstance = GetModuleHandle(NULL);
if (RegisterClassW(&wc) == 0)
die("registering main window class");
mainwin = CreateWindowExW(0,
L"mainwin", L"Main Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, GetModuleHandle(NULL), NULL);
if (mainwin == NULL)
die("creating main window");
ShowWindow(mainwin, SW_SHOWDEFAULT);
UpdateWindow(mainwin);
while (GetMessage(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
// MSDN doesn't list a constant that only includes tooltips but says this and a few others do
#define wantedICCClasses (ICC_BAR_CLASSES)
static ULONG_PTR comctlManifestCookie;
static HMODULE comctl32;
// note that this is an 8-bit character string we're writing; see the encoding clause
static const char manifest[] = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n<assemblyIdentity\n version=\"1.0.0.0\"\n processorArchitecture=\"*\"\n name=\"CompanyName.ProductName.YourApplication\"\n type=\"win32\"\n/>\n<description>Your application description here.</description>\n<dependency>\n <dependentAssembly>\n <assemblyIdentity\n type=\"win32\"\n name=\"Microsoft.Windows.Common-Controls\"\n version=\"6.0.0.0\"\n processorArchitecture=\"*\"\n publicKeyToken=\"6595b64144ccf1df\"\n language=\"*\"\n />\n </dependentAssembly>\n</dependency>\n</assembly>\n";
void initCommonControls(BOOL comctl5)
{
WCHAR temppath[MAX_PATH + 1];
WCHAR filename[MAX_PATH + 1];
HANDLE file;
DWORD nExpected, nGot;
ACTCTX actctx;
HANDLE ac;
INITCOMMONCONTROLSEX icc;
FARPROC f;
// this is listed as WINAPI in both Microsoft's and MinGW's headers, but not on MSDN for some reason
BOOL (*WINAPI ficc)(const LPINITCOMMONCONTROLSEX);
if (!comctl5) {
if (GetTempPathW(MAX_PATH + 1, temppath) == 0)
die("getting temporary path for writing manifest file");
if (GetTempFileNameW(temppath, L"manifest", 0, filename) == 0)
die("getting temporary filename for writing manifest file");
file = CreateFileW(filename, GENERIC_WRITE,
0, // don't share while writing
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (file == NULL)
die("creating manifest file");
nExpected = (sizeof manifest / sizeof manifest[0]) - 1; // - 1 to omit the terminating null character)
if (WriteFile(file, manifest, nExpected, &nGot, NULL) == 0)
die("writing manifest file");
if (nGot != nExpected)
die("short write to manifest file");
if (CloseHandle(file) == 0)
die("closing manifest file (this IS an error here because not doing so will prevent Windows from being able to use the manifest file in an activation context)");
ZeroMemory(&actctx, sizeof (ACTCTX));
actctx.cbSize = sizeof (ACTCTX);
actctx.dwFlags = ACTCTX_FLAG_SET_PROCESS_DEFAULT;
actctx.lpSource = filename;
ac = CreateActCtx(&actctx);
if (ac == INVALID_HANDLE_VALUE)
die("creating activation context for synthesized manifest file");
if (ActivateActCtx(ac, &comctlManifestCookie) == FALSE)
die("activating activation context for synthesized manifest file");
}
ZeroMemory(&icc, sizeof (INITCOMMONCONTROLSEX));
icc.dwSize = sizeof (INITCOMMONCONTROLSEX);
icc.dwICC = wantedICCClasses;
comctl32 = LoadLibraryW(L"comctl32.dll");
if (comctl32 == NULL)
die("loading comctl32.dll");
f = GetProcAddress(comctl32, "InitCommonControlsEx");
if (f == NULL)
die("loading InitCommonControlsEx()");
ficc = (BOOL (*WINAPI)(const LPINITCOMMONCONTROLSEX)) f;
if ((*ficc)(&icc) == FALSE)
die("initializing Common Controls (comctl32.dll)");
}
tooltip have not WS_CHILD style. all this kind of windows is full destroyed before owner windows receive WM_DESTROY. as result DestroyWindow(tooltip) and return error - ERROR_INVALID_WINDOW_HANDLE