Handling drag and drop files in a running Windows console application
Asked Answered
P

3

7

First, to clarify, I am not asking how to drag-and-drop a file onto an exe's icon. I want to know how to handle drag and drop onto an already running win32 console application. I'm also not asking how to handle drag and drop inside of WinMain based applications through the Windows message pump. I want to do this inside of a program with the entry point int main() that doesn't have a WndProc (yet) or anything.

That said, I'm wondering if my goal is achievable (and hoping that it is).

I have a server application that is running within a console window. Due to a large codebase and a lot of weird coupling, it is an 'output only' console for all intensive purposes. Within it though, I can still handle things like key presses, as I have an update loop ticking. I'd like to be able to drag and drop files full of commands (which use a custom syntax) onto my running application and have it process them.

Is this possible to do? I was thinking that potentially I could get a pointer to the HWND of the console (which hopefully is a thing?), and then maybe subclass that window to use a custom WndProc to listen for the WM_DROPFILES message.

I've never really tried to set up handling of windows messages in an int main() program instead of a WinMain program, but I'm hoping it's somehow possible.

Any help would be greatly appreciated! Weird solutions are fine.

Protrude answered 25/1, 2014 at 1:6 Comment(0)
S
7

AFAIK, a console window does not support drag&drop by default. You can always create your own separate popup window with its own message loop so the user has something to drag items onto.

To use drag&drop on the console window itself, try using GetConsoleWindow() to get the console HWND, then either:

  1. subclass the HWND using SetWindowLong/Ptr() or SetWindowSubClass(), then register the HWND using DragAcceptFiles() to start receiving WM_DROPFILES messages. Be sure to call DragAcceptFiles() again to stop receiving the messages and then unhook your subclass before exiting the app.

  2. implement the IDropTarget interface and then register the HWND using RegisterDragDrop() to start receiving notifications. Be sure to call RevokeDragDrop() before exiting the app.

WM_DROPFILES is easier to code for, but IDropTarget is more flexible as it handles virtual items as well as physical files.

Sulphanilamide answered 25/1, 2014 at 1:29 Comment(6)
Very interesting, thank you! I'm gonna leave it open for a bit longer to see if any real weird solutions come though, but this is a solid response. I didn't know about the differences between WM_DROPFILES and the IDropTarget. The server is already using some MFC somewhere so this might be viable.Protrude
Hmm, no, the console window is not actually owned by the console program. Subclassing it is therefore a complete dead end. Covered by this question as well.Slaver
If the program is the first process running in the console then it does own the console window (GetWindowThreadProcessId() returns the same IDs as GetCurrentProcessId() and GetCurrentThreadId()) but oddly subclassing fails (SetWindowLong/Ptr() returns error 5). If the program is not the first process in the console then it will not own the console window, though it will perform I/O using the existing console window.Sulphanilamide
I even tried SetWindowsHookEx(), and although it was able to install message hooks, it did not detect the WM_DROPFILES message. I did find that the console window has the WS_EX_ACCEPTFILES style enabled by default, so the console must have its own internal drag&drop handler, especially since I can drag a file over the window and the cursor changes to show a drop is allowed.Sulphanilamide
If you drop a file on a cmd.exe console the file path is pasted into the command line so I'd say that's what the drop handler is for. I don't think there's a way to hook into this however. Although to a running console process it might just look like the user typed the name of a file, so maybe that is enough for what the OP wants to achieve.Forras
Thanks for the investigation. It looks like I'll have to shut down this investigation for the time being... Creating a secondary listener window may be my only hope.Protrude
M
2
#include <vector>
#include <string>
#include <iostream>
#include <conio.h>

int main()
{
    std::cout << "Please drop files and press [Enter] when done ...\n";

    std::vector< std::string > files;

    for( int ch = _getch(); ch != '\r'; ch = _getch() ) {

        std::string file_name;

        if( ch == '\"' ) {  // path containing spaces. read til next '"' ...

            while( ( ch = _getch() ) != '\"' )
                file_name += ch;

        } else { // path not containing spaces. read as long as chars are coming rapidly.

            file_name += ch;

            while( _kbhit() )
                file_name += _getch();
        }

        files.push_back( file_name );
    }

    std::cout << "You dropped these files:\n";

    for( auto & i : files )
        std::cout << i << '\n';
}
Maddox answered 25/8, 2014 at 11:21 Comment(2)
Add some description so the other can understand your code and answer for this question.Electroscope
@Charlie, because the Win32 console window implementation has this built-in drop handler routine -- completely opaquely to user code. (Note: this is not just some "default" behavior in the sense that there's no way to customize it, AFAIK. E.g. window subclassing won't work, because console processes can't change the window procedure that runs their window (basically because it's managed by the console host process ("CSRSS", or some of its relatives), not the application itself).)Meras
D
-1

After spending about two days’ worth of researching about this subject, I was not able to find a single reliable resource describing the console window’s message loop. I found some unreliable ones (they were AI generated) describing vaguely that you could tap into the console window’s message loop, but then precise information on how to do this is lacking everywhere. Anyway, I finally gave up completely when the following code was not able to detect windows messages directed to the console window. I must give credit to Sz about the insularity of the "CSRSS" process.

/*
  This is the final work which shows whether it is possible
  or not to track messages sent to the command window. All
  tests have shown that this is not possible.
  15-04-2024
*/

#include <windows.h>

LRESULT CALLBACK WndProc(HWND hWnd, UINT messg, WPARAM wParam, LPARAM lParam);

