It is currently March 28th, 2024, 3:06 pm

Rainmeter CPU util slowly rising over time

Get help with installing and using Rainmeter.
User avatar
balala
Rainmeter Sage
Posts: 16109
Joined: October 11th, 2010, 6:27 pm
Location: Gheorgheni, Romania

Re: Rainmeter CPU util slowly rising over time

Post by balala »

rlnoonan wrote:I do think there was something about that skin that wasn't visible on the screen. It may have been misbehaving and consuming more resources over time.
If you want, post the code of that skin. Let's see how does it look (just to satisfy my curiosity).
User avatar
rlnoonan
Posts: 26
Joined: July 30th, 2018, 10:37 pm

Re: Rainmeter CPU util slowly rising over time

Post by rlnoonan »

I really hate to say it, but I'm not certain what skin it was. I am pretty sure that it was ModernGadgets/Config, but when I went to get that skin's code I found that there are a number of sub-categories under Config and I didn't look close enough when I was unloading the skin (did it via the right click on the notification icon instead of through the application itself, so I didn't have to navigate the hierarchy of the skins).

However, I went through all of the ModernGadgets/Config skins and loaded each one, one at a time. Sure enough, the Setup skin does not show up on the screen. So I'm pretty sure the skin in question is ModernGadgets/Config/Setup (version 1.3.1). I have attached the Config.ini file associated with that skin. Is that the code you are looking for? If not, can you point me to where I would find the code? Also, here is the section for that skin in Rainmeter.ini:

[ModernGadgets\Config\Setup]
Active=0
WindowX=835
WindowY=525
ClickThrough=0
Draggable=1
SnapEdges=1
KeepOnScreen=1
AlwaysOnTop=0

A further update: after another 24 hours the Rainmeter utilization is holding steady at about 2%. So I definitely think the problem has been eliminated. I'll try a couple of those other experiments now. I did already try exiting RainMeter and then running it again, without that skin loaded. The utilization does not seem to go back to 1%, but picks up pretty much where it left off (but the memory allocated does seem to reset). Next I'll see if logging off works the same as a system restart.
Attachments
Config.ini
(7.97 KiB) Downloaded 31 times
User avatar
balala
Rainmeter Sage
Posts: 16109
Joined: October 11th, 2010, 6:27 pm
Location: Gheorgheni, Romania

Re: Rainmeter CPU util slowly rising over time

Post by balala »

rlnoonan wrote:Sure enough, the Setup skin does not show up on the screen.
Are you sure? Because it is for me, see the attached image. That is how does it look like.
Just to be sure, did you talk about the ModernGadgets\Config\Setup\Config.ini skin (I hope I didn't misunderstand)?
Attachments
ModernGadgets.png
User avatar
rlnoonan
Posts: 26
Joined: July 30th, 2018, 10:37 pm

Re: Rainmeter CPU util slowly rising over time

Post by rlnoonan »

Yes, that is the correct skin that I was referring to. I know it sounds bizarre, but I just tried it again to be sure and when the skin is loaded, I am not seeing it at all. I'm pretty sure I had seen that same window back when I first installed the skin and was messing around with it because I recognize the image you posted. Is it possible that something has gone wrong with the skin and that is why it was consuming more and more CPU (and probably memory too)? Or is it possible it has somehow been moved off the visible boundaries of the screen and that is why I can't see it?
User avatar
balala
Rainmeter Sage
Posts: 16109
Joined: October 11th, 2010, 6:27 pm
Location: Gheorgheni, Romania

Re: Rainmeter CPU util slowly rising over time

Post by balala »

rlnoonan wrote:I know it sounds bizarre, but I just tried it again to be sure and when the skin is loaded, I am not seeing it at all. I'm pretty sure I had seen that same window back when I first installed the skin and was messing around with it because I recognize the image you posted.
No, it doesn't sound bizarre, because the Config.ini skin is written into a bizarre way (if I can say so). When you click Close, the skin isn't unloaded, but just hidden. That's why once you close it, it is kept loaded, but never shown up again (I admit I didn't enter too deeply to see how it could be shown up again, but nor did I find an easy way to do it). And as such it indeed can cause CPU usage increase.
From my point of view, it's a weirdly written code. I can't say what was the author's intention, what he did want to achieve with a such action.
User avatar
balala
Rainmeter Sage
Posts: 16109
Joined: October 11th, 2010, 6:27 pm
Location: Gheorgheni, Romania

Re: Rainmeter CPU util slowly rising over time

Post by balala »

Here is what you have to do to show up the Config.ini. First close it manually (right click the Rainmeter icon in Notification Area, the go to Skins -> ModernGadgets -> Config -> Setup and uncheck Config.ini. Open the skin's .ini file and modify the value of the loaded variable to 0, within the [Variables] section:

Code: Select all

[Variables]
...
loaded=0
Now if try to load the skin, it is shown up (well probably initially an "Import settings backup?" question is shown, but if you click any of the Yes or No strings, the main window appears).
Unfortunately every time you're loading the skin, the loaded variable is rewritten, so you have to modify it manually every time. Probably I am missing something and there is a good reason of this variable, but I couldn't find it yet. Maybe you could ask the author what was his intention with that variable.
User avatar
rlnoonan
Posts: 26
Joined: July 30th, 2018, 10:37 pm

Re: Rainmeter CPU util slowly rising over time

Post by rlnoonan »

I was out of the office last week (so my computer was off most of the time) and spent this week watching my CPU utilization rise again. Today, after about 4 days of uptime the utilization had reached about 8-9% so I decided it definitely wasn't normal and I needed to start digging again. A few observations:

- I am now running with the SysInfo package with 38 different skins. Previously, when I thought I had this figured out and pinned to the "invisible" skin, I wasn't using this package for simplicity (a pain to unload 38 skins).
- I did test the logout vs. system restart and found that RainMeter dropped from 8-9% down to about 5% after a logout. A system restart brings it down to 1-2%, so logging out improves things, but something is still hanging around.
- I decided to start unloading the SysInfo skins one by one. Sure enough, I stumbled on three skins that seem to be the problem ones. I can load and unload these skins and watch the utilization rise and fall by around 3% each (did this before the logout test above). The three skins are:
- SysInfo->Strings->Software->TopProcess->Top-Process-Percent-Column.ini
- SysInfo->Strings->Software->Processes->Processes-Left.ini
- SysInfo->Strings->Software->Threads->Threads-Left.ini
- I'm now running with my normal layout minus these three skins, which includes the other 35 SysInfo skins, and things seem to be holding steady at 1-2%.
- I did try your suggestion about editing the config.ini file and it worked exactly as you said. It now shows up correctly if I edit that file each time. Thanks for the tip.

So there definitely seemed to be an issue with that "invisible" skin, but I think the real culprit may be these three skins. I'll keep my eye on things over the weekend.

In the meantime, is there anything that I can do to try to figure out (and hopefully fix) what might be going on with these skins? I really like the SysInfo package and top process is one of the pieces of info I really want, so I'd love to at least fix that one. I don't know anything about developing skins (I do have plenty of coding experience though). Is it all contained in the .ini files where I could experiment? Or is there other code elsewhere that I likely wouldn't have access to?

As always, thanks for the help.
User avatar
balala
Rainmeter Sage
Posts: 16109
Joined: October 11th, 2010, 6:27 pm
Location: Gheorgheni, Romania

Re: Rainmeter CPU util slowly rising over time

Post by balala »

Thanks rlnoonanfor for your trust, but unfortunately (for you) I can't answer now, because I'm not home and as such have no access to my computer. If someone else doesn't answer you, I will when I get home, around the middle of next week.
User avatar
raiguard
Posts: 660
Joined: June 25th, 2015, 7:02 pm
Location: The Sky, USA
Contact:

Re: Rainmeter CPU util slowly rising over time

Post by raiguard »

Ah, it's a good thing that I looked at this thread. I KNEW having an invisible skin that runs in the background would eventually cause some problems... :( (I'm the author of the suite in question).

