C++ when pressing close: minimize to system tray and keep running - c++

I have an app in c++ for windows, which should minimize the window of the command line when the user presses the close button. It shouldn't be in the taskbar anymore and have an icon in the system tray.
What I mean is: when user presses close button, the program should only "hide" like i described.
I can only manage to make the program have an icon in the tray while running, but can't make it stay running when x is pressed
Thanks for help!
this is my code so far:
#include <iostream>
#include <Windows.h> // needed for console window and system tray functionality
// global variables
NOTIFYICONDATA trayIcon; // structure for the tray icon
HWND hwnd = GetConsoleWindow(); // handle to the console window
// function prototypes
void minimizeToTray(); // function to minimize the console window to the system tray
int main()
{
// set up the tray icon
trayIcon.cbSize = sizeof(NOTIFYICONDATA);
trayIcon.hWnd = hwnd;
trayIcon.uID = 1;
trayIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
trayIcon.hIcon = (HICON)LoadImage(NULL, "icon.ico", IMAGE_ICON, 0, 0, LR_LOADFROMFILE); // specify the icon file
trayIcon.uCallbackMessage = WM_USER + 1; // message identifier for tray icon clicks
trayIcon.uVersion = NOTIFYICON_VERSION_4;
strcpy_s(trayIcon.szTip, "Program Running");
// add the tray icon to the system tray
Shell_NotifyIcon(NIM_ADD, &trayIcon);
std::cout << "Program running..." << std::endl;
// set up a message loop to handle tray icon clicks and window messages
MSG msg;
while (true) // infinite loop
{
// check for messages
while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE))
{
// if the user clicks the close button, minimize the window to the tray
if (msg.message == WM_CLOSE)
{
minimizeToTray();
continue; // skip the rest of the message loop
}
// if the user clicks the tray icon, restore the window
if (msg.message == WM_USER + 1)
{
ShowWindow(hwnd, SW_RESTORE);
}
// pass the message to the default window procedure
DispatchMessage(&msg);
}
// do other tasks here
}
// remove the tray icon before exiting the program
Shell_NotifyIcon(NIM_DELETE, &trayIcon);
return 0;
}
// function to minimize the console window to the system tray
void minimizeToTray()
{
// hide the console window
ShowWindow(hwnd, SW_HIDE);
// update the tray icon
trayIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
Shell_NotifyIcon(NIM_MODIFY, &trayIcon);
}
When the user "closes" the window it should just hide and not close entirely, like ms teams or discord do

After testing your code, it definitely fails. It cannot get the message WM_CLOSE. Just as Hans Passant said, you can use SetConsoleCtrlHandler() to attached to the console receive the signal.
Here is my code. It runs well.
It used ShellExecuteW() API to restart the program to implement minimization.
Setting SW_MINIMIZE, it will minimize in the task bar.
Setting SW_HIDE, it will minimize in the system tray. But it cannot be opened again.
#include <Windows.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <iostream>
NOTIFYICONDATA trayIcon;
HWND hwnd = GetConsoleWindow();
BOOL WINAPI ConsoleHandler(DWORD CEvent)
{
char mesg[128];
switch (CEvent)
{
case CTRL_C_EVENT:
MessageBox(NULL,
L"CTRL+C received!", L"CEvent", MB_OK);
break;
case CTRL_BREAK_EVENT:
MessageBox(NULL,
L"CTRL+BREAK received!", L"CEvent", MB_OK);
break;
case CTRL_CLOSE_EVENT:
ShellExecuteW(NULL, L"open", L"yourexe.exe", NULL, NULL, SW_MINIMIZE);
break;
case CTRL_LOGOFF_EVENT:
MessageBox(NULL,
L"User is logging off!", L"CEvent", MB_OK);
break;
case CTRL_SHUTDOWN_EVENT:
MessageBox(NULL,
L"User is logging off!", L"CEvent", MB_OK);
break;
}
return TRUE;
}
int main()
{
trayIcon.cbSize = sizeof(NOTIFYICONDATA);
trayIcon.hWnd = hwnd;
trayIcon.uID = 1;
trayIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
trayIcon.hIcon = (HICON)LoadImage(NULL, L"icon1.ico", IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
trayIcon.uCallbackMessage = WM_USER + 1;
trayIcon.uVersion = NOTIFYICON_VERSION_4;
Shell_NotifyIcon(NIM_ADD, &trayIcon);
std::cout << "Program running..." << std::endl;
MSG msg;
if (SetConsoleCtrlHandler(
(PHANDLER_ROUTINE)ConsoleHandler, TRUE) == FALSE)
{
printf("Unable to install handler!\n");
return -1;
}
while (true)
{
while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE))
{
if (msg.message == WM_CLOSE)
{
continue;
}
if (msg.message == WM_USER + 1)
{
ShowWindow(hwnd, SW_RESTORE);
}
DispatchMessage(&msg);
}
}
Shell_NotifyIcon(NIM_DELETE, &trayIcon);
return 0;
}