char textString[] = "Click this area to create a Console Window";
HWND hConsoleWindow = (HWND)1; // needs to be global
HANDLE ohandle, ihandle; // needs to be global                              
LPDWORD lpThreadId; // needs to be global (!)

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{
    HWND hWnd;
    MSG lpMsg;
    WNDCLASS wndclss;
    TCHAR szBuffer[2056];

    if(!hPrevInst)
    {
        wndclss.lpszClassName = "Console Message Test";
        wndclss.hInstance = hInst;
        wndclss.lpfnWndProc = WndProc;
        wndclss.hCursor = LoadCursor(NULL, IDC_ARROW);
        wndclss.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wndclss.lpszMenuName = NULL;
        wndclss.hbrBackground = GetStockObject(WHITE_BRUSH);
        wndclss.style = 0;
        wndclss.cbClsExtra = 0;
        wndclss.cbWndExtra = 0;
        if(!RegisterClass(&wndclss)) return FALSE;
    }

    
    hWnd = CreateWindow("Console Message Test", "Console Message Test",
            WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 200, NULL,
            NULL, hInst, NULL);

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    while(GetMessage(&lpMsg, NULL, 0, 0)) // This is the message loop
    {
        TranslateMessage(&lpMsg);
        //hConsoleWindow = FindWindow("ConsoleWindowClass", NULL); // activate this line facultatively, as a test. Might have an effect if no other conole window is open.
            /* the above line will terminate the program if no console is present. All this means is that a NULL was returned. */
                if(lpMsg.hwnd == hConsoleWindow) return( lpMsg.wParam);
                    /* (the above line) if any message reaches the console window through this loop, the program closes! */
                //if(lpMsg.message == WM_DROPFILES) return( lpMsg.wParam); // activate this line facultatively (as a test)
            /* the above line will make the program close if a file is dropped into the white area */
        DispatchMessage(&lpMsg);
    }
    return(lpMsg.wParam);
}


DWORD WINAPI ConsoleThread(LPVOID Param)
{
    DWORD dwCount, dwMsgLen;
    TCHAR szBuffer[2056];
    for(;;){
            ReadConsole(ihandle, szBuffer, sizeof(szBuffer) - 2, &dwCount, NULL);
            WriteConsole(ohandle, "You typed: ", 11, &dwMsgLen, NULL);
            WriteConsole(ohandle, szBuffer, dwCount, &dwMsgLen, NULL);
    }
}


// Main Window Procedure
LRESULT CALLBACK WndProc(HWND hWnd, UINT messg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT pstruct;
        static HANDLE hThread = NULL;

    switch(messg)
    {
        case WM_PAINT:
            hdc = BeginPaint(hWnd, &pstruct);
            TextOut(hdc, 0, 0, textString, (sizeof(textString) - 1));
            EndPaint(hWnd, &pstruct);
        break;
        
        case WM_LBUTTONDOWN:
            AllocConsole();
                    hConsoleWindow = FindWindow("ConsoleWindowClass", NULL);
                    if(hConsoleWindow) Beep(1000, 250); // high note when console detected

                    //DragAcceptFiles(hConsoleWindow, TRUE); // this line seems to have no effect
                    DragAcceptFiles(hWnd,TRUE); /* this line makes the white area accept dragged
                objects once it is clicked */
 
            if(hThread == NULL) {
                ohandle = GetStdHandle(STD_OUTPUT_HANDLE);
                ihandle = GetStdHandle(STD_INPUT_HANDLE);
                hThread = CreateThread(NULL, 0, ConsoleThread, NULL, 0, lpThreadId);
            }
        break;
        
        case WM_DESTROY:
            PostQuitMessage(0);
        break;

        case WM_DROPFILES:
             Beep(500, 250); // low note when file drop detected
        break;
    
        default:
        return(DefWindowProc(hWnd, messg, wParam, lParam));
    }

    return(0);
}

PS I take the OP to refer to XP/Windows Server 2003 era Windows OSes’ command line. Later Windows OSes are different (i.e. multiple items dropped are accepted by default in a console program.)

Dim answered 17/4 at 17:31 Comment(5)
Gotta give credit for making sure to support 16-bit Windows. In 2024. Other than that I don't see anything useful here.Madness
This is veritable 32-bit code! 16-bit code used to start with a 'PASCAL' type qualifier. I don't know why people would deem my code unuseful. I don't see any other example actually testing for messages going to the console here.Dim
PASCAL is not a type qualifier. It's a preprocessor symbol that describes the calling convention. PASCAL is obsolete but expands to the same thing as WINAPI. What makes the code 16-bit Windows compatible is the hPrevInst test. This argument is always NULL starting with 32-bit Windows. There's a lot more wrong with the code, crucially the assumption that messages designated for a different thread would somehow magically show up in this program's primary thread. The conclusions are thus meaningless.Madness
When a child thread creates a new window then yes, that thread must contain a message loop and that window's messages are not seen by the parent thread. But that's not the case here (my thread doesn't contain a message loop). In fact this is the reason why you can't tap into the console window's message loop.Dim
I don't know what a "child thread" is supposed to be. I do know that lpMsg.hwnd == hConsoleWindow is never true. Why do you assume that messages were to magically surface in unrelated threads? This does not happen. "In fact this is the reason why you can't tap into the console window's message loop." - It certainly isn't. This is all made up. If you must intercept or monitor messages for the console window install an appropriate hook.Madness

© 2022 - 2024 — McMap. All rights reserved.