Alyce
Nov 23, 2015
=Creating a Shell= ==Painting the Desktop== //by Alyce Watson// - [[http://alycesrestaurant.com/]] [[user:Alyce]] [[toc]] ==What is a Shell?== A shell is an application that manages the user's applications and documents. It is not the same as an Operating System. A shell runs on top of an operating system. We can create a shell for Windows in Liberty BASIC. We'll paint a desktop over the existing desktop and allow the user to add icons to it for their applications. ==Setting Up a Desktop== We'll use a window of style "window_popup" to cover the desktop. This style does not have a titlebar, so it's a great choice for a desktop replacement. We'll make it the DisplayWidth and DisplayHeight dimensions, so it covers the entire existing desktop. We'll include a graphicbox that covers the entire client area of the window. The graphicbox allows us to capture mouse clicks and to flush the graphics to make them stick. We'll add a button to the lower left corner of the desktop that allows the user to exit our shell. That's the default location for the Windows start button, so the user will expect to find it there. The user could exit our shell by pressing and holding the Alt button, then pressing F-4, but it's more polished to provide an easy way to exit an application. [[code format ="vbnet"]]"lb"]] WindowWidth=DisplayWidth:WindowHeight=DisplayHeight graphicbox #desk.g, -1, -1, DisplayWidth+2, DisplayHeight+2 button #desk.exit, "Exit", DoExit, UL, 4, DisplayHeight-30, 56, 28 open "My Desktop Shell" for window_popup as #desk [[code]] ==Painting the Desktop== Windows provides an API function called "PaintDesktop". It paints the desktop image onto the Device Context specified. The desktop image includes the user's selected background color and image file. It does not include the icons displayed on the user's desktop. The function is provided to allow people to create their own Windows shell programs. For the Device Context, we will "GetDC" for the grahicbox at the start of the program and "ReleaseDC" when the program ends. Those functions look like this: [[codeformat="vbnet"]]format="lb"]] CallDLL #user32, "GetDC",_ hWnd as uLong,_ 'graphicbox handle hDC as uLong 'returns handle to Device Context CallDLL#user32,"ReleaseDC",_ hWnd as uLong,_ 'graphicbox handle hDC as uLong,_ 'handle to Device Context result As Long [[code]] Once we have the DC of the graphicbox, it is super-simple to paint the user's desktop image onto it. [[codeformat="vbnet"]]format="lb"]] calldll #user32, "PaintDesktop",_ hDC as ulong,_ 'handle of graphicbox device context re asbooleanlong 'nonzero = success [[code]] Any time we make a change to our desktop's appearance, we'll flush it with "GetBmp", then "DrawBmp" then "Flush". Because we are giving our segments a name when we flush them, we can easily delete each segment when we are ready to flush a new one. This conserves memory. Each flush operation consumes memory, but that memory can be released by deleting the flushed segments with "delsegment". [[codeformat="vbnet"]]format="lb"]] Sub DoFlush #desk.g "delsegment flushMe" #desk.g "getbmp desk 0 0 ";DisplayWidth;" ";DisplayHeight #desk.g "drawbmp desk 0 0; flush flushMe" end sub [[code]] ==Adding Application Icons== We'll extract icons from the applications the user chooses to place on his desktop with "ExtractIcon". We'll then draw them with "DrawIcon". These functions are explained in issue 134 of The Liberty BASIC Newsletter. We've set up a double dimensioned array to hold information about each application the user adds to his desktop. [[codeformat="vbnet"]]format="lb"]] 'apps$(i,1) = filename of exe 'apps$(i,2) = x location on desktop 'apps$(i,3) = y location on desktop 'apps$(i,4) = icon handle [[code]] We set up event handlers for mouse events on the desktop. When the user clicks the right mouse button, we allow him to add an icon to his desktop. He is first presented with a filedialog to select the application. When he has done this, we add the information about this application to our "apps$()" array. The xy location on the desktop are determined by checking the applications that are already in the array and incrementing the values accordingly. We've chosen to add icons in a row across the top of the desktop. We check to see if the width of the desktop has been reached, and if it has, we drop down to the next row to add the new icon. We extract the icon, draw it on the desktop, and flush the new image. Because we've put the information into an array, it is very easy to iterate through the array, looking for the first empty index, and filling that index with information about the new application being added by the user. [[codeformat="vbnet"]]format="lb"]] Sub AddApp handle$, mx, my for index = 1 to appMax if apps$(index,1) = "" then filedialog "Add App", "*.exe", exe$ if exe$ <> "" then apps$(index,1) = exe$ newX = val(apps$(index-1,2)) + gridWidth 'next column in this row newY = val(apps$(index-1,3)) 'use same row if newX > DisplayWidth - gridHeight then newX = gridWidth 'beginning of new row newY = newY + gridHeight 'move to next row end if apps$(index,2) = str$(newX) apps$(index,3) = str$(newY) hIcon = ExtractIcon(hwnd(#desk.g), apps$(index,1)) apps$(index,4) = str$(hIcon) 'draw icon on desktop at designated location result = DrawIcon(hDC, val(apps$(index,4)), val(apps$(index,2)), val(apps$(index,3)) ) end if exit for end if next call DoFlush if index = appMax then 'all slots are filled notice "All slots are filled. Cannot add more apps." end if end sub [[code]] ==Remembering the User's Choices== We'll use an API initialization file to remember and retrieve the user's choices. The functions to do this are explained in detail in issue 143 of the Liberty BASIC Newsletter. When the program closes, we write the information to the ini file by iterating through the apps$() array. The routine is as follows. [[codeformat="vbnet"]]format="lb"]] Sub WriteIniInfo for i = 1 to appMax if apps$(i,1) = "" then exit for call WriteIniFile "My Desktop Shell", "appName"+str$(i), apps$(i,1), "mydesktopshell.ini" call WriteIniFile "My Desktop Shell", "appX"+str$(i), apps$(i,2), "mydesktopshell.ini" call WriteIniFile "My Desktop Shell", "appY"+str$(i), apps$(i,3), "mydesktopshell.ini" next end sub [[code]] Each time the program opens, we'll call a routine that reads the ini file, then displays the user's selections on the desktop. It places the information into our apps$() array as it is read from the ini file. [[codeformat="vbnet"]]format="lb"]] Sub GetIniInfo for i = 1 to appMax exe$ = GetIniFile$("My Desktop Shell", "appName"+str$(i),"none","mydesktopshell.ini") if exe$ = "none" then exit for appX$ = GetIniFile$("My Desktop Shell", "appX"+str$(i),"0","mydesktopshell.ini") appY$ = GetIniFile$("My Desktop Shell", "appY"+str$(i),"0","mydesktopshell.ini") apps$(i,1) = exe$ 'full path to app exe apps$(i,2) = appX$ 'x location on desktop of app icon apps$(i,3) = appY$ 'y location on desktop of app icon 'get handle of icon extracted from exe: hIcon = ExtractIcon(hwnd(#desk.g), apps$(i,1)) apps$(i,4) = str$(hIcon) 'draw icon on desktop at designated location result = DrawIcon(hDC, val(apps$(i,4)), val(apps$(i,2)), val(apps$(i,3)) ) next call DoFlush end sub [[code]] ==Running Programs from the Shell== We set up a left button double-click event handler in the graphicbox. When the user double-clicks the left mouse button, we check to see if the mouse pointer is on one of the desktop icons. If it is, we use the "run" command to run the application. For more on using the "run" command, see issue 114 of The Liberty BASIC Newsletter. To determine if the mouse is clicking on one of the desktop icons, we iterate through the array, checking the xy value of each icon against the MouseX and MouseY value. If the mouse pointer X value is at least as large as an icon's X location, but no larger than the X location plus the gridWidth, then we do a similar check for the Y location of that icon compared to the MouseY location. If they match, we run the application associated with that icon. [[codeformat="vbnet"]]format="lb"]] Sub RunApp handle$, mx, my print "mx is ";mx print "my is ";my for i = 1 to appMax 'if apps$(i,1) = "" then exit for x = val(apps$(i,2)) y = val(apps$(i,3)) print "xy is ";x;" ";y if (mx>x) and (mx<x+gridWidth) then if (mx>y) and (my<y+gridHeight) then run apps$(i,1) exit for end if end if next end sub [[code]] ==Enchancements== The demo program included at the end of this article works fine, but it would benefit from some additional features. Feel free to modify the code to create your own shell program! You might want to check issue 137 of The Liberty BASIC Newsletter for information on "Running Control Panet Applets." You might also consider some of these enhancements: [[codeformat="vbnet"]]format="lb"]] 'possible enhancements: ' allow user to remove apps ' allow user to rearrange icons on desktop ' add labels below icons (increase gridWidth value) ' make text for labels have transparent background ' allow user to create custom folders on this desktop ' if no icon is extracted, use a default icon ' allow user to use custom icons, instead of icon extracted from exe ' enhance exit button with choices, like Windows start button ' create custom taskbar [[code]] ==Making Code Easy to Modify== We've used global variables for some values, so that they are visible inside subs and functions. We've set up variables to hold certain information, so that the code can be modified easily by changing the values of these variables at the top of the code. This allows us to make a single change, instead of going through the entire program code to change many instances of a value. One such variable is called "gridWidth". It contains the value we use to locate the icons in the X direction. It is set to 40. If you want to place the icons closer together, make this number smaller. If you want them further apart, make the number larger. Don't make it smaller than 32, which is the default width for icons, or the icons will overlap one another. ---- ==DEMO== [[codeformat="vbnet"]]format="lbt"]] 'Desktop Shell Demo 'if you use this code, ' please credit Alyce Watson, http://alycesrestaurant.com/ 'note that this demo allows user to add apps to desktop 'note that this demo does not allow user to remove apps or rearrange desktop 'possible enhancements: ' allow user to remove apps ' allow user to rearrange icons on desktop ' add labels below icons (increase gridWidth value) ' make text for labels have transparent background ' allow user to create custom folders on this desktop ' if no icon is extracted, use a default icon ' allow user to use custom icons, instead of icon extracted from exe ' enhance exit button with choices, like Windows start button ' create custom taskbar nomainwin global hDC, gridWidth, gridHeight, appMax gridWidth = 40 : gridHeight = 40 appMax = 50 'maximum number of apps on desktop dim apps$(50,4) 'arrays are global by default redim apps$(appMax, 4) 'apps$(i,1) = filename of exe 'apps$(i,2) = x location on desktop 'apps$(i,3) = y location on desktop 'apps$(i,4) = icon handle WindowWidth=DisplayWidth:WindowHeight=DisplayHeight graphicbox #desk.g, -1, -1, DisplayWidth+2, DisplayHeight+2 button #desk.exit, "Exit", DoExit, UL, 4, DisplayHeight-30, 56, 28 open "My Desktop Shell" for window_popup as #desk #desk "trapclose Quit" #desk.g "down; setfocus" #desk.g "when rightButtonUp AddApp" #desk.g "when leftButtonDouble RunApp" hDC = GetDC(hwnd(#desk.g)) call PaintDesktop hDC call GetIniInfo 'get and draw user icons, flush desktop wait Sub AddApp handle$, mx, my for index = 1 to appMax if apps$(index,1) = "" then filedialog "Add App", "*.exe", exe$ if exe$ <> "" then apps$(index,1) = exe$ newX = val(apps$(index-1,2)) + gridWidth 'next column in this row newY = val(apps$(index-1,3)) 'use same row if newX > DisplayWidth - gridHeight then newX = gridWidth 'beginning of new row newY = newY + gridHeight 'move to next row end if apps$(index,2) = str$(newX) apps$(index,3) = str$(newY) hIcon = ExtractIcon(hwnd(#desk.g), apps$(index,1)) apps$(index,4) = str$(hIcon) 'draw icon on desktop at designated location result = DrawIcon(hDC, val(apps$(index,4)), val(apps$(index,2)), val(apps$(index,3)) ) end if exit for end if next call DoFlush if index = appMax then 'all slots are filled notice "All slots are filled. Cannot add more apps." end if end sub Sub RunApp handle$, mx, my for i = 1 to appMax x = val(apps$(i,2)) y = val(apps$(i,3)) print "xy is ";x;" ";y if (mx>x) and (mx<x+gridWidth) then if (mx>y) and (my<y+gridHeight) then run apps$(i,1) exit for end if end if next end sub Sub GetIniInfo for i = 1 to appMax exe$ = GetIniFile$("My Desktop Shell", "appName"+str$(i),"none","mydesktopshell.ini") if exe$ = "none" then exit for appX$ = GetIniFile$("My Desktop Shell", "appX"+str$(i),"0","mydesktopshell.ini") appY$ = GetIniFile$("My Desktop Shell", "appY"+str$(i),"0","mydesktopshell.ini") apps$(i,1) = exe$ 'full path to app exe apps$(i,2) = appX$ 'x location on desktop of app icon apps$(i,3) = appY$ 'y location on desktop of app icon 'get handle of icon extracted from exe: hIcon = ExtractIcon(hwnd(#desk.g), apps$(i,1)) apps$(i,4) = str$(hIcon) 'draw icon on desktop at designated location result = DrawIcon(hDC, val(apps$(i,4)), val(apps$(i,2)), val(apps$(i,3)) ) next call DoFlush end sub Sub WriteIniInfo for i = 1 to appMax if apps$(i,1) = "" then exit for call WriteIniFile "My Desktop Shell", "appName"+str$(i), apps$(i,1), "mydesktopshell.ini" call WriteIniFile "My Desktop Shell", "appX"+str$(i), apps$(i,2), "mydesktopshell.ini" call WriteIniFile "My Desktop Shell", "appY"+str$(i), apps$(i,3), "mydesktopshell.ini" next end sub Sub DoFlush #desk.g "delsegment flushMe" #desk.g "getbmp desk 0 0 ";DisplayWidth;" ";DisplayHeight #desk.g "drawbmp desk 0 0; flush flushMe" end sub Sub Quit handle$ call ReleaseDC hwnd(#desk.g), hDC call WriteIniInfo for i = 1 to appMax if apps$(i,1) = "" then exit for hIcon = val(apps$(i,4)) calldll #user32, "DestroyIcon", hIcon as ulong, re as boolean next close #handle$:end end sub Sub DoExit handle$ call Quit "#desk" end sub '************* ' API WRAPPERS ' api wrappers are copied from http://alycesrestaurant.com/workshop.htm '************* sub PaintDesktop hwndDC calldll #user32, "PaintDesktop", hwndDC as ulong,_ re as boolean end sub Sub WriteIniFile lpAppName$, lpKeyName$, lpString$, lpFileName$ lpFileName$=DefaultDir$+"\"+lpFileName$ CallDLL #kernel32, "WritePrivateProfileStringA", _ lpAppName$ As ptr, _ 'section name lpKeyName$ As ptr, _ 'key name lpString$ As ptr, _ 'key value lpFileName$ As ptr, _ 'ini filename result Asbooleanlong 'nonzero = success end sub Function GetIniFile$(lpAppName$, lpKeyName$,lpDefault$,lpFileName$) lpFileName$=DefaultDir$+"\"+lpFileName$ nSize=100 lpReturnedString$=Space$(nSize)+Chr$(0) CallDLL #kernel32, "GetPrivateProfileStringA", _ lpAppName$ As ptr, _'section name lpKeyName$ As ptr, _'key name lpDefault$ As ptr, _'default string returned if there is no entry lpReturnedString$ As ptr, _ 'destination buffer nSize As long, _ 'size of destination buffer lpFileName$ As ptr, _ 'ini filename result As ulong 'number of characters copied to buffer GetIniFile$=Left$(lpReturnedString$,result) end function Function ExtractIcon(hW, file$) hInst=GetWindowLong(hW, _GWL_HINSTANCE) CallDLL #shell32, "ExtractIconA",hInst as uLong,_ file$ As Ptr, 0 As Long, ExtractIcon as uLong End Function Function DrawIcon(hdc,hIcon,x,y) CallDLL #user32, "DrawIcon",hdc as uLong, x As Long,_ y As Long, hIcon as uLong, DrawIcon As Long End Function Function GetWindowLong(hW, type) CallDLL #user32, "GetWindowLongA",hW as uLong,_ type As Long,GetWindowLong As Long End Function Function GetDC(hWnd) CallDLL #user32, "GetDC",hWnd as uLong,GetDC as uLong End Function Sub ReleaseDC hWnd, hDC CallDLL#user32,"ReleaseDC",hWnd as uLong,_ hDC as uLong,result As Long End Sub [[code]] ---- [[toc]]