Related

Handling the hotkey for menu item

I have created a new menu item "Extra" and add it to the window's main menu.
HMENU menu = GetMenu(hWnd);
HMENU popup = CreatePopupMenu();
AppendMenu(menu, MF_STRING | MF_POPUP, (UINT)popup, L"Extra");
SetMenu(hWnd, popup);
Next I inserted new item to "Extra" item with hotkey:
#define IDM_ITEM1 12310
and:
MENUITEMINFOW mmi;
mmi.cbSize = sizeof(MENUITEMINFOW);
mmi.fMask = MIIM_STRING | MIIM_ID;
mmi.fType = MFT_STRING;
mmi.dwTypeData = L"First item\tCtrl+N";
mmi.wID = IDM_ITEM1;
InsertMenuItemW(popup, 1, true, &mmi);
And handle "First item" click:
case WM_COMMAND:
{
switch(LOWORD(wParam))
{
case IDM_ITEM1:
{
MessageBox(0, L"First", L"", MB_OK);
break;
}
}
break;
}
And now when I click on "First item" a message box is appeared.
But when I press Ctrl+N hotkey - nothing happens. Why? How to fix this (without using accelerator resources)?
without using accelerator resources ?
You need to create an array of ACCEL and then call the CreateAcceleratorTable function:
ACCEL s_accel[2] = {{FCONTROL | FVIRTKEY, TEXT('C'), IDM_COLOR},
{FCONTROL | FVIRTKEY, TEXT('Q'), IDM_QUIT}};
HACCEL h_accel = CreateAcceleratorTable(s_accel, 2);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(hwnd, h_accel, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
DestroyAcceleratorTable(h_accel);

How to handle WM_ENDSESSION in a console app

I wrote a program to register mouse events, and I want it to terminate when the computer is shutdown (and then perform a flush and a final print).
I tried with a CtrlHandler, but it works only with Ctrl-C and not when the system is shutdown, because I am using a Win32 library, according to MSDN:
If a console application loads the gdi32.dll or user32.dll library, the HandlerRoutine function that you specify when you call SetConsoleCtrlHandler does not get called for the CTRL_LOGOFF_EVENT and CTRL_SHUTDOWN_EVENT events. The operating system recognizes processes that load gdi32.dll or user32.dll as Windows applications rather than console applications. This behavior also occurs for console applications that do not call functions in gdi32.dll or user32.dll directly, but do call functions such as Shell functions that do in turn call functions in gdi32.dll or user32.dll.
To receive events when a user signs out or the device shuts down in these circumstances, create a hidden window in your console application, and then handle the WM_QUERYENDSESSION and WM_ENDSESSION window messages that the hidden window receives. You can create a hidden window by calling the CreateWindowEx method with the dwExStyle parameter set to 0.
So, first I have to create a hidden window, and then I have to intercept the the WM_ENDSESSION message. But how?
I tried to read some examples, but I can't figure out how to do this.
Here is my code:
BOOL WINAPI CtrlHandler(DWORD fdwCtrlType)
{
switch (fdwCtrlType)
{
// Handle the CTRL-C signal.
/*case CTRL_C_EVENT:
printf("Ctrl-C event\n\n");
Beep(750, 300);
return FALSE; //TRUE
// CTRL-CLOSE: confirm that the user wants to exit.
case CTRL_CLOSE_EVENT:
Beep(600, 200);
printf("Ctrl-Close event\n\n");
return FALSE; //TRUE
// Pass other signals to the next handler.
case CTRL_BREAK_EVENT:
Beep(900, 200);
printf("Ctrl-Break event\n\n");
return FALSE;
*/case CTRL_LOGOFF_EVENT:
Beep(1000, 200);
printf("Ctrl-Logoff event\n\n");
myfile << "totale :" << tot;
myfile.flush();
myfile.close();
return TRUE; //FALSE
case CTRL_SHUTDOWN_EVENT:
Beep(750, 500);
printf("Ctrl-Shutdown event\n\n");
myfile << "totale :" << tot;
myfile.flush();
myfile.close();
return TRUE; //FALSE
default:
return FALSE;
}
}
int main(){
if (SetConsoleCtrlHandler(CtrlHandler, TRUE))
{
printf("\nThe Control Handler is installed.\n");
for(;;)
{
code that print the mouse event(........)
}
}
else
{
printf("\nERROR: Could not set control handler");
return 1;
}
return 0;
}
You can just create a hidden GUI window in console application and handle WM_ENDSESSION in the window procedure as shown below
#include <Windows.h>
HWND g_hidden_window = nullptr;
LRESULT CALLBACK wnd_proc(HWND, UINT, WPARAM, LPARAM);
// Main entry point of your app
int main() {
HMODULE current_instance = ::GetModuleHandle(L"");
// Register the window class
WNDCLASSEX window_class_ex = { 0 };
window_class_ex.cbSize = sizeof(WNDCLASSEX);
window_class_ex.lpfnWndProc = wnd_proc;
window_class_ex.lpszClassName = L"Foo";
window_class_ex.hInstance = current_instance;
if (!::RegisterClassEx(&window_class_ex)) {
return 1;
}
// Create an overlapped window
g_hidden_window = ::CreateWindow(
L"Foo",
L"",
WS_OVERLAPPED,
0, 0, 0, 0,
nullptr,
nullptr,
current_instance,
0);
if (!g_hidden_window) {
return 1;
}
MSG message;
// Main message loop
while (::GetMessage(&message, nullptr, 0, 0)) {
::DispatchMessage(&message);
}
}
Now, in your main window procedure, you should handle WM_ENDSESSION. In your case, I see no reason to handle WM_QUERYENDSESSION. You should also handle WM_CLOSE and/or WM_DESTROY to quit the main message loop:
// Main window procedure
LRESULT CALLBACK wnd_proc(HWND window_handle, UINT window_message, WPARAM wparam, LPARAM lparam) {
switch (window_message) {
case WM_ENDSESSION:
if(wparam) {
// According to MSDN this value will be 1 when the system is about to shut down: https://learn.microsoft.com/en-us/windows/win32/shutdown/wm-endsession
// Invoke your function here
CtrlHandler(CTRL_SHUTDOWN_EVENT);
}
break;
case WM_CLOSE:
DestroyWindow(window_handle);
break;
case WM_DESTROY:
::PostQuitMessage(0);
break;
default:
return ::DefWindowProc(window_handle, window_message, wparam, lparam);
}
return 0;
}
To gracefully shut down the app, you will have to break that message loop. To do so, you will have to send a WM_CLOSE message:
SendMessage(g_hidden_window, WM_CLOSE, 0, 0);
Or, explicitly destroy the window by calling:
DestroyWindow(g_hidden_window);
Let me know if it works. I have not tested it because I'm on a Mac right now, but it should work.

Winapi how to hide/show a third-party program window?

I'm trying to hide/show a third-party program (e.g. Discord):
#include <windows.h>
void show_hide(HWND window)
{
if (IsWindowVisible(window) == TRUE) {
ShowWindow(window, SW_HIDE);
} else {
ShowWindow(window, SW_SHOW);
}
}
BOOL CALLBACK enum_windows_cb(HWND window, const LPARAM lParam)
{
const char* target = "Discord";
char title[255];
if (GetWindowText(window, title, sizeof(title))) {
if (strcmpi(title, target) == 0) {
show_hide(window);
return FALSE;
}
}
return TRUE;
}
int main(int argc, char *argv[])
{
EnumWindows(&enum_windows_cb, 0);
return 0;
}
It's work fine, but if I hide a third-party program by click on close button, my program shows frozen window, until I press the tray icon.
Video demonstration:
https://www.youtube.com/watch?v=P-_Fi-Dew1w
I also wrote message log through microsoft spy++ https://pastebin.com/YwGt88yh
It recorded three actions: click on close button hides program, click on tray icon shows program, and my program tries to show window.
I also tried to add these functions one by one after ShowWindow:
SetForegroundWindow(window);
SendMessage(window, WM_SYSCOMMAND, SC_RESTORE, 0);
UpdateWindow(window);
RedrawWindow(window, 0, nil, RDW_INVALIDATE or RDW_ALLCHILDREN or RDW_UPDATENOW or RDW_FRAME);
SendMessage(window, WM_ACTIVATEAPP, TRUE, GetWindowThreadProcessId(window, 0));
But it did not help. What did I do wrong?
I use windows 10.

Maximize/Minimize window from another thread

I'm trying to find out the correct way to minimize/maximize a window owned by another thread. My target window can be fullscreen or not (i should be able to minimize and maximize it regardless of its state). I've tried various combinations of ShowWindow SW_MINIMIZE, SW_MAXIMIZE, SW_FORCEMINIMIZE etc... but the only result i've been able to achieve was restoring it (maximizing) when it was minimized AND fullscreen with ShowWindow(hWnd, SW_RESTORE).
Here it is the code i'm using to retrieve my handle:
#include <Windows.h>
#include <iostream>
// I'm a console application
int main(int argc, char* argv[]) {
HWND hWnd = FindWindow(TEXT("MyWindowClass"), NULL);
if(IsWindow(hWnd)) {
std::cout << "Window found!" << std::endl;
SetForegroundWindow(hWnd); // I'll give focus to my window. This is always working.
if(IsIconic(hWnd))
ShowWindow(hWnd, SW_RESTORE); // This is working only if the window is minimized while in fullscreen mode
Sleep(3000);
ShowWindow(hWnd, SW_MINIMIZE); // Not working. SW_FORCEMINIMIZE, SW_HIDE etc are not working either.
}
return 0;
}
After struggling for a whole day I've found a solution that works for both minimizing and maximizing the window regardless of its state: Post/SendMessage.
To maximize it:
PostMessage(hWnd, WM_SYSCOMMAND, SC_RESTORE, 0);
To minimize it:
PostMessage(hWnd, WM_SYSCOMMAND, SC_MINIMIZE, 0);
Try ShowWindow first, and then call SetForegroundWindow:
void show_and_setforeground(HWND hwnd)
{
WINDOWPLACEMENT place;
memset(&place, 0, sizeof(WINDOWPLACEMENT));
place.length = sizeof(WINDOWPLACEMENT);
GetWindowPlacement(hwnd, &place);
switch (place.showCmd)
{
case SW_SHOWMAXIMIZED:
ShowWindow(hwnd, SW_SHOWMAXIMIZED);
break;
case SW_SHOWMINIMIZED:
ShowWindow(hwnd, SW_RESTORE);
break;
default:
ShowWindow(hwnd, SW_NORMAL);
break;
}
SetForegroundWindow(hwnd);
}
In addition to IsWindow(hWnd) you may want to use IsWindowVisible(hWnd) because some programs use invisible windows which are not meant to be used.
hwnd = FindWindow(TEXT("MyWindowClass"), NULL);
if (IsWindow(hwnd))
{
if(IsWindowVisible(hwnd))//optional
{
show_and_setforeground(hwnd);
...
}
}

Browse For Folder dialog window handle C++

How to get the handle HWND of the dialog which user open when clicking on a button.
I'm using Spy++ to find the window class and tittle, but it says that no such window is found. And how then to get the handle of that dialog in C++ using Win API ?
I hope that I will be able to do that using simple functions as FindWindow, GetParent, any WIN APi function. I do not like to inject something or load DLL. Thanks
UPDATE:
the folder browser dialog is opened by other program. I want to get it's handle from different program , my program. Thanks.
The closest to want i need is for now the function WindowFromPoint
Accessibility will let you capture window creation events from other processes without DLL injection. You can modify the example to accommodate for the browsing window specifically. Here's an example I made previously to test that is based on the one from the article. Modify it however you wish:
#include <iostream>
#include <windows.h>
void CALLBACK proc(HWINEVENTHOOK hook, DWORD event, HWND hwnd, LONG obj, LONG child, DWORD thr, DWORD time) {
if (hwnd && obj == OBJID_WINDOW && child == CHILDID_SELF) {
switch (event) {
case EVENT_OBJECT_CREATE: {
std::cout << "Window created!\n";
break;
}
case EVENT_OBJECT_DESTROY: {
std::cout << "Window destroyed!\n";
break;
}
}
}
}
int main() {
HWINEVENTHOOK hook = SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_DESTROY, nullptr, proc, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (hook) {
UnhookWinEvent(hook);
}
}