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.
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
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:
CallDLL #user32, "GetDC",_
hWnd asuLong,_ 'graphicbox handle
hDC asuLong'returns handle to Device Context
CallDLL#user32,"ReleaseDC",_
hWnd asuLong,_ 'graphicbox handle
hDC asuLong,_ 'handle to Device Context
result AsLong
Once we have the DC of the graphicbox, it is super-simple to paint the user's desktop image onto it.
calldll #user32, "PaintDesktop",_
hDC asulong,_ 'handle of graphicbox device context
re asboolean'nonzero = success
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".
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.
'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
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.
Sub AddApp handle$, mx, my
for index =1to 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 rowif newX > DisplayWidth - gridHeight then
newX = gridWidth 'beginning of new row
newY = newY + gridHeight 'move to next rowendif
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)))endifexitforendifnextcall DoFlush
if index = appMax then'all slots are filled
notice "All slots are filled. Cannot add more apps."endifendsub
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.
Sub WriteIniInfo
for i =1to appMax
if apps$(i,1)=""thenexitforcall 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"nextendsub
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.
Sub GetIniInfo
for i =1to appMax
exe$ = GetIniFile$("My Desktop Shell", "appName"+str$(i),"none","mydesktopshell.ini")if exe$ ="none"thenexitfor
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)))nextcall DoFlush
endsub
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.
Sub RunApp handle$, mx, my
print "mx is ";mx
print "my is ";my
for i =1to 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)thenif(mx>y)and(my<y+gridHeight)then
run apps$(i,1)exitforendifendifnextendsub
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:
'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
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
'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 desktopdim apps$(50,4)'arrays are global by defaultredim 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 =1to 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 rowif newX > DisplayWidth - gridHeight then
newX = gridWidth 'beginning of new row
newY = newY + gridHeight 'move to next rowendif
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)))endifexitforendifnextcall DoFlush
if index = appMax then'all slots are filled
notice "All slots are filled. Cannot add more apps."endifendsubSub RunApp handle$, mx, my
print "mx is ";mx
print "my is ";my
for i =1to 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)thenif(mx>y)and(my<y+gridHeight)then
run apps$(i,1)exitforendifendifnextendsubSub GetIniInfo
for i =1to appMax
exe$ = GetIniFile$("My Desktop Shell", "appName"+str$(i),"none","mydesktopshell.ini")if exe$ ="none"thenexitfor
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)))nextcall DoFlush
endsubSub WriteIniInfo
for i =1to appMax
if apps$(i,1)=""thenexitforcall 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"nextendsubSub DoFlush
#desk.g"delsegment flushMe"
#desk.g"getbmp desk 0 0 ";DisplayWidth;" ";DisplayHeight
#desk.g"drawbmp desk 0 0; flush flushMe"endsubSub Quit handle$
call ReleaseDC hwnd(#desk.g), hDC
call WriteIniInfo
for i =1to appMax
if apps$(i,1)=""thenexitfor
hIcon =val(apps$(i,4))
calldll #user32, "DestroyIcon", hIcon asulong, re asbooleannext
close #handle$:endendsubSub DoExit handle$
call Quit "#desk"endsub'*************' API WRAPPERS' api wrappers are copied from http://alycesrestaurant.com/workshop.htm'*************sub PaintDesktop hwndDC
calldll #user32, "PaintDesktop", hwndDC asulong,_
re asbooleanendsubSub WriteIniFile lpAppName$, lpKeyName$, lpString$, 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 Asboolean'nonzero = successendsubFunction GetIniFile$(lpAppName$, lpKeyName$,lpDefault$,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 Aslong, _ 'size of destination buffer
lpFileName$ As ptr, _ 'ini filename
result Asulong'number of characters copied to buffer
GetIniFile$=Left$(lpReturnedString$,result)endfunctionFunction ExtractIcon(hW, file$)
hInst=GetWindowLong(hW, _GWL_HINSTANCE)
CallDLL #shell32, "ExtractIconA",hInst asuLong,_
file$ As Ptr, 0AsLong, ExtractIcon asuLongEndFunctionFunction DrawIcon(hdc,hIcon,x,y)
CallDLL #user32, "DrawIcon",hdc asuLong, x AsLong,_
y AsLong, hIcon asuLong, DrawIcon AsLongEndFunctionFunction GetWindowLong(hW, type)
CallDLL #user32, "GetWindowLongA",hW asuLong,_
type AsLong,GetWindowLong AsLongEndFunctionFunction GetDC(hWnd)
CallDLL #user32, "GetDC",hWnd asuLong,GetDC asuLongEndFunctionSub ReleaseDC hWnd, hDC
CallDLL#user32,"ReleaseDC",hWnd asuLong,_
hDC asuLong,result AsLongEndSub
Creating a Shell
Painting the Desktop
by Alyce Watson - http://alycesrestaurant.com/-
Click HERE for the demonstration program that accompanies this article.
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.
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:
Once we have the DC of the graphicbox, it is super-simple to paint the user's desktop image onto it.
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".
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.
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.
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.
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.
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.
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:
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