├── .gitattributes ├── .gitignore ├── SteamNotificationsTray.sln ├── SteamNotificationsTray ├── AppIcon.ico ├── CredentialStore.cs ├── IconUtils.cs ├── LoginForm.Designer.cs ├── LoginForm.cs ├── LoginForm.resx ├── NotificationCounts.cs ├── NotificationsClient.cs ├── NotificationsMenuColorTable.cs ├── NotificationsMenuRenderer.cs ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── SettingsForm.Designer.cs ├── SettingsForm.cs ├── SettingsForm.resx ├── SteamNotificationsTray.csproj ├── TrayAppContext.cs ├── TrayAppPopup.cs ├── WebLogin │ ├── Models │ │ ├── DoLoginRequest.cs │ │ ├── DoLoginResponse.cs │ │ ├── GetRsaKeyResponse.cs │ │ └── TransferParameters.cs │ └── SteamWebLogin.cs ├── app.config ├── graphics │ ├── inbox_account_alert.png │ ├── inbox_async_game.png │ ├── inbox_comments.png │ ├── inbox_gifts.png │ ├── inbox_invites.png │ ├── inbox_items.png │ ├── inbox_moderator_message.png │ ├── inbox_notification.ico │ ├── inbox_notification_inactive.ico │ ├── inbox_notification_inactive_disabled.ico │ ├── inbox_offlinemessages.png │ └── inbox_tradeoffers.png ├── packages.config └── thirdparty │ └── Cyotek.Windows.Forms.ColorPicker.dll └── readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.pch 68 | *.pdb 69 | *.pgc 70 | *.pgd 71 | *.rsp 72 | *.sbr 73 | *.tlb 74 | *.tli 75 | *.tlh 76 | *.tmp 77 | *.tmp_proj 78 | *.log 79 | *.vspscc 80 | *.vssscc 81 | .builds 82 | *.pidb 83 | *.svclog 84 | *.scc 85 | 86 | # Chutzpah Test files 87 | _Chutzpah* 88 | 89 | # Visual C++ cache files 90 | ipch/ 91 | *.aps 92 | *.ncb 93 | *.opendb 94 | *.opensdf 95 | *.sdf 96 | *.cachefile 97 | *.VC.db 98 | *.VC.VC.opendb 99 | 100 | # Visual Studio profiler 101 | *.psess 102 | *.vsp 103 | *.vspx 104 | *.sap 105 | 106 | # Visual Studio Trace Files 107 | *.e2e 108 | 109 | # TFS 2012 Local Workspace 110 | $tf/ 111 | 112 | # Guidance Automation Toolkit 113 | *.gpState 114 | 115 | # ReSharper is a .NET coding add-in 116 | _ReSharper*/ 117 | *.[Rr]e[Ss]harper 118 | *.DotSettings.user 119 | 120 | # JustCode is a .NET coding add-in 121 | .JustCode 122 | 123 | # TeamCity is a build add-in 124 | _TeamCity* 125 | 126 | # DotCover is a Code Coverage Tool 127 | *.dotCover 128 | 129 | # AxoCover is a Code Coverage Tool 130 | .axoCover/* 131 | !.axoCover/settings.json 132 | 133 | # Visual Studio code coverage results 134 | *.coverage 135 | *.coveragexml 136 | 137 | # NCrunch 138 | _NCrunch_* 139 | .*crunch*.local.xml 140 | nCrunchTemp_* 141 | 142 | # MightyMoose 143 | *.mm.* 144 | AutoTest.Net/ 145 | 146 | # Web workbench (sass) 147 | .sass-cache/ 148 | 149 | # Installshield output folder 150 | [Ee]xpress/ 151 | 152 | # DocProject is a documentation generator add-in 153 | DocProject/buildhelp/ 154 | DocProject/Help/*.HxT 155 | DocProject/Help/*.HxC 156 | DocProject/Help/*.hhc 157 | DocProject/Help/*.hhk 158 | DocProject/Help/*.hhp 159 | DocProject/Help/Html2 160 | DocProject/Help/html 161 | 162 | # Click-Once directory 163 | publish/ 164 | 165 | # Publish Web Output 166 | *.[Pp]ublish.xml 167 | *.azurePubxml 168 | # Note: Comment the next line if you want to checkin your web deploy settings, 169 | # but database connection strings (with potential passwords) will be unencrypted 170 | *.pubxml 171 | *.publishproj 172 | 173 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 174 | # checkin your Azure Web App publish settings, but sensitive information contained 175 | # in these scripts will be unencrypted 176 | PublishScripts/ 177 | 178 | # NuGet Packages 179 | *.nupkg 180 | # The packages folder can be ignored because of Package Restore 181 | **/[Pp]ackages/* 182 | # except build/, which is used as an MSBuild target. 183 | !**/[Pp]ackages/build/ 184 | # Uncomment if necessary however generally it will be regenerated when needed 185 | #!**/[Pp]ackages/repositories.config 186 | # NuGet v3's project.json files produces more ignorable files 187 | *.nuget.props 188 | *.nuget.targets 189 | 190 | # Microsoft Azure Build Output 191 | csx/ 192 | *.build.csdef 193 | 194 | # Microsoft Azure Emulator 195 | ecf/ 196 | rcf/ 197 | 198 | # Windows Store app package directories and files 199 | AppPackages/ 200 | BundleArtifacts/ 201 | Package.StoreAssociation.xml 202 | _pkginfo.txt 203 | *.appx 204 | 205 | # Visual Studio cache files 206 | # files ending in .cache can be ignored 207 | *.[Cc]ache 208 | # but keep track of directories ending in .cache 209 | !*.[Cc]ache/ 210 | 211 | # Others 212 | ClientBin/ 213 | ~$* 214 | *~ 215 | *.dbmdl 216 | *.dbproj.schemaview 217 | *.jfm 218 | *.pfx 219 | *.publishsettings 220 | orleans.codegen.cs 221 | 222 | # Including strong name files can present a security risk 223 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 224 | #*.snk 225 | 226 | # Since there are multiple workflows, uncomment next line to ignore bower_components 227 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 228 | #bower_components/ 229 | 230 | # RIA/Silverlight projects 231 | Generated_Code/ 232 | 233 | # Backup & report files from converting an old project file 234 | # to a newer Visual Studio version. Backup files are not needed, 235 | # because we have git ;-) 236 | _UpgradeReport_Files/ 237 | Backup*/ 238 | UpgradeLog*.XML 239 | UpgradeLog*.htm 240 | 241 | # SQL Server files 242 | *.mdf 243 | *.ldf 244 | *.ndf 245 | 246 | # Business Intelligence projects 247 | *.rdl.data 248 | *.bim.layout 249 | *.bim_*.settings 250 | 251 | # Microsoft Fakes 252 | FakesAssemblies/ 253 | 254 | # GhostDoc plugin setting file 255 | *.GhostDoc.xml 256 | 257 | # Node.js Tools for Visual Studio 258 | .ntvs_analysis.dat 259 | node_modules/ 260 | 261 | # TypeScript v1 declaration files 262 | typings/ 263 | 264 | # Visual Studio 6 build log 265 | *.plg 266 | 267 | # Visual Studio 6 workspace options file 268 | *.opt 269 | 270 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 271 | *.vbw 272 | 273 | # Visual Studio LightSwitch build output 274 | **/*.HTMLClient/GeneratedArtifacts 275 | **/*.DesktopClient/GeneratedArtifacts 276 | **/*.DesktopClient/ModelManifest.xml 277 | **/*.Server/GeneratedArtifacts 278 | **/*.Server/ModelManifest.xml 279 | _Pvt_Extensions 280 | 281 | # Paket dependency manager 282 | .paket/paket.exe 283 | paket-files/ 284 | 285 | # FAKE - F# Make 286 | .fake/ 287 | 288 | # JetBrains Rider 289 | .idea/ 290 | *.sln.iml 291 | 292 | # CodeRush 293 | .cr/ 294 | 295 | # Python Tools for Visual Studio (PTVS) 296 | __pycache__/ 297 | *.pyc 298 | 299 | # Cake - Uncomment if you are using it 300 | # tools/** 301 | # !tools/packages.config 302 | 303 | # Tabs Studio 304 | *.tss 305 | 306 | # Telerik's JustMock configuration file 307 | *.jmconfig 308 | 309 | # BizTalk build output 310 | *.btp.cs 311 | *.btm.cs 312 | *.odx.cs 313 | *.xsd.cs 314 | 315 | # OpenCover UI analysis results 316 | OpenCover/ 317 | 318 | # Azure Stream Analytics local run output 319 | ASALocalRun/ 320 | 321 | # MSBuild Binary and Structured Log 322 | *.binlog 323 | 324 | Thumbs.db 325 | -------------------------------------------------------------------------------- /SteamNotificationsTray.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.40629.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamNotificationsTray", "SteamNotificationsTray\SteamNotificationsTray.csproj", "{4A4523CB-5D77-4430-AA32-D8A9FE62977E}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D50333DB-CB78-49E5-8BE3-25329ACD0AF2}" 9 | ProjectSection(SolutionItems) = preProject 10 | readme.md = readme.md 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {4A4523CB-5D77-4430-AA32-D8A9FE62977E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {4A4523CB-5D77-4430-AA32-D8A9FE62977E}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {4A4523CB-5D77-4430-AA32-D8A9FE62977E}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {4A4523CB-5D77-4430-AA32-D8A9FE62977E}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | EndGlobal 28 | -------------------------------------------------------------------------------- /SteamNotificationsTray/AppIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/SteamNotificationsTray/8674d06bb28f11f867d22b1206bee42973867dc4/SteamNotificationsTray/AppIcon.ico -------------------------------------------------------------------------------- /SteamNotificationsTray/CredentialStore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Security.Cryptography; 7 | using System.Net; 8 | using SteamNotificationsTray.WebLogin.Models; 9 | using Newtonsoft.Json; 10 | 11 | namespace SteamNotificationsTray 12 | { 13 | static class CredentialStore 14 | { 15 | static int failedAuthCount; 16 | static int maxFailedAuthsBeforeClear = 10; 17 | 18 | public static int MaxFailedAuthsBeforeClear 19 | { 20 | get { return maxFailedAuthsBeforeClear; } 21 | set { maxFailedAuthsBeforeClear = value; } 22 | } 23 | 24 | public static void NotifyAuthAttempt(bool success) 25 | { 26 | if (success) 27 | failedAuthCount = 0; 28 | else 29 | ++failedAuthCount; 30 | } 31 | 32 | public static bool ShouldClearAuth() 33 | { 34 | return failedAuthCount >= maxFailedAuthsBeforeClear; 35 | } 36 | 37 | static byte[] GetStrongNameKey() 38 | { 39 | var assembly = System.Reflection.Assembly.GetEntryAssembly(); 40 | return assembly.GetName().GetPublicKey(); 41 | } 42 | 43 | internal static TransferParameters GetTransferParameters() 44 | { 45 | var encryptedParams = Properties.Settings.Default.Credentials; 46 | if (string.IsNullOrEmpty(encryptedParams)) return null; 47 | try 48 | { 49 | byte[] encryptedBlob = Convert.FromBase64String(encryptedParams); 50 | byte[] decryptedBlob = ProtectedData.Unprotect(encryptedBlob, GetStrongNameKey(), DataProtectionScope.CurrentUser); 51 | string decryptedParams = Encoding.UTF8.GetString(decryptedBlob); 52 | return JsonConvert.DeserializeObject(decryptedParams); 53 | } 54 | catch 55 | { 56 | return null; 57 | } 58 | } 59 | 60 | internal static void SaveTransferParameters(TransferParameters transferParams) 61 | { 62 | TransferParameters newParams = new TransferParameters 63 | { 64 | SteamId = transferParams.SteamId, 65 | WebCookie = transferParams.WebCookie, 66 | RememberLoginToken = transferParams.RememberLoginToken 67 | }; 68 | string serialized = JsonConvert.SerializeObject(newParams); 69 | byte[] blob = Encoding.UTF8.GetBytes(serialized); 70 | byte[] cryptedBlob = ProtectedData.Protect(blob, GetStrongNameKey(), DataProtectionScope.CurrentUser); 71 | string cryptedParams = Convert.ToBase64String(cryptedBlob); 72 | Properties.Settings.Default.Credentials = cryptedParams; 73 | Properties.Settings.Default.Save(); 74 | NotifyAuthAttempt(true); 75 | } 76 | 77 | internal static CookieContainer GetCommunityCookies() 78 | { 79 | TransferParameters transferParams = GetTransferParameters(); 80 | if (transferParams == null) return null; 81 | CookieContainer cookies = new CookieContainer(); 82 | // 1. Persistent login cookie 83 | cookies.Add(new Cookie("steamRememberLogin", WebUtility.UrlEncode(string.Format("{0}||{1}", transferParams.SteamId, transferParams.RememberLoginToken)), "/", "steamcommunity.com")); 84 | // 2. Machine auth token 85 | cookies.Add(new Cookie(string.Format("steamMachineAuth{0}", transferParams.SteamId), transferParams.WebCookie, "/", "steamcommunity.com") { Secure = true }); 86 | return cookies; 87 | } 88 | 89 | internal static void SaveCommunityCookies(CookieContainer cookies) 90 | { 91 | TransferParameters transferParams = GetTransferParameters(); 92 | if (transferParams == null) transferParams = new TransferParameters(); 93 | foreach (Cookie cookie in cookies.GetCookies(new Uri("https://steamcommunity.com/"))) 94 | { 95 | if (cookie.Name == "steamRememberLogin") 96 | { 97 | string[] bits = WebUtility.UrlDecode(cookie.Value).Split(new[] { "||" }, 2, StringSplitOptions.None); 98 | transferParams.SteamId = ulong.Parse(bits[0]); 99 | transferParams.RememberLoginToken = bits[1]; 100 | } 101 | else if ( cookie.Name.StartsWith("steamMachineAuth")) 102 | { 103 | string steamId = cookie.Name.Replace("steamMachineAuth", ""); 104 | transferParams.SteamId = ulong.Parse(steamId); 105 | transferParams.WebCookie = cookie.Value; 106 | } 107 | } 108 | SaveTransferParameters(transferParams); 109 | } 110 | 111 | internal static void ClearCredentials() 112 | { 113 | Properties.Settings.Default.Credentials = null; 114 | Properties.Settings.Default.Save(); 115 | } 116 | 117 | internal static bool CredentialsAvailable() 118 | { 119 | return !string.IsNullOrEmpty(Properties.Settings.Default.Credentials); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /SteamNotificationsTray/IconUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Drawing; 6 | using System.Drawing.Imaging; 7 | 8 | namespace SteamNotificationsTray 9 | { 10 | static class IconUtils 11 | { 12 | public static Icon CreateIconWithBackground(Icon origIcon, Color background, Size size) 13 | { 14 | using (Bitmap bmp = new Bitmap(size.Width, size.Height, PixelFormat.Format32bppArgb)) 15 | { 16 | using (Graphics g = Graphics.FromImage(bmp)) 17 | { 18 | g.Clear(background); 19 | int iconXPos = (bmp.Width - origIcon.Width) / 2; 20 | int iconYPos = (bmp.Height - origIcon.Height) / 2; 21 | g.DrawIcon(origIcon, iconXPos, iconYPos); 22 | g.Flush(); 23 | } 24 | 25 | IntPtr iconHandle = bmp.GetHicon(); 26 | Icon icon = Icon.FromHandle(iconHandle); 27 | return icon; 28 | } 29 | } 30 | 31 | public static Icon CreateIconWithText(string text, Font font, Color textColor, Color background, Size size) 32 | { 33 | using (Bitmap bmp = new Bitmap(size.Width, size.Height, PixelFormat.Format32bppArgb)) 34 | { 35 | using (Graphics g = Graphics.FromImage(bmp)) 36 | { 37 | g.Clear(background); 38 | SizeF textSize = g.MeasureString(text, font); 39 | int textXPos = (bmp.Width - (int)Math.Ceiling(textSize.Width)) / 2; 40 | int textYPos = (bmp.Height - (int)Math.Ceiling(textSize.Height)) / 2; 41 | g.DrawString(text, font, new SolidBrush(textColor), new PointF(textXPos, textYPos)); 42 | g.Flush(); 43 | } 44 | 45 | IntPtr iconHandle = bmp.GetHicon(); 46 | Icon icon = Icon.FromHandle(iconHandle); 47 | return icon; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /SteamNotificationsTray/LoginForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace SteamNotificationsTray 2 | { 3 | partial class LoginForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(LoginForm)); 32 | this.label1 = new System.Windows.Forms.Label(); 33 | this.usernameTextBox = new System.Windows.Forms.TextBox(); 34 | this.label2 = new System.Windows.Forms.Label(); 35 | this.passwordTextBox = new System.Windows.Forms.TextBox(); 36 | this.mainLayoutPanel = new System.Windows.Forms.TableLayoutPanel(); 37 | this.buttonsPanel = new System.Windows.Forms.Panel(); 38 | this.cancelButton = new System.Windows.Forms.Button(); 39 | this.loginButton = new System.Windows.Forms.Button(); 40 | this.emailCodePanel = new System.Windows.Forms.Panel(); 41 | this.friendlyNameTextBox = new System.Windows.Forms.TextBox(); 42 | this.label5 = new System.Windows.Forms.Label(); 43 | this.label3 = new System.Windows.Forms.Label(); 44 | this.emailAuthTextBox = new System.Windows.Forms.TextBox(); 45 | this.messageLabel = new System.Windows.Forms.Label(); 46 | this.mobileAuthPanel = new System.Windows.Forms.Panel(); 47 | this.mobileAuthTextBox = new System.Windows.Forms.TextBox(); 48 | this.label4 = new System.Windows.Forms.Label(); 49 | this.captchaPanel = new System.Windows.Forms.Panel(); 50 | this.captchaTextBox = new System.Windows.Forms.TextBox(); 51 | this.captchaRefreshButton = new System.Windows.Forms.Button(); 52 | this.captchaPictureBox = new System.Windows.Forms.PictureBox(); 53 | this.label6 = new System.Windows.Forms.Label(); 54 | this.mainLayoutPanel.SuspendLayout(); 55 | this.buttonsPanel.SuspendLayout(); 56 | this.emailCodePanel.SuspendLayout(); 57 | this.mobileAuthPanel.SuspendLayout(); 58 | this.captchaPanel.SuspendLayout(); 59 | ((System.ComponentModel.ISupportInitialize)(this.captchaPictureBox)).BeginInit(); 60 | this.SuspendLayout(); 61 | // 62 | // label1 63 | // 64 | this.label1.AutoSize = true; 65 | this.label1.Location = new System.Drawing.Point(16, 11); 66 | this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); 67 | this.label1.Name = "label1"; 68 | this.label1.Size = new System.Drawing.Size(77, 17); 69 | this.label1.TabIndex = 0; 70 | this.label1.Text = "Username:"; 71 | // 72 | // usernameTextBox 73 | // 74 | this.usernameTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 75 | | System.Windows.Forms.AnchorStyles.Right))); 76 | this.usernameTextBox.Location = new System.Drawing.Point(16, 31); 77 | this.usernameTextBox.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 78 | this.usernameTextBox.Name = "usernameTextBox"; 79 | this.usernameTextBox.Size = new System.Drawing.Size(332, 22); 80 | this.usernameTextBox.TabIndex = 1; 81 | // 82 | // label2 83 | // 84 | this.label2.AutoSize = true; 85 | this.label2.Location = new System.Drawing.Point(16, 59); 86 | this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); 87 | this.label2.Name = "label2"; 88 | this.label2.Size = new System.Drawing.Size(73, 17); 89 | this.label2.TabIndex = 2; 90 | this.label2.Text = "Password:"; 91 | // 92 | // passwordTextBox 93 | // 94 | this.passwordTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 95 | | System.Windows.Forms.AnchorStyles.Right))); 96 | this.passwordTextBox.Location = new System.Drawing.Point(16, 79); 97 | this.passwordTextBox.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 98 | this.passwordTextBox.Name = "passwordTextBox"; 99 | this.passwordTextBox.Size = new System.Drawing.Size(332, 22); 100 | this.passwordTextBox.TabIndex = 3; 101 | this.passwordTextBox.UseSystemPasswordChar = true; 102 | // 103 | // mainLayoutPanel 104 | // 105 | this.mainLayoutPanel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 106 | | System.Windows.Forms.AnchorStyles.Right))); 107 | this.mainLayoutPanel.AutoSize = true; 108 | this.mainLayoutPanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; 109 | this.mainLayoutPanel.ColumnCount = 1; 110 | this.mainLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); 111 | this.mainLayoutPanel.Controls.Add(this.buttonsPanel, 0, 4); 112 | this.mainLayoutPanel.Controls.Add(this.emailCodePanel, 0, 1); 113 | this.mainLayoutPanel.Controls.Add(this.messageLabel, 0, 0); 114 | this.mainLayoutPanel.Controls.Add(this.mobileAuthPanel, 0, 2); 115 | this.mainLayoutPanel.Controls.Add(this.captchaPanel, 0, 3); 116 | this.mainLayoutPanel.Location = new System.Drawing.Point(8, 111); 117 | this.mainLayoutPanel.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 118 | this.mainLayoutPanel.Name = "mainLayoutPanel"; 119 | this.mainLayoutPanel.RowCount = 5; 120 | this.mainLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle()); 121 | this.mainLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle()); 122 | this.mainLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle()); 123 | this.mainLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle()); 124 | this.mainLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle()); 125 | this.mainLayoutPanel.Size = new System.Drawing.Size(349, 371); 126 | this.mainLayoutPanel.TabIndex = 4; 127 | // 128 | // buttonsPanel 129 | // 130 | this.buttonsPanel.AutoSize = true; 131 | this.buttonsPanel.Controls.Add(this.cancelButton); 132 | this.buttonsPanel.Controls.Add(this.loginButton); 133 | this.buttonsPanel.Dock = System.Windows.Forms.DockStyle.Fill; 134 | this.buttonsPanel.Location = new System.Drawing.Point(4, 331); 135 | this.buttonsPanel.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 136 | this.buttonsPanel.Name = "buttonsPanel"; 137 | this.buttonsPanel.Size = new System.Drawing.Size(341, 36); 138 | this.buttonsPanel.TabIndex = 4; 139 | // 140 | // cancelButton 141 | // 142 | this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 143 | this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; 144 | this.cancelButton.Location = new System.Drawing.Point(237, 4); 145 | this.cancelButton.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 146 | this.cancelButton.Name = "cancelButton"; 147 | this.cancelButton.Size = new System.Drawing.Size(100, 28); 148 | this.cancelButton.TabIndex = 1; 149 | this.cancelButton.Text = "Cancel"; 150 | this.cancelButton.UseVisualStyleBackColor = true; 151 | this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click); 152 | // 153 | // loginButton 154 | // 155 | this.loginButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); 156 | this.loginButton.Location = new System.Drawing.Point(129, 4); 157 | this.loginButton.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 158 | this.loginButton.Name = "loginButton"; 159 | this.loginButton.Size = new System.Drawing.Size(100, 28); 160 | this.loginButton.TabIndex = 0; 161 | this.loginButton.Text = "Sign in"; 162 | this.loginButton.UseVisualStyleBackColor = true; 163 | this.loginButton.Click += new System.EventHandler(this.loginButton_Click); 164 | // 165 | // emailCodePanel 166 | // 167 | this.emailCodePanel.AutoSize = true; 168 | this.emailCodePanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; 169 | this.emailCodePanel.Controls.Add(this.friendlyNameTextBox); 170 | this.emailCodePanel.Controls.Add(this.label5); 171 | this.emailCodePanel.Controls.Add(this.label3); 172 | this.emailCodePanel.Controls.Add(this.emailAuthTextBox); 173 | this.emailCodePanel.Dock = System.Windows.Forms.DockStyle.Fill; 174 | this.emailCodePanel.Location = new System.Drawing.Point(4, 29); 175 | this.emailCodePanel.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 176 | this.emailCodePanel.Name = "emailCodePanel"; 177 | this.emailCodePanel.Size = new System.Drawing.Size(341, 94); 178 | this.emailCodePanel.TabIndex = 1; 179 | this.emailCodePanel.Visible = false; 180 | // 181 | // friendlyNameTextBox 182 | // 183 | this.friendlyNameTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 184 | | System.Windows.Forms.AnchorStyles.Right))); 185 | this.friendlyNameTextBox.Location = new System.Drawing.Point(4, 68); 186 | this.friendlyNameTextBox.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 187 | this.friendlyNameTextBox.Name = "friendlyNameTextBox"; 188 | this.friendlyNameTextBox.Size = new System.Drawing.Size(332, 22); 189 | this.friendlyNameTextBox.TabIndex = 3; 190 | // 191 | // label5 192 | // 193 | this.label5.AutoSize = true; 194 | this.label5.Location = new System.Drawing.Point(4, 48); 195 | this.label5.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); 196 | this.label5.Name = "label5"; 197 | this.label5.Size = new System.Drawing.Size(103, 17); 198 | this.label5.TabIndex = 2; 199 | this.label5.Text = "Friendly Name:"; 200 | // 201 | // label3 202 | // 203 | this.label3.AutoSize = true; 204 | this.label3.Location = new System.Drawing.Point(4, 0); 205 | this.label3.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); 206 | this.label3.Name = "label3"; 207 | this.label3.Size = new System.Drawing.Size(133, 17); 208 | this.label3.TabIndex = 0; 209 | this.label3.Text = "Steam Guard Code:"; 210 | // 211 | // emailAuthTextBox 212 | // 213 | this.emailAuthTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 214 | | System.Windows.Forms.AnchorStyles.Right))); 215 | this.emailAuthTextBox.CharacterCasing = System.Windows.Forms.CharacterCasing.Upper; 216 | this.emailAuthTextBox.Location = new System.Drawing.Point(4, 20); 217 | this.emailAuthTextBox.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 218 | this.emailAuthTextBox.Name = "emailAuthTextBox"; 219 | this.emailAuthTextBox.Size = new System.Drawing.Size(332, 22); 220 | this.emailAuthTextBox.TabIndex = 1; 221 | // 222 | // messageLabel 223 | // 224 | this.messageLabel.AutoSize = true; 225 | this.messageLabel.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(204)))), ((int)(((byte)(51)))), ((int)(((byte)(0))))); 226 | this.messageLabel.Location = new System.Drawing.Point(4, 0); 227 | this.messageLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); 228 | this.messageLabel.Name = "messageLabel"; 229 | this.messageLabel.Padding = new System.Windows.Forms.Padding(4, 4, 4, 4); 230 | this.messageLabel.Size = new System.Drawing.Size(76, 25); 231 | this.messageLabel.TabIndex = 0; 232 | this.messageLabel.Text = "Message!"; 233 | this.messageLabel.Visible = false; 234 | // 235 | // mobileAuthPanel 236 | // 237 | this.mobileAuthPanel.AutoSize = true; 238 | this.mobileAuthPanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; 239 | this.mobileAuthPanel.Controls.Add(this.mobileAuthTextBox); 240 | this.mobileAuthPanel.Controls.Add(this.label4); 241 | this.mobileAuthPanel.Dock = System.Windows.Forms.DockStyle.Fill; 242 | this.mobileAuthPanel.Location = new System.Drawing.Point(4, 131); 243 | this.mobileAuthPanel.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 244 | this.mobileAuthPanel.Name = "mobileAuthPanel"; 245 | this.mobileAuthPanel.Size = new System.Drawing.Size(341, 46); 246 | this.mobileAuthPanel.TabIndex = 2; 247 | this.mobileAuthPanel.Visible = false; 248 | // 249 | // mobileAuthTextBox 250 | // 251 | this.mobileAuthTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 252 | | System.Windows.Forms.AnchorStyles.Right))); 253 | this.mobileAuthTextBox.CharacterCasing = System.Windows.Forms.CharacterCasing.Upper; 254 | this.mobileAuthTextBox.Location = new System.Drawing.Point(4, 20); 255 | this.mobileAuthTextBox.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 256 | this.mobileAuthTextBox.Name = "mobileAuthTextBox"; 257 | this.mobileAuthTextBox.Size = new System.Drawing.Size(332, 22); 258 | this.mobileAuthTextBox.TabIndex = 1; 259 | // 260 | // label4 261 | // 262 | this.label4.AutoSize = true; 263 | this.label4.Location = new System.Drawing.Point(4, 0); 264 | this.label4.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); 265 | this.label4.Name = "label4"; 266 | this.label4.Size = new System.Drawing.Size(178, 17); 267 | this.label4.TabIndex = 0; 268 | this.label4.Text = "Mobile Authenticator Code:"; 269 | // 270 | // captchaPanel 271 | // 272 | this.captchaPanel.AutoSize = true; 273 | this.captchaPanel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; 274 | this.captchaPanel.Controls.Add(this.captchaTextBox); 275 | this.captchaPanel.Controls.Add(this.captchaRefreshButton); 276 | this.captchaPanel.Controls.Add(this.captchaPictureBox); 277 | this.captchaPanel.Controls.Add(this.label6); 278 | this.captchaPanel.Dock = System.Windows.Forms.DockStyle.Fill; 279 | this.captchaPanel.Location = new System.Drawing.Point(4, 185); 280 | this.captchaPanel.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 281 | this.captchaPanel.Name = "captchaPanel"; 282 | this.captchaPanel.Size = new System.Drawing.Size(341, 138); 283 | this.captchaPanel.TabIndex = 3; 284 | this.captchaPanel.Visible = false; 285 | // 286 | // captchaTextBox 287 | // 288 | this.captchaTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 289 | | System.Windows.Forms.AnchorStyles.Right))); 290 | this.captchaTextBox.Location = new System.Drawing.Point(4, 112); 291 | this.captchaTextBox.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 292 | this.captchaTextBox.Name = "captchaTextBox"; 293 | this.captchaTextBox.Size = new System.Drawing.Size(332, 22); 294 | this.captchaTextBox.TabIndex = 2; 295 | // 296 | // captchaRefreshButton 297 | // 298 | this.captchaRefreshButton.Anchor = System.Windows.Forms.AnchorStyles.Top; 299 | this.captchaRefreshButton.Location = new System.Drawing.Point(208, 76); 300 | this.captchaRefreshButton.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 301 | this.captchaRefreshButton.Name = "captchaRefreshButton"; 302 | this.captchaRefreshButton.Size = new System.Drawing.Size(100, 28); 303 | this.captchaRefreshButton.TabIndex = 1; 304 | this.captchaRefreshButton.Text = "Refresh"; 305 | this.captchaRefreshButton.UseVisualStyleBackColor = true; 306 | this.captchaRefreshButton.Click += new System.EventHandler(this.captchaRefreshButton_Click); 307 | // 308 | // captchaPictureBox 309 | // 310 | this.captchaPictureBox.Anchor = System.Windows.Forms.AnchorStyles.Top; 311 | this.captchaPictureBox.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; 312 | this.captchaPictureBox.Location = new System.Drawing.Point(33, 20); 313 | this.captchaPictureBox.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 314 | this.captchaPictureBox.Name = "captchaPictureBox"; 315 | this.captchaPictureBox.Size = new System.Drawing.Size(274, 49); 316 | this.captchaPictureBox.TabIndex = 1; 317 | this.captchaPictureBox.TabStop = false; 318 | // 319 | // label6 320 | // 321 | this.label6.AutoSize = true; 322 | this.label6.Location = new System.Drawing.Point(4, 0); 323 | this.label6.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); 324 | this.label6.Name = "label6"; 325 | this.label6.Size = new System.Drawing.Size(194, 17); 326 | this.label6.TabIndex = 0; 327 | this.label6.Text = "Type these characters below:"; 328 | // 329 | // LoginForm 330 | // 331 | this.AcceptButton = this.loginButton; 332 | this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); 333 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 334 | this.AutoSize = true; 335 | this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; 336 | this.CancelButton = this.cancelButton; 337 | this.ClientSize = new System.Drawing.Size(365, 485); 338 | this.Controls.Add(this.mainLayoutPanel); 339 | this.Controls.Add(this.passwordTextBox); 340 | this.Controls.Add(this.label2); 341 | this.Controls.Add(this.usernameTextBox); 342 | this.Controls.Add(this.label1); 343 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; 344 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 345 | this.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); 346 | this.MaximizeBox = false; 347 | this.Name = "LoginForm"; 348 | this.Text = "Log into Steam"; 349 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.LoginForm_FormClosing); 350 | this.mainLayoutPanel.ResumeLayout(false); 351 | this.mainLayoutPanel.PerformLayout(); 352 | this.buttonsPanel.ResumeLayout(false); 353 | this.emailCodePanel.ResumeLayout(false); 354 | this.emailCodePanel.PerformLayout(); 355 | this.mobileAuthPanel.ResumeLayout(false); 356 | this.mobileAuthPanel.PerformLayout(); 357 | this.captchaPanel.ResumeLayout(false); 358 | this.captchaPanel.PerformLayout(); 359 | ((System.ComponentModel.ISupportInitialize)(this.captchaPictureBox)).EndInit(); 360 | this.ResumeLayout(false); 361 | this.PerformLayout(); 362 | 363 | } 364 | 365 | #endregion 366 | 367 | private System.Windows.Forms.Label label1; 368 | private System.Windows.Forms.TextBox usernameTextBox; 369 | private System.Windows.Forms.Label label2; 370 | private System.Windows.Forms.TextBox passwordTextBox; 371 | private System.Windows.Forms.TableLayoutPanel mainLayoutPanel; 372 | private System.Windows.Forms.Panel buttonsPanel; 373 | private System.Windows.Forms.Button cancelButton; 374 | private System.Windows.Forms.Button loginButton; 375 | private System.Windows.Forms.Panel emailCodePanel; 376 | private System.Windows.Forms.Label messageLabel; 377 | private System.Windows.Forms.Label label3; 378 | private System.Windows.Forms.TextBox emailAuthTextBox; 379 | private System.Windows.Forms.Panel mobileAuthPanel; 380 | private System.Windows.Forms.TextBox mobileAuthTextBox; 381 | private System.Windows.Forms.Label label4; 382 | private System.Windows.Forms.TextBox friendlyNameTextBox; 383 | private System.Windows.Forms.Label label5; 384 | private System.Windows.Forms.Panel captchaPanel; 385 | private System.Windows.Forms.TextBox captchaTextBox; 386 | private System.Windows.Forms.Button captchaRefreshButton; 387 | private System.Windows.Forms.PictureBox captchaPictureBox; 388 | private System.Windows.Forms.Label label6; 389 | 390 | } 391 | } -------------------------------------------------------------------------------- /SteamNotificationsTray/LoginForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | using System.Security.Cryptography; 11 | using SteamNotificationsTray.WebLogin; 12 | using SteamNotificationsTray.WebLogin.Models; 13 | 14 | namespace SteamNotificationsTray 15 | { 16 | public partial class LoginForm : Form 17 | { 18 | DoLoginResponse loginResponse; 19 | SteamWebLogin loginClient = new SteamWebLogin(); 20 | 21 | public LoginForm() 22 | { 23 | InitializeComponent(); 24 | } 25 | 26 | static byte[] hexToBytes(string str) 27 | { 28 | if (str.Length % 2 != 0) throw new ArgumentException("Input does not have even bytes.", "str"); 29 | byte[] data = new byte[str.Length / 2]; 30 | for (int i = 0; i < data.Length; ++i) 31 | { 32 | string part = str.Substring(i * 2, 2); 33 | byte b; 34 | if (!byte.TryParse(part, System.Globalization.NumberStyles.HexNumber, null, out b)) 35 | throw new ArgumentException("Input contains non-hex characters.", "str"); 36 | data[i] = b; 37 | } 38 | return data; 39 | } 40 | 41 | async Task doLogin() 42 | { 43 | // Assume validity checks have been done 44 | // 1. Get RSA key 45 | GetRsaKeyResponse rsaResponse = await loginClient.GetRsaKeyAsync(usernameTextBox.Text); 46 | if (!rsaResponse.Success) 47 | { 48 | setMessage(!string.IsNullOrEmpty(rsaResponse.Message) ? rsaResponse.Message : "Can't get RSA key for sending login info."); 49 | return false; 50 | } 51 | 52 | // 2. Encrypt password 53 | string encryptedPassword; 54 | using (var rsa = new RSACryptoServiceProvider()) 55 | { 56 | rsa.ImportParameters(new RSAParameters 57 | { 58 | Modulus = hexToBytes(rsaResponse.PublicKeyMod), 59 | Exponent = hexToBytes(rsaResponse.PublicKeyExp) 60 | }); 61 | 62 | // Filter password to ASCII characters (the login script does this) 63 | string password = System.Text.RegularExpressions.Regex.Replace(passwordTextBox.Text, "[^\u0000-\u007F]", string.Empty); 64 | byte[] passwordBlob = Encoding.UTF8.GetBytes(password); 65 | byte[] crypted = rsa.Encrypt(passwordBlob, false); 66 | encryptedPassword = Convert.ToBase64String(crypted); 67 | } 68 | 69 | // 3. Send request to server 70 | DoLoginRequest request = new DoLoginRequest 71 | { 72 | Password = encryptedPassword, 73 | Username = usernameTextBox.Text, 74 | TwoFactorCode = mobileAuthTextBox.Text, 75 | EmailAuth = emailAuthTextBox.Text, 76 | LoginFriendlyName = friendlyNameTextBox.Text, 77 | CaptchaText = captchaTextBox.Text, 78 | RsaTimeStamp = rsaResponse.Timestamp, 79 | RememberLogin = true 80 | }; 81 | 82 | if (loginResponse != null) 83 | { 84 | request.CaptchaGid = loginResponse.CaptchaGid; 85 | request.EmailSteamId = loginResponse.EmailSteamId; 86 | } 87 | else 88 | { 89 | request.CaptchaGid = -1; 90 | } 91 | 92 | loginResponse = await loginClient.DoLoginAsync(request); 93 | if (loginResponse == null) return false; 94 | return loginResponse.Success && loginResponse.LoginComplete; 95 | } 96 | 97 | void updateGuiFromResponse() 98 | { 99 | if (loginResponse == null) 100 | { 101 | emailAuthTextBox.Text = string.Empty; 102 | emailCodePanel.Visible = false; 103 | mobileAuthTextBox.Text = string.Empty; 104 | mobileAuthPanel.Visible = false; 105 | if (captchaPictureBox.Image != null) captchaPictureBox.Image.Dispose(); 106 | captchaPictureBox.Image = null; 107 | captchaTextBox.Text = string.Empty; 108 | captchaPanel.Visible = false; 109 | } 110 | else 111 | { 112 | emailCodePanel.Visible = loginResponse.EmailAuthNeeded; 113 | mobileAuthPanel.Visible = loginResponse.RequiresTwoFactor; 114 | captchaPanel.Visible = loginResponse.CaptchaNeeded || loginResponse.IsBadCaptcha; 115 | if (loginResponse.ClearPasswordField) passwordTextBox.Text = string.Empty; 116 | 117 | // Default focus fields when additional info required 118 | if (emailCodePanel.Visible) 119 | { 120 | emailAuthTextBox.Focus(); 121 | } 122 | else if (mobileAuthPanel.Visible) 123 | { 124 | mobileAuthTextBox.Focus(); 125 | } 126 | else if (captchaPanel.Visible) 127 | { 128 | captchaTextBox.Focus(); 129 | } 130 | } 131 | } 132 | 133 | void setMessage(string message) 134 | { 135 | if (InvokeRequired) 136 | { 137 | Invoke(new Action(setMessage), message); 138 | return; 139 | } 140 | 141 | if (string.IsNullOrEmpty(message)) 142 | { 143 | messageLabel.Visible = false; 144 | } 145 | else 146 | { 147 | messageLabel.Visible = true; 148 | messageLabel.Text = message; 149 | } 150 | } 151 | 152 | bool validateEntry() 153 | { 154 | List messages = new List(); 155 | if (usernameTextBox.TextLength == 0) 156 | messages.Add("your username"); 157 | if (passwordTextBox.TextLength == 0) 158 | messages.Add("your password"); 159 | 160 | if (loginResponse != null) 161 | { 162 | if (loginResponse.EmailAuthNeeded && emailAuthTextBox.TextLength == 0) 163 | messages.Add("the Steam Guard code from your email"); 164 | if (loginResponse.RequiresTwoFactor && mobileAuthTextBox.TextLength == 0) 165 | messages.Add("the Mobile Authenticator code"); 166 | if (loginResponse.CaptchaNeeded && captchaTextBox.TextLength == 0) 167 | messages.Add("the CAPTCHA"); 168 | } 169 | 170 | if (messages.Count > 0) 171 | { 172 | MessageBox.Show(this, string.Format("Please enter {0}.", string.Join(", ", messages)), Text, MessageBoxButtons.OK, MessageBoxIcon.Warning); 173 | return false; 174 | } 175 | 176 | return true; 177 | } 178 | 179 | async Task loadNewCaptcha() 180 | { 181 | if (loginResponse == null) return; 182 | await Task.Run(() => captchaPictureBox.LoadAsync(loginClient.GetRenderCaptchaUrl(loginResponse.CaptchaGid))); 183 | } 184 | 185 | async Task refreshCaptcha() 186 | { 187 | if (loginResponse == null) return; 188 | loginResponse.CaptchaGid = await loginClient.RefreshCaptchaAsync(); 189 | if (loginResponse.CaptchaGid == -1) 190 | { 191 | captchaPanel.Visible = false; 192 | } 193 | else 194 | { 195 | await Task.Run(() => captchaPictureBox.LoadAsync(loginClient.GetRenderCaptchaUrl(loginResponse.CaptchaGid))); 196 | } 197 | } 198 | 199 | private void cancelButton_Click(object sender, EventArgs e) 200 | { 201 | DialogResult = System.Windows.Forms.DialogResult.Cancel; 202 | Close(); 203 | } 204 | 205 | private async void loginButton_Click(object sender, EventArgs e) 206 | { 207 | loginButton.Enabled = false; 208 | cancelButton.Enabled = false; 209 | try 210 | { 211 | if (!validateEntry()) return; 212 | bool success = await Task.Run(() => doLogin()); 213 | if (success) 214 | { 215 | CredentialStore.SaveTransferParameters(loginResponse.TransferParameters); 216 | DialogResult = System.Windows.Forms.DialogResult.OK; 217 | Close(); 218 | } 219 | else 220 | { 221 | updateGuiFromResponse(); 222 | if (loginResponse == null) 223 | { 224 | messageLabel.Text = "Could not communicate with Steam Community."; 225 | } 226 | else if (!string.IsNullOrEmpty(loginResponse.Message)) 227 | { 228 | messageLabel.Text = loginResponse.Message; 229 | } 230 | else 231 | { 232 | messageLabel.Text = string.Empty; 233 | if (loginResponse.EmailAuthNeeded) 234 | { 235 | if (string.IsNullOrEmpty(loginResponse.EmailDomain)) 236 | { 237 | messageLabel.Text += "Steam Guard code incorrect. "; 238 | } 239 | else 240 | { 241 | messageLabel.Text += "Steam Guard code required. "; 242 | } 243 | } 244 | if (loginResponse.RequiresTwoFactor) 245 | messageLabel.Text += "Mobile Authenticator code required. "; 246 | if (loginResponse.CaptchaNeeded && !loginResponse.IsBadCaptcha) 247 | messageLabel.Text += "CAPTCHA entry required. "; 248 | if (loginResponse.IsBadCaptcha) 249 | messageLabel.Text += "CAPTCHA entry incorrect. "; 250 | if (loginResponse.DeniedIpt) 251 | messageLabel.Text += "Intel® Identity Protection Technology access denied. "; 252 | } 253 | messageLabel.Visible = messageLabel.Text.Length > 0; 254 | if (loginResponse != null && loginResponse.CaptchaNeeded) 255 | await loadNewCaptcha(); 256 | } 257 | } 258 | catch (Exception ex) 259 | { 260 | MessageBox.Show(this, "Could not log in: " + ex.Message, Text, MessageBoxButtons.OK, MessageBoxIcon.Error); 261 | } 262 | finally 263 | { 264 | loginButton.Enabled = true; 265 | cancelButton.Enabled = true; 266 | } 267 | } 268 | 269 | private void captchaRefreshButton_Click(object sender, EventArgs e) 270 | { 271 | Task.Run(() => refreshCaptcha()); 272 | } 273 | 274 | private void LoginForm_FormClosing(object sender, FormClosingEventArgs e) 275 | { 276 | loginResponse = null; 277 | updateGuiFromResponse(); 278 | messageLabel.Text = string.Empty; 279 | messageLabel.Visible = false; 280 | } 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /SteamNotificationsTray/NotificationCounts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SteamNotificationsTray 8 | { 9 | class NotificationCounts 10 | { 11 | public int Comments { get; set; } // 4 12 | public int Items { get; set; } // 5 13 | public int Invites { get; set; } // 6 14 | public int Gifts { get; set; } // 8 15 | public int OfflineMessages { get; set; } // 9 16 | public int TradeOffers { get; set; } // 1 17 | public int AsyncGames { get; set; } // 2 18 | public int ModeratorMessages { get; set; } // 3 19 | public int HelpRequestReplies { get; set; } // 10 20 | public int AccountAlerts { get; set; } // 11 21 | 22 | public int TotalNotifications { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SteamNotificationsTray/NotificationsClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Net; 7 | using System.Net.Http; 8 | using Newtonsoft.Json.Linq; 9 | 10 | namespace SteamNotificationsTray 11 | { 12 | class NotificationsClient 13 | { 14 | CookieContainer cookies; 15 | 16 | public NotificationCounts PrevCounts { get; private set; } 17 | public NotificationCounts CurrentCounts { get; private set; } 18 | 19 | public async Task PollNotificationCountsAsync() 20 | { 21 | HttpClientHandler handler = new HttpClientHandler { CookieContainer = cookies }; 22 | using (HttpClient client = new HttpClient(handler)) 23 | { 24 | string response = await client.GetStringAsync("https://steamcommunity.com/actions/GetNotificationCounts"); 25 | if (response == "null") return null; 26 | NotificationCounts counts = new NotificationCounts(); 27 | JObject respObj = JObject.Parse(response); 28 | JToken notifsObj = respObj["notifications"]; 29 | foreach (JProperty notif in notifsObj) 30 | { 31 | switch (notif.Name) 32 | { 33 | case "4": 34 | counts.Comments = (int)notif.Value; 35 | break; 36 | case "5": 37 | counts.Items = (int)notif.Value; 38 | break; 39 | case "6": 40 | counts.Invites = (int)notif.Value; 41 | break; 42 | case "8": 43 | counts.Gifts = (int)notif.Value; 44 | break; 45 | case "9": 46 | counts.OfflineMessages = (int)notif.Value; 47 | break; 48 | case "1": 49 | counts.TradeOffers = (int)notif.Value; 50 | break; 51 | case "2": 52 | counts.AsyncGames = (int)notif.Value; 53 | break; 54 | case "3": 55 | counts.ModeratorMessages = (int)notif.Value; 56 | break; 57 | case "10": 58 | counts.HelpRequestReplies = (int)notif.Value; 59 | break; 60 | case "11": 61 | counts.AccountAlerts = (int)notif.Value; 62 | break; 63 | } 64 | counts.TotalNotifications += (int)notif.Value; 65 | } 66 | PrevCounts = CurrentCounts; 67 | CurrentCounts = counts; 68 | return counts; 69 | } 70 | } 71 | 72 | public void SetCookies(CookieContainer cookies) 73 | { 74 | this.cookies = cookies; 75 | } 76 | 77 | public void SaveCredentials() 78 | { 79 | if (cookies != null) CredentialStore.SaveCommunityCookies(cookies); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /SteamNotificationsTray/NotificationsMenuColorTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows.Forms; 7 | using System.Drawing; 8 | using SteamNotificationsTray; 9 | 10 | namespace SteamNotificationsTray 11 | { 12 | class NotificationsMenuColorTable : ProfessionalColorTable 13 | { 14 | Properties.Settings Settings 15 | { 16 | get { return Properties.Settings.Default; } 17 | } 18 | 19 | public override Color MenuBorder 20 | { 21 | get 22 | { 23 | return Settings.NotificationPopupBorderColor; 24 | } 25 | } 26 | 27 | public override Color MenuItemSelected 28 | { 29 | get 30 | { 31 | return Settings.NotificationFocusColor; 32 | } 33 | } 34 | 35 | public override Color MenuItemBorder 36 | { 37 | get 38 | { 39 | return Settings.NotificationFocusColor; 40 | } 41 | } 42 | 43 | public override Color ImageMarginGradientBegin 44 | { 45 | get 46 | { 47 | return Settings.NotificationPopupBackgroundColor; 48 | } 49 | } 50 | 51 | public override Color ImageMarginGradientMiddle 52 | { 53 | get 54 | { 55 | return Settings.NotificationPopupBackgroundColor; 56 | } 57 | } 58 | 59 | public override Color ImageMarginGradientEnd 60 | { 61 | get 62 | { 63 | return Settings.NotificationPopupBackgroundColor; 64 | } 65 | } 66 | 67 | public override Color SeparatorLight 68 | { 69 | get 70 | { 71 | return Settings.NotificationPopupSeparatorColor; 72 | } 73 | } 74 | 75 | public override Color SeparatorDark 76 | { 77 | get 78 | { 79 | return Settings.NotificationPopupSeparatorColor; 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /SteamNotificationsTray/NotificationsMenuRenderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows.Forms; 7 | using System.Drawing; 8 | 9 | namespace SteamNotificationsTray 10 | { 11 | class NotificationsMenuRenderer : ToolStripProfessionalRenderer 12 | { 13 | public NotificationsMenuRenderer() 14 | : base(new NotificationsMenuColorTable()) 15 | { } 16 | 17 | protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e) 18 | { 19 | if ((int)e.Item.Tag > 0) e.TextColor = Properties.Settings.Default.NotificationActiveColor; 20 | base.OnRenderItemText(e); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SteamNotificationsTray/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Windows.Forms; 5 | 6 | namespace SteamNotificationsTray 7 | { 8 | static class Program 9 | { 10 | /// 11 | /// The main entry point for the application. 12 | /// 13 | [STAThread] 14 | static void Main() 15 | { 16 | Application.EnableVisualStyles(); 17 | Application.SetCompatibleTextRenderingDefault(false); 18 | Application.Run(new TrayAppContext()); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SteamNotificationsTray/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Steam Notifications Tray App")] 9 | [assembly: AssemblyDescription("Displays Steam notification counts in the notification area.")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("GMWare")] 12 | [assembly: AssemblyProduct("Steam Notifications Tray App")] 13 | [assembly: AssemblyCopyright("Copyright © cyanic 2016-2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("5d5ac850-a790-415b-bbc4-07f74ea2330c")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.1.4.2")] 36 | [assembly: AssemblyFileVersion("1.1.4.2")] 37 | -------------------------------------------------------------------------------- /SteamNotificationsTray/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace SteamNotificationsTray.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SteamNotificationsTray.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to {0} account alerts. 65 | /// 66 | internal static string AccountAlertsPlural { 67 | get { 68 | return ResourceManager.GetString("AccountAlertsPlural", resourceCulture); 69 | } 70 | } 71 | 72 | /// 73 | /// Looks up a localized string similar to 1 account alert. 74 | /// 75 | internal static string AccountAlertsSingular { 76 | get { 77 | return ResourceManager.GetString("AccountAlertsSingular", resourceCulture); 78 | } 79 | } 80 | 81 | /// 82 | /// Looks up a localized string similar to {0} new turns waiting. 83 | /// 84 | internal static string AsyncGamesPlural { 85 | get { 86 | return ResourceManager.GetString("AsyncGamesPlural", resourceCulture); 87 | } 88 | } 89 | 90 | /// 91 | /// Looks up a localized string similar to 1 turn waiting. 92 | /// 93 | internal static string AsyncGamesSingular { 94 | get { 95 | return ResourceManager.GetString("AsyncGamesSingular", resourceCulture); 96 | } 97 | } 98 | 99 | /// 100 | /// Looks up a localized string similar to {0} new comments. 101 | /// 102 | internal static string CommentsPlural { 103 | get { 104 | return ResourceManager.GetString("CommentsPlural", resourceCulture); 105 | } 106 | } 107 | 108 | /// 109 | /// Looks up a localized string similar to 1 new comment. 110 | /// 111 | internal static string CommentsSingular { 112 | get { 113 | return ResourceManager.GetString("CommentsSingular", resourceCulture); 114 | } 115 | } 116 | 117 | /// 118 | /// Looks up a localized string similar to Error polling for notifications: . 119 | /// 120 | internal static string ErrorPolling { 121 | get { 122 | return ResourceManager.GetString("ErrorPolling", resourceCulture); 123 | } 124 | } 125 | 126 | /// 127 | /// Looks up a localized string similar to Exception: . 128 | /// 129 | internal static string Exception { 130 | get { 131 | return ResourceManager.GetString("Exception", resourceCulture); 132 | } 133 | } 134 | 135 | /// 136 | /// Looks up a localized string similar to Exit. 137 | /// 138 | internal static string Exit { 139 | get { 140 | return ResourceManager.GetString("Exit", resourceCulture); 141 | } 142 | } 143 | 144 | /// 145 | /// Looks up a localized string similar to {0} new gifts. 146 | /// 147 | internal static string GiftsPlural { 148 | get { 149 | return ResourceManager.GetString("GiftsPlural", resourceCulture); 150 | } 151 | } 152 | 153 | /// 154 | /// Looks up a localized string similar to 1 new gift. 155 | /// 156 | internal static string GiftsSingular { 157 | get { 158 | return ResourceManager.GetString("GiftsSingular", resourceCulture); 159 | } 160 | } 161 | 162 | /// 163 | /// Looks up a localized string similar to {0} replies from Steam Support. 164 | /// 165 | internal static string HelpRequestRepliesPlural { 166 | get { 167 | return ResourceManager.GetString("HelpRequestRepliesPlural", resourceCulture); 168 | } 169 | } 170 | 171 | /// 172 | /// Looks up a localized string similar to 1 reply from Steam Support. 173 | /// 174 | internal static string HelpRequestRepliesSingular { 175 | get { 176 | return ResourceManager.GetString("HelpRequestRepliesSingular", resourceCulture); 177 | } 178 | } 179 | 180 | /// 181 | /// Looks up a localized resource of type System.Drawing.Bitmap. 182 | /// 183 | internal static System.Drawing.Bitmap IconAccountAlerts { 184 | get { 185 | object obj = ResourceManager.GetObject("IconAccountAlerts", resourceCulture); 186 | return ((System.Drawing.Bitmap)(obj)); 187 | } 188 | } 189 | 190 | /// 191 | /// Looks up a localized resource of type System.Drawing.Bitmap. 192 | /// 193 | internal static System.Drawing.Bitmap IconAsyncGames { 194 | get { 195 | object obj = ResourceManager.GetObject("IconAsyncGames", resourceCulture); 196 | return ((System.Drawing.Bitmap)(obj)); 197 | } 198 | } 199 | 200 | /// 201 | /// Looks up a localized resource of type System.Drawing.Bitmap. 202 | /// 203 | internal static System.Drawing.Bitmap IconComments { 204 | get { 205 | object obj = ResourceManager.GetObject("IconComments", resourceCulture); 206 | return ((System.Drawing.Bitmap)(obj)); 207 | } 208 | } 209 | 210 | /// 211 | /// Looks up a localized resource of type System.Drawing.Bitmap. 212 | /// 213 | internal static System.Drawing.Bitmap IconGifts { 214 | get { 215 | object obj = ResourceManager.GetObject("IconGifts", resourceCulture); 216 | return ((System.Drawing.Bitmap)(obj)); 217 | } 218 | } 219 | 220 | /// 221 | /// Looks up a localized resource of type System.Drawing.Bitmap. 222 | /// 223 | internal static System.Drawing.Bitmap IconInvites { 224 | get { 225 | object obj = ResourceManager.GetObject("IconInvites", resourceCulture); 226 | return ((System.Drawing.Bitmap)(obj)); 227 | } 228 | } 229 | 230 | /// 231 | /// Looks up a localized resource of type System.Drawing.Bitmap. 232 | /// 233 | internal static System.Drawing.Bitmap IconItems { 234 | get { 235 | object obj = ResourceManager.GetObject("IconItems", resourceCulture); 236 | return ((System.Drawing.Bitmap)(obj)); 237 | } 238 | } 239 | 240 | /// 241 | /// Looks up a localized resource of type System.Drawing.Bitmap. 242 | /// 243 | internal static System.Drawing.Bitmap IconModeratorMessages { 244 | get { 245 | object obj = ResourceManager.GetObject("IconModeratorMessages", resourceCulture); 246 | return ((System.Drawing.Bitmap)(obj)); 247 | } 248 | } 249 | 250 | /// 251 | /// Looks up a localized resource of type System.Drawing.Bitmap. 252 | /// 253 | internal static System.Drawing.Bitmap IconOfflineMessages { 254 | get { 255 | object obj = ResourceManager.GetObject("IconOfflineMessages", resourceCulture); 256 | return ((System.Drawing.Bitmap)(obj)); 257 | } 258 | } 259 | 260 | /// 261 | /// Looks up a localized resource of type System.Drawing.Bitmap. 262 | /// 263 | internal static System.Drawing.Bitmap IconTradeOffers { 264 | get { 265 | object obj = ResourceManager.GetObject("IconTradeOffers", resourceCulture); 266 | return ((System.Drawing.Bitmap)(obj)); 267 | } 268 | } 269 | 270 | /// 271 | /// Looks up a localized string similar to {0} new invites. 272 | /// 273 | internal static string InvitesPlural { 274 | get { 275 | return ResourceManager.GetString("InvitesPlural", resourceCulture); 276 | } 277 | } 278 | 279 | /// 280 | /// Looks up a localized string similar to 1 new invite. 281 | /// 282 | internal static string InvitesSingular { 283 | get { 284 | return ResourceManager.GetString("InvitesSingular", resourceCulture); 285 | } 286 | } 287 | 288 | /// 289 | /// Looks up a localized string similar to {0} new items in your inventory. 290 | /// 291 | internal static string ItemsPlural { 292 | get { 293 | return ResourceManager.GetString("ItemsPlural", resourceCulture); 294 | } 295 | } 296 | 297 | /// 298 | /// Looks up a localized string similar to 1 new item in your inventory. 299 | /// 300 | internal static string ItemsSingular { 301 | get { 302 | return ResourceManager.GetString("ItemsSingular", resourceCulture); 303 | } 304 | } 305 | 306 | /// 307 | /// Looks up a localized string similar to Log in. 308 | /// 309 | internal static string LogIn { 310 | get { 311 | return ResourceManager.GetString("LogIn", resourceCulture); 312 | } 313 | } 314 | 315 | /// 316 | /// Looks up a localized string similar to {0} community moderation messages. 317 | /// 318 | internal static string ModeratorMessagesPlural { 319 | get { 320 | return ResourceManager.GetString("ModeratorMessagesPlural", resourceCulture); 321 | } 322 | } 323 | 324 | /// 325 | /// Looks up a localized string similar to 1 community moderation message. 326 | /// 327 | internal static string ModeratorMessagesSingular { 328 | get { 329 | return ResourceManager.GetString("ModeratorMessagesSingular", resourceCulture); 330 | } 331 | } 332 | 333 | /// 334 | /// Looks up a localized string similar to New notifications since last check:. 335 | /// 336 | internal static string NewNotifsSince { 337 | get { 338 | return ResourceManager.GetString("NewNotifsSince", resourceCulture); 339 | } 340 | } 341 | 342 | /// 343 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 344 | /// 345 | internal static System.Drawing.Icon NotificationActive { 346 | get { 347 | object obj = ResourceManager.GetObject("NotificationActive", resourceCulture); 348 | return ((System.Drawing.Icon)(obj)); 349 | } 350 | } 351 | 352 | /// 353 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 354 | /// 355 | internal static System.Drawing.Icon NotificationDefault { 356 | get { 357 | object obj = ResourceManager.GetObject("NotificationDefault", resourceCulture); 358 | return ((System.Drawing.Icon)(obj)); 359 | } 360 | } 361 | 362 | /// 363 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 364 | /// 365 | internal static System.Drawing.Icon NotificationDisabled { 366 | get { 367 | object obj = ResourceManager.GetObject("NotificationDisabled", resourceCulture); 368 | return ((System.Drawing.Icon)(obj)); 369 | } 370 | } 371 | 372 | /// 373 | /// Looks up a localized string similar to Not logged in. 374 | /// 375 | internal static string NotLoggedIn { 376 | get { 377 | return ResourceManager.GetString("NotLoggedIn", resourceCulture); 378 | } 379 | } 380 | 381 | /// 382 | /// Looks up a localized string similar to {0} unread chat messages. 383 | /// 384 | internal static string OfflineMessagesPlural { 385 | get { 386 | return ResourceManager.GetString("OfflineMessagesPlural", resourceCulture); 387 | } 388 | } 389 | 390 | /// 391 | /// Looks up a localized string similar to 1 unread chat message. 392 | /// 393 | internal static string OfflineMessagesSingular { 394 | get { 395 | return ResourceManager.GetString("OfflineMessagesSingular", resourceCulture); 396 | } 397 | } 398 | 399 | /// 400 | /// Looks up a localized string similar to Refresh now. 401 | /// 402 | internal static string RefreshNow { 403 | get { 404 | return ResourceManager.GetString("RefreshNow", resourceCulture); 405 | } 406 | } 407 | 408 | /// 409 | /// Looks up a localized string similar to Settings. 410 | /// 411 | internal static string Settings { 412 | get { 413 | return ResourceManager.GetString("Settings", resourceCulture); 414 | } 415 | } 416 | 417 | /// 418 | /// Looks up a localized string similar to Mute balloons temporarily. 419 | /// 420 | internal static string TempMute { 421 | get { 422 | return ResourceManager.GetString("TempMute", resourceCulture); 423 | } 424 | } 425 | 426 | /// 427 | /// Looks up a localized string similar to {0} new trade notifications. 428 | /// 429 | internal static string TradeOffersPlural { 430 | get { 431 | return ResourceManager.GetString("TradeOffersPlural", resourceCulture); 432 | } 433 | } 434 | 435 | /// 436 | /// Looks up a localized string similar to 1 new trade notification. 437 | /// 438 | internal static string TradeOffersSingular { 439 | get { 440 | return ResourceManager.GetString("TradeOffersSingular", resourceCulture); 441 | } 442 | } 443 | 444 | /// 445 | /// Looks up a localized string similar to {0} unread Steam notifications. 446 | /// 447 | internal static string UnreadNotificationsPlural { 448 | get { 449 | return ResourceManager.GetString("UnreadNotificationsPlural", resourceCulture); 450 | } 451 | } 452 | 453 | /// 454 | /// Looks up a localized string similar to 1 unread Steam notification. 455 | /// 456 | internal static string UnreadNotificationsSingular { 457 | get { 458 | return ResourceManager.GetString("UnreadNotificationsSingular", resourceCulture); 459 | } 460 | } 461 | } 462 | } 463 | -------------------------------------------------------------------------------- /SteamNotificationsTray/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | {0} new turns waiting 122 | 123 | 124 | 1 turn waiting 125 | 126 | 127 | {0} new comments 128 | 129 | 130 | 1 new comment 131 | 132 | 133 | Error polling for notifications: 134 | 135 | 136 | Exception: 137 | 138 | 139 | Exit 140 | 141 | 142 | {0} new gifts 143 | 144 | 145 | 1 new gift 146 | 147 | 148 | {0} replies from Steam Support 149 | 150 | 151 | 1 reply from Steam Support 152 | 153 | 154 | 155 | ..\graphics\inbox_async_game.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 156 | 157 | 158 | ..\graphics\inbox_comments.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 159 | 160 | 161 | ..\graphics\inbox_gifts.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 162 | 163 | 164 | ..\graphics\inbox_invites.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 165 | 166 | 167 | ..\graphics\inbox_items.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 168 | 169 | 170 | ..\graphics\inbox_moderator_message.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 171 | 172 | 173 | ..\graphics\inbox_offlinemessages.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 174 | 175 | 176 | ..\graphics\inbox_tradeoffers.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 177 | 178 | 179 | {0} new invites 180 | 181 | 182 | 1 new invite 183 | 184 | 185 | {0} new items in your inventory 186 | 187 | 188 | 1 new item in your inventory 189 | 190 | 191 | Log in 192 | 193 | 194 | {0} community moderation messages 195 | 196 | 197 | 1 community moderation message 198 | 199 | 200 | New notifications since last check: 201 | 202 | 203 | ..\graphics\inbox_notification.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 204 | 205 | 206 | ..\graphics\inbox_notification_inactive.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 207 | 208 | 209 | ..\graphics\inbox_notification_inactive_disabled.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 210 | 211 | 212 | Not logged in 213 | 214 | 215 | {0} unread chat messages 216 | 217 | 218 | 1 unread chat message 219 | 220 | 221 | Refresh now 222 | 223 | 224 | Settings 225 | 226 | 227 | {0} new trade notifications 228 | 229 | 230 | 1 new trade notification 231 | 232 | 233 | {0} unread Steam notifications 234 | 235 | 236 | 1 unread Steam notification 237 | 238 | 239 | {0} account alerts 240 | 241 | 242 | 1 account alert 243 | 244 | 245 | ..\graphics\inbox_account_alert.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 246 | 247 | 248 | Mute balloons temporarily 249 | 250 | -------------------------------------------------------------------------------- /SteamNotificationsTray/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace SteamNotificationsTray.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.UserScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | [global::System.Configuration.DefaultSettingValueAttribute("")] 29 | public string Credentials { 30 | get { 31 | return ((string)(this["Credentials"])); 32 | } 33 | set { 34 | this["Credentials"] = value; 35 | } 36 | } 37 | 38 | [global::System.Configuration.UserScopedSettingAttribute()] 39 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 40 | [global::System.Configuration.DefaultSettingValueAttribute("92, 126, 16")] 41 | public global::System.Drawing.Color InboxAvailableColor { 42 | get { 43 | return ((global::System.Drawing.Color)(this["InboxAvailableColor"])); 44 | } 45 | set { 46 | this["InboxAvailableColor"] = value; 47 | } 48 | } 49 | 50 | [global::System.Configuration.UserScopedSettingAttribute()] 51 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 52 | [global::System.Configuration.DefaultSettingValueAttribute("0, 0, 0, 0")] 53 | public global::System.Drawing.Color InboxNoneColor { 54 | get { 55 | return ((global::System.Drawing.Color)(this["InboxNoneColor"])); 56 | } 57 | set { 58 | this["InboxNoneColor"] = value; 59 | } 60 | } 61 | 62 | [global::System.Configuration.UserScopedSettingAttribute()] 63 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 64 | [global::System.Configuration.DefaultSettingValueAttribute("158, 194, 29")] 65 | public global::System.Drawing.Color InboxNewColor { 66 | get { 67 | return ((global::System.Drawing.Color)(this["InboxNewColor"])); 68 | } 69 | set { 70 | this["InboxNewColor"] = value; 71 | } 72 | } 73 | 74 | [global::System.Configuration.UserScopedSettingAttribute()] 75 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 76 | [global::System.Configuration.DefaultSettingValueAttribute("30000")] 77 | public int RefreshInterval { 78 | get { 79 | return ((int)(this["RefreshInterval"])); 80 | } 81 | set { 82 | this["RefreshInterval"] = value; 83 | } 84 | } 85 | 86 | [global::System.Configuration.UserScopedSettingAttribute()] 87 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 88 | [global::System.Configuration.DefaultSettingValueAttribute("59, 57, 56")] 89 | public global::System.Drawing.Color NotificationPopupBackgroundColor { 90 | get { 91 | return ((global::System.Drawing.Color)(this["NotificationPopupBackgroundColor"])); 92 | } 93 | set { 94 | this["NotificationPopupBackgroundColor"] = value; 95 | } 96 | } 97 | 98 | [global::System.Configuration.UserScopedSettingAttribute()] 99 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 100 | [global::System.Configuration.DefaultSettingValueAttribute("130, 128, 124")] 101 | public global::System.Drawing.Color NotificationPopupBorderColor { 102 | get { 103 | return ((global::System.Drawing.Color)(this["NotificationPopupBorderColor"])); 104 | } 105 | set { 106 | this["NotificationPopupBorderColor"] = value; 107 | } 108 | } 109 | 110 | [global::System.Configuration.UserScopedSettingAttribute()] 111 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 112 | [global::System.Configuration.DefaultSettingValueAttribute("191, 191, 191")] 113 | public global::System.Drawing.Color NotificationInactiveColor { 114 | get { 115 | return ((global::System.Drawing.Color)(this["NotificationInactiveColor"])); 116 | } 117 | set { 118 | this["NotificationInactiveColor"] = value; 119 | } 120 | } 121 | 122 | [global::System.Configuration.UserScopedSettingAttribute()] 123 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 124 | [global::System.Configuration.DefaultSettingValueAttribute("33, 45, 61")] 125 | public global::System.Drawing.Color NotificationFocusColor { 126 | get { 127 | return ((global::System.Drawing.Color)(this["NotificationFocusColor"])); 128 | } 129 | set { 130 | this["NotificationFocusColor"] = value; 131 | } 132 | } 133 | 134 | [global::System.Configuration.UserScopedSettingAttribute()] 135 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 136 | [global::System.Configuration.DefaultSettingValueAttribute("112, 186, 36")] 137 | public global::System.Drawing.Color NotificationActiveColor { 138 | get { 139 | return ((global::System.Drawing.Color)(this["NotificationActiveColor"])); 140 | } 141 | set { 142 | this["NotificationActiveColor"] = value; 143 | } 144 | } 145 | 146 | [global::System.Configuration.UserScopedSettingAttribute()] 147 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 148 | [global::System.Configuration.DefaultSettingValueAttribute("107, 104, 101")] 149 | public global::System.Drawing.Color NotificationPopupSeparatorColor { 150 | get { 151 | return ((global::System.Drawing.Color)(this["NotificationPopupSeparatorColor"])); 152 | } 153 | set { 154 | this["NotificationPopupSeparatorColor"] = value; 155 | } 156 | } 157 | 158 | [global::System.Configuration.UserScopedSettingAttribute()] 159 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 160 | [global::System.Configuration.DefaultSettingValueAttribute("True")] 161 | public bool AlwaysShowComments { 162 | get { 163 | return ((bool)(this["AlwaysShowComments"])); 164 | } 165 | set { 166 | this["AlwaysShowComments"] = value; 167 | } 168 | } 169 | 170 | [global::System.Configuration.UserScopedSettingAttribute()] 171 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 172 | [global::System.Configuration.DefaultSettingValueAttribute("True")] 173 | public bool AlwaysShowItems { 174 | get { 175 | return ((bool)(this["AlwaysShowItems"])); 176 | } 177 | set { 178 | this["AlwaysShowItems"] = value; 179 | } 180 | } 181 | 182 | [global::System.Configuration.UserScopedSettingAttribute()] 183 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 184 | [global::System.Configuration.DefaultSettingValueAttribute("True")] 185 | public bool AlwaysShowInvites { 186 | get { 187 | return ((bool)(this["AlwaysShowInvites"])); 188 | } 189 | set { 190 | this["AlwaysShowInvites"] = value; 191 | } 192 | } 193 | 194 | [global::System.Configuration.UserScopedSettingAttribute()] 195 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 196 | [global::System.Configuration.DefaultSettingValueAttribute("True")] 197 | public bool AlwaysShowGifts { 198 | get { 199 | return ((bool)(this["AlwaysShowGifts"])); 200 | } 201 | set { 202 | this["AlwaysShowGifts"] = value; 203 | } 204 | } 205 | 206 | [global::System.Configuration.UserScopedSettingAttribute()] 207 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 208 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 209 | public bool AlwaysShowOfflineMessages { 210 | get { 211 | return ((bool)(this["AlwaysShowOfflineMessages"])); 212 | } 213 | set { 214 | this["AlwaysShowOfflineMessages"] = value; 215 | } 216 | } 217 | 218 | [global::System.Configuration.UserScopedSettingAttribute()] 219 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 220 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 221 | public bool AlwaysShowTradeOffers { 222 | get { 223 | return ((bool)(this["AlwaysShowTradeOffers"])); 224 | } 225 | set { 226 | this["AlwaysShowTradeOffers"] = value; 227 | } 228 | } 229 | 230 | [global::System.Configuration.UserScopedSettingAttribute()] 231 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 232 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 233 | public bool AlwaysShowAsyncGames { 234 | get { 235 | return ((bool)(this["AlwaysShowAsyncGames"])); 236 | } 237 | set { 238 | this["AlwaysShowAsyncGames"] = value; 239 | } 240 | } 241 | 242 | [global::System.Configuration.UserScopedSettingAttribute()] 243 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 244 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 245 | public bool AlwaysShowModeratorMessages { 246 | get { 247 | return ((bool)(this["AlwaysShowModeratorMessages"])); 248 | } 249 | set { 250 | this["AlwaysShowModeratorMessages"] = value; 251 | } 252 | } 253 | 254 | [global::System.Configuration.UserScopedSettingAttribute()] 255 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 256 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 257 | public bool AlwaysShowHelpRequestReplies { 258 | get { 259 | return ((bool)(this["AlwaysShowHelpRequestReplies"])); 260 | } 261 | set { 262 | this["AlwaysShowHelpRequestReplies"] = value; 263 | } 264 | } 265 | 266 | [global::System.Configuration.UserScopedSettingAttribute()] 267 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 268 | [global::System.Configuration.DefaultSettingValueAttribute("True")] 269 | public bool EnableBalloons { 270 | get { 271 | return ((bool)(this["EnableBalloons"])); 272 | } 273 | set { 274 | this["EnableBalloons"] = value; 275 | } 276 | } 277 | 278 | [global::System.Configuration.UserScopedSettingAttribute()] 279 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 280 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 281 | public bool SingleIcon { 282 | get { 283 | return ((bool)(this["SingleIcon"])); 284 | } 285 | set { 286 | this["SingleIcon"] = value; 287 | } 288 | } 289 | 290 | [global::System.Configuration.UserScopedSettingAttribute()] 291 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 292 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 293 | public bool AlwaysShowAccountAlerts { 294 | get { 295 | return ((bool)(this["AlwaysShowAccountAlerts"])); 296 | } 297 | set { 298 | this["AlwaysShowAccountAlerts"] = value; 299 | } 300 | } 301 | 302 | [global::System.Configuration.UserScopedSettingAttribute()] 303 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 304 | [global::System.Configuration.DefaultSettingValueAttribute("White")] 305 | public global::System.Drawing.Color NotificationCountColor { 306 | get { 307 | return ((global::System.Drawing.Color)(this["NotificationCountColor"])); 308 | } 309 | set { 310 | this["NotificationCountColor"] = value; 311 | } 312 | } 313 | 314 | [global::System.Configuration.UserScopedSettingAttribute()] 315 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 316 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 317 | public bool EnableAntiFlapping { 318 | get { 319 | return ((bool)(this["EnableAntiFlapping"])); 320 | } 321 | set { 322 | this["EnableAntiFlapping"] = value; 323 | } 324 | } 325 | 326 | [global::System.Configuration.UserScopedSettingAttribute()] 327 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 328 | [global::System.Configuration.DefaultSettingValueAttribute("True")] 329 | public bool OpenLinksOnBalloonClick { 330 | get { 331 | return ((bool)(this["OpenLinksOnBalloonClick"])); 332 | } 333 | set { 334 | this["OpenLinksOnBalloonClick"] = value; 335 | } 336 | } 337 | 338 | [global::System.Configuration.UserScopedSettingAttribute()] 339 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 340 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 341 | public bool SettingsUpgraded { 342 | get { 343 | return ((bool)(this["SettingsUpgraded"])); 344 | } 345 | set { 346 | this["SettingsUpgraded"] = value; 347 | } 348 | } 349 | 350 | [global::System.Configuration.UserScopedSettingAttribute()] 351 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 352 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 353 | public bool UseSteamBrowserProtocolLinks { 354 | get { 355 | return ((bool)(this["UseSteamBrowserProtocolLinks"])); 356 | } 357 | set { 358 | this["UseSteamBrowserProtocolLinks"] = value; 359 | } 360 | } 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /SteamNotificationsTray/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 92, 126, 16 10 | 11 | 12 | 0, 0, 0, 0 13 | 14 | 15 | 158, 194, 29 16 | 17 | 18 | 30000 19 | 20 | 21 | 59, 57, 56 22 | 23 | 24 | 130, 128, 124 25 | 26 | 27 | 191, 191, 191 28 | 29 | 30 | 33, 45, 61 31 | 32 | 33 | 112, 186, 36 34 | 35 | 36 | 107, 104, 101 37 | 38 | 39 | True 40 | 41 | 42 | True 43 | 44 | 45 | True 46 | 47 | 48 | True 49 | 50 | 51 | False 52 | 53 | 54 | False 55 | 56 | 57 | False 58 | 59 | 60 | False 61 | 62 | 63 | False 64 | 65 | 66 | True 67 | 68 | 69 | False 70 | 71 | 72 | False 73 | 74 | 75 | White 76 | 77 | 78 | False 79 | 80 | 81 | True 82 | 83 | 84 | False 85 | 86 | 87 | False 88 | 89 | 90 | -------------------------------------------------------------------------------- /SteamNotificationsTray/SettingsForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Windows.Forms; 9 | using System.Reflection; 10 | using SteamNotificationsTray.Properties; 11 | 12 | namespace SteamNotificationsTray 13 | { 14 | public partial class SettingsForm : Form 15 | { 16 | public event EventHandler SettingsApplied; 17 | public event EventHandler LoggingOut; 18 | 19 | public SettingsForm() 20 | { 21 | InitializeComponent(); 22 | } 23 | 24 | private void SettingsForm_Load(object sender, EventArgs e) 25 | { 26 | // Set up version info 27 | using (Icon icon = new Icon(Icon, 64, 64)) 28 | iconPictureBox.Image = icon.ToBitmap(); 29 | productNameLabel.Text = Application.ProductName; 30 | versionLabel.Text = string.Format(versionLabel.Text, Application.ProductVersion); 31 | var copyrightAttr = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false).FirstOrDefault() as AssemblyCopyrightAttribute; 32 | if (copyrightAttr != null) copyrightLabel.Text = copyrightAttr.Copyright; 33 | 34 | loadSettings(); 35 | } 36 | 37 | void loadSettings() 38 | { 39 | var settings = Settings.Default; 40 | 41 | // Load general settings 42 | intervalNumericUpDown.Value = settings.RefreshInterval / 1000; 43 | openInSteamCheckBox.Checked = settings.UseSteamBrowserProtocolLinks; 44 | enableBalloonsCheckBox.Checked = settings.EnableBalloons; 45 | openLinksOnBalloonClickCheckBox.Checked = settings.OpenLinksOnBalloonClick; 46 | singleIconCheckBox.Checked = settings.SingleIcon; 47 | enableAntiFlappingCheckBox.Checked = settings.EnableAntiFlapping; 48 | 49 | // Load item settings 50 | commentsCheckBox.Checked = settings.AlwaysShowComments; 51 | itemsCheckBox.Checked = settings.AlwaysShowItems; 52 | invitesCheckBox.Checked = settings.AlwaysShowInvites; 53 | giftsCheckBox.Checked = settings.AlwaysShowGifts; 54 | offlineMessagesCheckBox.Checked = settings.AlwaysShowOfflineMessages; 55 | tradeOffersCheckBox.Checked = settings.AlwaysShowTradeOffers; 56 | asyncGamesCheckBox.Checked = settings.AlwaysShowAsyncGames; 57 | moderatorMessagesCheckBox.Checked = settings.AlwaysShowModeratorMessages; 58 | helpRequestRepliesCheckBox.Checked = settings.AlwaysShowHelpRequestReplies; 59 | accountAlertsCheckBox.Checked = settings.AlwaysShowAccountAlerts; 60 | 61 | // Load colors 62 | noNotifsButton.BackColor = settings.InboxNoneColor; 63 | unreadNotifsButton.BackColor = settings.InboxAvailableColor; 64 | newNotifsButton.BackColor = settings.InboxNewColor; 65 | textButton.BackColor = settings.NotificationInactiveColor; 66 | activeTextButton.BackColor = settings.NotificationActiveColor; 67 | backgroundButton.BackColor = settings.NotificationPopupBackgroundColor; 68 | borderButton.BackColor = settings.NotificationPopupBorderColor; 69 | focusedButton.BackColor = settings.NotificationFocusColor; 70 | separatorButton.BackColor = settings.NotificationPopupSeparatorColor; 71 | countColorButton.BackColor = settings.NotificationCountColor; 72 | } 73 | 74 | private void resetbutton_Click(object sender, EventArgs e) 75 | { 76 | string auth = Settings.Default.Credentials; 77 | Settings.Default.Reset(); 78 | Settings.Default.Credentials = auth; 79 | loadSettings(); 80 | } 81 | 82 | private void logoutButton_Click(object sender, EventArgs e) 83 | { 84 | DialogResult = System.Windows.Forms.DialogResult.Cancel; 85 | Close(); 86 | var handler = LoggingOut; 87 | if (handler != null) handler(this, EventArgs.Empty); 88 | } 89 | 90 | private void cancelButton_Click(object sender, EventArgs e) 91 | { 92 | DialogResult = System.Windows.Forms.DialogResult.Cancel; 93 | Close(); 94 | } 95 | 96 | private void okButton_Click(object sender, EventArgs e) 97 | { 98 | DialogResult = System.Windows.Forms.DialogResult.OK; 99 | Close(); 100 | applySettings(); 101 | } 102 | 103 | private void applyButton_Click(object sender, EventArgs e) 104 | { 105 | applySettings(); 106 | } 107 | 108 | void applySettings() 109 | { 110 | var settings = Settings.Default; 111 | 112 | // Set general settings 113 | settings.RefreshInterval = (int)intervalNumericUpDown.Value * 1000; 114 | settings.UseSteamBrowserProtocolLinks = openInSteamCheckBox.Checked; 115 | settings.EnableBalloons = enableBalloonsCheckBox.Checked; 116 | settings.OpenLinksOnBalloonClick = openLinksOnBalloonClickCheckBox.Checked; 117 | settings.SingleIcon = singleIconCheckBox.Checked; 118 | settings.EnableAntiFlapping = enableAntiFlappingCheckBox.Checked; 119 | 120 | // Set item settings 121 | settings.AlwaysShowComments = commentsCheckBox.Checked; 122 | settings.AlwaysShowItems = itemsCheckBox.Checked; 123 | settings.AlwaysShowInvites = invitesCheckBox.Checked; 124 | settings.AlwaysShowGifts = giftsCheckBox.Checked; 125 | settings.AlwaysShowOfflineMessages = offlineMessagesCheckBox.Checked; 126 | settings.AlwaysShowTradeOffers = tradeOffersCheckBox.Checked; 127 | settings.AlwaysShowAsyncGames = asyncGamesCheckBox.Checked; 128 | settings.AlwaysShowModeratorMessages = moderatorMessagesCheckBox.Checked; 129 | settings.AlwaysShowHelpRequestReplies = helpRequestRepliesCheckBox.Checked; 130 | settings.AlwaysShowAccountAlerts = accountAlertsCheckBox.Checked; 131 | 132 | // Set colors 133 | settings.InboxNoneColor = noNotifsButton.BackColor; 134 | settings.InboxAvailableColor = unreadNotifsButton.BackColor; 135 | settings.InboxNewColor = newNotifsButton.BackColor; 136 | settings.NotificationInactiveColor = textButton.BackColor; 137 | settings.NotificationActiveColor = activeTextButton.BackColor; 138 | settings.NotificationPopupBackgroundColor = backgroundButton.BackColor; 139 | settings.NotificationPopupBorderColor = borderButton.BackColor; 140 | settings.NotificationFocusColor = focusedButton.BackColor; 141 | settings.NotificationPopupSeparatorColor = separatorButton.BackColor; 142 | settings.NotificationCountColor = countColorButton.BackColor; 143 | 144 | settings.Save(); 145 | var handler = SettingsApplied; 146 | if (handler != null) handler(this, EventArgs.Empty); 147 | } 148 | 149 | private void githubLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 150 | { 151 | System.Diagnostics.Process.Start("https://github.com/GMMan/SteamNotificationsTray"); 152 | } 153 | 154 | private void SettingsForm_FormClosing(object sender, FormClosingEventArgs e) 155 | { 156 | if (DialogResult != System.Windows.Forms.DialogResult.OK) 157 | { 158 | Settings.Default.Reload(); 159 | } 160 | } 161 | 162 | private void colorButton_Click(object sender, EventArgs e) 163 | { 164 | var button = sender as Button; 165 | if (button == null) return; 166 | 167 | using (var picker = new Cyotek.Windows.Forms.ColorPickerDialog()) 168 | { 169 | picker.Color = button.BackColor; 170 | if (picker.ShowDialog(this) == System.Windows.Forms.DialogResult.OK) 171 | { 172 | button.BackColor = picker.Color; 173 | } 174 | } 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /SteamNotificationsTray/SteamNotificationsTray.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {4A4523CB-5D77-4430-AA32-D8A9FE62977E} 8 | WinExe 9 | Properties 10 | SteamNotificationsTray 11 | SteamNotificationsTray 12 | v4.5.1 13 | 512 14 | 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | false 26 | false 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | false 37 | 38 | 39 | AppIcon.ico 40 | 41 | 42 | 43 | False 44 | ..\..\Cyotek.Windows.Forms.ColorPicker-master\Cyotek.Windows.Forms.ColorPicker\bin\Release\Cyotek.Windows.Forms.ColorPicker.dll 45 | 46 | 47 | ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll 48 | True 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | Form 68 | 69 | 70 | LoginForm.cs 71 | 72 | 73 | 74 | 75 | 76 | 77 | Form 78 | 79 | 80 | SettingsForm.cs 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | LoginForm.cs 93 | 94 | 95 | ResXFileCodeGenerator 96 | Resources.Designer.cs 97 | Designer 98 | 99 | 100 | True 101 | Resources.resx 102 | True 103 | 104 | 105 | SettingsForm.cs 106 | 107 | 108 | 109 | 110 | SettingsSingleFileGenerator 111 | Settings.Designer.cs 112 | 113 | 114 | True 115 | Settings.settings 116 | True 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 148 | -------------------------------------------------------------------------------- /SteamNotificationsTray/TrayAppContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Windows.Forms; 6 | using System.Drawing; 7 | using System.Runtime.InteropServices; 8 | using System.Net; 9 | using System.Threading.Tasks; 10 | using System.Reflection; 11 | 12 | namespace SteamNotificationsTray 13 | { 14 | partial class TrayAppContext : ApplicationContext 15 | { 16 | NotifyIcon mainIcon = new NotifyIcon(); 17 | NotifyIcon countIcon = new NotifyIcon(); 18 | ContextMenu appContextMenu; 19 | MenuItem loginMenuItem; 20 | MenuItem refreshMenuItem; 21 | MenuItem muteMenuItem; 22 | Timer refreshTimer = new Timer(); 23 | NotificationsClient client = new NotificationsClient(); 24 | bool newNotifAcknowledged; 25 | bool hasNotifications; 26 | bool isLoggedIn; 27 | bool muted; 28 | MethodInfo NotifyIcon_ShowContextMenu; 29 | NotificationCounts oldCounts; 30 | NotificationCounts countsDiff; 31 | 32 | // Experimental: try to prevent flapping when Steam Community is down, where 33 | // it returns zero notifications one poll, then the proper numbers the next poll 34 | int updatesSinceLastNonZeroNotificationsCount; 35 | int updatesUntilSetCountsToZero = 2; 36 | 37 | public TrayAppContext() 38 | { 39 | NotifyIcon_ShowContextMenu = typeof(NotifyIcon).GetMethod("ShowContextMenu", BindingFlags.NonPublic | BindingFlags.Instance); 40 | 41 | // Upgrade settings first 42 | var settings = Properties.Settings.Default; 43 | if (!settings.SettingsUpgraded) 44 | { 45 | settings.Upgrade(); 46 | settings.SettingsUpgraded = true; 47 | settings.Save(); 48 | } 49 | 50 | loginMenuItem = new MenuItem(Properties.Resources.LogIn, (sender, e) => 51 | { 52 | promptLogin(); 53 | }) { Visible = false }; 54 | refreshMenuItem = new MenuItem(Properties.Resources.RefreshNow, (sender, e) => 55 | { 56 | updateNotifications(); 57 | }); 58 | muteMenuItem = new MenuItem(Properties.Resources.TempMute, (sender, e) => 59 | { 60 | muted = !muted; 61 | muteMenuItem.Checked = muted; 62 | }) { Visible = settings.EnableBalloons }; 63 | 64 | appContextMenu = new ContextMenu(new MenuItem[] { 65 | loginMenuItem, 66 | refreshMenuItem, 67 | muteMenuItem, 68 | new MenuItem(Properties.Resources.Settings, (sender, e) => 69 | { 70 | var settingsForm = new SettingsForm(); 71 | settingsForm.SettingsApplied += settingsForm_SettingsApplied; 72 | settingsForm.LoggingOut += settingsForm_LoggingOut; 73 | settingsForm.Show(); 74 | }), 75 | new MenuItem(Properties.Resources.Exit, (sender, e) => { 76 | client.SaveCredentials(); 77 | Application.Exit(); 78 | }) 79 | }); 80 | 81 | setupNotificationsPopup(); 82 | 83 | refreshTimer.Interval = settings.RefreshInterval; 84 | refreshTimer.Tick += refreshTimer_Tick; 85 | 86 | // Must do this true/false charade to get context menus associated for some reason 87 | countIcon.ContextMenu = appContextMenu; 88 | countIcon.Visible = true; 89 | countIcon.Visible = false; 90 | mainIcon.ContextMenu = appContextMenu; 91 | mainIcon.Text = Application.ProductName; 92 | mainIcon.Visible = true; 93 | mainIcon.Visible = false; 94 | 95 | mainIcon.MouseDown += notifyIcon_MouseDown; 96 | mainIcon.MouseClick += notifyIcon_MouseClick; 97 | mainIcon.MouseDoubleClick += notifyIcon_MouseDoubleClick; 98 | countIcon.MouseDown += notifyIcon_MouseDown; 99 | countIcon.MouseClick += notifyIcon_MouseClick; 100 | countIcon.MouseDoubleClick += notifyIcon_MouseDoubleClick; 101 | countIcon.BalloonTipClicked += countIcon_BalloonTipClicked; 102 | 103 | // If no cookies available, show login form 104 | //CredentialStore.ClearCredentials(); 105 | if (!CredentialStore.CredentialsAvailable()) 106 | { 107 | promptLogin(); 108 | } 109 | else 110 | { 111 | finishSetup(); 112 | } 113 | } 114 | 115 | void promptLogin() 116 | { 117 | mainIcon.Visible = false; 118 | LoginForm loginForm = new LoginForm(); 119 | loginForm.FormClosed += loginForm_FormClosed; 120 | MainForm = loginForm; 121 | loginForm.Show(); 122 | } 123 | 124 | void loginForm_FormClosed(object sender, FormClosedEventArgs e) 125 | { 126 | Form form = sender as Form; 127 | MainForm = null; 128 | if (form.DialogResult == DialogResult.OK) 129 | finishSetup(); // Login OK, start the rest of the app 130 | else 131 | Application.Exit(); // Login canceled, exit 132 | } 133 | 134 | void finishSetup() 135 | { 136 | // Set up cookies 137 | CookieContainer cookies = CredentialStore.GetCommunityCookies(); 138 | client.SetCookies(cookies); 139 | 140 | // Update right click menu 141 | loginMenuItem.Visible = false; 142 | refreshMenuItem.Visible = true; 143 | 144 | isLoggedIn = true; 145 | 146 | // Set main icon visible 147 | ReplaceNotifyIcon(mainIcon, IconUtils.CreateIconWithBackground(Properties.Resources.NotificationDefault, Properties.Settings.Default.InboxNoneColor, SystemInformation.SmallIconSize)); 148 | mainIcon.Visible = true; 149 | 150 | // Need this to force initial update if counts are zero 151 | updatesSinceLastNonZeroNotificationsCount = updatesUntilSetCountsToZero; 152 | 153 | // Set up timer and fire 154 | refreshTimer.Start(); 155 | updateNotifications(); 156 | } 157 | 158 | async void refreshTimer_Tick(object sender, EventArgs e) 159 | { 160 | await updateNotifications(); 161 | } 162 | 163 | async Task updateNotifications() 164 | { 165 | try 166 | { 167 | NotificationCounts counts = await client.PollNotificationCountsAsync(); 168 | if (counts != null) 169 | { 170 | CredentialStore.NotifyAuthAttempt(true); 171 | updateUi(counts); 172 | } 173 | } 174 | catch (System.Net.Http.HttpRequestException ex) 175 | { 176 | if (ex.Message.Contains("401")) 177 | { 178 | // Login info expired 179 | CredentialStore.NotifyAuthAttempt(false); 180 | if (CredentialStore.ShouldClearAuth()) 181 | logOut(); 182 | } 183 | else 184 | { 185 | markException(Properties.Resources.ErrorPolling + ex.Message); 186 | } 187 | } 188 | catch (Exception ex) 189 | { 190 | markException(Properties.Resources.Exception + ex.Message); 191 | } 192 | } 193 | 194 | void logOut() 195 | { 196 | refreshTimer.Stop(); 197 | isLoggedIn = false; 198 | client.SetCookies(null); 199 | Properties.Settings.Default.Credentials = null; 200 | Properties.Settings.Default.Save(); 201 | ReplaceNotifyIcon(mainIcon, IconUtils.CreateIconWithBackground(Properties.Resources.NotificationDisabled, Properties.Settings.Default.InboxNoneColor, SystemInformation.SmallIconSize)); 202 | countIcon.Visible = false; 203 | loginMenuItem.Visible = true; 204 | refreshMenuItem.Visible = false; 205 | markException(Properties.Resources.NotLoggedIn); 206 | } 207 | 208 | void updateUi(NotificationCounts counts) 209 | { 210 | try 211 | { 212 | if (oldCounts == null) 213 | { 214 | countsDiff = new NotificationCounts(); 215 | } 216 | else 217 | { 218 | // Experimental anti-flapping: don't set counts to zero unless we have updatesUntilSetCountsToZero 219 | // of confirmed polls with zero notifications 220 | if (Properties.Settings.Default.EnableAntiFlapping) 221 | { 222 | if (counts.TotalNotifications > 0) 223 | { 224 | updatesSinceLastNonZeroNotificationsCount = 0; 225 | } 226 | else 227 | { 228 | ++updatesSinceLastNonZeroNotificationsCount; 229 | if (updatesSinceLastNonZeroNotificationsCount < updatesUntilSetCountsToZero) 230 | return; 231 | } 232 | } 233 | 234 | var prev = oldCounts; 235 | countsDiff = new NotificationCounts 236 | { 237 | Comments = counts.Comments - prev.Comments, 238 | Items = counts.Items - prev.Items, 239 | Invites = counts.Invites - prev.Invites, 240 | Gifts = counts.Gifts - prev.Gifts, 241 | OfflineMessages = counts.OfflineMessages - prev.OfflineMessages, 242 | TradeOffers = counts.TradeOffers - prev.TradeOffers, 243 | AsyncGames = counts.AsyncGames - prev.AsyncGames, 244 | ModeratorMessages = counts.ModeratorMessages - prev.ModeratorMessages, 245 | HelpRequestReplies = counts.HelpRequestReplies - prev.HelpRequestReplies, 246 | AccountAlerts = counts.AccountAlerts - prev.AccountAlerts, 247 | TotalNotifications = counts.TotalNotifications - prev.TotalNotifications, 248 | }; 249 | } 250 | 251 | updatePopupCounts(counts); 252 | mainIcon.Text = Application.ProductName; 253 | countIcon.Text = string.Format(counts.TotalNotifications == 1 ? Properties.Resources.UnreadNotificationsSingular : Properties.Resources.UnreadNotificationsPlural, counts.TotalNotifications); 254 | 255 | if (counts.TotalNotifications == 0) 256 | { 257 | hasNotifications = false; 258 | countIcon.Visible = false; 259 | ReplaceNotifyIcon(mainIcon, IconUtils.CreateIconWithBackground(Properties.Resources.NotificationDefault, Properties.Settings.Default.InboxNoneColor, SystemInformation.SmallIconSize)); 260 | mainIcon.Visible = true; 261 | } 262 | else 263 | { 264 | hasNotifications = true; 265 | Color newColor; 266 | if (oldCounts == null) 267 | { 268 | newNotifAcknowledged = true; 269 | newColor = Properties.Settings.Default.InboxAvailableColor; 270 | } 271 | else 272 | { 273 | if (counts.TotalNotifications > oldCounts.TotalNotifications) 274 | { 275 | newNotifAcknowledged = false; 276 | newColor = Properties.Settings.Default.InboxNewColor; 277 | } 278 | else if (counts.TotalNotifications == oldCounts.TotalNotifications) 279 | { 280 | newColor = newNotifAcknowledged ? Properties.Settings.Default.InboxAvailableColor : Properties.Settings.Default.InboxNewColor; 281 | } 282 | else 283 | { 284 | newNotifAcknowledged = true; 285 | newColor = Properties.Settings.Default.InboxAvailableColor; 286 | } 287 | } 288 | 289 | ReplaceNotifyIcon(mainIcon, IconUtils.CreateIconWithBackground(Properties.Resources.NotificationActive, newColor, SystemInformation.SmallIconSize)); 290 | 291 | // 7 point for 3 digits 292 | // 8 point for 2 digits 293 | // 9 point for 1 digit 294 | string text = counts.TotalNotifications.ToString(); 295 | ReplaceNotifyIcon(countIcon, IconUtils.CreateIconWithText(text, new Font("Arial", 10 - text.Length, FontStyle.Regular, GraphicsUnit.Point), 296 | Properties.Settings.Default.NotificationCountColor, newColor, SystemInformation.SmallIconSize)); 297 | 298 | if (!countIcon.Visible) 299 | { 300 | // Hide main icon first, then show in this order so the count is on the left 301 | mainIcon.Visible = false; 302 | countIcon.Visible = true; 303 | } 304 | mainIcon.Visible = !Properties.Settings.Default.SingleIcon; 305 | 306 | if (Properties.Settings.Default.EnableBalloons && !muted) 307 | { 308 | List notifications = new List(); 309 | if (countsDiff.Comments > 0) notifications.Add(countsDiff.Comments == 1 ? Properties.Resources.CommentsSingular : string.Format(Properties.Resources.CommentsPlural, countsDiff.Comments)); 310 | if (countsDiff.Items > 0) notifications.Add(countsDiff.Items == 1 ? Properties.Resources.ItemsSingular : string.Format(Properties.Resources.ItemsPlural, countsDiff.Items)); 311 | if (countsDiff.Invites > 0) notifications.Add(countsDiff.Invites == 1 ? Properties.Resources.InvitesSingular : string.Format(Properties.Resources.InvitesPlural, countsDiff.Invites)); 312 | if (countsDiff.Gifts > 0) notifications.Add(countsDiff.Gifts == 1 ? Properties.Resources.GiftsSingular : string.Format(Properties.Resources.GiftsPlural, countsDiff.Gifts)); 313 | if (countsDiff.OfflineMessages > 0) notifications.Add(countsDiff.OfflineMessages == 1 ? Properties.Resources.OfflineMessagesSingular : string.Format(Properties.Resources.OfflineMessagesPlural, countsDiff.OfflineMessages)); 314 | if (countsDiff.TradeOffers > 0) notifications.Add(countsDiff.TradeOffers == 1 ? Properties.Resources.TradeOffersSingular : string.Format(Properties.Resources.TradeOffersPlural, countsDiff.TradeOffers)); 315 | if (countsDiff.AsyncGames > 0) notifications.Add(countsDiff.AsyncGames == 1 ? Properties.Resources.AsyncGamesSingular : string.Format(Properties.Resources.AsyncGamesPlural, countsDiff.AsyncGames)); 316 | if (countsDiff.ModeratorMessages > 0) notifications.Add(countsDiff.ModeratorMessages == 1 ? Properties.Resources.ModeratorMessagesSingular : string.Format(Properties.Resources.ModeratorMessagesPlural, countsDiff.ModeratorMessages)); 317 | if (countsDiff.HelpRequestReplies > 0) notifications.Add(countsDiff.HelpRequestReplies == 1 ? Properties.Resources.HelpRequestRepliesSingular : string.Format(Properties.Resources.HelpRequestRepliesPlural, countsDiff.HelpRequestReplies)); 318 | if (countsDiff.AccountAlerts > 0) notifications.Add(countsDiff.AccountAlerts == 1 ? Properties.Resources.AccountAlertsSingular : string.Format(Properties.Resources.AccountAlertsPlural, countsDiff.AccountAlerts)); 319 | 320 | if (notifications.Count > 0) 321 | { 322 | countIcon.BalloonTipIcon = ToolTipIcon.Info; 323 | countIcon.BalloonTipTitle = countIcon.Text; 324 | 325 | StringBuilder sb = new StringBuilder(); 326 | sb.AppendLine(Properties.Resources.NewNotifsSince); 327 | sb.AppendLine(); 328 | foreach (string notif in notifications) 329 | sb.AppendLine(notif); 330 | countIcon.BalloonTipText = sb.ToString(); 331 | countIcon.ShowBalloonTip(10000); // Per MSDN, timeout doesn't make a difference (since Vista) 332 | } 333 | } 334 | } 335 | 336 | oldCounts = counts; 337 | } 338 | catch (Exception ex) 339 | { 340 | markException(Properties.Resources.Exception + ex.Message); 341 | } 342 | } 343 | 344 | void countIcon_BalloonTipClicked(object sender, EventArgs e) 345 | { 346 | if (Properties.Settings.Default.OpenLinksOnBalloonClick && countsDiff != null) 347 | { 348 | // Steam client doesn't open things in new tabs, so if we have more than one 349 | // type of notification, open in browser instead. 350 | bool origOpenInSteam = Properties.Settings.Default.UseSteamBrowserProtocolLinks; 351 | if (origOpenInSteam) 352 | { 353 | int typesOfNewNotifs = 0; 354 | if (countsDiff.Comments > 0) ++typesOfNewNotifs; 355 | if (countsDiff.Items > 0) ++typesOfNewNotifs; 356 | if (countsDiff.Invites > 0) ++typesOfNewNotifs; 357 | if (countsDiff.Gifts > 0) ++typesOfNewNotifs; 358 | if (countsDiff.OfflineMessages > 0) ++typesOfNewNotifs; 359 | if (countsDiff.TradeOffers > 0) ++typesOfNewNotifs; 360 | if (countsDiff.AsyncGames > 0) ++typesOfNewNotifs; 361 | if (countsDiff.ModeratorMessages > 0) ++typesOfNewNotifs; 362 | if (countsDiff.HelpRequestReplies > 0) ++typesOfNewNotifs; 363 | if (countsDiff.AccountAlerts > 0) ++typesOfNewNotifs; 364 | 365 | if (typesOfNewNotifs > 1) 366 | Properties.Settings.Default.UseSteamBrowserProtocolLinks = false; 367 | } 368 | 369 | if (countsDiff.Comments > 0) commentsMenuItem_Click(sender, EventArgs.Empty); 370 | if (countsDiff.Items > 0) itemsMenuItem_Click(sender, EventArgs.Empty); 371 | if (countsDiff.Invites > 0) invitesMenuItem_Click(sender, EventArgs.Empty); 372 | if (countsDiff.Gifts > 0) giftsMenuItem_Click(sender, EventArgs.Empty); 373 | if (countsDiff.OfflineMessages > 0) offlineMessagesMenuItem_Click(sender, EventArgs.Empty); 374 | if (countsDiff.TradeOffers > 0) tradeOffersMenuItem_Click(sender, EventArgs.Empty); 375 | if (countsDiff.AsyncGames > 0) asyncGameMenuItem_Click(sender, EventArgs.Empty); 376 | if (countsDiff.ModeratorMessages > 0) moderatorMessageMenuItem_Click(sender, EventArgs.Empty); 377 | if (countsDiff.HelpRequestReplies > 0) helpRequestReplyMenuItem_Click(sender, EventArgs.Empty); 378 | if (countsDiff.AccountAlerts > 0) accountAlertReplyMenuItem_Click(sender, EventArgs.Empty); 379 | 380 | Properties.Settings.Default.UseSteamBrowserProtocolLinks = origOpenInSteam; 381 | } 382 | } 383 | 384 | void markException(string message) 385 | { 386 | if (message.Length >= 64) message = message.Substring(0, 62) + "…"; 387 | if (mainIcon.Visible) 388 | { 389 | mainIcon.Text = message; 390 | } 391 | else 392 | { 393 | countIcon.Text = message; 394 | } 395 | } 396 | 397 | void notifyIcon_MouseDown(object sender, MouseEventArgs e) 398 | { 399 | if (e.Button == MouseButtons.Left) 400 | { 401 | mainIcon.ContextMenu = null; 402 | mainIcon.ContextMenuStrip = notificationsContextMenu; 403 | countIcon.ContextMenu = null; 404 | countIcon.ContextMenuStrip = notificationsContextMenu; 405 | } 406 | else if (e.Button == MouseButtons.Right) 407 | { 408 | mainIcon.ContextMenu = appContextMenu; 409 | mainIcon.ContextMenuStrip = null; 410 | countIcon.ContextMenu = appContextMenu; 411 | countIcon.ContextMenuStrip = null; 412 | } 413 | } 414 | 415 | void notifyIcon_MouseClick(object sender, MouseEventArgs e) 416 | { 417 | if (e.Button == MouseButtons.Left) 418 | { 419 | if (isLoggedIn) 420 | { 421 | if (hasNotifications) 422 | { 423 | // Make icon normal colored 424 | newNotifAcknowledged = true; 425 | ReplaceNotifyIcon(mainIcon, IconUtils.CreateIconWithBackground(Properties.Resources.NotificationActive, Properties.Settings.Default.InboxAvailableColor, SystemInformation.SmallIconSize)); 426 | string text = client.CurrentCounts.TotalNotifications.ToString(); 427 | ReplaceNotifyIcon(countIcon, IconUtils.CreateIconWithText(text, new Font("Arial", 10 - text.Length, FontStyle.Regular, GraphicsUnit.Point), 428 | Properties.Settings.Default.NotificationCountColor, Properties.Settings.Default.InboxAvailableColor, SystemInformation.SmallIconSize)); 429 | } 430 | 431 | if (sender is NotifyIcon) NotifyIcon_ShowContextMenu.Invoke(sender, null); 432 | } 433 | } 434 | } 435 | 436 | void notifyIcon_MouseDoubleClick(object sender, MouseEventArgs e) 437 | { 438 | if (e.Button == MouseButtons.Left) 439 | { 440 | System.Diagnostics.Process.Start("steam://open/main"); 441 | } 442 | } 443 | 444 | void settingsForm_SettingsApplied(object sender, EventArgs e) 445 | { 446 | refreshTimer.Interval = Properties.Settings.Default.RefreshInterval; 447 | updatePopupColors(); 448 | updateUi(oldCounts ?? client.CurrentCounts); 449 | // TODO: this logic should probably go into updateUi() 450 | if (!isLoggedIn) 451 | { 452 | // Logged out, hide count and show main 453 | countIcon.Visible = false; 454 | mainIcon.Visible = true; 455 | } 456 | muteMenuItem.Visible = Properties.Settings.Default.EnableBalloons; 457 | } 458 | 459 | void settingsForm_LoggingOut(object sender, EventArgs e) 460 | { 461 | logOut(); 462 | } 463 | 464 | protected override void ExitThreadCore() 465 | { 466 | mainIcon.Visible = false; 467 | countIcon.Visible = false; 468 | base.ExitThreadCore(); 469 | } 470 | 471 | static void ReplaceNotifyIcon(NotifyIcon notify, Icon newIcon) 472 | { 473 | if (notify.Icon != null) DestroyIcon(notify.Icon.Handle); 474 | notify.Icon = newIcon; 475 | } 476 | 477 | [DllImport("user32.dll", CharSet = CharSet.Auto)] 478 | extern static bool DestroyIcon(IntPtr handle); 479 | } 480 | } 481 | -------------------------------------------------------------------------------- /SteamNotificationsTray/TrayAppPopup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows.Forms; 7 | using System.Drawing; 8 | using System.Diagnostics; 9 | using SteamNotificationsTray.Properties; 10 | 11 | namespace SteamNotificationsTray 12 | { 13 | partial class TrayAppContext 14 | { 15 | ContextMenuStrip notificationsContextMenu; 16 | ToolStripProfessionalRenderer renderer; 17 | ToolStripMenuItem commentsMenuItem; 18 | ToolStripSeparator itemsSeparator; 19 | ToolStripMenuItem itemsMenuItem; 20 | ToolStripSeparator invitesSeparator; 21 | ToolStripMenuItem invitesMenuItem; 22 | ToolStripSeparator giftsSeparator; 23 | ToolStripMenuItem giftsMenuItem; 24 | ToolStripSeparator offlineMessagesSeparator; 25 | ToolStripMenuItem offlineMessagesMenuItem; 26 | ToolStripSeparator tradeOffersSeparator; 27 | ToolStripMenuItem tradeOffersMenuItem; 28 | ToolStripSeparator asyncGameMenuSeparator; 29 | ToolStripMenuItem asyncGameMenuItem; 30 | ToolStripSeparator moderatorMessageSeparator; 31 | ToolStripMenuItem moderatorMessageMenuItem; 32 | ToolStripSeparator helpRequestReplySeparator; 33 | ToolStripMenuItem helpRequestReplyMenuItem; 34 | ToolStripSeparator accountAlertReplySeparator; 35 | ToolStripMenuItem accountAlertReplyMenuItem; 36 | 37 | void setupNotificationsPopup() 38 | { 39 | commentsMenuItem = new ToolStripMenuItem 40 | { 41 | Image = Resources.IconComments 42 | }; 43 | itemsSeparator = new ToolStripSeparator(); 44 | itemsMenuItem = new ToolStripMenuItem 45 | { 46 | Image = Resources.IconItems 47 | }; 48 | invitesSeparator = new ToolStripSeparator(); 49 | invitesMenuItem = new ToolStripMenuItem 50 | { 51 | Image = Resources.IconInvites 52 | }; 53 | giftsSeparator = new ToolStripSeparator(); 54 | giftsMenuItem = new ToolStripMenuItem 55 | { 56 | Image = Resources.IconGifts 57 | }; 58 | offlineMessagesSeparator = new ToolStripSeparator(); 59 | offlineMessagesMenuItem = new ToolStripMenuItem 60 | { 61 | Image = Resources.IconOfflineMessages, 62 | }; 63 | tradeOffersSeparator = new ToolStripSeparator(); 64 | tradeOffersMenuItem = new ToolStripMenuItem 65 | { 66 | Image = Resources.IconTradeOffers, 67 | }; 68 | asyncGameMenuSeparator = new ToolStripSeparator(); 69 | asyncGameMenuItem = new ToolStripMenuItem 70 | { 71 | Image = Resources.IconAsyncGames, 72 | }; 73 | moderatorMessageSeparator = new ToolStripSeparator(); 74 | moderatorMessageMenuItem = new ToolStripMenuItem 75 | { 76 | Image = Resources.IconModeratorMessages, 77 | }; 78 | helpRequestReplySeparator = new ToolStripSeparator(); 79 | helpRequestReplyMenuItem = new ToolStripMenuItem 80 | { 81 | Image = Resources.IconModeratorMessages, 82 | }; 83 | accountAlertReplySeparator = new ToolStripSeparator(); 84 | accountAlertReplyMenuItem = new ToolStripMenuItem 85 | { 86 | Image = Resources.IconAccountAlerts, 87 | }; 88 | renderer = new NotificationsMenuRenderer(); 89 | notificationsContextMenu = new ContextMenuStrip(); 90 | notificationsContextMenu.Renderer = renderer; 91 | notificationsContextMenu.Items.AddRange(new ToolStripItem[] { 92 | commentsMenuItem, 93 | itemsSeparator, 94 | itemsMenuItem, 95 | invitesSeparator, 96 | invitesMenuItem, 97 | giftsSeparator, 98 | giftsMenuItem, 99 | offlineMessagesSeparator, 100 | offlineMessagesMenuItem, 101 | tradeOffersSeparator, 102 | tradeOffersMenuItem, 103 | asyncGameMenuSeparator, 104 | asyncGameMenuItem, 105 | moderatorMessageSeparator, 106 | moderatorMessageMenuItem, 107 | helpRequestReplySeparator, 108 | helpRequestReplyMenuItem, 109 | accountAlertReplySeparator, 110 | accountAlertReplyMenuItem, 111 | }); 112 | 113 | updatePopupColors(); 114 | updatePopupCounts(new NotificationCounts()); 115 | 116 | commentsMenuItem.Click += commentsMenuItem_Click; 117 | itemsMenuItem.Click += itemsMenuItem_Click; 118 | invitesMenuItem.Click += invitesMenuItem_Click; 119 | giftsMenuItem.Click += giftsMenuItem_Click; 120 | offlineMessagesMenuItem.Click += offlineMessagesMenuItem_Click; 121 | tradeOffersMenuItem.Click += tradeOffersMenuItem_Click; 122 | asyncGameMenuItem.Click += asyncGameMenuItem_Click; 123 | moderatorMessageMenuItem.Click += moderatorMessageMenuItem_Click; 124 | helpRequestReplyMenuItem.Click += helpRequestReplyMenuItem_Click; 125 | accountAlertReplyMenuItem.Click += accountAlertReplyMenuItem_Click; 126 | } 127 | 128 | void commentsMenuItem_Click(object sender, EventArgs e) 129 | { 130 | if (Settings.Default.UseSteamBrowserProtocolLinks) 131 | Process.Start("steam://url/CommentNotifications"); 132 | else 133 | Process.Start("https://steamcommunity.com/my/commentnotifications/"); 134 | } 135 | 136 | void itemsMenuItem_Click(object sender, EventArgs e) 137 | { 138 | if (Settings.Default.UseSteamBrowserProtocolLinks) 139 | Process.Start("steam://url/CommunityInventory"); 140 | else 141 | Process.Start("https://steamcommunity.com/my/inventory/"); 142 | } 143 | 144 | void invitesMenuItem_Click(object sender, EventArgs e) 145 | { 146 | if (Settings.Default.UseSteamBrowserProtocolLinks) 147 | Process.Start("steam://url/SteamIDInvitesPage"); 148 | else 149 | Process.Start("https://steamcommunity.com/my/home/invites/"); 150 | } 151 | 152 | void giftsMenuItem_Click(object sender, EventArgs e) 153 | { 154 | if (Settings.Default.UseSteamBrowserProtocolLinks) 155 | Process.Start("steam://url/ManageGiftsPage"); 156 | else 157 | Process.Start("https://steamcommunity.com/my/inventory/#pending_gifts"); 158 | } 159 | 160 | void offlineMessagesMenuItem_Click(object sender, EventArgs e) 161 | { 162 | if (Settings.Default.UseSteamBrowserProtocolLinks) 163 | // Web chat has a friends list, so we open the Friends window here 164 | Process.Start("steam://open/friends"); 165 | else 166 | Process.Start("https://steamcommunity.com/chat/"); 167 | } 168 | 169 | void tradeOffersMenuItem_Click(object sender, EventArgs e) 170 | { 171 | if (Settings.Default.UseSteamBrowserProtocolLinks) 172 | Process.Start("steam://url/TradeOffers"); 173 | else 174 | Process.Start("https://steamcommunity.com/my/tradeoffers/"); 175 | } 176 | 177 | void asyncGameMenuItem_Click(object sender, EventArgs e) 178 | { 179 | if (Settings.Default.UseSteamBrowserProtocolLinks) 180 | Process.Start("steam://url/AsyncGames"); 181 | else 182 | Process.Start("https://steamcommunity.com/my/gamenotifications"); 183 | } 184 | 185 | void moderatorMessageMenuItem_Click(object sender, EventArgs e) 186 | { 187 | if (Settings.Default.UseSteamBrowserProtocolLinks) 188 | Process.Start("steam://url/ModeratorMessages"); 189 | else 190 | Process.Start("https://steamcommunity.com/my/moderatormessages"); 191 | } 192 | 193 | void helpRequestReplyMenuItem_Click(object sender, EventArgs e) 194 | { 195 | if (Settings.Default.UseSteamBrowserProtocolLinks) 196 | Process.Start("steam://url/MyHelpRequests"); 197 | else 198 | Process.Start("https://help.steampowered.com/en/wizard/HelpRequests"); 199 | } 200 | 201 | void accountAlertReplyMenuItem_Click(object sender, EventArgs e) 202 | { 203 | // No Steam Browser Protocol link needed here, because the window will remain 204 | // open until the alert is dismissed. Should we open Steam or the browser? 205 | Process.Start("https://store.steampowered.com/supportmessages/"); 206 | } 207 | 208 | void updatePopupColors() 209 | { 210 | var settings = Settings.Default; 211 | notificationsContextMenu.BackColor = settings.NotificationPopupBackgroundColor; 212 | notificationsContextMenu.ForeColor = settings.NotificationInactiveColor; 213 | } 214 | 215 | void updatePopupCounts(NotificationCounts counts) 216 | { 217 | var settings = Settings.Default; 218 | commentsMenuItem.Text = counts.Comments == 1 ? Resources.CommentsSingular : string.Format(Resources.CommentsPlural, counts.Comments); 219 | commentsMenuItem.Tag = counts.Comments; 220 | commentsMenuItem.Available = counts.Comments > 0 || settings.AlwaysShowComments; 221 | 222 | itemsMenuItem.Text = counts.Items == 1 ? Resources.ItemsSingular : string.Format(Resources.ItemsPlural, counts.Items); 223 | itemsMenuItem.Tag = counts.Items; 224 | itemsSeparator.Available = itemsMenuItem.Available = counts.Items > 0 || settings.AlwaysShowItems; 225 | 226 | invitesMenuItem.Text = counts.Invites == 1 ? Resources.InvitesSingular : string.Format(Resources.InvitesPlural, counts.Invites); 227 | invitesMenuItem.Tag = counts.Invites; 228 | invitesSeparator.Available = invitesMenuItem.Available = counts.Invites > 0 || settings.AlwaysShowInvites; 229 | 230 | giftsMenuItem.Text = counts.Gifts == 1 ? Resources.GiftsSingular : string.Format(Resources.GiftsPlural, counts.Gifts); 231 | giftsMenuItem.Tag = counts.Gifts; 232 | giftsSeparator.Available = giftsMenuItem.Available = counts.Gifts > 0 || settings.AlwaysShowGifts; 233 | 234 | offlineMessagesMenuItem.Text = counts.OfflineMessages == 1 ? Resources.OfflineMessagesSingular : string.Format(Resources.OfflineMessagesPlural, counts.OfflineMessages); 235 | offlineMessagesMenuItem.Tag = counts.OfflineMessages; 236 | offlineMessagesSeparator.Available = offlineMessagesMenuItem.Available = counts.OfflineMessages > 0 || settings.AlwaysShowOfflineMessages; 237 | 238 | tradeOffersMenuItem.Text = counts.TradeOffers == 1 ? Resources.TradeOffersSingular : string.Format(Resources.TradeOffersPlural, counts.TradeOffers); 239 | tradeOffersMenuItem.Tag = counts.TradeOffers; 240 | tradeOffersSeparator.Available = tradeOffersMenuItem.Available = counts.TradeOffers > 0 || settings.AlwaysShowTradeOffers; 241 | 242 | asyncGameMenuItem.Text = counts.AsyncGames == 1 ? Resources.AsyncGamesSingular : string.Format(Resources.AsyncGamesPlural, counts.AsyncGames); 243 | asyncGameMenuItem.Tag = counts.AsyncGames; 244 | asyncGameMenuSeparator.Available = asyncGameMenuItem.Available = counts.AsyncGames > 0 || settings.AlwaysShowAsyncGames; 245 | 246 | moderatorMessageMenuItem.Text = counts.ModeratorMessages == 1 ? Resources.ModeratorMessagesSingular : string.Format(Resources.ModeratorMessagesPlural, counts.ModeratorMessages); 247 | moderatorMessageMenuItem.Tag = counts.ModeratorMessages; 248 | moderatorMessageSeparator.Available = moderatorMessageMenuItem.Available = counts.ModeratorMessages > 0 || settings.AlwaysShowModeratorMessages; 249 | 250 | helpRequestReplyMenuItem.Text = counts.HelpRequestReplies == 1 ? Resources.HelpRequestRepliesSingular : string.Format(Resources.HelpRequestRepliesPlural, counts.HelpRequestReplies); 251 | helpRequestReplyMenuItem.Tag = counts.HelpRequestReplies; 252 | helpRequestReplySeparator.Available = helpRequestReplyMenuItem.Available = counts.HelpRequestReplies > 0 || settings.AlwaysShowHelpRequestReplies; 253 | 254 | accountAlertReplyMenuItem.Text = counts.AccountAlerts == 1 ? Resources.AccountAlertsSingular : string.Format(Resources.AccountAlertsPlural, counts.AccountAlerts); 255 | accountAlertReplyMenuItem.Tag = counts.AccountAlerts; 256 | accountAlertReplySeparator.Available = accountAlertReplyMenuItem.Available = counts.AccountAlerts > 0 || settings.AlwaysShowAccountAlerts; 257 | 258 | // Hide top separator if it's the first item 259 | foreach (ToolStripItem item in notificationsContextMenu.Items) 260 | { 261 | if (item.Available) 262 | { 263 | if (item is ToolStripMenuItem) break; 264 | item.Available = false; 265 | break; 266 | } 267 | } 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /SteamNotificationsTray/WebLogin/Models/DoLoginRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SteamNotificationsTray.WebLogin.Models 8 | { 9 | class DoLoginRequest 10 | { 11 | public string Password { get; set; } // Assume already encrypted by caller 12 | public string Username { get; set; } 13 | public string TwoFactorCode { get; set; } 14 | public string EmailAuth { get; set; } 15 | public string LoginFriendlyName { get; set; } 16 | public long CaptchaGid { get; set; } 17 | public string CaptchaText { get; set; } 18 | public ulong? EmailSteamId { get; set; } 19 | public long RsaTimeStamp { get; set; } 20 | public bool RememberLogin { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SteamNotificationsTray/WebLogin/Models/DoLoginResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Newtonsoft.Json; 6 | 7 | namespace SteamNotificationsTray.WebLogin.Models 8 | { 9 | class DoLoginResponse 10 | { 11 | [JsonProperty("success")] 12 | public bool Success { get; set; } 13 | [JsonProperty("requires_twofactor")] 14 | public bool RequiresTwoFactor { get; set; } 15 | [JsonProperty("clear_password_field")] 16 | public bool ClearPasswordField { get; set; } 17 | [JsonProperty("captcha_needed")] 18 | public bool CaptchaNeeded { get; set; } 19 | [JsonProperty("captcha_gid")] 20 | public long CaptchaGid { get; set; } 21 | [JsonProperty("bad_captcha")] 22 | public bool IsBadCaptcha { get; set; } 23 | [JsonProperty("message")] 24 | public string Message { get; set; } 25 | 26 | [JsonProperty("emailauth_needed")] 27 | public bool EmailAuthNeeded { get; set; } 28 | [JsonProperty("emaildomain")] 29 | public string EmailDomain { get; set; } // if empty, incorrect code 30 | [JsonProperty("emailsteamid")] 31 | public ulong? EmailSteamId { get; set; } 32 | 33 | [JsonProperty("denied_ipt")] 34 | public bool DeniedIpt { get; set; } 35 | 36 | [JsonProperty("login_complete")] 37 | public bool LoginComplete { get; set; } 38 | [JsonProperty("transfer_url")] 39 | public string TransferUrl { get; set; } 40 | [JsonProperty("transfer_urls")] 41 | public List TransferUrls { get; set; } 42 | [JsonProperty("transfer_parameters")] 43 | public TransferParameters TransferParameters { get; set; } 44 | 45 | public DoLoginRequest ToRequest() 46 | { 47 | return new DoLoginRequest 48 | { 49 | CaptchaGid = CaptchaGid, 50 | EmailSteamId = EmailSteamId 51 | }; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /SteamNotificationsTray/WebLogin/Models/GetRsaKeyResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Newtonsoft.Json; 6 | 7 | namespace SteamNotificationsTray.WebLogin.Models 8 | { 9 | class GetRsaKeyResponse 10 | { 11 | [JsonProperty("success")] 12 | public bool Success { get; set; } 13 | [JsonProperty("publickey_mod")] 14 | public string PublicKeyMod { get; set; } 15 | [JsonProperty("publickey_exp")] 16 | public string PublicKeyExp { get; set; } 17 | [JsonProperty("timestamp")] 18 | public long Timestamp { get; set; } 19 | [JsonProperty("token_gid")] 20 | public string TokenGid { get; set; } 21 | [JsonProperty("message")] 22 | public string Message { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SteamNotificationsTray/WebLogin/Models/TransferParameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Newtonsoft.Json; 6 | 7 | namespace SteamNotificationsTray.WebLogin.Models 8 | { 9 | class TransferParameters 10 | { 11 | [JsonProperty("steamid")] 12 | public ulong SteamId { get; set; } 13 | [JsonProperty("token")] 14 | public string Token { get; set; } 15 | [JsonProperty("auth")] 16 | public string Auth { get; set; } 17 | [JsonProperty("remember_login")] 18 | public bool RememberLogin { get; set; } 19 | [JsonProperty("webcookie")] 20 | public string WebCookie { get; set; } 21 | [JsonProperty("token_secure")] 22 | public string TokenSecure { get; set; } 23 | 24 | // Special property that's not in the actual response 25 | public string RememberLoginToken { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SteamNotificationsTray/WebLogin/SteamWebLogin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Net; 7 | using System.Net.Http; 8 | using System.Security.Cryptography; 9 | using Newtonsoft.Json; 10 | using SteamNotificationsTray.WebLogin.Models; 11 | 12 | namespace SteamNotificationsTray.WebLogin 13 | { 14 | class SteamWebLogin 15 | { 16 | static readonly string BaseDomain = "https://steamcommunity.com/"; 17 | static readonly string Endpoint = BaseDomain + "login/"; 18 | static readonly string GetRsaKeyPath = Endpoint + "getrsakey/"; 19 | static readonly string DoLoginPath = Endpoint + "dologin/"; 20 | static readonly string RefreshCaptchaPath = Endpoint + "refreshcaptcha/"; 21 | static readonly string RenderCaptchaPath = Endpoint + "rendercaptcha/"; 22 | // Not handling: account recovery stuff 23 | 24 | public async Task GetRsaKeyAsync(string username) 25 | { 26 | using (HttpClient client = new HttpClient()) 27 | { 28 | var p = new FormUrlEncodedContent(new[] { 29 | new KeyValuePair("username", username) 30 | }); 31 | HttpResponseMessage resp = await client.PostAsync(GetRsaKeyPath, p); 32 | if (resp.IsSuccessStatusCode) 33 | { 34 | string respText = await resp.Content.ReadAsStringAsync(); 35 | return JsonConvert.DeserializeObject(respText); 36 | } 37 | else 38 | { 39 | return null; 40 | } 41 | } 42 | } 43 | 44 | public async Task DoLoginAsync(DoLoginRequest req) 45 | { 46 | HttpClientHandler handler = new HttpClientHandler { CookieContainer = new CookieContainer() }; 47 | using (HttpClient client = new HttpClient(handler)) 48 | { 49 | var p = new FormUrlEncodedContent(new[] { 50 | new KeyValuePair("password", req.Password ?? string.Empty), 51 | new KeyValuePair("username", req.Username ?? string.Empty), 52 | new KeyValuePair("twofactorcode", req.TwoFactorCode ?? string.Empty), 53 | new KeyValuePair("emailauth", req.EmailAuth ?? string.Empty), 54 | new KeyValuePair("loginfriendlyname", req.LoginFriendlyName ?? string.Empty), 55 | new KeyValuePair("captchagid", req.CaptchaGid.ToString()), 56 | new KeyValuePair("captcha_text", req.CaptchaText ?? string.Empty), 57 | new KeyValuePair("emailsteamid", req.EmailSteamId.HasValue ? req.EmailSteamId.Value.ToString() : string.Empty), 58 | new KeyValuePair("rsatimestamp", req.RsaTimeStamp.ToString()), 59 | new KeyValuePair("remember_login", req.RememberLogin.ToString().ToLowerInvariant()), 60 | }); 61 | HttpResponseMessage resp = await client.PostAsync(DoLoginPath, p); 62 | if (resp.IsSuccessStatusCode) 63 | { 64 | string respText = await resp.Content.ReadAsStringAsync(); 65 | DoLoginResponse respObj = JsonConvert.DeserializeObject(respText); 66 | if (req.RememberLogin && respObj.TransferParameters != null) 67 | { 68 | foreach (Cookie cookie in handler.CookieContainer.GetCookies(new Uri(BaseDomain))) 69 | { 70 | if (cookie.Name == "steamRememberLogin") 71 | { 72 | string[] bits = WebUtility.UrlDecode(cookie.Value).Split(new[] { "||" }, 2, StringSplitOptions.None); 73 | respObj.TransferParameters.RememberLoginToken = bits[1]; 74 | break; 75 | } 76 | } 77 | } 78 | return respObj; 79 | } 80 | else 81 | { 82 | return null; 83 | } 84 | } 85 | } 86 | 87 | public async Task RefreshCaptchaAsync() 88 | { 89 | using (HttpClient client = new HttpClient()) 90 | { 91 | var p = new FormUrlEncodedContent(new KeyValuePair[] { }); 92 | HttpResponseMessage resp = await client.PostAsync(RefreshCaptchaPath, p); 93 | if (resp.IsSuccessStatusCode) 94 | { 95 | string respText = await resp.Content.ReadAsStringAsync(); 96 | var obj = Newtonsoft.Json.Linq.JObject.Parse(respText); 97 | return obj.Value("gid"); 98 | } 99 | else 100 | { 101 | return -1; 102 | } 103 | } 104 | } 105 | 106 | public string GetRenderCaptchaUrl(long gid) 107 | { 108 | string query; 109 | using (var p = new FormUrlEncodedContent(new[] { 110 | new KeyValuePair("gid", gid.ToString()) 111 | })) 112 | { 113 | query = p.ReadAsStringAsync().Result; 114 | } 115 | UriBuilder builder = new UriBuilder(RenderCaptchaPath); 116 | builder.Query = query; 117 | return builder.Uri.ToString(); 118 | } 119 | 120 | public async Task RenderCaptchaAsync(long gid) 121 | { 122 | using (HttpClient client = new HttpClient()) 123 | { 124 | string query; 125 | using (var p = new FormUrlEncodedContent(new[] { 126 | new KeyValuePair("gid", gid.ToString()) 127 | })) 128 | { 129 | query = await p.ReadAsStringAsync(); 130 | } 131 | UriBuilder builder = new UriBuilder(RenderCaptchaPath); 132 | builder.Query = query; 133 | 134 | HttpResponseMessage resp = await client.GetAsync(builder.Uri); 135 | if (resp.IsSuccessStatusCode) 136 | { 137 | return await resp.Content.ReadAsByteArrayAsync(); 138 | } 139 | else 140 | { 141 | return null; 142 | } 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /SteamNotificationsTray/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 92, 126, 16 15 | 16 | 17 | 0, 0, 0, 0 18 | 19 | 20 | 158, 194, 29 21 | 22 | 23 | 30000 24 | 25 | 26 | 59, 57, 56 27 | 28 | 29 | 130, 128, 124 30 | 31 | 32 | 191, 191, 191 33 | 34 | 35 | 33, 45, 61 36 | 37 | 38 | 112, 186, 36 39 | 40 | 41 | 107, 104, 101 42 | 43 | 44 | True 45 | 46 | 47 | True 48 | 49 | 50 | True 51 | 52 | 53 | True 54 | 55 | 56 | False 57 | 58 | 59 | False 60 | 61 | 62 | False 63 | 64 | 65 | False 66 | 67 | 68 | False 69 | 70 | 71 | True 72 | 73 | 74 | False 75 | 76 | 77 | False 78 | 79 | 80 | White 81 | 82 | 83 | False 84 | 85 | 86 | True 87 | 88 | 89 | False 90 | 91 | 92 | False 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /SteamNotificationsTray/graphics/inbox_account_alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/SteamNotificationsTray/8674d06bb28f11f867d22b1206bee42973867dc4/SteamNotificationsTray/graphics/inbox_account_alert.png -------------------------------------------------------------------------------- /SteamNotificationsTray/graphics/inbox_async_game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/SteamNotificationsTray/8674d06bb28f11f867d22b1206bee42973867dc4/SteamNotificationsTray/graphics/inbox_async_game.png -------------------------------------------------------------------------------- /SteamNotificationsTray/graphics/inbox_comments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/SteamNotificationsTray/8674d06bb28f11f867d22b1206bee42973867dc4/SteamNotificationsTray/graphics/inbox_comments.png -------------------------------------------------------------------------------- /SteamNotificationsTray/graphics/inbox_gifts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/SteamNotificationsTray/8674d06bb28f11f867d22b1206bee42973867dc4/SteamNotificationsTray/graphics/inbox_gifts.png -------------------------------------------------------------------------------- /SteamNotificationsTray/graphics/inbox_invites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/SteamNotificationsTray/8674d06bb28f11f867d22b1206bee42973867dc4/SteamNotificationsTray/graphics/inbox_invites.png -------------------------------------------------------------------------------- /SteamNotificationsTray/graphics/inbox_items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/SteamNotificationsTray/8674d06bb28f11f867d22b1206bee42973867dc4/SteamNotificationsTray/graphics/inbox_items.png -------------------------------------------------------------------------------- /SteamNotificationsTray/graphics/inbox_moderator_message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/SteamNotificationsTray/8674d06bb28f11f867d22b1206bee42973867dc4/SteamNotificationsTray/graphics/inbox_moderator_message.png -------------------------------------------------------------------------------- /SteamNotificationsTray/graphics/inbox_notification.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/SteamNotificationsTray/8674d06bb28f11f867d22b1206bee42973867dc4/SteamNotificationsTray/graphics/inbox_notification.ico -------------------------------------------------------------------------------- /SteamNotificationsTray/graphics/inbox_notification_inactive.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/SteamNotificationsTray/8674d06bb28f11f867d22b1206bee42973867dc4/SteamNotificationsTray/graphics/inbox_notification_inactive.ico -------------------------------------------------------------------------------- /SteamNotificationsTray/graphics/inbox_notification_inactive_disabled.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/SteamNotificationsTray/8674d06bb28f11f867d22b1206bee42973867dc4/SteamNotificationsTray/graphics/inbox_notification_inactive_disabled.ico -------------------------------------------------------------------------------- /SteamNotificationsTray/graphics/inbox_offlinemessages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/SteamNotificationsTray/8674d06bb28f11f867d22b1206bee42973867dc4/SteamNotificationsTray/graphics/inbox_offlinemessages.png -------------------------------------------------------------------------------- /SteamNotificationsTray/graphics/inbox_tradeoffers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/SteamNotificationsTray/8674d06bb28f11f867d22b1206bee42973867dc4/SteamNotificationsTray/graphics/inbox_tradeoffers.png -------------------------------------------------------------------------------- /SteamNotificationsTray/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /SteamNotificationsTray/thirdparty/Cyotek.Windows.Forms.ColorPicker.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GMMan/SteamNotificationsTray/8674d06bb28f11f867d22b1206bee42973867dc4/SteamNotificationsTray/thirdparty/Cyotek.Windows.Forms.ColorPicker.dll -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Steam Notifications Tray App 2 | ============================ 3 | This program displays your Steam notifications count in your notifications 4 | area. Clicking on the icon opens up the notifications list, just like it 5 | does on Steam. From there you can click on the notification item, and it 6 | will open the matching page in your browser. Double click on the icon to 7 | open Steam. 8 | 9 | You will most likely need to tell Windows to not hide the icons for the 10 | two icons' positions to stay fixed. Best to do this when you have a 11 | notification. The intended order is for the count to come first, and the 12 | mail icon to come second, the same way it's displayed on Steam. 13 | 14 | System requirements 15 | ------------------- 16 | Requires .NET Framework 4.5.1. 17 | 18 | Settings 19 | ======== 20 | 21 | General 22 | ------- 23 | - **Refresh interval:** how often to refresh the notifications count. Ranges 24 | from 1 second to 1 day. 25 | - **Open links in Steam:** self-explanatory; opens notifications in the Steam 26 | client instead of the browser. 27 | - **Enable balloon notifications:** show a balloon notification when there 28 | are (more) new notifications since the last check. 29 | - **Open links to new items on clicking balloon:** click on the balloon popup 30 | to open the pages associated with the items listed in the popup. 31 | - **Single icon mode:** show only the count when there are unread notifications 32 | instead of both the count and mail icon. 33 | - **Enable anti-flapping (experimental):** when Steam Community is down for 34 | maintenance, it often returns zero notifications one poll, then the proper 35 | count the next poll. This can get annoying if you have balloon popups 36 | enabled. This feature attempts to reduce that, by requiring a few zero- 37 | notifications polls before actually setting notification counts to zero. 38 | - **Reset:** resets settings to defaults. 39 | - **Log Out:** clears authentication tokens and stops checking for 40 | notifications. 41 | 42 | Items 43 | ----- 44 | Here, you can choose which items are always visible on the popup menu. 45 | 46 | Colors 47 | ------ 48 | These are the colors used in rendering the icon and the popup menu. You can 49 | customize them to your liking. 50 | 51 | - **No notifications:** the color used in the tray icon when there are no 52 | notifications. This is transparent by default. 53 | - **Unread notifications:** the color used in the tray icon when there are 54 | unread notifications. 55 | - **New notifications:** the color used in the tray icon when there are new 56 | notifications since you last opened the popup menu. 57 | - **Notification item text:** the color of an item with 0 count on the popup. 58 | - **Unread notification item text:** the color of an item with non-0 count 59 | on the popup. 60 | - **Popup background:** the color of the background of the popup menu. 61 | - **Popup border:** the color of the border of the popup menu. 62 | - **Popup item focused:** the color of the highlighting when you hover over 63 | a notification item in the popup menu. 64 | - **Popup item separator:** the color of the separator between notification 65 | items on the popup menu. 66 | 67 | About login security 68 | ==================== 69 | Originally, it was planned that Internet Explorer's cookies would be used for 70 | authenticating with Steam servers. However, that turned out to be a bit messy 71 | and didn't really work, so I implemented my own authentication system based 72 | off of Steam Community's login system. This program only stores your Steam 73 | ID, "remember login" token, and machine auth token, just like what a regular 74 | browser would store. It's stored along with the rest of the app settings, in 75 | a .NET Framework defined location, and is encrypted using DPAPI. If you would 76 | like a bit of extra security against someone stealing your credentials, you 77 | can recompile the program with your own strong name signing key, which will 78 | modify the encryption on the credentials. 79 | 80 | Bugs and suggestions 81 | ==================== 82 | Please use GitHub's Issues tab for tracking. 83 | --------------------------------------------------------------------------------