Before I continue, it's important to mention that everything I'm going to say will be based off a newer version of the suite than what you have. You have v1.3.1, I'm running v1.4.0-rc.3 (the most recent beta release). It would be a good idea for you to get this version, which you can do by clicking here, or by clicking the "Opt-in to development testing" toggle in the global settings skin.

There are significant differences between the .INI you provided and the most recent revision of it, so here is the updated code:

Code: Select all

[Rainmeter]
MiddleMouseDownAction=[!Refresh]
Group=ModernGadgets | ModernGadgetsSetup
; LeftMouseUpAction=[!CommandMeasure MeasureSettingsBackupScript "ImportBackup()"]
; RightMouseUpAction=[!ShowMeterGroup Essentials][!ShowMeterGroup ImportBackupPrompt][!Redraw]
; OnRefreshAction=[!CommandMeasure MeasureUpdateCheckerScript "CheckForUpdate('1.1.2', '1.2.0')"]
; OnRefreshAction=[!ShowMeterGroup Essentials][!ShowMeterGroup ImportBackupPrompt][!SetOption BackgroundHeightAdjuster Y R][!UpdateMeter BackgroundHeightAdjuster][!UpdateMeterGroup Essentials][!Redraw][!ShowFade]
; OnRefreshAction=[!CommandMeasure MeasureSettingsBackupScript "CheckForBackup()"]
; OnRefreshAction=[!Move "((#SCREENAREAWIDTH# / 2) - (#CURRENTCONFIGWIDTH# / 2))" "((#SCREENAREAHEIGHT# / 2) - (#CURRENTCONFIGHEIGHT# / 2))"]
AccurateText=1

[Metadata]
Name=Setup
Author=raiguard
Information=Background skin for ModernGadgets, manages settings backups and update checking.
License=Creative Commons Attribution-NonCommercial-ShareAlike 3.0
Version=1.4.0-rc.3

; ========= Variables and Styles =========

[Variables]
@includeStyleSheet=#@#StyleSheet.inc
@includeGlobalSettings=#@#Settings\GlobalSettings.inc

bgWidth=250
bgHeight=149
loaded=1
contentMarginAbs=4

