Laptop Digital Picture Frame

The finished product
The laptop before I started attacking it

I managed to get my hands on an old Dell Inspiron 3800 laptop and, after spending a day or so fiddling with it, I finally decided it had no use as a laptop. The laptop was useless for a number of reasons: The battery didn’t work, it had a single USB 1.1 port and only 64MB of RAM. Now, I know I could have bought some more RAM, but considering it takes 144-pin modules it was something I wasn’t prepared to buy.

So what now? There was only one way to go. Turning your old laptop into a digital picture frame seems rather popular at the moment and since Christmas is on its way, I thought I’d give it a go myself.

The Software

I apologise now. The laptop runs Windows XP. I know that for it to be at all reputable I shouldn’t have done that, but after playing with a number of Linux distributions and not being happy with any, it was a lot quicker to get XP running. If I’d had more time then I would have done the job properly but as I said, Christmas was closing in!

For obvious reasons I wanted the picture frame to be controlled from another machine on the network, so the first thing to be installed was Apache. Again, for quick setup I decided to install trusty old PHPDev, which I’d had lying around for ages. True, it’s not very secure, and using an old installer meant I got rather outdated versions of Apache and PHP, but the out-of-the-box setup was good enough for me.

I wanted the picture frame to show pictures from a specific network location if it was available — my parents have a network drive with all their pictures on it — so I messed around with showing pictures in a Flash slideshow that I’d made for our Media Center screensaver. The RAM stick reared its ugly head again; the cross fade in the slideshow was very jerky, so the Flash idea was ditched.

The next option was to write a program that would flip through random photographs on a network drive, but after revisiting Christoffer Järnåker’s write-up I went for IrfanView to produce the slideshows. Success! Cross fading was back!

IrfanView also features a lot of nice command-line options that meant I could control it using PHP’s exec() function. Or so I thought.

With Apache running as a service, I could either run it as LocalSystem and allow it to interact with the desktop, or give it network privileges. Not both. I decided to give Apache the network privileges it required and work out some sort of message-passing system to start the slideshows on the desktop.

The rather clunky but strangely satisfying method I went for was this: a PHP script output a text file containing paths to images that should be shown in the slideshow, and then updates a configuration file with instructions to execute the new slideshow. A constantly running VB program checks the config file and sends Shell() commands to IrfanView accordingly. Phew!

The Code

Here’s the code for the VB program (I’ve neglected to include the module that handles the forced shutdown of the laptop):

Option Explicit

Private Declare Function GetLogicalDriveStrings Lib "kernel32" Alias _
    "GetLogicalDriveStringsA" (ByVal nBufferLength As Long, _
    ByVal lpBuffer As String) As Long

Const bDebug = False

Dim sIrfanPath As String
Dim sDebugFlashDrive As String
Dim sNoDebugFlashDrive As String
Dim sFlashDrive As String
Dim sHandlerPath As String
Dim sTestImagePath As String
Dim sFlashSlideshowPath As String
Dim sDefaultImagesPath As String

Dim sSettingsDate As String
Dim bDeleteFile As Boolean
Dim bInDefault As Boolean
Dim bUpdate As Boolean
Dim sAction As String
Dim sActionCommand As String
Dim fTestImage As File

Private Sub Form_Click()
    End
End Sub

Private Sub Form_Load()
    Dim lFileHandle As Long

    lFileHandle = FreeFile()
    Open App.Path & "\settings.ini" For Input As lFileHandle
    Line Input #lFileHandle, sIrfanPath
    Line Input #lFileHandle, sDebugFlashDrive
    Line Input #lFileHandle, sNoDebugFlashDrive
    Line Input #lFileHandle, sHandlerPath
    Line Input #lFileHandle, sTestImagePath
    Line Input #lFileHandle, sFlashSlideshowPath
    Line Input #lFileHandle, sDefaultImagesPath
    Close #lFileHandle

    sIrfanPath = Trim(sIrfanPath) & " "
    sHandlerPath = App.Path & "\" & sHandlerPath
    sTestImagePath = App.Path & "\" & sTestImagePath
    sFlashSlideshowPath = App.Path & "\" & sFlashSlideshowPath
    sDefaultImagesPath = App.Path & "\" & sDefaultImagesPath

    sFlashDrive = IIf(bDebug, sDebugFlashDrive, sNoDebugFlashDrive)
    If bDebug Then
        Me.Visible = True
        Me.BackColor = vbWhite
        Me.Width = Screen.Width / 2
        Me.Height = Screen.Height / 2
    End If

    Dim oFSO As Object, fFolder As Folder, fSubfolder As Folder, fFile As File
    Set oFSO = CreateObject("Scripting.FileSystemObject")
    Set fTestImage = oFSO.GetFile(sTestImagePath)
    bDeleteFile = False
End Sub

