QB64 Phoenix Edition
Day 025: _CLIPBOARD$ - Printable Version

+- QB64 Phoenix Edition (https://qb64phoenix.com/forum)
+-- Forum: Official Links (https://qb64phoenix.com/forum/forumdisplay.php?fid=16)
+--- Forum: Learning Resources and Archives (https://qb64phoenix.com/forum/forumdisplay.php?fid=13)
+---- Forum: Keyword of the Day! (https://qb64phoenix.com/forum/forumdisplay.php?fid=49)
+---- Thread: Day 025: _CLIPBOARD$ (/showthread.php?tid=1238)



Day 025: _CLIPBOARD$ - Pete - 12-06-2022

_CLIPBOARD$ captures the string contents of the operating system clipboard contents.

The neat thing about this platform cross-compatible feature is the ability to use it outside the immediate program. I'll explain...

_CLIPBOARD$ can be used to capture any copied text from any running application. Once captured, the string can be used inside your app, or transferred to another QB64 app. _CLIPBOARD$ is therefore one of a few ways we can communicate with other QB64 programs running simultaneously. Now as exciting as that may be, the use of _CLIPBOARD, for inter-program communications, is somewhat frowned upon by Microsoft. The preferred M$ method is to establish a TCP/IP communications, which will be discussed a bit later along with piping to the clipboard using Windows SHELL command.

Right now, let's take a look at a copying, parse and print text example.

For this demo, start the app and then come back to this page, do Ctrl + A to copy all the tet, and Ctrl + C to copy it to the clipboard. Upon copying, the app will parse and display the clipboard text capture.

Code: (Select All)
$CONSOLE:ONLY
_CLIPBOARD$ = ""
COLOR 15, 1
CLS
PRINT " Copy the Keyword of the Day page..."
DO: _LIMIT 1: LOOP UNTIL LEN(_CLIPBOARD$)
CLS
a$ = _CLIPBOARD$ + CHR$(13)
DO
    x$ = MID$(a$, 1, INSTR(a$, CHR$(13)))
    a$ = LTRIM$(MID$(a$, LEN(x$) + 2))
    x$ = _TRIM$(MID$(x$, 1, INSTR(x$, CHR$(13)) - 1))
    IF LEN(x$) THEN
        IF MID$(x$, 1, 11) = "IP Address:" OR INSTR(x$, "AM") AND LEFT$(x$, 1) = "(" OR INSTR(x$, "PM") AND LEFT$(x$, 1) = "(" THEN pon = 0: spacer = 0
        IF pon THEN
            w = _WIDTH - 2
            IF MID$(a$, 1, 2) = CHR$(13) + CHR$(10) AND last = 0 THEN spacer = 1
            IF w > 0 THEN
                DO
                    t$ = MID$(x$, 1, w)
                    chop = 1
                    IF MID$(x$, w + 1, 1) <> " " THEN ' Now we have to chop it.
                        IF INSTR(x$, " ") > 1 AND INSTR(t$, " ") <> 0 AND LEN(x$) > w THEN
                            t$ = MID$(t$, 1, _INSTRREV(t$, " ") - 1)
                            chop = 2
                        END IF
                    ELSE
                        chop = 2
                    END IF
                    IF w = 1 THEN chop = 1
                    x$ = MID$(x$, LEN(t$) + chop)
                    '-----------------------------------------------------------------------
                    IF LEN(t$) THEN LOCATE , 2: PRINT LTRIM$(t$): IF spacer = 0 THEN last = 0
                    '-----------------------------------------------------------------------
                LOOP UNTIL LEN(t$) AND LEN(LTRIM$(x$)) = 0
                IF spacer = 1 THEN PRINT: spacer = 0
            END IF
        END IF
        IF INSTR(x$, ",") <> 0 AND INSTR(x$, "-") <> 0 THEN
            IF INSTR(x$, "AM") OR INSTR(x$, "PM") AND LEFT$(x$, 1) <> "(" THEN
                pon = 1
            END IF
        END IF
    ELSE

    END IF
LOOP UNTIL a$ = ""

PRINT: PRINT "Click the 'X' in the title bar to close this window."
DO: _LIMIT 1: SLEEP: LOOP

Okay, now how about we have a look at using _CLIPBOARD$ to make a small chat app...

For this demo you will need to save the second app as: "myclip.exe" and run the first app to access it.

Code: (Select All)
_SCREENMOVE 0, 0 ' Set up this host window to the left of your desktop.
WIDTH 60, 25
IF NOT _FILEEXISTS("myclip.exe") THEN PRINT "Cannot find file: "; "myclip.exe. Ending...": END
a$ = "Opening as host." '
PRINT a$: PRINT
SHELL _HIDE "start myclip.exe" ' Open the client window.
_SCREENCLICK 30 * 8, 10
PRINT "Connection established.": PRINT
DO
    _CLIPBOARD$ = ""
    ' Okay, time to input something on the host that will be communicated to the client.
    INPUT "Input a message: "; msg$: PRINT
    _CLIPBOARD$ = msg$
    _KEYCLEAR
    _DELAY 2
    _CLIPBOARD$ = ""
    DO: _LIMIT 5: LOOP UNTIL LEN(_CLIPBOARD$)
    _SCREENCLICK 30 * 8, 10
    PRINT "Reply received: "; _CLIPBOARD$: PRINT
LOOP

Save this as "myclip.exe" but don't run it. Run the first app (it doesn't matter if it's named) to start the chat sequence.
Code: (Select All)
_SCREENMOVE 60 * 8 + 10, 0 ' Set up this client window to the right of host.
WIDTH 60, 25
_CLIPBOARD$ = ""
a$ = "Opening as host." '
PRINT a$: PRINT
PRINT "Connection established.": PRINT
DO
    DO: _LIMIT 5: LOOP UNTIL LEN(_CLIPBOARD$)
    _SCREENCLICK 90 * 8, 10: _DELAY .25
    PRINT "Message received: "; _CLIPBOARD$: PRINT
    ' Okay, time to input something on the client that will be communicated to the host.
    INPUT "Input a message: "; msg$: PRINT
    _CLIPBOARD$ = msg$
    _KEYCLEAR
    _DELAY 2
    _CLIPBOARD$ = ""
LOOP

So M$ recommends using TCP/IP to accomplish what we just demoed with _CLIPBOARD. This example covers how to do that, but it's much more involved. Also, because it uses TCP/IP, you will have to clear it with Windows Defender.

This is a Windows only demo. It uses min/restore to regain focus to each window, instead of _SCREENCLICK like the _CLIPBOARD demo above. Save, but don't run the second app as "messenger_client.exe" then run the first one. Clear for use with Windows Defender when the alert pops up on your screen.

Code: (Select All)
DECLARE DYNAMIC LIBRARY "user32"
    FUNCTION FindWindowA%& (BYVAL ClassName AS _OFFSET, WindowName$) 'handle by title
    FUNCTION ShowWindow& (BYVAL hwnd AS _OFFSET, BYVAL nCmdShow AS LONG) 'maximize process
    FUNCTION SetForegroundWindow%& (BYVAL hwnd AS _OFFSET) 'set foreground window process(focus)
    FUNCTION GetForegroundWindow%& 'Find currently focused process handle
END DECLARE

_SCREENMOVE 0, 0
title$ = "Messenger_Host"
_TITLE (title$)
_DELAY .1

_SCREENMOVE 0, 0 ' Set up this host window to the left of your desktop.
WIDTH 60, 25
IF NOT _FILEEXISTS("messenger_client.exe") THEN PRINT "Cannot find file: messenger_client.exe. Ending...": END
DIM host_msg AS STRING, client_msg AS STRING
DO
    IF initiate = 0 THEN ' This only needs to be performed once, to open the client window.
        DO UNTIL x ' Stay in loop until window determines if it is the host or client window.
            x = _OPENCLIENT("TCP/IP:1234:localhost") ' Used to establish a TCP/IP routine.
            IF x = 0 THEN
                x = _OPENHOST("TCP/IP:1234") ' Note the host and clinet must have the same 1234 I.D. number.
                a$ = "Opening as host." ' x channel is now open and this window becomes the host.
            ELSE
                a$ = "Opening as client." ' Should not go here for this demo.
            END IF
            PRINT a$
        LOOP
        SHELL _HIDE _DONTWAIT "messenger_client.exe" ' Open the client window.
        initiate = -1 ' Switches this block statement off for all subsequent loops.
    END IF

    IF z = 0 THEN ' Initiates an open channel number when zero.
        DO
            z = _OPENCONNECTION(x) ' Checks if host is available to transfer data.
        LOOP UNTIL z
        PRINT "Connection established."
        _DELAY 1
        LOCATE 2: PRINT SPACE$(_WIDTH * 2) ' Remove these lines.
        LOCATE 3, 1
        GOSUB focus ' Sends focus back to host window.
    END IF

    ' Okay, time to input something on the host that will be communicated to the client.
    LINE INPUT "Message to client: "; host_msg: PRINT

    PUT #z, , host_msg ' Input is now entered into TCP/IP routine.

    DO
        GET #z, , client_msg
    LOOP UNTIL LEN(client_msg) ' Exits loop when a return msg is received.

    PRINT "Message from client: "; client_msg: PRINT

    host_msg = "": PUT #z, , host_msg$ ' Now put our client value back into the routine. Failure to do so would result in the client not waiting in the GET #x DO/LOOP.
    _KEYCLEAR ' Prevents typing before ready.

    GOSUB focus
LOOP

focus:
DO UNTIL hwnd%&
    _LIMIT 10
    hwnd%& = FindWindowA(0, title$)
LOOP
FGwin%& = GetForegroundWindow%& 'get current process in focus.
_DELAY .1
IF FGwin%& <> hwnd%& THEN
    y& = ShowWindow&(hwnd%&, 0)
    y& = ShowWindow&(hwnd%&, 2)
    y& = ShowWindow&(hwnd%&, 9)
    DO
        _LIMIT 10
        FGwin%& = GetForegroundWindow%&
    LOOP UNTIL FGwin%& = hwnd%&
END IF
RETURN

This client app must be saved as: messenger_client.exe Run the first app after this app is saved.
Code: (Select All)
DECLARE DYNAMIC LIBRARY "user32"
    FUNCTION FindWindowA%& (BYVAL ClassName AS _OFFSET, WindowName$) 'handle by title
    FUNCTION ShowWindow& (BYVAL hwnd AS _OFFSET, BYVAL nCmdShow AS LONG) 'maximize process
    FUNCTION SetForegroundWindow%& (BYVAL hwnd AS _OFFSET) 'set foreground window process(focus)
    FUNCTION GetForegroundWindow%& 'Find currently focused process handle
END DECLARE

title$ = "Messenger_Client"
_TITLE (title$)
_DELAY .1

DIM host_msg AS STRING, client_msg AS STRING
_SCREENMOVE 600, 0 ' Set up this client window next to your host window.
WIDTH 50, 25
x = _OPENCLIENT("TCP/IP:1234:localhost") ' Used to establish a TCP/IP routine.
PRINT "Opened as client.": PRINT
DO UNTIL x = 0 ' Prevents running if this app is opened without using host.
    DO
        _LIMIT 30
        GET #x, , host_msg ' Waits until it receives message sent from the host.
    LOOP UNTIL LEN(host_msg)

    PRINT "Message from host: "; host_msg
    PRINT
    _KEYCLEAR ' Prevents typing before ready.

    GOSUB focus

    LINE INPUT "Message to host: "; client_msg: PRINT

    PUT #x, , client_msg
LOOP
END

focus:
DO UNTIL hwnd%&
    _LIMIT 10
    hwnd%& = FindWindowA(0, title$)
LOOP
FGwin%& = GetForegroundWindow%& 'get current process in focus.
_DELAY .1
IF FGwin%& <> hwnd%& THEN
    y& = ShowWindow&(hwnd%&, 0)
    y& = ShowWindow&(hwnd%&, 2)
    y& = ShowWindow&(hwnd%&, 9)
    DO
        _LIMIT 10
        FGwin%& = GetForegroundWindow%&
    LOOP UNTIL FGwin%& = hwnd%&
END IF
RETURN

PIPING:

In Windows, _CLIPBOARD can be used with SHELL to extract the directory contents. This method avoids then need to make and read temp file, which would be: SHELL _HIDE "dir /b *.bas>temp.tmp"

Windows Piping Example:
Code: (Select All)
$CONSOLE:ONLY
SHELL _HIDE "dir /b *.bas | clip"
PRINT _CLIPBOARD$

For Windows users, _CLIPBOARD can also be used with Win32API SENDKEYS, which allows you to use your program to copy text from other apps, instead of manually doing a copy to the clipboard. See my Sam-Clip thread for more info: https://qb64phoenix.com/forum/showthread.php?tid=1042&highlight=sam-clip

Pete


RE: Day 025: _CLIPBOARD$ - Pete - 12-11-2022

Find your IP Address for Windows systems. (See the caution at the top of the code). English and German, apparently supported.

Code: (Select All)
'' CAUTION - THE SHELL STATEMENT WILL OVERWRITE ANY PREEXISTING FILE YOU HAVE IN YOUR LOCAL DIRECTORY NAMED "tmp.tmp".
' Change tmp.tmp to something else if that's a problem.
SHELL _HIDE "ipconfig>tmp.tmp"
OPEN "tmp.tmp" FOR BINARY AS #1
a$ = SPACE$(LOF(1))
GET #1, , a$
CLOSE #1
DO
    j = INSTR(LCASE$(a$), search$)
    IPAddy$ = MID$(a$, j)
    IPAddy$ = MID$(IPAddy$, 1, INSTR(IPAddy$, CHR$(13)) - 1)
    IPAddy$ = _TRIM$(MID$(IPAddy$, INSTR(IPAddy$, ":") + 1))
    IF j AND LEN(search$) > 0 THEN
        PRINT "Your IP Address is: "; IPAddy$
        EXIT DO
    ELSE
        SELECT CASE cnt
            CASE 0
                search$ = "ipv4 address"
            CASE 1
                search$ = "ip address"
            CASE 2
                search$ = "ipv4 "
            CASE 3
                search$ = "ipv4-address"
            CASE 4
                search$ = "ip-address"
            CASE 5
                search$ = "ipv4-"
            CASE ELSE
                PRINT "Sorry, can't find IP addy. Opening Notepad so you can find it..."
                SHELL _DONTWAIT "Notepad tmp.tmp"
                EXIT DO
        END SELECT
    END IF
    cnt = cnt + 1
LOOP


Pete


RE: Day 025: _CLIPBOARD$ - mnrvovrfc - 12-11-2022

Must have the web browser opened before running any program created with QB64(PE) which uses the clipboard. Otherwise that clipboard will be wiped out of all text. Also in some conditions the browser stubbornly refuses to allow text pasted, not even from its own address bar or from a text field like the one I used to type this message.

I was able to paste into said text field, but it sensibly rejects an attempt to paste a password. However it's irritating when somebody chose an username that could be misspelled easily, has it written down correctly somewhere and cannot paste that.

It successfully frustrated a quick utility I called "Spanish write", which didn't require changing keyboard setups, it was only "o/" for ó, "n-" or "n/" for ñ, "u-" for ü and more.

There is also "_CLIPBOARDIMAGE" which doesn't work away from Windows.


RE: Day 025: _CLIPBOARD$ - Pete - 12-11-2022

Eeeww I hatez those sitez that don't letz ya paste in your pass-ee-word.

Windows only.
Code: (Select All)
_KEYCLEAR
a$ = _CLIPBOARD$
' Click in the password field within 5 seconds...
SLEEP 5
_SCREENPRINT a$ '  Prints instead of pastes.

Pete