{"id":9454,"date":"2023-02-07T19:52:01","date_gmt":"2023-02-07T19:52:01","guid":{"rendered":"https:\/\/max-drake.cc\/?p=9454"},"modified":"2023-03-20T21:26:43","modified_gmt":"2023-03-20T21:26:43","slug":"auto-logger-data-capture-script-with-autohotkey-part-2","status":"publish","type":"post","link":"https:\/\/max-drake.cc\/?p=9454","title":{"rendered":"Auto Logger data capture script with AutoHotKey- part 2"},"content":{"rendered":"\n<p>In <strong><a href=\"https:\/\/max-drake.cc\/?p=9449\">Auto Logger and timesheet exploration with AutoHotKey and Python Part 1<\/a> <\/strong>I gave an overview of the project, in this article I want to go into the development of the background process of auto-logging computer activity on the PC. <\/p>\n\n\n\n<p>My initial start, as mentioned in the previous article was to start with the code in article F<strong><a rel=\"noreferrer noopener\" href=\"https:\/\/www.makeuseof.com\/log-active-windows-with-autohotkey\/\" target=\"_blank\">ind Where You Waste Your Time on Windows With AutoHotKey<\/a><\/strong> by <a href=\"https:\/\/www.makeuseof.com\/author\/odysseas-kourafalos\/?_locale=en\">Odysseas Kourafalos<\/a>, the code is as below.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.\n; #Warn ; Enable warnings to assist with detecting common errors.\nSendMode Input ; Recommended for new scripts due to its superior speed and reliability.\nSetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory.\n\n; Variables\n; ---------\nAppLoggingRate = 10 ; Time interval (in seconds) between active window title captures.\nSleepTime := AppLoggingRate * 1000\nLogPath = %A_ScriptDir%\nLastActiveWindow = \n\n; Logic\n; -----\nLoop\n{\n Sleep %SleepTime%\n \n WinGetActiveTitle, ActiveWindow\n StoreActiveWindow = %ActiveWindow%\n \n If ActiveWindow != %LastActiveWindow%\n {\n FormatTime, LogTime,, HH:mm:ss\n FormatTime, LogFilename, , yyyy-MM-dd\n \n LogWindow := Regexreplace(ActiveWindow, \"&#91;^a-zA-Z0-9]\", \" \")\n \n LogFilename = %LogFilename%_AppLog.md\n LogFile = %LogPath%\\%LogFilename%\n \n FileContent = `n%LogTime% - %LogWindow%`n`n- - -`n\n \n sleep 50\n \n FileAppend, %FileContent%, %LogFile%\n LastActiveWindow = %ActiveWindow%\n }\n}\nExit<\/code><\/pre>\n\n\n\n<p>The output is a text based markdown file and so not easy to use afterwards, so  straight away changed the output to: LogFilename = %LogFilename%_AppLog<strong>.csv<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Developing the code to see what could use AutoHotKey to get with its own internal variables <\/h3>\n\n\n\n<p>At first glance it seems simple, at its most basic level you just record the current Active Window and when you switch windows you stop recording the old window and create a new log line for the new window. But what do you record? <\/p>\n\n\n\n<p>As I decided that I was going to do this project in AutoHotKey there are some internal AutoHotKey variables we can use for leverage such as window title, window class, window  ahk_ID and Process ID. We can obtain these items with either Windows Spy or using Pulover&#8217;s Macro creator Window button <\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"703\" height=\"296\" data-src=\"https:\/\/max-drake.cc\/wp-content\/uploads\/2023\/02\/Edit-Post-Auto-Logger-data-capture-script-with-AutoHotKey-part-2-\u2039-Vast-\u2014-Wor29_18.jpg\" alt=\"\" class=\"wp-image-9456 lazyload\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 703px; --smush-placeholder-aspect-ratio: 703\/296;\" \/><figcaption class=\"wp-element-caption\">AutoHotKey Windows Spy<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"875\" height=\"453\" data-src=\"https:\/\/max-drake.cc\/wp-content\/uploads\/2023\/02\/Edit-Post-Auto-Logger-data-capture-script-with-AutoHotKey-part-2-\u2039-Vast-\u2014-Wor30_09.jpg\" alt=\"\" class=\"wp-image-9457 lazyload\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 875px; --smush-placeholder-aspect-ratio: 875\/453;\" \/><figcaption class=\"wp-element-caption\">Pulover&#8217;s Macro Creator Window Inforation<\/figcaption><\/figure>\n\n\n\n<p>So my immediate action was to log these details to see what the output would be. <\/p>\n\n\n\n<p>I also noted that the original time was for start time of when an active window was started and it was in H:M:S format. I decided to also get Epoch or UTC time setup as well, as this is better for time addition and subtraction,as you are only adding\/subtracting time in one variable, seconds, rather than having to do more complex arithmatic to add\/subtract H:M:S from H:M:S. At the end we can always do the conversion to the time format that we want after doing the maths. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#NoEnv\n#SingleInstance, Force\nSendMode, Input\nSetBatchLines, -1\nSetWorkingDir, %A_ScriptDir%\n\n; Variables\n; ---------\nAppLoggingRate = 30 ; Time interval (in seconds) between active window title captures.\nSleepTime := AppLoggingRate * 1000\nLogPath = %A_ScriptDir%\nLastActiveWindow = \n\n; Logic\n; -----\nLoop\n{\n Sleep %SleepTime%\n \n WinGetActiveTitle, ActiveWindow\n StoreActiveWindow = %ActiveWindow%\n WinGet, activePID, PID, A\n WinGet, activeID, ID, A\n WinGetClass, activeClass, A\n \n  If ActiveWindow != %LastActiveWindow%\n {\n FormatTime, LogTime,, HH:mm:ss\n FormatTime, LogFilename, , yyyy-MM-dd\n \n LogWindow := Regexreplace(ActiveWindow, \"&#91;^a-zA-Z0-9]\", \" \")\n \n LogFilename = %LogFilename%_AppLog.csv\n LogFile = %LogPath%\\%LogFilename%\n \n FileContent = `n%A_NowUTC% , %LogTime% ,  %LogWindow% , %activeClass%, %activeID%, %activePID%  \n  ;FileContent = `n%A_NowUTC% , %LogTime% ,  %LogWindow% , %ahk_class%, %ahk_id%, %ahk_pid%  \n sleep 50\n  FileAppend, %FileContent%, %LogFile%\n LastActiveWindow = %ActiveWindow%\n }\n}\nExit<\/code><\/pre>\n\n\n\n<p>So the raw output became as below (I added headers in file for clarity)<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>UTC Start Time, H:M:S start time, Windows Title, Win Class, AHK_ID, PID (Process)\n20230129192931 , 08:29:31 ,  AutoHotKey get active window class at DuckDuckGo   Mozilla Firefox , 0x304de, 0x304de, 9876\n20230129193001 , 08:30:01 ,    ScreenLogger ahk   AHK Working   Visual Studio Code , 0x50b70, 0x50b70, 17268\n20230129193049 , 08:30:49 ,  WinGetClass   Syntax   Usage   AutoHotkey   Mozilla Firefox , MozillaWindowClass, 0x304de, 9876\n20230129193119 , 08:31:19 ,  Scoop   New Zealand News   Mozilla Firefox , MozillaWindowClass, 0x304de, 9876\n20230129193149 , 08:31:49 ,  Four dead or missing after flooding  Police   Scoop News   Mozilla Firefox , MozillaWindowClass, 0x304de, 9876<\/code><\/pre>\n\n\n\n<p>On looking at the output of a much larger data set a few things became obvious: <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The Win Classes were not unique to specific programs, I use Firefox (Browser) and Thunderbird (Email management) and they both have the same winclass  &#8220;MozillaWindowClass&#8221;. Also VS Code &amp; Obsidian have the same win class &#8220;Chrome_WidgetWin_1&#8221;<\/li>\n\n\n\n<li>The ahk_ID &amp; Process ID can change as soon as you open a new instance of a program. Also when you close an instance down of a program and open it again it creates new ahk_ID&#8217;s &amp; Process ID&#8217;s. <\/li>\n<\/ul>\n\n\n\n<p>The only thing that seems to be unique to a logging of an active window is the win title that is broken into a number of elements. For a browser, it states the program , maybe the main site and a particular title on that site. For a program, VS Code in this instance, it will show program, directory that is active and the filename that is actively open. <\/p>\n\n\n\n<p>This is not very satisfactory, there are too many things that are transient and which can change, especially from computer to computer. The one thing we could use is the Win Title with a lot of string manipulation to extract program being used and activity, but this will require a lot of coding and for loops. Is there a better way? <\/p>\n\n\n\n<p>We go back to the original purpose of the AutoLogger which is for Time Sheet information and to see where we are wasting time.  Things look in too much of a state of flux to help us in this matter, we need to do something else. <\/p>\n\n\n\n<p>If you look at Task Manager you can see Process ID&#8217;s of Programs , there can be several relating to the same program, such as the 6 AutoHotKey ones (I&#8217; running 6 different scripts in the background) and lots of VSCode ones (one for main program and one for each tab\/file open &amp; if I was to close any one down and start again it would change its PID  : <\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"688\" height=\"997\" data-src=\"https:\/\/max-drake.cc\/wp-content\/uploads\/2023\/02\/Task-Manager28_18.jpg\" alt=\"\" class=\"wp-image-9459 lazyload\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 688px; --smush-placeholder-aspect-ratio: 688\/997;\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">AutoHotKey WinGet using Programme.exe ID <\/h3>\n\n\n\n<p>So we are starting to define the issues, we only have start times of the activities, we have variables for the programs that change and we need unique identifiers so that we can classify what program we are using for activities. <\/p>\n\n\n\n<p>If we go back to the origina l code there is a loop that is activated at specific intervals, initially set at 10 seconds, where it gets the ActiveWindowTitle, and then the next part of the code is an IF statement where it compares the current Active Window with the previous active window, if they are the same then it does nothing, but if they are different then it creates a new log with a start time of A_now(), which gives the current time. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Loop\n{\n Sleep %SleepTime%\n \n WinGetActiveTitle, ActiveWindow\n StoreActiveWindow = %ActiveWindow%\n \n If ActiveWindow != %LastActiveWindow%\n {<\/code><\/pre>\n\n\n\n<p>So we can use the area in the loop, before the IF statement to go and find what the ahk_ID &amp; Process ID of a specific program is with the following AutoHotKey function, where we give it the program name that we want to find the active, but not necessarily current Active window (ie it can be minimised or not actively open (left pane or right pane in a split screen)) of that particular program : <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code> WinGet, ff_hWind, PID, ahk_exe Firefox.exe<\/code><\/pre>\n\n\n\n<p>So what this will return is the PID, which will be the same if you have 2 or 3 instances open of the same program, unlike ahk_ID which is different for every open instance of a program. <\/p>\n\n\n\n<p>One program that doesn&#8217;t work this way is explorer.exe, the reason being, if you have no instances of File Explorer open there will still be a PID. So when you call the PID it gives you the underlying Process, not the current open windows. So the way to manage this is by class as it is &#8220;CabinetWClass&#8221;. Explorer is a unique case. <\/p>\n\n\n\n<p>At the end of the day, we are not really interested in the PID or ahk_ID, as these will change across computers and active programs, what we want is to identify the correct active program, and we use the PID to find this, and then use a logic such as :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>      if activePID = %tb_hWind% \n            { \n            prog = tb ; thunderbird\n        FileContent = `n%Duration%,%LogTime%,%LogWindow%,%prog%,\n                }\n        if activePID = %ob_hWind%\n                { \n                prog = ob  ; obsidian\n            FileContent = `n%Duration%,%LogTime%,%LogWindow%,%prog%,\n                        }\n         if (activeClass != CabinetWClass) and (activePID = )\n               { \n                prog = ?? \n            FileContent = `n%Duration%,%LogTime%,%LogWindow%,%prog%,\n                                            } <\/code><\/pre>\n\n\n\n<p>Where we compare the activeWindowPID with ones we have obtained for specific programs , and if they match, then we add an abbreviation to indicate what program that is. If there isn&#8217;t a match then we can just put a different marker to indicate its not one of the specific programs we are monitoring, say &#8220;Paint.exe&#8221; that you use very rarely. By giving them a specific mark, in this case &#8220;??&#8221; we can cluster these together and review them later to see if we want to add the specific activity to the timesheet. Or see if we are stating to use other programs more actively, in which case we can specifically monitor them. Their only unique identifier will be by Win Title. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Duration of Activities <\/h3>\n\n\n\n<p>We are not really interested in Start Time of an activity, but rather start AND end times, so we can calculate duration of how long we were doing a task. So we need some method of getting the end time of a project. To do this in the loop in the program we need to hold the Time Variable <strong>A_Now() <\/strong>that gives current time ( In fact we will use <strong>A_NowUTC()<\/strong> that gives us the information in seconds-(we could use something like SetTimer but I&#8217;ve been using Epoch time and it has more information in it)).  <\/p>\n\n\n\n<p>So every 10 seconds the program repeats the loop of checking to see if currentActiveWindow is the same as LastActiveWindow, and as soon as that changes then it logs a new line to file, and adds the current <strong>A_Now()<\/strong> to the new line in the log as the start time. So it only gets the <strong>A_Now()<\/strong> if it meets the IF statement requirements to create a new line. <\/p>\n\n\n\n<p>So in that inner IF statement we can also add a variable, EndTime = %A_NowUTC% , which was the start time of the current log line. Because its inside of the IF statement, it\u2019s a local variable and its reset every time that IF statement is exited, so we have to convert it into a GLOBAL variable, as we do in the code, by assigning global EndTime before the loop begins. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>global EndTime\n\nLoop\n{\n Sleep %SleepTime%\n \n WinGetActiveTitle, ActiveWindow\n StoreActiveWindow = %ActiveWindow%\n\n \n If ActiveWindow != %LastActiveWindow%\n {\n FormatTime, LogTime,, HH:mm:ss\n ;Other stuff happens here ........\n;We write the times to be appended to the file here\n        FileContent = `n%A_NowUTC% ,%EndTime% ,%Duration%,  %Dur%, %LogTime% ,  %LogWindow% , %activeClass%, %activeID%, %activePID%,    \n ; NOTE- We write EndTime to file before we define it below! Otherwise Start &amp; ;End Times would be the same\n  \n  FileAppend, %FileContent%, %LogFile%\n  LastActiveWindow = %ActiveWindow%\n\n      EndTime = %A_NowUTC%  ; assign start time to EndTime var for next loop, so know when previous time completed\n  ;msgbox, EndTime =%EndTime% \n      }\n  }\nExit<\/code><\/pre>\n\n\n\n<p>For duration and time conversion from Seconds to Hours:Minutes:Seconds we can use the following<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  Duration := % A_NowUTC - EndTime\n\n            if (Duration == \"\")\n            Duration := 0\n            \n\n        Duration := Floor(Duration)\n        Hours := Floor(Duration\/3600)\n        Minutes := Floor((Duration - (Hours * 3600)) \/ 60)\n        Seconds := Duration - (Hours * 3600) - (Minutes * 60)\n        Dur = %Hours%:%Minutes%:%Seconds%<\/code><\/pre>\n\n\n\n<p>This will calculate the Duration in seconds of time spent on that particular activity and also Dur in H:M:S for that activity, we don&#8217;t actually need the Dur as we will be doing some more maths, summing up categories of activities later, so we will convert to H:M:S at a later time, its just added here for an example on how to convert seconds to H:M:S. <\/p>\n\n\n\n\n\n\n\n<p>A little curly thing about this is the Duration relates to the activity that has already been logged into the file, and we only calculate it when we are about to write the new line in the file for the new activity.  So how to we attach the duration to the correct activity? <\/p>\n\n\n\n<p>The way we do this is when we write the next activity we put the DURATION first, and then start a new line. That way the duration is tagged onto the end of the previous activity.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>FileContent = %Duration% `n%A_NowUTC%,%LogTime%,%LogWindow%,%sURL%,%prog%, <\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Not adding headers to file at this point <\/h3>\n\n\n\n<p>When I transfer file to be processed with Python Pandas it comes into Dataframe without a header, and so header needs to be added for columns so that the data can be better manipulated. <\/p>\n\n\n\n<p>If I tried adding Headers while logging, after writing the header I&#8217;d need to start on a new line to ensure that the duration is not added as an extra column in the dataframe. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Regex stuff <\/h3>\n\n\n\n<p>In original code there is a line of regex conversion, see below, that I removed, I have since replaced it. The reason for this is that in the titles of articles etc there can be a &#8220;comma&#8221; which will add another column to a file so it becomes messy to process in Python. Maybe there is a way to filter out just the &#8220;comma&#8221;. This may need exploring. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>LogWindow := Regexreplace(ActiveWindow, \"&#91;^a-zA-Z0-9]\", \" \")<\/code><\/pre>\n\n\n\n<p>At some point I expect I&#8217;ll have to go in and do some string editing on titles to get further information for the timesheet. <\/p>\n\n\n\n\n\n\n\n<h3 class=\"wp-block-heading\">Getting URL for Browser <\/h3>\n\n\n\n<p>One thing that I saw in the Python logger video was that if you were in browser Hallden also got the URL of the site he was visiting. He used some string editing to get the specific site, so if it was something like &#8216;stackOverflow&#8221; he would classify that as an activity to add to his timesheet, but if it was say &#8220;Fox News&#8221; he would ingnore it. <\/p>\n\n\n\n<p>This brings about 2 issues: <\/p>\n\n\n\n<p>1\/ You are adding another column of data to one program, but not to others, this has an impact  when transferring across to Python Panda&#8217;s dataframes which will give an error with uneven column numbers, so for other progams you have to add another empty comma for the added column. This is only picked up when I tested the data by trying to process it in Python. <\/p>\n\n\n\n<p>2\/ Getting the URL programmatically from the browser. This I had several attempts at, as it can be different for other browsers. In the end I found, on the AHK forum <strong><a rel=\"noreferrer noopener\" href=\"https:\/\/www.autohotkey.com\/board\/topic\/111114-get-the-url-of-the-current-active-browser-tab\/\" target=\"_blank\">a class that could be used to do this<\/a><\/strong> and so I used that. Prior to that I&#8217;d have to go to active window and, for firefox use F6 or !d to focus the cursor in the address bar, then copy the contents to clipboard and later paste the clipboard contents into the variable for appending to the file, it worked, to an extent but it was a specific firefox solution. The class method was much better. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Switching between programs to test output <\/h3>\n\n\n\n<p>After I&#8217;d roughly sorted out the output that gets written to a daily file, I was able to look at importing it into Python Dataframes to look at how I could themn process the output. <\/p>\n\n\n\n<p>I had quite a lot of debugging to do to get the format right, to ensure that the data was clean and uploaded neatly into Pandas dataframes. I&#8217;m still working on this. <\/p>\n\n\n\n\n\n\n\n<p> Currently, as &#8220;DURATION&#8221; is written on a line and then a new line created, when starting the file at the beginning there is a &#8220;0&#8221; on the top line. So importing into dataFrames it reads the file as having only one column and this is not correct.  So I&#8217;ll have to find a method to either add a header at the beginning of the file or find a method to delete the first row of the file at some point. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Current code script <\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>#NoEnv\n#SingleInstance, Force\nSendMode, Input\nSetBatchLines, -1\nSetWorkingDir, %A_ScriptDir%\n\n; Variables\n; ---------\nAppLoggingRate = 10 ; Time interval (in seconds) between active window title captures.\nSleepTime := AppLoggingRate * 1000\nLogPath = %A_ScriptDir%\nLastActiveWindow = \n\n global EndTime\n global prog\n ModernBrowsers := \"ApplicationFrameWindow,Chrome_WidgetWin_0,Chrome_WidgetWin_1,Maxthon3Cls_MainFrm,MozillaWindowClass,Slimjet_WidgetWin_1\"\nLegacyBrowsers := \"IEFrame,OperaWindowClass\"\n\nFormatTime, LogFilename, , yyyy-MM-dd\n            \nLogFilename = %LogFilename%_AppLog.csv\nLogFile = %LogPath%\\%LogFilename%\n\nif !FileExist(LogFile)\nFileAppend, %FileHeader%, %LogFile%\n\n; Logic\n; -----\nLoop\n{\n        WinGetActiveTitle, ActiveWindow\n        StoreActiveWindow = %ActiveWindow%\n        WinGet, activePID, PID, A\n        WinGet, activeID, ID, A\n        WinGetClass, activeClass, A\n        WinGet, ff_hWind, PID, ahk_exe firefox.exe\n        WinGet, co_hWind, PID, ahk_exe code.exe\n        WinGet, tb_hWind, PID, ahk_exe thunderbird.exe\n        WinGet, ob_hWind, PID, ahk_exe obsidian.exe\n        WinGet, npp_hWind, PID, ahk_exe notepad++.exe\n        WinGet, np_hWind, PID, ahk_exe notepad.exe\n        WinGet, xl_hWind, PID, ahk_exe excel.exe\n        WinGet, wd_hWind, PID, ahk_exe word.exe\n        \n        ;msgbox,  co_hWind %co_hWind%  tb_hWind %tb_hWind% ob_hWind %ob_hWind%  npp_hWind %npp_hWind%  xl_hWind %xl_hWind% \n\n        If ActiveWindow != %LastActiveWindow%\n        {\n\n            FormatTime, LogTime,, HH:mm:ss\n            FormatTime, LogFilename, , yyyy-MM-dd\n            \n            LogWindow := Regexreplace(ActiveWindow, \"&#91;^a-zA-Z0-9]\", \" \")\n            LogFilename = %LogFilename%_AppLog.csv\n            LogFile = %LogPath%\\%LogFilename%\n            \n            Duration := % A_NowUTC - EndTime\n\n            if (Duration == \"\")\n            Duration := 0\n            \n        Duration := Floor(Duration)\n        Hours := Floor(Duration\/3600)\n        Minutes := Floor((Duration - (Hours * 3600)) \/ 60)\n        Seconds := Duration - (Hours * 3600) - (Minutes * 60)\n        Dur = %Hours%:%Minutes%:%Seconds%\n\n        if activePID = %ff_hWind%    \n            {\n        sURL := GetActiveBrowserURL()\n        \n        prog=ff\n        \n        FileContent = %Duration% `n%A_NowUTC%,%LogTime%,%LogWindow%,%sURL%,%prog%,  \n\n            } \n        if activeClass = CabinetWClass\n            { \n                prog = ex \n            FileContent = %Duration% `n%A_NowUTC%,%LogTime%,%LogWindow%,,%prog%,\n                                            }     \n        if activePID = %co_hWind% \n            {\n            prog = cd \n        FileContent = %Duration% `n%A_NowUTC%,%LogTime%,%LogWindow%,,%prog%,\n                }\n        if activePID = %tb_hWind% \n            { \n            prog = tb ; thunderbird\n        FileContent = %Duration% `n%A_NowUTC%,%LogTime%,%LogWindow%,,%prog%,\n                }\n        if activePID = %ob_hWind%\n                { \n                prog = ob  ; obsidian\n            FileContent = %Duration% `n%A_NowUTC%,%LogTime%,%LogWindow%,,%prog%,\n                        }\n        \n        if activePID = %xl_hWind% \n            {  \n                prog =xl  ;Excel\n            FileContent = %Duration% `n%A_NowUTC%,%LogTime%,%LogWindow%,,%prog%,\n                }\n        if activePID = %wd_hWind% \n            {  \n                prog = wd \n            FileContent = %Duration% `n%A_NowUTC%,%LogTime%,%LogWindow%,,%prog%,\n                }\n        if activePID = %npp_hWind%\n            { \n                prog = pn ;plus notes , notepad++\n            FileContent = %Duration% `n%A_NowUTC%,%LogTime%,%LogWindow%,,%prog%,\n               } \n        if activePID = %np_hWind% \n        {  \n            prog = np    \n        FileContent = %Duration% `n%A_NowUTC%,%LogTime%,%LogWindow%,,%prog%,\n            }                \n        if activePID = \n           { \n            prog = 00 \n        FileContent = %Duration% `n%A_NowUTC%,%LogTime%,%LogWindow%,,%prog%,\n            }  \n \n        sleep 50\n        FileAppend, %FileContent%, %LogFile%\n        LastActiveWindow = %ActiveWindow%\n            EndTime = %A_NowUTC%  ; assign start time to EndTime var for next loop, so know when previous time completed\n  \n            }\n        }\nExit\n\n\n\n;CLASSESfor Browser\nGetActiveBrowserURL() {\n\tglobal ModernBrowsers, LegacyBrowsers\n\tWinGetClass, sClass, A\n\tIf sClass In % ModernBrowsers\n\t\tReturn GetBrowserURL_ACC(sClass)\n\tElse If sClass In % LegacyBrowsers\n\t\tReturn GetBrowserURL_DDE(sClass) ; empty string if DDE not supported (or not a browser)\n\tElse\n\t\tReturn \"\"\n}\n\n; \"GetBrowserURL_DDE\" adapted from DDE code by Sean, (AHK_L version by maraskan_user)\n; Found at http:\/\/autohotkey.com\/board\/topic\/17633-\/?p=434518\n\nGetBrowserURL_DDE(sClass) {\n\tWinGet, sServer, ProcessName, % \"ahk_class \" sClass\n\tStringTrimRight, sServer, sServer, 4\n\tiCodePage := A_IsUnicode ? 0x04B0 : 0x03EC ; 0x04B0 = CP_WINUNICODE, 0x03EC = CP_WINANSI\n\tDllCall(\"DdeInitialize\", \"UPtrP\", idInst, \"Uint\", 0, \"Uint\", 0, \"Uint\", 0)\n\thServer := DllCall(\"DdeCreateStringHandle\", \"UPtr\", idInst, \"Str\", sServer, \"int\", iCodePage)\n\thTopic := DllCall(\"DdeCreateStringHandle\", \"UPtr\", idInst, \"Str\", \"WWW_GetWindowInfo\", \"int\", iCodePage)\n\thItem := DllCall(\"DdeCreateStringHandle\", \"UPtr\", idInst, \"Str\", \"0xFFFFFFFF\", \"int\", iCodePage)\n\thConv := DllCall(\"DdeConnect\", \"UPtr\", idInst, \"UPtr\", hServer, \"UPtr\", hTopic, \"Uint\", 0)\n\thData := DllCall(\"DdeClientTransaction\", \"Uint\", 0, \"Uint\", 0, \"UPtr\", hConv, \"UPtr\", hItem, \"UInt\", 1, \"Uint\", 0x20B0, \"Uint\", 10000, \"UPtrP\", nResult) ; 0x20B0 = XTYP_REQUEST, 10000 = 10s timeout\n\tsData := DllCall(\"DdeAccessData\", \"Uint\", hData, \"Uint\", 0, \"Str\")\n\tDllCall(\"DdeFreeStringHandle\", \"UPtr\", idInst, \"UPtr\", hServer)\n\tDllCall(\"DdeFreeStringHandle\", \"UPtr\", idInst, \"UPtr\", hTopic)\n\tDllCall(\"DdeFreeStringHandle\", \"UPtr\", idInst, \"UPtr\", hItem)\n\tDllCall(\"DdeUnaccessData\", \"UPtr\", hData)\n\tDllCall(\"DdeFreeDataHandle\", \"UPtr\", hData)\n\tDllCall(\"DdeDisconnect\", \"UPtr\", hConv)\n\tDllCall(\"DdeUninitialize\", \"UPtr\", idInst)\n\tcsvWindowInfo := StrGet(&amp;sData, \"CP0\")\n\tStringSplit, sWindowInfo, csvWindowInfo, `\" ;\"; comment to avoid a syntax highlighting issue in autohotkey.com\/boards\n\tReturn sWindowInfo2\n}\n\nGetBrowserURL_ACC(sClass) {\n\tglobal nWindow, accAddressBar\n\tIf (nWindow != WinExist(\"ahk_class \" sClass)) ; reuses accAddressBar if it's the same window\n\t{\n\t\tnWindow := WinExist(\"ahk_class \" sClass)\n\t\taccAddressBar := GetAddressBar(Acc_ObjectFromWindow(nWindow))\n\t}\n\tTry sURL := accAddressBar.accValue(0)\n\tIf (sURL == \"\") {\n\t\tWinGet, nWindows, List, % \"ahk_class \" sClass ; In case of a nested browser window as in the old CoolNovo (TO DO: check if still needed)\n\t\tIf (nWindows &gt; 1) {\n\t\t\taccAddressBar := GetAddressBar(Acc_ObjectFromWindow(nWindows2))\n\t\t\tTry sURL := accAddressBar.accValue(0)\n\t\t}\n\t}\n\tIf ((sURL != \"\") and (SubStr(sURL, 1, 4) != \"http\")) ; Modern browsers omit \"http:\/\/\"\n\t\tsURL := \"http:\/\/\" sURL\n\tIf (sURL == \"\")\n\t\tnWindow := -1 ; Don't remember the window if there is no URL\n\tReturn sURL\n}\n\n; \"GetAddressBar\" based in code by uname\n; Found at http:\/\/autohotkey.com\/board\/topic\/103178-\/?p=637687\n\nGetAddressBar(accObj) {\n\tTry If ((accObj.accRole(0) == 42) and IsURL(accObj.accValue(0)))\n\t\tReturn accObj\n\tTry If ((accObj.accRole(0) == 42) and IsURL(\"http:\/\/\" accObj.accValue(0))) ; Modern browsers omit \"http:\/\/\"\n\t\tReturn accObj\n\tFor nChild, accChild in Acc_Children(accObj)\n\t\tIf IsObject(accAddressBar := GetAddressBar(accChild))\n\t\t\tReturn accAddressBar\n}\n\nIsURL(sURL) {\n\tReturn RegExMatch(sURL, \"^(?&lt;Protocol&gt;https?|ftp):\/\/(?&lt;Domain&gt;(?:&#91;\\w-]+\\.)+\\w\\w+)(?::(?&lt;Port&gt;\\d+))?\/?(?&lt;Path&gt;(?:&#91;^:\/?# ]*\/?)+)(?:\\?(?&lt;Query&gt;&#91;^#]+)?)?(?:\\#(?&lt;Hash&gt;.+)?)?$\")\n}\n\n; The code below is part of the Acc.ahk Standard Library by Sean (updated by jethrow)\n; Found at http:\/\/autohotkey.com\/board\/topic\/77303-\/?p=491516\n\nAcc_Init()\n{\n\tstatic h\n\tIf Not h\n\t\th:=DllCall(\"LoadLibrary\",\"Str\",\"oleacc\",\"Ptr\")\n}\nAcc_ObjectFromWindow(hWnd, idObject = 0)\n{\n\tAcc_Init()\n\tIf DllCall(\"oleacc\\AccessibleObjectFromWindow\", \"Ptr\", hWnd, \"UInt\", idObject&amp;=0xFFFFFFFF, \"Ptr\", -VarSetCapacity(IID,16)+NumPut(idObject==0xFFFFFFF0?0x46000000000000C0:0x719B3800AA000C81,NumPut(idObject==0xFFFFFFF0?0x0000000000020400:0x11CF3C3D618736E0,IID,\"Int64\"),\"Int64\"), \"Ptr*\", pacc)=0\n\tReturn ComObjEnwrap(9,pacc,1)\n}\nAcc_Query(Acc) {\n\tTry Return ComObj(9, ComObjQuery(Acc,\"{618736e0-3c3d-11cf-810c-00aa00389b71}\"), 1)\n}\nAcc_Children(Acc) {\n\tIf ComObjType(Acc,\"Name\") != \"IAccessible\"\n\t\tErrorLevel := \"Invalid IAccessible Object\"\n\tElse {\n\t\tAcc_Init(), cChildren:=Acc.accChildCount, Children:=&#91;]\n\t\tIf DllCall(\"oleacc\\AccessibleChildren\", \"Ptr\",ComObjValue(Acc), \"Int\",0, \"Int\",cChildren, \"Ptr\",VarSetCapacity(varChildren,cChildren*(8+2*A_PtrSize),0)*0+&amp;varChildren, \"Int*\",cChildren)=0 {\n\t\t\tLoop %cChildren%\n\t\t\t\ti:=(A_Index-1)*(A_PtrSize*2+8)+8, child:=NumGet(varChildren,i), Children.Insert(NumGet(varChildren,i-8)=9?Acc_Query(child):child), NumGet(varChildren,i-8)=9?ObjRelease(child):\n\t\t\tReturn Children.MaxIndex()?Children:\n\t\t} Else\n\t\t\tErrorLevel := \"AccessibleChildren DllCall Failed\"\n\t}\n}<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" data-src=\"https:\/\/max-drake.cc\/wp-content\/uploads\/2023\/02\/2023-02-08_AppLog.csv-AHK-Working-Visual-Studio-Code49_26.jpg\" alt=\"\" class=\"wp-image-9464 lazyload\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 1437px; --smush-placeholder-aspect-ratio: 1437\/1232;\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">End Comment <\/h3>\n\n\n\n<p>This script is still in development and will change based on what happens with the Python Dataframe analysis of the information. <\/p>\n\n\n\n<p>Overall the logging process is simpler, it is just gathering the data, but adapting it to the full requirements of a time sheet process will require the processing of the gathered data. That is on-going at present and the next area of focus for the project. <\/p>\n\n\n\n\n","protected":false},"excerpt":{"rendered":"<p>In Auto Logger and timesheet exploration with AutoHotKey and Python Part 1 I gave an overview of the project, in this article I want to go into the development of the background process of auto-logging computer activity on the PC. My initial start, as mentioned in the previous article was to start with the code [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":9535,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[466,204,3,42],"tags":[],"class_list":["post-9454","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-autohotkey","category-automation","category-data-extraction","category-productivity"],"featured_image_src":"https:\/\/max-drake.cc\/wp-content\/uploads\/2023\/02\/1.jpg","featured_image_src_square":"https:\/\/max-drake.cc\/wp-content\/uploads\/2023\/02\/1.jpg","author_info":{"display_name":"Max Drake","author_link":"https:\/\/max-drake.cc\/?author=1"},"_links":{"self":[{"href":"https:\/\/max-drake.cc\/index.php?rest_route=\/wp\/v2\/posts\/9454","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/max-drake.cc\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/max-drake.cc\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/max-drake.cc\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/max-drake.cc\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=9454"}],"version-history":[{"count":7,"href":"https:\/\/max-drake.cc\/index.php?rest_route=\/wp\/v2\/posts\/9454\/revisions"}],"predecessor-version":[{"id":9466,"href":"https:\/\/max-drake.cc\/index.php?rest_route=\/wp\/v2\/posts\/9454\/revisions\/9466"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/max-drake.cc\/index.php?rest_route=\/wp\/v2\/media\/9535"}],"wp:attachment":[{"href":"https:\/\/max-drake.cc\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=9454"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/max-drake.cc\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=9454"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/max-drake.cc\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=9454"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}