Private Sub tmrPause_Timer()
    Dim sNewSettingsDate As String, sNewAction As String, sNewActionCommand As String
    Dim lFileHandle As Long, bManualChange As Boolean
    bManualChange = False
    bUpdate = True

    If DriveExists(sFlashDrive) Then 'should show some flash drive stuff
        If sActionCommand <> sFlashSlideshowPath Then
            bManualChange = True
            bInDefault = False
            CreateFlashDriveSlideshow
        End If
        sNewAction = "flash"
        sNewActionCommand = sFlashSlideshowPath

    ElseIf DriveExists("Z") Then 'should show networked stuff
        If FileExists(sHandlerPath) Then

            sNewSettingsDate = FileDateTime(sHandlerPath)

            If (bInDefault Or sNewSettingsDate <> sSettingsDate) _
              And sNewSettingsDate <> vbNullString _
              And FileLen(sHandlerPath) > 0 Then
                lFileHandle = FreeFile()
                bInDefault = False

                Open sHandlerPath For Input As lFileHandle
                Line Input #lFileHandle, sNewAction
                Line Input #lFileHandle, sNewActionCommand
                Close #lFileHandle
            End If

        Else
            If sActionCommand <> sDefaultImagesPath Then
                bManualChange = True
            End If
            sNewAction = "default"
            sNewActionCommand = sDefaultImagesPath
            bInDefault = True
        End If

    Else 'should do some default stuff
        If sActionCommand <> sDefaultImagesPath Then
            bManualChange = True
        End If
        sNewAction = "default"
        sNewActionCommand = sDefaultImagesPath
        bInDefault = True
    End If

    If bDeleteFile Then DeleteFile sActionCommand

    If sNewSettingsDate <> sSettingsDate Or bManualChange Then
        Select Case sNewAction
            Case "slideshow", "flash", "default"
                bDeleteFile = False

                If bDebug Then
                    Print "Changing to slideshow at " & sNewActionCommand
                Else
                    Shell sIrfanPath & "/killmesoftly"
                    Shell sIrfanPath & "/slideshow=" & sNewActionCommand
                End If
            Case "single"
                RestorePreviousHandler

                bDeleteFile = True
                If FileLen(sNewActionCommand) > 0 Then
                    If bDebug Then
                        Print "Changing to uploaded image at " & sNewActionCommand
                    Else
                        Shell sIrfanPath & "/killmesoftly"
                        Shell sIrfanPath & sNewActionCommand
                    End If
                End If
            Case "delay"
                tmrPause.Interval = (CInt(sActionCommand)) * 1000

                If bDebug Then Print "Changing timer interval to " & (CInt(sActionCommand)) * 1000
            Case "kill"
                RestorePreviousHandler
                Shell sIrfanPath & "/killmesoftly"
                End

            Case "shutdown"
                RestorePreviousHandler

                If bDebug Then
                    Print "Shutting down"
                Else
                    Shell sIrfanPath & "/killmesoftly"
                    ShutdownSystem EWX_FORCESHUTDOWN
                End If
        End Select
    Else
        If bDebug Then Print "Do nothing"
        bUpdate = False
    End If

    If bUpdate Then
        sAction = sNewAction
        sActionCommand = sNewActionCommand
    End If
    If FileExists(sHandlerPath) Then sSettingsDate = sNewSettingsDate
End Sub

Sub DeleteFile(sPathName As String) 'deletes given file
    If FileExists(sPathName) Then
        Dim oFSO As Object
        Set oFSO = CreateObject("Scripting.FileSystemObject")
        oFSO.DeleteFile sPathName 'delete the file
    End If
End Sub

Function DriveExists(ByVal sDrive As String) As Boolean
    Dim buffer As String
    buffer = Space(64)
    ' return False if invalid argument
    If Len(sDrive) = 0 Then Exit Function
    ' get the string that contains all drives
    GetLogicalDriveStrings Len(buffer), buffer
    ' check that the letter we're looking for is there
    DriveExists = InStr(1, buffer, Left$(sDrive, 1), vbTextCompare)
End Function

Function FileExists(sPathName As String) As Boolean ' checks if a file exists
    If Len(sPathName) > 0 Then ' checks filename entered
        FileExists = (Len(Dir(sPathName)) > 0) ' dir function returns directory if file exists
    Else: FileExists = False
    End If
End Function

Sub CreateFlashDriveSlideshow()
    Dim lFileHandle As Long
    lFileHandle = FreeFile()

    Open sFlashSlideshowPath For Output As lFileHandle
    ParseFolder sFlashDrive, lFileHandle
    Close #lFileHandle
End Sub