updateCheckRate=30
webParserUpdateCheckRate=(#updateCheckRate# * 60)

; ==================================================
;  MEASURES
; ==================================================

[MeasureSettingsBackupScript]
Measure=Script
ScriptFile=#@#Scripts\SettingsBackups.lua

[MeasureUpdateCheckerScript]
Measure=Script
ScriptFile=#@#Scripts\UpdateChecker.lua
CheckForPrereleases=#checkForPrereleases#
;UpToDateAction=[!Log "ModernGadgets is up-to-date" "Notice"]
;DevAction=[!Log "Dev version" "Notice"]
UpdateAvailableAction=[!Log "An update is available!" "Notice"][!CommandMeasure MeasureCreateBackup "Run"][!Hide][!HideMeterGroup ImportBackupPrompt][!UpdateMeterGroup UpdateAvailable][!ShowMeterGroup UpdateAvailable][!UpdateMeter BackgroundHeightAdjuster][!UpdateMeterGroup Essentials][!UpdateMeter Background][!ShowMeterGroup Essentials][!Redraw][!EnableMeasure MeasureMove][!UpdateMeasure MeasureMove]
ParsingErrorAction=[!Log "Error parsing version numbers" "Error"]

[MeasureMove]
Measure=Calc
Formula=1
IfCondition=1
IfTrueAction=[!Move "((#SCREENAREAWIDTH# / 2) - (#CURRENTCONFIGWIDTH# / 2))" "((#SCREENAREAHEIGHT# / 2) - (#CURRENTCONFIGHEIGHT# / 2))"][!ShowFade][!DisableMeasure MeasureMove]
DynamicVariables=1
Disabled=1

[MeasureUpdateCheck]
Measure=WebParser
URL=https://api.github.com/repos/raiguard/ModernGadgets/releases
; URL=file://#@#TestRemote.txt
RegExp=(?siU)^(.*)$
StringIndex=1
UpdateRate=#webParserUpdateCheckRate#
OnConnectErrorAction=[!Log "Couldn't connect to update server" "Error"]
FinishAction=[!CommandMeasure MeasureUpdateCheckerScript "CheckForUpdate('#mgVersion#', 'MeasureUpdateCheck')"]
DynamicVariables=1
Disabled=(#notifyUpdates# = 0)

[MeasureCreateBackup]
Measure=Plugin
Plugin=RunCommand
Parameter=xcopy /s "#@#Settings" "#SETTINGSPATH#ModernGadgetsSettings" /Y
FinishAction=[!Log "Created settings backup" "Notice"]
IfCondition=(#loaded# = 0)
IfTrueAction=[!CommandMeasure MeasureCopyDefaults "Run"][!WriteKeyValue Variables loaded 1][!CommandMeasure MeasureSettingsBackupScript "CheckForBackup()"]

[MeasureSettingsBackup]
Measure=Calc
IfCondition=(#autoBackups# = 1)
IfTrueAction=[!CommandMeasure MeasureCreateBackup "Run"][!DisableMeasure MeasureSettingsBackup]
IfFalseAction=[!DisableMeasure MeasureSettingsBackup]
Disabled=1
DynamicVariables=1

[MeasureCopyDefaults]
Measure=Plugin
Plugin=RunCommand
Parameter=xcopy /s "#@#Settings" "#SETTINGSPATH#ModernGadgetsSettings\Defaults" /Y
FinishAction=[!Log "Copied defaults" "Notice"]

[MeasureDownloadUpdate]
Measure=WebParser
URL=[&MeasureUpdateCheckerScript:GetReleaseInfo('downloadUrl')]
Download=1
OnConnectErrorAction=[!CommandMeasure MeasureDownloading "Stop 1"][!HideMeter MeterDownloadingUpdate][!ShowMeter MeterDownloadUpdateButtonLabel][!SetOption MeterDownloadUpdateButtonLabel Text "ERROR!"][!UpdateMeter MeterDownloadUpdateButtonLabel][!Redraw]
OnDownloadErrorAction=[!CommandMeasure MeasureDownloading "Stop 1"][!HideMeter MeterDownloadingUpdate][!ShowMeter MeterDownloadUpdateButtonLabel][!SetOption MeterDownloadUpdateButtonLabel Text "ERROR!"][!UpdateMeter MeterDownloadUpdateButtonLabel][!Redraw]
FinishAction=[!CommandMeasure MeasureDownloading "Stop 1"][!HideMeter MeterDownloadingUpdate][!ShowMeter MeterDownloadUpdateButtonLabel][!SetOption MeterDownloadUpdateButtonLabel Text "Installing..."][!UpdateMeter MeterDownloadUpdateButtonLabel][!Redraw]["[MeasureDownloadUpdate]"]
DynamicVariables=1
Disabled=1

[MeasureDownloading]
Measure=Plugin
Plugin=ActionTimer
ActionList1=ActionDownloading0 | Wait 500 | ActionDownloading1 | Wait 500 | ActionDownloading2 | Wait 500 | ActionDownloading3 | Wait 500 | DoOver
ActionDownloading0=[!SetOption MeterDownloadingUpdate Text "Downloading"][!UpdateMeter MeterDownloadingUpdate][!Redraw]
ActionDownloading1=[!SetOption MeterDownloadingUpdate Text "Downloading."][!UpdateMeter MeterDownloadingUpdate][!Redraw]
ActionDownloading2=[!SetOption MeterDownloadingUpdate Text "Downloading.."][!UpdateMeter MeterDownloadingUpdate][!Redraw]
ActionDownloading3=[!SetOption MeterDownloadingUpdate Text "Downloading..."][!UpdateMeter MeterDownloadingUpdate][!Redraw]
DoOver=[!CommandMeasure MeasureDownloading "Execute 1"]

; ==================================================
;  METERS
; ==================================================

[Background]
Meter=Image
MeterStyle=StyleBackground
Group=Essentials
Hidden=1

[CloseButton]
Meter=Image
MeterStyle=StyleCloseButton
LeftMouseUpAction=[!HideFade]
Hidden=1
Group=UpdateAvailable

[UpdateAvailableLabelString]
Meter=String
MeterStyle=StyleString
FontSize=14
StringAlign=Center
X=#contentMarginCenter#
Y=20
Text=Update available!
Hidden=1
Group=UpdateAvailable

[ChangelogString]
Meter=String
MeterStyle=StyleString | StyleStringParagraph
FontSize=8
FontColor=180,180,180
X=(#contentMargin# + 4)
Text=[&MeasureUpdateCheckerScript:GetReleaseInfo('changelog')]
DynamicVariables=1
Group=UpdateAvailable
Hidden=1

[MeterDownloadUpdateButtonLabel]
Meter=String
MeterStyle=StyleString | StyleStringArrowButtonLabel
Text=Download
Hidden=1
Group=UpdateAvailable
DynamicVariables=1

[DownloadUpdateButton]
Meter=Image
MeterStyle=StyleArrowButton
MouseOverAction=[!SetOption DownloadUpdateButton ImageTint "#colorButtonPress#"][!UpdateMeter DownloadUpdateButton][!Redraw]
MouseLeaveAction=[!SetOption DownloadUpdateButton ImageTint ""][!UpdateMeter DownloadUpdateButton][!Redraw]
LeftMouseUpAction=[!HideMeter MeterDownloadUpdateButtonLabel][!ShowMeter MeterDownloadingUpdate][!CommandMeasure MeasureDownloading "Execute 1"][!EnableMeasure MeasureDownloadUpdate][!UpdateMeasure MeasureDownloadUpdate]
Hidden=1
Group=UpdateAvailable
DynamicVariables=1

[MeterDownloadingUpdate]
Meter=String
MeterStyle=StyleString | StyleStringArrowButtonLabel
StringAlign=Left
X=-90r
Y=r
Text=Downloading
Hidden=1

[NeverShowAgainString]
Meter=String
MeterStyle=StyleString
FontColor=#colorAccent#
X=#contentMargin# + 4
Y=4r
Text=Never show this again
MouseOverAction=[!SetOption NeverShowAgainString FontColor "#colorButtonPress#"][!UpdateMeter NeverShowAgainString][!Redraw]
MouseLeaveAction=[!SetOption NeverShowAgainString FontColor "#colorAccent#"][!UpdateMeter NeverShowAgainString][!Redraw]
LeftMouseUpAction=[!WriteKeyValue Variables notifyUpdates 0 "#globalSettingsPath#"][!DeactivateConfig]
ToolTipText=Update notifications will be turned off and this skin will unload
Hidden=1
Group=UpdateAvailable

[ImportBackupLabelString]
Meter=String
MeterStyle=StyleString
FontSize=14
StringAlign=Center
X=#contentMarginCenter#
Y=20
Text=Import settings backup?
Hidden=1
Group=ImportBackupPrompt

[ImportBackupInfoString]
Meter=String
MeterStyle=StyleString | StyleStringInfo
FontSize=10
Y=5R
W=(#contentWidth# - 20)
ClipString=2
Text=This will restore your customizations and update the gadgets to reflect them#CRLF##CRLF#NOTICE: THIS UPDATE CHANGES THE HWiNFO SETTINGS FORMAT. EVEN IF YOU IMPORT THE BACKUP, YOU WILL STILL NEED TO RECONFIGURE YOUR HWiNFO IDs!
InlinePattern=NOTICE.*
InlineSetting=Color | #colorButtonPress#
Hidden=1
Group=ImportBackupPrompt

[ImportBackupYesLabelString]
Meter=String
MeterStyle=StyleString | StyleStringArrowButtonLabel
Y=5R
Text=Yes
Text=Import settings backup?
Hidden=1
Group=ImportBackupPrompt

[ImportBackupYesButton]
Meter=Image
MeterStyle=StyleArrowButton
MouseOverAction=[!SetOption ImportBackupYesButton ImageTint "#colorButtonPress#"][!UpdateMeter ImportBackupYesButton][!Redraw]
MouseLeaveAction=[!SetOption ImportBackupYesButton ImageTint "#colorAccent#"][!UpdateMeter ImportBackupYesButton][!Redraw]
LeftMouseUpAction=[!CommandMeasure MeasureSettingsBackupScript "ImportBackup()"][!HideFade][!HideMeterGroup ImportBackupPrompt][!ActivateConfig "ModernGadgets\Config\GadgetManager" "Config.ini"]
Hidden=1
Group=ImportBackupPrompt

[ImportBackupNoLabelString]
Meter=String
MeterStyle=StyleString | StyleStringArrowButtonLabel
Text=No
Text=Import settings backup?
Hidden=1
Group=ImportBackupPrompt

[ImportBackupNoButton]
Meter=Image
MeterStyle=StyleArrowButton
MouseOverAction=[!SetOption ImportBackupNoButton ImageTint "#colorButtonPress#"][!UpdateMeter ImportBackupNoButton][!Redraw]
MouseLeaveAction=[!SetOption ImportBackupNoButton ImageTint "#colorAccent#"][!UpdateMeter ImportBackupNoButton][!Redraw]
LeftMouseUpAction=[!HideFade][!HideMeterGroup ImportBackupPrompt][!ActivateConfig "ModernGadgets\Config\GadgetManager" "Config.ini"]
Hidden=1
Group=ImportBackupPrompt

[BackgroundHeightAdjuster]
Meter=Image
Y=([NeverShowAgainString:Y] + [NeverShowAgainString:H])
DynamicVariables=1
Group=UpdateAvailable

[BackgroundHeight]
Meter=Image
MeterStyle=StyleackgroundHeight
Y=(#contentMarginAbs# + #bgOffset# + 3)R
Group=Essentials
Anyway, you are actually seeing two separate skins. The one you provided the .INI file for is the "Setup" skin, which runs in the background constantly. It periodically checks for updates as well as creates settings backups for use when you update ModernGadgets. The second one that has the "load gadgets" toggles is the Gadget Manager.

Here is ModernGadgets\Config\GadgetManager and ModernGadgets\Config\Setup side-by-side:
2018-08-16 20_16_46-Window.png
As for why it is invisible, when the skin loads, it is invisible by default. It only shows itself for two reasons: If you have just loaded it for the first time (loaded=0, which causes it to show the "import settings backup" prompt), or if there is an update available, in which case it'll show something like this:
2018-08-16 20_35_22-Window.png
As for the rising CPU usage, there are two possible things that could be causing it: The settings backups or the update checking. The settings backups are performed with the following measures and script:

Code: Select all

[MeasureSettingsBackupScript]
Measure=Script
ScriptFile=#@#Scripts\SettingsBackups.lua

[MeasureCreateBackup]
Measure=Plugin
Plugin=RunCommand
Parameter=xcopy /s "#@#Settings" "#SETTINGSPATH#ModernGadgetsSettings" /Y
FinishAction=[!Log "Created settings backup" "Notice"]
IfCondition=(#loaded# = 0)
IfTrueAction=[!CommandMeasure MeasureCopyDefaults "Run"][!WriteKeyValue Variables loaded 1][!CommandMeasure MeasureSettingsBackupScript "CheckForBackup()"]

Code: Select all

-- MODERNGADGETS SETTINGS BACKUP SCRIPT
--
-- This script makes backups of the settings files every two hours, which
-- prevents them from being lost when updating the suite.

debug = false

function Initialize()

  dofile(SKIN:GetVariable('scriptPath') .. 'Utilities.lua')

  fileNames = { 'GlobalSettings.inc',
                'CpuSettings.inc',
                'NetworkSettings.inc',
                'GpuSettings.inc',
                'DisksSettings.inc',
                'ProcessSettings.inc',
                'ChronometerSettings.inc',
                'GPU Variants\\GpuSettings1.inc',
                'GPU Variants\\GpuSettings2.inc',
                'GPU Variants\\GpuSettings3.inc'  }


  backupsPath = SKIN:GetVariable('SETTINGSPATH') .. 'ModernGadgetsSettings\\'

  filesPath = SKIN:GetVariable('@') .. 'Settings\\'

  cpuMeterPath = SKIN:GetVariable('cpuMeterPath')
  networkMeterPath = SKIN:GetVariable('networkMeterPath')
  gpuMeterPath = SKIN:GetVariable('gpuMeterPathBase')
  disksMeterPath = SKIN:GetVariable('disksMeterPath')

end 

function Update() end

function ImportBackup()

  for i=1, 10 do
    local bTable = ReadIni(backupsPath .. fileNames[i])
    local sTable = ReadIni(filesPath .. fileNames[i])
    CrossCheck(bTable, sTable, filesPath .. fileNames[i])
  end
  
  SKIN:Bang('!RefreshGroup', 'MgImportRefresh')

  LogHelper('Imported settings backup', 'Notice')

end

function CrossCheck(bTable, sTable, filePath)

  for i,v in pairs(bTable) do
    if type(v) == 'table' then
      for a,b in pairs(v) do
        if sTable[i][a] then
          SKIN:Bang('!WriteKeyValue', i, a, b, filePath)
        else
          LogHelper('Key \'' .. a .. '\' does not exist in local', 'Debug')
        end
      end
    end
  end

end

function CheckForBackup()

  local file = io.open(backupsPath .. fileNames[1])
  if file == nil then
    SKIN:Bang('!ActivateConfig', 'ModernGadgets\\Config\\GadgetManager', 'Config.ini')
    SKIN:Bang('!CommandMeasure', 'MeasureCreateBackup', 'Run')
  else
    SKIN:Bang('!Hide')
    SKIN:Bang('!ShowMeterGroup', 'Essentials')
    SKIN:Bang('!ShowMeterGroup', 'ImportBackupPrompt')
    SKIN:Bang('!SetOption', 'BackgroundHeightAdjuster', 'Y', 'R')
    SKIN:Bang('!UpdateMeter', 'BackgroundHeightAdjuster')
    SKIN:Bang('!UpdateMeterGroup', 'Essentials')
    SKIN:Bang('!Redraw')
    SKIN:Bang('!EnableMeasure', 'MeasureMove')
    SKIN:Bang('!UpdateMeasure', 'MeasureMove')
    file:close()
  end
  
end

-- parses a INI formatted text file into a 'Table[Section][Key] = Value' table
function ReadIni(inputfile)
   local file = assert(io.open(inputfile, 'r'), 'Unable to open ' .. inputfile)
   local tbl, num, section = {}, 0

   for line in file:lines() do
      num = num + 1
      if not line:match('^%s-;') then
         local key, command = line:match('^([^=]+)=(.+)')
         if line:match('^%s-%[.+') then
            section = line:match('^%s-%[([^%]]+)')
            if section == '' or not section then
               section = nil
               LogHelper('Empty section name found in ' .. inputfile, 'Debug')
            end
            if not tbl[section] then tbl[section] = {} end
         elseif key and command and section then
            tbl[section][key:match('^%s*(%S*)%s*$')] = command:match('^%s*(.-)%s*$'):gsub('#(.-)#', '#\*%1\*#')
         elseif #line > 0 and section and not key or command then
            LogHelper(num .. ': Invalid property or value.', 'Debug')
         end
      end
   end

   file:close()
   if not section then LogHelper('No sections found in ' .. inputfile, 'Debug') end
   
   return tbl
end
And the update check is performed with the following measures and script:

Code: Select all

[MeasureUpdateCheckerScript]
Measure=Script
ScriptFile=#@#Scripts\UpdateChecker.lua
CheckForPrereleases=#checkForPrereleases#
;UpToDateAction=[!Log "ModernGadgets is up-to-date" "Notice"]
;DevAction=[!Log "Dev version" "Notice"]
UpdateAvailableAction=[!Log "An update is available!" "Notice"][!CommandMeasure MeasureCreateBackup "Run"][!Hide][!HideMeterGroup ImportBackupPrompt][!UpdateMeterGroup UpdateAvailable][!ShowMeterGroup UpdateAvailable][!UpdateMeter BackgroundHeightAdjuster][!UpdateMeterGroup Essentials][!UpdateMeter Background][!ShowMeterGroup Essentials][!Redraw][!EnableMeasure MeasureMove][!UpdateMeasure MeasureMove]
ParsingErrorAction=[!Log "Error parsing version numbers" "Error"]

[MeasureMove]
Measure=Calc
Formula=1
IfCondition=1
IfTrueAction=[!Move "((#SCREENAREAWIDTH# / 2) - (#CURRENTCONFIGWIDTH# / 2))" "((#SCREENAREAHEIGHT# / 2) - (#CURRENTCONFIGHEIGHT# / 2))"][!ShowFade][!DisableMeasure MeasureMove]
DynamicVariables=1
Disabled=1

[MeasureUpdateCheck]
Measure=WebParser
; URL=https://api.github.com/repos/raiguard/ModernGadgets/releases
URL=file://#CURRENTPATH#TestRemote.txt
RegExp=(?siU)^(.*)$
StringIndex=1
UpdateRate=#webParserUpdateCheckRate#
OnConnectErrorAction=[!Log "Couldn't connect to update server" "Error"]
FinishAction=[!CommandMeasure MeasureUpdateCheckerScript "CheckForUpdate('#mgVersion#', 'MeasureUpdateCheck')"]
DynamicVariables=1
Disabled=(#notifyUpdates# = 0)

Code: Select all

--[[
--------------------------------------------------

Update Checker for Rainmeter
v6.1.0
By raiguard

Implements 'semver.lua' by kikito (https://github.com/kikito/semver.lua)
Implements 'JSON.lua' by rxi (https://github.com/rxi/json.lua)

--------------------------------------------------

Release Notes:
v6.1.0 - 2018-7-4
- Added 'RawChangelog' option
v6.0.0 - 2018-7-2
- Switched to use GitHub REST v3 API, rather than a customized INI file
v5.1.0 - 2018-6-21
- Switched to using the WebParser measure output rather than a downloaded file
  for the ReadINI function
v5.0.0 - 2017-12-20
- Removed hard-coded actions and replaced with arguments in the script measure
- Switched to using INI format for the remote version data
- Added 'GetIniValue' function for retrieving other information from remote
v4.0.0 - 2016-11-18
- Implemented "semver.lua" for more robust comparisons
v3.0.0 - 2016-11-14
- Added support for update checking on development versions
v2.1.0 - 2016-11-14
- Fixed oversight where if the user is on a development version for an
  outdated release, it would not return UpdateAvailable()
- Added 'ParsingError' return
v2.0.0 - 2016-8-4
- Removed dependancy on an output meter in favor of hard-coded actions, added
  more documentation
v1.0.1 - 2016-7-1
- Optimized gmatch function, more debug functionality
v1.0.0 - 2016-1-25
- Initial release

--------------------------------------------------

This script compares two semver-formatted version strings and compares them,
returning whether the current version is outdated, up-to-date, or a development
version. By default, it uses the GitHub REST API to download release information,
automatically extracting the most recent release from the specified repository.
However, you do not need to use this if you have another method for retrieving
the most recently released version.

Please keep in mind that version strings must be formatted using the Semantic
Versioning 2.0.0 format. See http://semver.org/ for additional information.

--------------------------------------------------

INSTRUCTIONS FOR USE:

[MeasureUpdateCheckerScript]
Measure=Script
Script=#@#Scripts\UpdateChecker.lua
CheckForPrereleases=#checkForPrereleases#
UpToDateAction=[!ShowMeter "UpToDateString"]
DevAction=[!ShowMeter "DevString"]
UpdateAvailableAction=[!ShowMeter "UpdateAvailableString"]
ParsingErrorAction=[!ShowMeter "ParsingErrorString"]

This is an example of the script measure you will use to invoke this script.
The 'CheckForPrereleases' defines whether the update checker will compare
with the most recent full release, or the most recent release whatsoever.
Each action option is a series of bangs to execute when that outcome is
reached by the comparison function.

[MeasureUpdateCheck]
Measure=WebParser
URL=#webParserUrl#
RegExp=(?siU)^(.*)$
StringIndex=1
UpdateRate=#updateCheckRate#
OnConnectErrorAction=[!Log "Couldn't connect to update server" "Error"]
FinishAction=[!CommandMeasure MeasureUpdateCheckerScript "CheckForUpdate('#version#', 'MeasureUpdateCheck')"]
DynamicVariables=1

This is an example of the webparser measure used to download the information.
The important thing to note here is the last argument on the 'FinishAction'
line. This must be the name of the WebParser measure, whatever that may
be. This allows the script to actually retrieve the string that was downloaded,
which lets the update check actually take place.

The script retrieves the raw JSON data from the GitHub API, then uses a JSON
parser function to convert it into a LUA table. From there, the script extracts
data for the most recent full release, and the most recent release regardless
of whether or not it is a prerelease. Which release is compared to and provides
the release information is determined by the 'CheckForPrereleases' option in the
script measure.

There are four values for each release that you can retrieve for displaying in
your skin. Each value is retrieved using
[&MeasureUpdateCheckerScript:GetReleaseInfo('key')]. Replace key with one of the
following options:

'name' - returns the tag version of the release
'date' - returns the published date of the release
'changelog' - returns the release's changelog, with the release version and
              published date added to the beginning
'downloadUrl' - returns the URL that can be used to download the .RMSKIN
                attached to the release

Please note that any meter that extracts information from the script in this way
will need 'DynamicVariables=1' set in order to function properly.

By default, the changelog will include the release's tag version and published
date attached to the beginning of the string. If you wish to disable this, add
'RawChangelog=1' to the script measure.

--------------------------------------------------
]]--

debug = false

function Initialize()

  upToDateAction = SELF:GetOption('UpToDateAction')
  updateAvailableAction = SELF:GetOption('UpdateAvailableAction')
  parsingErrorAction = SELF:GetOption('ParsingErrorAction')
  devAction = SELF:GetOption('DevAction')
  rawChangelog = tonumber(SELF:GetOption('RawChangelog', '0'))
  checkForPrereleases = tonumber(SELF:GetOption('CheckForPrereleases', '1'))
  if devAction == '' or devAction == nil then devAction = upToDateAction end
  printIndent = ' '
  releases = {}

end

function Update() end

function CheckForUpdate(cVersion, measureName)

  showPrereleases = tonumber(SELF:GetOption('ShowPrereleases', 1))
  apiJson = json.decode(SKIN:GetMeasure(measureName or 'MeasureUpdateCheck'):GetStringValue())
  releases = AssembleReleaseInfo(apiJson)

  Compare(cVersion, releases[checkForPrereleases + 1]['name'])

end

-- compares two semver-formatted version strings
function Compare(cVersion, rVersion)

  cVersion = v(cVersion)
  rVersion = v(rVersion)
  if cVersion == rVersion then
    LogHelper('Up-to-date', 'Debug')
    SKIN:Bang(upToDateAction)
  elseif cVersion > rVersion then
    LogHelper('Development version', 'Debug')
    SKIN:Bang(devAction)
  elseif cVersion < rVersion then
    LogHelper('Update available', 'Debug')
    SKIN:Bang(updateAvailableAction)
  else
    LogHelper('Parsing error', 'Debug')
    SKIN:Bang(parsingErrorAction)
  end

end

function GetReleaseInfo(key)
  return releases[checkForPrereleases + 1] and releases[checkForPrereleases + 1][key] or '---'
end

function AssembleReleaseInfo(jsonTable)

  local releases = {}

  local i = 0
  local k = 0
  while i == 0 do
    k = k + 1
    if jsonTable[k]['prerelease'] == false then
      i = 1
      releases[1] = {}
      releases[1]['name'] = jsonTable[k]['tag_name']:gsub('v', '')
      releases[1]['date'] = jsonTable[k]['published_at']:gsub('(.*)T(.*)', '%1')
      releases[1]['changelog'] = (rawChangelog == 0 and  'v' .. releases[1]['name'] .. ' - ' .. releases[1]['date'] .. '\n' or '') .. jsonTable[k]['body']:gsub('\r\n', '\n')
      releases[1]['downloadUrl'] = jsonTable[k]['assets'][1]['browser_download_url']
    end
  end

  releases[2] = {}
  releases[2]['name'] = jsonTable[1]['tag_name']:gsub('v', '')
  releases[2]['date'] = jsonTable[1]['published_at']:gsub('(.*)T(.*)', '%1')
  releases[2]['changelog'] = (rawChangelog == 0 and  'v' .. releases[2]['name'] .. ' - ' .. releases[2]['date'] .. '\n' or '') .. jsonTable[1]['body']:gsub('\r\n', '\n')
  releases[2]['downloadUrl'] = jsonTable[1]['assets'][1]['browser_download_url']
  return releases

end

-- function to make logging messages less cluttered
function LogHelper(message, type)

  if type == nil then type = 'Debug' end

  if debug == true then
    SKIN:Bang("!Log", message, type)
  elseif type ~= 'Debug' then
    SKIN:Bang("!Log", message, type)
  end

end


--[[
  --------------------------------------------------

  SEMVER.LUA
  By kitito

  MIT LICENSE

  Copyright (c) 2015 Enrique García Cota

  Permission is hereby granted, free of charge, to any person obtaining a
  copy of tother software and associated documentation files (the
  "Software"), to deal in the Software without restriction, including
  without limitation the rights to use, copy, modify, merge, publish,
  distribute, sublicense, and/or sell copies of the Software, and to
  permit persons to whom the Software is furnished to do so, subject to
  the following conditions:

  The above copyright notice and tother permission notice shall be included
  in all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

  --------------------------------------------------
]]--


local function checkPositiveInteger(number, name)
  assert(number >= 0, name .. ' must be a valid positive number')
  assert(math.floor(number) == number, name .. ' must be an integer')
end

local function present(value)
  return value and value ~= ''
end

-- splitByDot("a.bbc.d") == {"a", "bbc", "d"}
local function splitByDot(str)
  str = str or ""
  local t, count = {}, 0
  str:gsub("([^%.]+)", function(c)
    count = count + 1
    t[count] = c
  end)
  return t
end

local function parsePrereleaseAndBuildWithSign(str)
  local prereleaseWithSign, buildWithSign = str:match("^(-[^+]+)(+.+)$")
  if not (prereleaseWithSign and buildWithSign) then
    prereleaseWithSign = str:match("^(-.+)$")
    buildWithSign      = str:match("^(+.+)$")
  end
  assert(prereleaseWithSign or buildWithSign, ("The parameter %q must begin with + or - to denote a prerelease or a build"):format(str))
  return prereleaseWithSign, buildWithSign
end

local function parsePrerelease(prereleaseWithSign)
  if prereleaseWithSign then
    local prerelease = prereleaseWithSign:match("^-(%w[%.%w-]*)$")
    assert(prerelease, ("The prerelease %q is not a slash followed by alphanumerics, dots and slashes"):format(prereleaseWithSign))
    return prerelease
  end
end

local function parseBuild(buildWithSign)
  if buildWithSign then
    local build = buildWithSign:match("^%+(%w[%.%w-]*)$")
    assert(build, ("The build %q is not a + sign followed by alphanumerics, dots and slashes"):format(buildWithSign))
    return build
  end
end

local function parsePrereleaseAndBuild(str)
  if not present(str) then return nil, nil end

  local prereleaseWithSign, buildWithSign = parsePrereleaseAndBuildWithSign(str)

  local prerelease = parsePrerelease(prereleaseWithSign)
  local build = parseBuild(buildWithSign)

  return prerelease, build
end

local function parseVersion(str)
  local sMajor, sMinor, sPatch, sPrereleaseAndBuild = str:match("^(%d+)%.?(%d*)%.?(%d*)(.-)$")
  assert(type(sMajor) == 'string', ("Could not extract version number(s) from %q"):format(str))
  local major, minor, patch = tonumber(sMajor), tonumber(sMinor), tonumber(sPatch)
  local prerelease, build = parsePrereleaseAndBuild(sPrereleaseAndBuild)
  return major, minor, patch, prerelease, build
end


-- return 0 if a == b, -1 if a < b, and 1 if a > b
local function compare(a,b)
  return a == b and 0 or a < b and -1 or 1
end

local function compareIds(myId, otherId)
  if myId == otherId then return  0
  elseif not myId    then return -1
  elseif not otherId then return  1
  end

  local selfNumber, otherNumber = tonumber(myId), tonumber(otherId)

  if selfNumber and otherNumber then -- numerical comparison
    return compare(selfNumber, otherNumber)
  -- numericals are always smaller than alphanums
  elseif selfNumber then
    return -1
  elseif otherNumber then
    return 1
  else
    return compare(myId, otherId) -- alphanumerical comparison
  end
end

local function smallerIdList(myIds, otherIds)
  local myLength = #myIds
  local comparison

  for i=1, myLength do
    comparison = compareIds(myIds[i], otherIds[i])
    if comparison ~= 0 then
      return comparison == -1
    end
    -- if comparison == 0, continue loop
  end

  return myLength < #otherIds
end

local function smallerPrerelease(mine, other)
  if mine == other or not mine then return false
  elseif not other then return true
  end

  return smallerIdList(splitByDot(mine), splitByDot(other))
end

local methods = {}

function methods:nextMajor()
  return semver(self.major + 1, 0, 0)
end
function methods:nextMinor()
  return semver(self.major, self.minor + 1, 0)
end
function methods:nextPatch()
  return semver(self.major, self.minor, self.patch + 1)
end

local mt = { __index = methods }
function mt:__eq(other)
  return self.major == other.major and
         self.minor == other.minor and
         self.patch == other.patch and
         self.prerelease == other.prerelease
         -- notice that build is ignored for precedence in semver 2.0.0
end
function mt:__lt(other)
  if self.major ~= other.major then return self.major < other.major end
  if self.minor ~= other.minor then return self.minor < other.minor end
  if self.patch ~= other.patch then return self.patch < other.patch end
  return smallerPrerelease(self.prerelease, other.prerelease)
  -- notice that build is ignored for precedence in semver 2.0.0
end
-- This works like the "pessimisstic operator" in Rubygems.
-- if a and b are versions, a ^ b means "b is backwards-compatible with a"
-- in other words, "it's safe to upgrade from a to b"
function mt:__pow(other)
  if self.major == 0 then
    return self == other
  end
  return self.major == other.major and
         self.minor <= other.minor
end
function mt:__tostring()
  local buffer = { ("%d.%d.%d"):format(self.major, self.minor, self.patch) }
  if self.prerelease then table.insert(buffer, "-" .. self.prerelease) end
  if self.build      then table.insert(buffer, "+" .. self.build) end
  return table.concat(buffer)
end

function v(major, minor, patch, prerelease, build)
  assert(major, "At least one parameter is needed")

  if type(major) == 'string' then
    major,minor,patch,prerelease,build = parseVersion(major)
  end
  patch = patch or 0
  minor = minor or 0

  checkPositiveInteger(major, "major")
  checkPositiveInteger(minor, "minor")
  checkPositiveInteger(patch, "patch")

  local result = {major=major, minor=minor, patch=patch, prerelease=prerelease, build=build}
  return setmetatable(result, mt)
end

--
-- json.lua
--
-- Copyright (c) 2018 rxi
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
--

json = { _version = "0.1.1" }

-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------

local encode

local escape_char_map = {
  [ "\\" ] = "\\\\",
  [ "\"" ] = "\\\"",
  [ "\b" ] = "\\b",
  [ "\f" ] = "\\f",
  [ "\n" ] = "\\n",
  [ "\r" ] = "\\r",
  [ "\t" ] = "\\t",
}

local escape_char_map_inv = { [ "\\/" ] = "/" }
for k, v in pairs(escape_char_map) do
  escape_char_map_inv[v] = k
end


local function escape_char(c)
  return escape_char_map[c] or string.format("\\u%04x", c:byte())
end


local function encode_nil(val)
  return "null"
end


local function encode_table(val, stack)
  local res = {}
  stack = stack or {}

  -- Circular reference?
  if stack[val] then error("circular reference") end

  stack[val] = true

  if val[1] ~= nil or next(val) == nil then
    -- Treat as array -- check keys are valid and it is not sparse
    local n = 0
    for k in pairs(val) do
      if type(k) ~= "number" then
        error("invalid table: mixed or invalid key types")
      end
      n = n + 1
    end
    if n ~= #val then
      error("invalid table: sparse array")
    end
    -- Encode
    for i, v in ipairs(val) do
      table.insert(res, encode(v, stack))
    end
    stack[val] = nil
    return "[" .. table.concat(res, ",") .. "]"

  else
    -- Treat as an object
    for k, v in pairs(val) do
      if type(k) ~= "string" then
        error("invalid table: mixed or invalid key types")
      end
      table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
    end
    stack[val] = nil
    return "{" .. table.concat(res, ",") .. "}"
  end
end


local function encode_string(val)
  return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end


local function encode_number(val)
  -- Check for NaN, -inf and inf
  if val ~= val or val <= -math.huge or val >= math.huge then
    error("unexpected number value '" .. tostring(val) .. "'")
  end
  return string.format("%.14g", val)
end


local type_func_map = {
  [ "nil"     ] = encode_nil,
  [ "table"   ] = encode_table,
  [ "string"  ] = encode_string,
  [ "number"  ] = encode_number,
  [ "boolean" ] = tostring,
}


encode = function(val, stack)
  local t = type(val)
  local f = type_func_map[t]
  if f then
    return f(val, stack)
  end
  error("unexpected type '" .. t .. "'")
end


function json.encode(val)
  return ( encode(val) )
end


-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------

local parse

local function create_set(...)
  local res = {}
  for i = 1, select("#", ...) do
    res[ select(i, ...) ] = true
  end
  return res
end

local space_chars   = create_set(" ", "\t", "\r", "\n")
local delim_chars   = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars  = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals      = create_set("true", "false", "null")

local literal_map = {
  [ "true"  ] = true,
  [ "false" ] = false,
  [ "null"  ] = nil,
}


local function next_char(str, idx, set, negate)
  for i = idx, #str do
    if set[str:sub(i, i)] ~= negate then
      return i
    end
  end
  return #str + 1
end


local function decode_error(str, idx, msg)
  local line_count = 1
  local col_count = 1
  for i = 1, idx - 1 do
    col_count = col_count + 1
    if str:sub(i, i) == "\n" then
      line_count = line_count + 1
      col_count = 1
    end
  end
  error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end


local function codepoint_to_utf8(n)
  -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
  local f = math.floor
  if n <= 0x7f then
    return string.char(n)
  elseif n <= 0x7ff then
    return string.char(f(n / 64) + 192, n % 64 + 128)
  elseif n <= 0xffff then
    return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
  elseif n <= 0x10ffff then
    return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
                       f(n % 4096 / 64) + 128, n % 64 + 128)
  end
  error( string.format("invalid unicode codepoint '%x'", n) )
end


local function parse_unicode_escape(s)
  local n1 = tonumber( s:sub(3, 6),  16 )
  local n2 = tonumber( s:sub(9, 12), 16 )
  -- Surrogate pair?
  if n2 then
    return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
  else
    return codepoint_to_utf8(n1)
  end
end


local function parse_string(str, i)
  local has_unicode_escape = false
  local has_surrogate_escape = false
  local has_escape = false
  local last
  for j = i + 1, #str do
    local x = str:byte(j)

    if x < 32 then
      decode_error(str, j, "control character in string")
    end

    if last == 92 then -- "\\" (escape char)
      if x == 117 then -- "u" (unicode escape sequence)
        local hex = str:sub(j + 1, j + 5)
        if not hex:find("%x%x%x%x") then
          decode_error(str, j, "invalid unicode escape in string")
        end
        if hex:find("^[dD][89aAbB]") then
          has_surrogate_escape = true
        else
          has_unicode_escape = true
        end
      else
        local c = string.char(x)
        if not escape_chars[c] then
          decode_error(str, j, "invalid escape char '" .. c .. "' in string")
        end
        has_escape = true
      end
      last = nil

    elseif x == 34 then -- '"' (end of string)
      local s = str:sub(i + 1, j - 1)
      if has_surrogate_escape then
        s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
      end
      if has_unicode_escape then
        s = s:gsub("\\u....", parse_unicode_escape)
      end
      if has_escape then
        s = s:gsub("\\.", escape_char_map_inv)
      end
      return s, j + 1

    else
      last = x
    end
  end
  decode_error(str, i, "expected closing quote for string")
end


local function parse_number(str, i)
  local x = next_char(str, i, delim_chars)
  local s = str:sub(i, x - 1)
  local n = tonumber(s)
  if not n then
    decode_error(str, i, "invalid number '" .. s .. "'")
  end
  return n, x
end


local function parse_literal(str, i)
  local x = next_char(str, i, delim_chars)
  local word = str:sub(i, x - 1)
  if not literals[word] then
    decode_error(str, i, "invalid literal '" .. word .. "'")
  end
  return literal_map[word], x
end


local function parse_array(str, i)
  local res = {}
  local n = 1
  i = i + 1
  while 1 do
    local x
    i = next_char(str, i, space_chars, true)
    -- Empty / end of array?
    if str:sub(i, i) == "]" then
      i = i + 1
      break
    end
    -- Read token
    x, i = parse(str, i)
    res[n] = x
    n = n + 1
    -- Next token
    i = next_char(str, i, space_chars, true)
    local chr = str:sub(i, i)
    i = i + 1
    if chr == "]" then break end
    if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
  end
  return res, i
end


local function parse_object(str, i)
  local res = {}
  i = i + 1
  while 1 do
    local key, val
    i = next_char(str, i, space_chars, true)
    -- Empty / end of object?
    if str:sub(i, i) == "}" then
      i = i + 1
      break
    end
    -- Read key
    if str:sub(i, i) ~= '"' then
      decode_error(str, i, "expected string for key")
    end
    key, i = parse(str, i)
    -- Read ':' delimiter
    i = next_char(str, i, space_chars, true)
    if str:sub(i, i) ~= ":" then
      decode_error(str, i, "expected ':' after key")
    end
    i = next_char(str, i + 1, space_chars, true)
    -- Read value
    val, i = parse(str, i)
    -- Set
    res[key] = val
    -- Next token
    i = next_char(str, i, space_chars, true)
    local chr = str:sub(i, i)
    i = i + 1
    if chr == "}" then break end
    if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
  end
  return res, i
end


local char_func_map = {
  [ '"' ] = parse_string,
  [ "0" ] = parse_number,
  [ "1" ] = parse_number,
  [ "2" ] = parse_number,
  [ "3" ] = parse_number,
  [ "4" ] = parse_number,
  [ "5" ] = parse_number,
  [ "6" ] = parse_number,
  [ "7" ] = parse_number,
  [ "8" ] = parse_number,
  [ "9" ] = parse_number,
  [ "-" ] = parse_number,
  [ "t" ] = parse_literal,
  [ "f" ] = parse_literal,
  [ "n" ] = parse_literal,
  [ "[" ] = parse_array,
  [ "{" ] = parse_object,
}


parse = function(str, idx)
  local chr = str:sub(idx, idx)
  local f = char_func_map[chr]
  if f then
    return f(str, idx)
  end
  decode_error(str, idx, "unexpected character '" .. chr .. "'")
end


function json.decode(str)
  if type(str) ~= "string" then
    error("expected argument of type string, got " .. type(str))
  end
  local res, idx = parse(str, next_char(str, 1, space_chars, true))
  idx = next_char(str, idx, space_chars, true)
  if idx <= #str then
    decode_error(str, idx, "trailing garbage")
  end
  return res
end


return json
I honestly don't know how these things could be causing the rising CPU usage with this skin, but I hope this info can help. If you need clarifications or more info, I'll do my best to accommodate.

If it helps, you can find the complete source code of the suite here.
shoek
Posts: 32
Joined: March 10th, 2017, 1:47 am

Re: Rainmeter CPU util slowly rising over time

Post by shoek »

For what it is worth, I also see RainMeter CPU usage rising over time (I keep my PC on all the time and only reboot for Windows Updates monthly). I usually see it creeping up after 5 days or so of continuous use.

I do not run ModernGadgets / SysInfo
I do run SilverAzide's Gadgets, however, which are similar: CPU/GPU usage, Network, Disk, etc.

My suspicion is that it has something to do with the performance monitoring that these gadgets do. Along with RainMeter having high CPU usage, I also see WMIPrvSE.exe, which is the WMI service that is used for performance stats.

As mentioned by others, simply exiting RainMeter and restarting does not cure the CPU usage issue; it starts out higher (5-8%, but I've seen it at 15-30% before as well) than it does after a reboot (1-2% normally) in that scenario.

-shoek
Post Reply