Sub ParseFolder(sFolder As String, lFileHandle As Long)
    Dim oFSO As Object, fFolder As Folder, fSubfolder As Folder, fFile As File
    Set oFSO = CreateObject("Scripting.FileSystemObject")
    Set fFolder = oFSO.GetFolder(sFolder)

    For Each fSubfolder In fFolder.SubFolders
        ParseFolder fSubfolder.Path, lFileHandle
    Next fSubfolder

    For Each fFile In fFolder.Files
        If fFile.Type = fTestImage.Type Then
            Print #lFileHandle, fFile.Path
        End If
    Next fFile
End Sub

Sub RestorePreviousHandler()
    Dim lFileHandle As Long

    lFileHandle = FreeFile()
    Open sHandlerPath For Output As lFileHandle
    Print #lFileHandle, sAction
    Print #lFileHandle, sActionCommand
    Close #lFileHandle
End Sub

I left in the debugging mode just in case I needed to make some changes later on. All the program constants are loaded in from another configuration file, hopefully meaning minimal recompilation.

In addition to the VB handler, I needed to set IrfanView up to show everything correctly. I wanted the images to be displayed randomly, the slideshow to be full-screen and the cursor to be hidden.

To summarise the software, the picture frame will show photographs at random from a pre-determined network location, unless none are available, in which case it defaults to a small collection of images on the hard drive. If someone inserts a USB flash drive containing images, then the current slideshow is exited and the images from the flash drive are displayed randomly until it is removed, at which point the picture frame resumes the previous slideshow. Using the Web interface, a single image can be uploaded to the picture frame that will be displayed for one cycle before returning to the previous slideshow. At the last minute I decided to enable remote desktop if anything should go wrong.

To summarise the summary: I’m very happy with the software!

The Disassembly

I don’t need most of what’s provided in this laptop unit. I don’t want the battery or CD-ROM drive, the keyboard or mouse pad, or any of the casing. All I need is the screen, the hard drive and the stripped-down motherboard.

I started with the screen. All the screws on my display unit are located under rubber covers; however they’re just glued down so they prise straight out. After removing those, I flipped over the laptop and removed the screws holding in the keyboard (these are nicely labelled “K” on many Dell laptops) and the display (marked “D”) before turning it back over and pulling the screen away from the main assembly.

I moved the keyboard out of the way so I could get to the connector for the screen. These connectors are rather sturdy but it’s still not advisable to pull the cable — attempt to gently rock the connector out.

With the screen loose I could remove it from the lid. I then set about removing unwanted components from the motherboard. I actually left the keyboard connected until the very end, thinking that I’d need it for the laptop to start properly. It wasn’t needed, so it was removed later on. I found the connecting cable was very stiff but precision brute force saved the day.

The rest of the components were hidden under the casing, so I removed the rest of the screws (Marked “P”) and dispensed with it. A few more screws and the COM ports held the metal tray to the motherboard; these were also removed. I had to temporarily remove the cover of the CPU to get one of the screws holding in the heat sink.

At this point, I decided a test was in order to see if everything was working. The laptop booted cleanly to the default slide show (with no network card).

The next part was to remove the track pad print layout from its metal frame. It was glued on rather well, but a sharp knife separated the two nicely. This also includes the speakers, which I don’t really want, so they were removed.

I decided to give it one last test, but somehow I managed to forget to hook up the keyboard and mouse. To my surprise, it booted fine, so I removed the keyboard and mouse completely, saving precious space behind the frame. Now the only connected devices are the PCMCIA slots and the hard drive.

The Reassembly

The power button is a push-to-make switch with a built-in LED that comes on when the laptop is switched on.

The power button needed extending so it could be built into the frame. At first I used one of the buttons from the print layout, but then I found a nice button with a built-in LED, so I hooked that up to the laptop’s power LED.

The frame required a bit of cutting to get the laptop to fit in — the biggest problem was the wireless PCMCIA card sticking out from the side of the motherboard, but a little cutting into the (thankfully soft) frame and it fitted. However the bit stuck out from the back of the frame about 8mm — a couple of lengths of wood sorted this out. I applied a couple of coats of wood stain to the spacers to match the frame colour.

To finish, the spacers were glued to the backboard (to which the motherboard is affixed) and the back was screwed to the frame. I’m quite happy with the finished result; Sometimes the laptop struggles to connect to the network drive, but that’s OK since it defaults back to showing some pictures on the hard drive anyway.

The total cost of this project was around £30 for a wireless card and a frame, only slightly helped by what I had lying around the house. The laptop was free, but you can pick them up for very little money from eBay. You don’t even need a working hard drive, as many other projects have demonstrated — you can boot from a CD-ROM, which would also make the project a lot quieter.

This is a really fun project that got me doing a bit of coding, a bit of woodworking and a bit of taking things apart to see how they work — all were thoroughly enjoyable and I’d recommend giving one of these projects a go. It took me a day to fully set up the laptop and code the software, then an afternoon to take it apart and a couple of evenings to construct the frame. And, like everyone says, if you do attempt one of these, take pictures!

You can also view pictures of the project.