del /AudioWallpaper.1.2.1.nupkg

This commit is contained in:
2024-10-18 08:30:59 +08:00
parent d351e442ab
commit dba24c0cbb
3773 changed files with 8960 additions and 79217 deletions

5
.gitattributes vendored
View File

@@ -2,7 +2,6 @@
# Set default behavior to automatically normalize line endings. # Set default behavior to automatically normalize line endings.
############################################################################### ###############################################################################
* text=auto * text=auto
############################################################################### ###############################################################################
# Set default behavior for command prompt diff. # Set default behavior for command prompt diff.
# #
@@ -11,7 +10,6 @@
# Note: This is only used by command line # Note: This is only used by command line
############################################################################### ###############################################################################
#*.cs diff=csharp #*.cs diff=csharp
############################################################################### ###############################################################################
# Set the merge driver for project and solution files # Set the merge driver for project and solution files
# #
@@ -34,7 +32,6 @@
#*.modelproj merge=binary #*.modelproj merge=binary
#*.sqlproj merge=binary #*.sqlproj merge=binary
#*.wwaproj merge=binary #*.wwaproj merge=binary
############################################################################### ###############################################################################
# behavior for image files # behavior for image files
# #
@@ -43,7 +40,6 @@
#*.jpg binary #*.jpg binary
#*.png binary #*.png binary
#*.gif binary #*.gif binary
############################################################################### ###############################################################################
# diff behavior for common document formats # diff behavior for common document formats
# #
@@ -61,3 +57,4 @@
#*.PDF diff=astextplain #*.PDF diff=astextplain
#*.rtf diff=astextplain #*.rtf diff=astextplain
#*.RTF diff=astextplain #*.RTF diff=astextplain
AudioVisualizer/bin/Release/AudioWallpaper.1.2.1.nupkg filter=lfs diff=lfs merge=lfs -text

2
.gitignore vendored
View File

@@ -361,3 +361,5 @@ MigrationBackup/
# Fody - auto-generated XML schema # Fody - auto-generated XML schema
FodyWeavers.xsd FodyWeavers.xsd
**/*.nupkg

Binary file not shown.

View File

@@ -0,0 +1,188 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<None Update="shell\surface0000.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0000_2001.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0000_2002.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0000_2004.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0001.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0002.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0003.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0004.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0005.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0006.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0007.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0008.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0009.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0010.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0020.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0025.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0029.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0030.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0032.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0033.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0034.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0035.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0036.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0037.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0038.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0042.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0043.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0044.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0091.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0092.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface0093.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface1001.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface1002.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface1003.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface1004.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface2001.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface2002.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface2003.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface2004.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3000.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3001.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3002.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3003.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3004.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3005.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3006.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3007.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3100.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3101.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3102.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3103.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3104.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3200.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3201.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3202.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3203.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface3204.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="shell\surface9999.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace AccompanyingAssistant {
public class BubbleForm : Form {
private string _message = "";
private int cornerRadius = 12;
private int tailHeight = 10;
private Color bubbleColor = Color.FromArgb(255, 255, 230);
private Color borderColor = Color.FromArgb(220, 220, 200);
private Font messageFont = new Font("微软雅黑", 9);
public string Message {
get { return _message; }
set {
_message = value;
CalculateSize();
Invalidate();
}
}
public BubbleForm() {
this.FormBorderStyle = FormBorderStyle.None;
this.ShowInTaskbar = false;
this.StartPosition = FormStartPosition.Manual;
this.Padding = new Padding(12, 12, 12, 12 + tailHeight);
this.BackColor = Color.Magenta;
this.TransparencyKey = Color.Magenta;
this.DoubleBuffered = true;
// 初始大小
this.Size = new Size(200, 100);
// 添加关闭按钮
var closeButton = new Button();
closeButton.Text = "×";
closeButton.FlatStyle = FlatStyle.Flat;
closeButton.FlatAppearance.BorderSize = 0;
closeButton.BackColor = Color.Transparent;
closeButton.ForeColor = Color.Gray;
closeButton.Font = new Font("Arial", 8, FontStyle.Bold);
closeButton.Size = new Size(20, 20);
closeButton.Location = new Point(this.Width - closeButton.Width - 5, 5);
closeButton.Click += (s, e) => this.Hide();
this.Controls.Add(closeButton);
}
public void UpdateSize() {
CalculateSize();
}
private void CalculateSize() {
using (Graphics g = this.CreateGraphics()) {
// 增加最大宽度限制,防止气泡过宽
int maxWidth = Screen.PrimaryScreen.WorkingArea.Width / 3;
SizeF textSize = g.MeasureString(_message, messageFont, maxWidth);
int newWidth = (int)Math.Max(150, Math.Min(maxWidth, textSize.Width + this.Padding.Horizontal));
int newHeight = (int)(textSize.Height + this.Padding.Vertical);
this.Size = new Size(newWidth, newHeight);
if (this.Controls.Count > 0) {
this.Controls[0].Location = new Point(this.Width - 25, 5);
}
}
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
// 创建气泡路径
GraphicsPath path = new GraphicsPath();
Rectangle mainRect = new Rectangle(0, 0, this.Width, this.Height - tailHeight);
// 绘制圆角矩形主体
path.AddArc(mainRect.X, mainRect.Y, cornerRadius, cornerRadius, 180, 90);
path.AddArc(mainRect.X + mainRect.Width - cornerRadius, mainRect.Y, cornerRadius, cornerRadius, 270, 90);
path.AddArc(mainRect.X + mainRect.Width - cornerRadius, mainRect.Y + mainRect.Height - cornerRadius, cornerRadius, cornerRadius, 0, 90);
// 添加尾巴
int tailWidth = 20;
int tailX = this.Width / 2 - tailWidth / 2;
path.AddLine(mainRect.X + mainRect.Width - cornerRadius, mainRect.Y + mainRect.Height, tailX + tailWidth, mainRect.Y + mainRect.Height);
path.AddLine(tailX + tailWidth, mainRect.Y + mainRect.Height, this.Width / 2, this.Height);
path.AddLine(this.Width / 2, this.Height, tailX, mainRect.Y + mainRect.Height);
path.AddLine(tailX, mainRect.Y + mainRect.Height, mainRect.X + cornerRadius, mainRect.Y + mainRect.Height);
path.AddArc(mainRect.X, mainRect.Y + mainRect.Height - cornerRadius, cornerRadius, cornerRadius, 90, 90);
// 填充气泡
using (Brush bubbleBrush = new SolidBrush(bubbleColor)) {
g.FillPath(bubbleBrush, path);
}
// 绘制边框
using (Pen borderPen = new Pen(borderColor, 1)) {
g.DrawPath(borderPen, path);
}
// 绘制文本
using (Brush textBrush = new SolidBrush(Color.Black)) {
g.DrawString(_message, messageFont, textBrush,
new RectangleF(
this.Padding.Left,
this.Padding.Top,
this.Width - this.Padding.Horizontal,
this.Height - this.Padding.Vertical));
}
}
private void InitializeComponent() {
this.SuspendLayout();
//
// BubbleForm
//
this.ClientSize = new System.Drawing.Size(284, 261);
this.Name = "BubbleForm";
this.ResumeLayout(false);
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

191
AccompanyingAssistant/MainForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,191 @@
namespace AccompanyingAssistant {
partial class MainForm {
/// <summary>
/// 必需的设计器变量。
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// 清理所有正在使用的资源。
/// </summary>
/// <param name="disposing">如果应释放托管资源,为 true否则为 false。</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows
/// <summary>
/// 设计器支持所需的方法 - 不要
/// 使用代码编辑器修改此方法的内容。
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
this.tmrDrag = new System.Windows.Forms.Timer(this.components);
this.rightClickMenu = new System.Windows.Forms.ContextMenuStrip(this.components);
this.退ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.tmrBlink = new System.Windows.Forms.Timer(this.components);
this.tmrBubble = new System.Windows.Forms.Timer(this.components);
this.notifyIcon1 = new System.Windows.Forms.NotifyIcon(this.components);
this.ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.rightClickMenu.SuspendLayout();
this.SuspendLayout();
//
// tmrDrag
//
this.tmrDrag.Tick += new System.EventHandler(this.tmrDrag_Tick);
//
// rightClickMenu
//
this.rightClickMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.退ToolStripMenuItem,
this.ToolStripMenuItem,
this.ToolStripMenuItem,
this.ToolStripMenuItem});
this.rightClickMenu.Name = "rightClickMenu";
this.rightClickMenu.Size = new System.Drawing.Size(181, 114);
//
// 退出ToolStripMenuItem
//
this.退ToolStripMenuItem.Name = "退出ToolStripMenuItem";
this.退ToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
this.退ToolStripMenuItem.Text = "退出";
this.退ToolStripMenuItem.Click += new System.EventHandler(this.退ToolStripMenuItem_Click);
//
// 眨眼ToolStripMenuItem
//
this.ToolStripMenuItem.Name = "眨眼ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
this.ToolStripMenuItem.Text = "眨眼";
this.ToolStripMenuItem.Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// 衣柜ToolStripMenuItem
//
this.ToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.ToolStripMenuItem,
this.ToolStripMenuItem,
this.ToolStripMenuItem,
this.ToolStripMenuItem,
this.ToolStripMenuItem,
this.ToolStripMenuItem});
this.ToolStripMenuItem.Name = "衣柜ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
this.ToolStripMenuItem.Text = "衣柜";
//
// 圣诞帽子ToolStripMenuItem
//
this.ToolStripMenuItem.Name = "圣诞帽子ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(157, 22);
this.ToolStripMenuItem.Text = "帽子 — 圣诞";
this.ToolStripMenuItem.Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// 樱花帽子ToolStripMenuItem
//
this.ToolStripMenuItem.Name = "樱花帽子ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(157, 22);
this.ToolStripMenuItem.Text = "帽子 — 樱花";
this.ToolStripMenuItem.Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// 水手帽ToolStripMenuItem
//
this.ToolStripMenuItem.Name = "水手帽ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(157, 22);
this.ToolStripMenuItem.Text = "帽子 — 水手帽";
this.ToolStripMenuItem.Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// 风车帽子ToolStripMenuItem
//
this.ToolStripMenuItem.Name = "风车帽子ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(157, 22);
this.ToolStripMenuItem.Text = "帽子 — 风车";
this.ToolStripMenuItem.Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// 圣诞衣服ToolStripMenuItem
//
this.ToolStripMenuItem.Name = "圣诞衣服ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(157, 22);
this.ToolStripMenuItem.Text = "衣服 — 圣诞";
this.ToolStripMenuItem.Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// 和服ToolStripMenuItem
//
this.ToolStripMenuItem.Name = "和服ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(157, 22);
this.ToolStripMenuItem.Text = "衣服 — 和服";
this.ToolStripMenuItem.Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// tmrBlink
//
this.tmrBlink.Tick += new System.EventHandler(this.tmrBlink_Tick);
//
// tmrBubble
//
this.tmrBubble.Tick += new System.EventHandler(this.tmrBubble_Tick);
//
// notifyIcon1
//
this.notifyIcon1.ContextMenuStrip = this.rightClickMenu;
this.notifyIcon1.Icon = ((System.Drawing.Icon)(resources.GetObject("notifyIcon1.Icon")));
this.notifyIcon1.Text = "notifyIcon1";
this.notifyIcon1.Visible = true;
//
// 消息测试ToolStripMenuItem
//
this.ToolStripMenuItem.Name = "消息测试ToolStripMenuItem";
this.ToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
this.ToolStripMenuItem.Text = "消息测试";
this.ToolStripMenuItem.Click += new System.EventHandler(this.ToolStripMenuItem_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(266, 242);
this.ContextMenuStrip = this.rightClickMenu;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.Name = "Form1";
this.ShowInTaskbar = false;
this.Text = "Form1";
this.TopMost = true;
this.Load += new System.EventHandler(this.Form1_Load);
this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown);
this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseMove);
this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseUp);
this.rightClickMenu.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Timer tmrDrag;
private System.Windows.Forms.ContextMenuStrip rightClickMenu;
private System.Windows.Forms.ToolStripMenuItem ToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem 退ToolStripMenuItem;
private System.Windows.Forms.Timer tmrBlink;
private System.Windows.Forms.Timer tmrBubble;
private System.Windows.Forms.NotifyIcon notifyIcon1;
private System.Windows.Forms.ToolStripMenuItem ToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem ToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem ToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem ToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem ToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem ToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem ToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem ToolStripMenuItem;
}
}

View File

@@ -0,0 +1,342 @@
using System.ComponentModel;
namespace AccompanyingAssistant {
public partial class MainForm : Form {
bool haveHandle = true; //<2F><><EFBFBD><EFBFBD>SetBits
bool bFormDragging = false; // <20><><EFBFBD>ڴ<EFBFBD><DAB4><EFBFBD><EFBFBD>ƶ<EFBFBD>
Point oPointClicked; // <20><><EFBFBD>ڴ<EFBFBD><DAB4><EFBFBD><EFBFBD>ƶ<EFBFBD>
int dragFrame = 0; //<2F><>ק֡<D7A7><D6A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
int blinkFrame = 0; //գ<><D5A3>֡<EFBFBD><D6A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
int hatNum = -1;
int clothesNum = -1;
private enum PetStates { General = 0, Drag = 1 };
Bitmap[] pet = new Bitmap[30];
Bitmap[] petDrag = new Bitmap[3];
Bitmap[] petBlink = new Bitmap[2];
Bitmap[,] petHat = new Bitmap[10, 2];
Bitmap[,] petClothes = new Bitmap[5, 4];
Bitmap[] petWithClothes = new Bitmap[30];
Bitmap[] petDragWithClothes = new Bitmap[3];
Bitmap[] petBlinkWithClothes = new Bitmap[3];
private Queue<string> messageQueue = new Queue<string>();
private bool isBubbleVisible = false;
private int bubbleShowTime = 5000; // <20><>ʾʱ<CABE><CAB1>(<28><><EFBFBD><EFBFBD>)
private DateTime bubbleStartTime;
private BubbleForm bubbleForm;
public MainForm() {
InitializeComponent();
}
#region <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
protected override void OnClosing(CancelEventArgs e) {
e.Cancel = true;
base.OnClosing(e);
haveHandle = false;
}
protected override void OnHandleCreated(EventArgs e) {
InitializeStyles();
base.OnHandleCreated(e);
haveHandle = true;
}
private void InitializeStyles() {
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.UserPaint, true);
UpdateStyles();
}
protected override CreateParams CreateParams {
get {
CreateParams cParms = base.CreateParams;
cParms.ExStyle |= 0x00080000; // WS_EX_LAYERED
return cParms;
}
}
#endregion
public void SetBits(Bitmap bitmap) {
if (!haveHandle) return;
if (!Bitmap.IsCanonicalPixelFormat(bitmap.PixelFormat) || !Bitmap.IsAlphaPixelFormat(bitmap.PixelFormat))
MessageBox.Show("Error Bitmap");
IntPtr oldBits = IntPtr.Zero;
IntPtr screenDC = Win32.GetDC(IntPtr.Zero);
IntPtr hBitmap = IntPtr.Zero;
IntPtr memDc = Win32.CreateCompatibleDC(screenDC);
try {
Win32.Point topLoc = new Win32.Point(Left, Top);
Win32.Size bitMapSize = new Win32.Size(bitmap.Width, bitmap.Height);
Win32.BLENDFUNCTION blendFunc = new Win32.BLENDFUNCTION();
Win32.Point srcLoc = new Win32.Point(0, 0);
hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
oldBits = Win32.SelectObject(memDc, hBitmap);
blendFunc.BlendOp = Win32.AC_SRC_OVER;
blendFunc.SourceConstantAlpha = 255;
blendFunc.AlphaFormat = Win32.AC_SRC_ALPHA;
blendFunc.BlendFlags = 0;
Win32.UpdateLayeredWindow(Handle, screenDC, ref topLoc, ref bitMapSize, memDc, ref srcLoc, 0, ref blendFunc, Win32.ULW_ALPHA);
} finally {
if (hBitmap != IntPtr.Zero) {
Win32.SelectObject(memDc, oldBits);
Win32.DeleteObject(hBitmap);
}
Win32.ReleaseDC(IntPtr.Zero, screenDC);
Win32.DeleteDC(memDc);
}
}
private Bitmap CombinedPic(Bitmap bottom, Bitmap top, int x, int y) {
Bitmap bitmap = new Bitmap(bottom.Width, bottom.Height);
Graphics g = Graphics.FromImage(bitmap);
g.DrawImage(bottom, new Rectangle(0, 0, bottom.Width, bottom.Height), new Rectangle(0, 0, bottom.Width, bottom.Height), GraphicsUnit.Pixel);
g.DrawImage(top, new Rectangle(x, y, top.Width, top.Height), new Rectangle(0, 0, top.Width, top.Height), GraphicsUnit.Pixel);
return bitmap;
}
private Bitmap Dress(Bitmap img, int state) {
Bitmap bitmap = new Bitmap(img.Width, img.Height);
bitmap = img;
if (clothesNum != -1) {
bitmap = CombinedPic(bitmap, petClothes[clothesNum, state], 0, 0);
}
if (hatNum != -1) {
bitmap = CombinedPic(bitmap, petHat[hatNum, state], 0, 0);
}
return bitmap;
}
protected override void OnLocationChanged(EventArgs e) {
base.OnLocationChanged(e);
if (isBubbleVisible) {
bubbleForm.Location = CalculateBubblePosition();
}
}
private void Form1_Load(object sender, EventArgs e) {
pet[0] = new Bitmap(Application.StartupPath + "\\shell\\surface0000.png");
pet[1] = new Bitmap(Application.StartupPath + "\\shell\\surface0001.png");
pet[2] = new Bitmap(Application.StartupPath + "\\shell\\surface0002.png");
pet[3] = new Bitmap(Application.StartupPath + "\\shell\\surface0003.png");
pet[4] = new Bitmap(Application.StartupPath + "\\shell\\surface0004.png");
pet[5] = new Bitmap(Application.StartupPath + "\\shell\\surface0005.png");
pet[6] = new Bitmap(Application.StartupPath + "\\shell\\surface0006.png");
pet[7] = new Bitmap(Application.StartupPath + "\\shell\\surface0007.png");
pet[8] = new Bitmap(Application.StartupPath + "\\shell\\surface0008.png");
pet[9] = new Bitmap(Application.StartupPath + "\\shell\\surface0009.png");
petDrag[0] = new Bitmap(Application.StartupPath + "\\shell\\surface0091.png");
petDrag[1] = new Bitmap(Application.StartupPath + "\\shell\\surface0092.png");
petDrag[2] = new Bitmap(Application.StartupPath + "\\shell\\surface0093.png");
petBlink[0] = new Bitmap(Application.StartupPath + "\\shell\\surface1003.png");
petBlink[1] = new Bitmap(Application.StartupPath + "\\shell\\surface1004.png");
petHat[0, 0] = new Bitmap(Application.StartupPath + "\\shell\\surface3000.png");
petHat[0, 1] = new Bitmap(Application.StartupPath + "\\shell\\surface3001.png");
petHat[1, 0] = new Bitmap(Application.StartupPath + "\\shell\\surface3002.png");
petHat[1, 1] = new Bitmap(Application.StartupPath + "\\shell\\surface3003.png");
petHat[2, 0] = new Bitmap(Application.StartupPath + "\\shell\\surface3004.png");
petHat[2, 1] = new Bitmap(Application.StartupPath + "\\shell\\surface3005.png");
petHat[3, 0] = new Bitmap(Application.StartupPath + "\\shell\\surface3006.png");
petHat[3, 1] = new Bitmap(Application.StartupPath + "\\shell\\surface3007.png");
petClothes[0, 0] = new Bitmap(Application.StartupPath + "\\shell\\surface3100.png");
petClothes[0, 1] = new Bitmap(Application.StartupPath + "\\shell\\surface3101.png");
petClothes[0, 2] = new Bitmap(Application.StartupPath + "\\shell\\surface3102.png");
petClothes[0, 3] = new Bitmap(Application.StartupPath + "\\shell\\surface3103.png");
petClothes[1, 0] = new Bitmap(Application.StartupPath + "\\shell\\surface3200.png");
petClothes[1, 1] = new Bitmap(Application.StartupPath + "\\shell\\surface3201.png");
petClothes[1, 2] = new Bitmap(Application.StartupPath + "\\shell\\surface3202.png");
petClothes[1, 3] = new Bitmap(Application.StartupPath + "\\shell\\surface3203.png");
DressAll();
SetBits(petWithClothes[0]);
// <20><>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD><EFBFBD>ݴ<EFBFBD><DDB4><EFBFBD>
InitializeBubbleForm();
}
// <20><>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD><EFBFBD>ݴ<EFBFBD><DDB4><EFBFBD>
private void InitializeBubbleForm() {
bubbleForm = new BubbleForm();
bubbleForm.Visible = false;
bubbleForm.Owner = this;
bubbleForm.Location = CalculateBubblePosition();
}
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD>ã<EFBFBD><C3A3>ڳ<EFBFBD><DAB3><EFBFBD><EFBFBD>Ϸ<EFBFBD><CFB7><EFBFBD>
private Point CalculateBubblePosition() {
// <20><>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFB4B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ĵ<EFBFBD>
Point petCenter = new Point(
this.Left + (this.Width / 2),
this.Top
);
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD>ã<EFBFBD><C3A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڳ<EFBFBD><DAB3><EFBFBD><EFBFBD>Ϸ<EFBFBD><CFB7><EFBFBD>
return new Point(
petCenter.X - (bubbleForm.Width / 2),
petCenter.Y - bubbleForm.Height - 10
);
}
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʾ<EFBFBD><CABE>Ϣ<EFBFBD><CFA2><EFBFBD><EFBFBD>
public void ShowMessage(string message) {
if (isBubbleVisible) {
messageQueue.Enqueue(message);
return;
}
bubbleForm.Message = message;
// <20><>ȷ<EFBFBD><C8B7><EFBFBD><EFBFBD><EFBFBD>ݴ<EFBFBD>С<EFBFBD>Ѹ<EFBFBD><D1B8><EFBFBD>
bubbleForm.UpdateSize();
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȷλ<C8B7><CEBB>
Point bubblePos = CalculateBubblePosition();
bubbleForm.Location = bubblePos;
bubbleForm.Show();
bubbleForm.BringToFront();
isBubbleVisible = true;
bubbleStartTime = DateTime.Now;
tmrBubble.Interval = 100;
tmrBubble.Enabled = true;
}
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>رյ<D8B1>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD>
public void CloseBubble() {
bubbleForm.Hide();
isBubbleVisible = false;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><C7B7>д<EFBFBD><D0B4><EFBFBD>ʾ<EFBFBD><CABE>Ϣ
if (messageQueue.Count > 0) {
ShowMessage(messageQueue.Dequeue());
}
}
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ݼ<EFBFBD>ʱ<EFBFBD><CAB1>Tick<63>¼<EFBFBD>
private void tmrBubble_Tick(object sender, EventArgs e) {
// <20><><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><C7B7><EFBFBD><EFB5BD>ʾʱ<CABE><CAB1>
if ((DateTime.Now - bubbleStartTime).TotalMilliseconds >= bubbleShowTime) {
CloseBubble();
}
}
#region <EFBFBD><EFBFBD>ק
private void Form1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e) {
if (e.Button == System.Windows.Forms.MouseButtons.Left) {
bFormDragging = true;
oPointClicked = new Point(e.X, e.Y);
tmrDrag.Interval = 110;
tmrDrag.Enabled = true;
}
}
private void Form1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) {
if (e.Button == System.Windows.Forms.MouseButtons.Left) {
bFormDragging = false;
tmrDrag.Enabled = false;
SetBits(petWithClothes[0]);
}
}
private void Form1_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) {
if (bFormDragging) {
Point oMoveToPoint = default(Point);
//<2F>Ե<EFBFBD>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD>λ<EFBFBD><CEBB>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҳ<EFBFBD>Ŀ<EFBFBD><C4BF>λ<EFBFBD><CEBB>
oMoveToPoint = PointToScreen(new Point(e.X, e.Y));
oMoveToPoint.Offset(oPointClicked.X * -1, (oPointClicked.Y + SystemInformation.CaptionHeight + SystemInformation.BorderSize.Height) * -1 + 24);
Location = oMoveToPoint;
}
}
private void tmrDrag_Tick(object sender, EventArgs e) {
if (dragFrame < 2) {
SetBits(petDragWithClothes[dragFrame]);
dragFrame += 1;
} else {
SetBits(petDragWithClothes[dragFrame]);
dragFrame = 0;
}
}
#endregion
private void <EFBFBD>˳<EFBFBD>ToolStripMenuItem_Click(object sender, EventArgs e) {
Application.Exit();
}
private void գ<EFBFBD><EFBFBD>ToolStripMenuItem_Click(object sender, EventArgs e) {
tmrBlink.Interval = 40;
tmrBlink.Start();
}
private void tmrBlink_Tick(object sender, EventArgs e) {
if (blinkFrame < 2) {
SetBits(petBlinkWithClothes[blinkFrame]);
blinkFrame += 1;
} else {
SetBits(petWithClothes[0]);
blinkFrame = 0;
tmrBlink.Stop();
}
}
private void ñ<EFBFBD><EFBFBD>ToolStripMenuItem_Click(object sender, EventArgs e) {
bool itemChecked = (sender as ToolStripMenuItem).Checked;
if (itemChecked) {
hatNum = -1;
} else {
hatNum = this.<EFBFBD>¹<EFBFBD>ToolStripMenuItem.DropDownItems.IndexOf(sender as ToolStripMenuItem);
}
this.ʥ<EFBFBD><EFBFBD>ñ<EFBFBD><EFBFBD>ToolStripMenuItem.Checked = false;
this.ӣ<EFBFBD><EFBFBD>ñ<EFBFBD><EFBFBD>ToolStripMenuItem.Checked = false;
this.ˮ<EFBFBD><EFBFBD>ñToolStripMenuItem.Checked = false;
this.<EFBFBD>ñ<EFBFBD><EFBFBD>ToolStripMenuItem.Checked = false;
(sender as ToolStripMenuItem).Checked = !itemChecked;
DressAll();
SetBits(petWithClothes[0]);
}
private void <EFBFBD>·<EFBFBD>ToolStripMenuItem_Click(object sender, EventArgs e) {
bool itemChecked = (sender as ToolStripMenuItem).Checked;
if (itemChecked) {
clothesNum = -1;
} else {
clothesNum = this.<EFBFBD>¹<EFBFBD>ToolStripMenuItem.DropDownItems.IndexOf(sender as ToolStripMenuItem) - 4;
}
this.ʥ<EFBFBD><EFBFBD><EFBFBD>·<EFBFBD>ToolStripMenuItem.Checked = false;
this.<EFBFBD>ͷ<EFBFBD>ToolStripMenuItem.Checked = false;
(sender as ToolStripMenuItem).Checked = !itemChecked;
DressAll();
SetBits(petWithClothes[0]);
}
private void DressAll() {
int i;
for (i = 0; i < 10; i++) {
petWithClothes[i] = Dress(pet[i], (int)PetStates.General);
}
for (i = 0; i < 3; i++) {
petDragWithClothes[i] = Dress(petDrag[i], (int)PetStates.Drag);
}
for (i = 0; i < 2; i++) {
petBlinkWithClothes[i] = Dress(petBlink[i], (int)PetStates.General);
}
}
private void <EFBFBD><EFBFBD>Ϣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ToolStripMenuItem_Click(object sender, EventArgs e) {
ShowMessage("<22><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϣ<EFBFBD><CFA2><EFBFBD>ݣ<EFBFBD>\n<><6E><EFBFBD><EFBFBD><EFBFBD><EFBFBD>5<EFBFBD><35><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><D4B6><EFBFBD>ʧ<EFBFBD><CAA7>\n<><6E><EFBFBD><EFBFBD><EFBFBD>Ե<EFBFBD><D4B5><EFBFBD><EFBFBD>Ҽ<EFBFBD><D2BC><EFBFBD><E9BFB4><EFBFBD><EFBFBD>ѡ<EFBFBD>");
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,14 @@
namespace AccompanyingAssistant {
internal static class Program {
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main() {
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.Run(new MainForm());
}
}
}

View File

@@ -0,0 +1,67 @@
namespace AccompanyingAssistant {
using System;
using System.Runtime.InteropServices;
internal class Win32 {
public const byte AC_SRC_ALPHA = 1;
public const byte AC_SRC_OVER = 0;
public const int ULW_ALPHA = 2;
[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
[DllImport("gdi32.dll")]
public static extern Bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll")]
public static extern Bool DeleteObject(IntPtr hObject);
[DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr handle);
[DllImport("user32.dll", ExactSpelling = true)]
public static extern int ReleaseDC(IntPtr handle, IntPtr hDC);
[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
[DllImport("user32.dll")]
public static extern Bool UpdateLayeredWindow(IntPtr handle, IntPtr hdcDst, ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, int crKey, ref BLENDFUNCTION pblend, int dwFlags);
[DllImport("user32.dll")]
public static extern Bool ReleaseCapture();
[DllImport("user32.dll")]
public static extern Bool SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
[StructLayout(LayoutKind.Sequential)]
public struct BLENDFUNCTION {
public byte BlendOp;
public byte BlendFlags;
public byte SourceConstantAlpha;
public byte AlphaFormat;
}
public enum Bool {
False,
True
}
[StructLayout(LayoutKind.Sequential)]
public struct Point {
public int x;
public int y;
public Point(int x, int y) {
this = new Win32.Point();
this.x = x;
this.y = y;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct Size {
public int cx;
public int cy;
public Size(int cx, int cy) {
this = new Win32.Size();
this.cx = cx;
this.cy = cy;
}
}
}
}

View File

@@ -0,0 +1,51 @@
using AccompanyingAssistant;
public class WindowManager {
private MainForm? mainForm = null;
private Thread? uiThread = null;
public void ShowMain() {
if (mainForm == null || mainForm.IsDisposed) {
uiThread = new Thread(() => {
ApplicationConfiguration.Initialize();
mainForm = new MainForm();
Application.Run(mainForm);
});
uiThread.SetApartmentState(ApartmentState.STA);
uiThread.IsBackground = true;
uiThread.Start();
// 等待窗体创建完毕(简单阻塞)
while (mainForm == null || !mainForm.IsHandleCreated) {
Thread.Sleep(10);
}
} else {
mainForm.Invoke(() => mainForm.Show());
}
}
public void ShowBubble(string message) {
ShowMain();
// 此处保证 ShowMessage 在 UI 线程上执行
if (mainForm != null && mainForm.IsHandleCreated) {
mainForm.Invoke(() => mainForm.ShowMessage(message));
}
}
public void CloseBubble() {
if (mainForm != null && mainForm.IsHandleCreated) {
mainForm.Invoke(() => mainForm.CloseBubble());
}
}
public void CloseMain() {
if (mainForm != null && mainForm.IsHandleCreated) {
mainForm.Invoke(() => {
mainForm.Close();
mainForm.Dispose();
mainForm = null;
});
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -25,78 +25,126 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioWallpaperManager", "Au
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioWallpaper", "AudioVisualizer\AudioWallpaper.csproj", "{4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioWallpaper", "AudioVisualizer\AudioWallpaper.csproj", "{4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccompanyingAssistant", "AccompanyingAssistant\AccompanyingAssistant.csproj", "{9C088677-7177-4D46-801F-426575401B1B}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64 Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64 Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|Any CPU.Build.0 = Debug|Any CPU {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|Any CPU.Build.0 = Debug|Any CPU
{459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|x64.ActiveCfg = Debug|x64 {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|x64.ActiveCfg = Debug|x64
{459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|x64.Build.0 = Debug|x64 {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|x64.Build.0 = Debug|x64
{459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|x86.ActiveCfg = Debug|Any CPU
{459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Debug|x86.Build.0 = Debug|Any CPU
{459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|Any CPU.ActiveCfg = Release|Any CPU {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|Any CPU.ActiveCfg = Release|Any CPU
{459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|Any CPU.Build.0 = Release|Any CPU {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|Any CPU.Build.0 = Release|Any CPU
{459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|x64.ActiveCfg = Release|x64 {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|x64.ActiveCfg = Release|x64
{459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|x64.Build.0 = Release|x64 {459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|x64.Build.0 = Release|x64
{459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|x86.ActiveCfg = Release|Any CPU
{459FE289-A8E1-4BEC-9113-FC42B9CE9436}.Release|x86.Build.0 = Release|Any CPU
{15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|Any CPU.Build.0 = Debug|Any CPU {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|Any CPU.Build.0 = Debug|Any CPU
{15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|x64.ActiveCfg = Debug|x64 {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|x64.ActiveCfg = Debug|x64
{15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|x64.Build.0 = Debug|x64 {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|x64.Build.0 = Debug|x64
{15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|x86.ActiveCfg = Debug|Any CPU
{15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Debug|x86.Build.0 = Debug|Any CPU
{15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|Any CPU.ActiveCfg = Release|Any CPU {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|Any CPU.ActiveCfg = Release|Any CPU
{15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|Any CPU.Build.0 = Release|Any CPU {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|Any CPU.Build.0 = Release|Any CPU
{15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|x64.ActiveCfg = Release|x64 {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|x64.ActiveCfg = Release|x64
{15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|x64.Build.0 = Release|x64 {15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|x64.Build.0 = Release|x64
{15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|x86.ActiveCfg = Release|Any CPU
{15A2D4B9-BD52-411F-96E9-2BFACFEC4579}.Release|x86.Build.0 = Release|Any CPU
{20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|Any CPU.Build.0 = Debug|Any CPU {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|Any CPU.Build.0 = Debug|Any CPU
{20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|x64.ActiveCfg = Debug|x64 {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|x64.ActiveCfg = Debug|x64
{20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|x64.Build.0 = Debug|x64 {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|x64.Build.0 = Debug|x64
{20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|x86.ActiveCfg = Debug|Any CPU
{20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Debug|x86.Build.0 = Debug|Any CPU
{20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|Any CPU.ActiveCfg = Release|Any CPU {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|Any CPU.ActiveCfg = Release|Any CPU
{20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|Any CPU.Build.0 = Release|Any CPU {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|Any CPU.Build.0 = Release|Any CPU
{20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|x64.ActiveCfg = Release|x64 {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|x64.ActiveCfg = Release|x64
{20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|x64.Build.0 = Release|x64 {20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|x64.Build.0 = Release|x64
{20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|x86.ActiveCfg = Release|Any CPU
{20B17F7E-11C3-4E46-9E79-DE9A3B25B640}.Release|x86.Build.0 = Release|Any CPU
{EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|Any CPU.Build.0 = Debug|Any CPU {EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|x64.ActiveCfg = Debug|x64 {EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|x64.ActiveCfg = Debug|x64
{EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|x64.Build.0 = Debug|x64 {EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|x64.Build.0 = Debug|x64
{EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|x86.ActiveCfg = Debug|Any CPU
{EC24D98A-2776-400D-8494-7B8C954EAF84}.Debug|x86.Build.0 = Debug|Any CPU
{EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|Any CPU.ActiveCfg = Release|Any CPU {EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|Any CPU.Build.0 = Release|Any CPU {EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|Any CPU.Build.0 = Release|Any CPU
{EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|x64.ActiveCfg = Release|x64 {EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|x64.ActiveCfg = Release|x64
{EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|x64.Build.0 = Release|x64 {EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|x64.Build.0 = Release|x64
{EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|x86.ActiveCfg = Release|Any CPU
{EC24D98A-2776-400D-8494-7B8C954EAF84}.Release|x86.Build.0 = Release|Any CPU
{4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|Any CPU.Build.0 = Debug|Any CPU {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|x64.ActiveCfg = Debug|x64 {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|x64.ActiveCfg = Debug|x64
{4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|x64.Build.0 = Debug|x64 {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|x64.Build.0 = Debug|x64
{4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|x86.ActiveCfg = Debug|Any CPU
{4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Debug|x86.Build.0 = Debug|Any CPU
{4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|Any CPU.ActiveCfg = Release|Any CPU {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|Any CPU.Build.0 = Release|Any CPU {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|Any CPU.Build.0 = Release|Any CPU
{4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|x64.ActiveCfg = Release|x64 {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|x64.ActiveCfg = Release|x64
{4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|x64.Build.0 = Release|x64 {4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|x64.Build.0 = Release|x64
{4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|x86.ActiveCfg = Release|Any CPU
{4398D445-4BE7-4671-83C1-BA7320BEF1EE}.Release|x86.Build.0 = Release|Any CPU
{F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|Any CPU.Build.0 = Debug|Any CPU {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|x64.ActiveCfg = Debug|x64 {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|x64.ActiveCfg = Debug|x64
{F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|x64.Build.0 = Debug|x64 {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|x64.Build.0 = Debug|x64
{F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|x86.ActiveCfg = Debug|Any CPU
{F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Debug|x86.Build.0 = Debug|Any CPU
{F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|Any CPU.ActiveCfg = Release|Any CPU {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|Any CPU.Build.0 = Release|Any CPU {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|Any CPU.Build.0 = Release|Any CPU
{F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|x64.ActiveCfg = Release|x64 {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|x64.ActiveCfg = Release|x64
{F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|x64.Build.0 = Release|x64 {F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|x64.Build.0 = Release|x64
{F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|x86.ActiveCfg = Release|Any CPU
{F31F51C2-25D8-4AF1-891C-01087BB7E5EC}.Release|x86.Build.0 = Release|Any CPU
{E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Debug|Any CPU.Build.0 = Debug|Any CPU {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Debug|x64.ActiveCfg = Debug|x64 {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Debug|x64.ActiveCfg = Debug|x64
{E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Debug|x64.Build.0 = Debug|x64 {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Debug|x64.Build.0 = Debug|x64
{E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Debug|x86.ActiveCfg = Debug|Any CPU
{E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Debug|x86.Build.0 = Debug|Any CPU
{E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Release|Any CPU.ActiveCfg = Release|Any CPU {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Release|Any CPU.Build.0 = Release|Any CPU {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Release|Any CPU.Build.0 = Release|Any CPU
{E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Release|x64.ActiveCfg = Release|x64 {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Release|x64.ActiveCfg = Release|x64
{E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Release|x64.Build.0 = Release|x64 {E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Release|x64.Build.0 = Release|x64
{E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Release|x86.ActiveCfg = Release|Any CPU
{E77BF32B-9AEF-4BE3-A1BD-4A13533E02B5}.Release|x86.Build.0 = Release|Any CPU
{4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Debug|Any CPU.Build.0 = Debug|Any CPU {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Debug|x64.ActiveCfg = Debug|x64 {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Debug|x64.ActiveCfg = Debug|x64
{4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Debug|x64.Build.0 = Debug|x64 {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Debug|x64.Build.0 = Debug|x64
{4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Debug|x86.ActiveCfg = Debug|Any CPU
{4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Debug|x86.Build.0 = Debug|Any CPU
{4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Release|Any CPU.ActiveCfg = Release|Any CPU {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Release|Any CPU.Build.0 = Release|Any CPU {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Release|Any CPU.Build.0 = Release|Any CPU
{4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Release|x64.ActiveCfg = Release|x64 {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Release|x64.ActiveCfg = Release|x64
{4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Release|x64.Build.0 = Release|x64 {4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Release|x64.Build.0 = Release|x64
{4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Release|x86.ActiveCfg = Release|Any CPU
{4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}.Release|x86.Build.0 = Release|Any CPU
{9C088677-7177-4D46-801F-426575401B1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9C088677-7177-4D46-801F-426575401B1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9C088677-7177-4D46-801F-426575401B1B}.Debug|x64.ActiveCfg = Debug|Any CPU
{9C088677-7177-4D46-801F-426575401B1B}.Debug|x64.Build.0 = Debug|Any CPU
{9C088677-7177-4D46-801F-426575401B1B}.Debug|x86.ActiveCfg = Debug|Any CPU
{9C088677-7177-4D46-801F-426575401B1B}.Debug|x86.Build.0 = Debug|Any CPU
{9C088677-7177-4D46-801F-426575401B1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9C088677-7177-4D46-801F-426575401B1B}.Release|Any CPU.Build.0 = Release|Any CPU
{9C088677-7177-4D46-801F-426575401B1B}.Release|x64.ActiveCfg = Release|Any CPU
{9C088677-7177-4D46-801F-426575401B1B}.Release|x64.Build.0 = Release|Any CPU
{9C088677-7177-4D46-801F-426575401B1B}.Release|x86.ActiveCfg = Release|Any CPU
{9C088677-7177-4D46-801F-426575401B1B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@@ -0,0 +1,53 @@
using AudioWallpaper.ActivityWatch;
using AudioWallpaper.SSO;
using AudioWallpaper.Tools;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AudioWallpaper {
public class AccAssManage {
private SentenceHelper SentenceHelper;
public static AccAssManage? Instance = null;
public AccAssManage() {
SentenceHelper = new SentenceHelper();
SentenceHelper.SentenceChanged += SentenceHelper_SentenceChanged;
SentenceHelper.Start();
Instance = this;
}
public void UpdateLoginState(LoginMeta loginMeta) {
SentenceHelper.UpdateLoginState(loginMeta);
}
public void UpdateUseState(bool isUse) {
SentenceHelper.UpdateUseState(isUse);
}
public void SetInterval(int newInterval) {
SentenceHelper.SetInterval(newInterval);
}
public void Stop() {
SentenceHelper.Stop();
}
public void Dispose() {
SentenceHelper.SentenceChanged -= SentenceHelper_SentenceChanged;
SentenceHelper.Dispose();
}
/// <summary>
/// 句子数据更改时执行
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <exception cref="NotImplementedException"></exception>
private void SentenceHelper_SentenceChanged(object? sender, FocusStatusRecord fsr) {
Console.WriteLine("假设这是执行了事件的代码");
Console.WriteLine("这是从事件回调中获取到的数据:" + fsr.Message);
}
}
}

View File

@@ -0,0 +1,497 @@
using AudioWallpaper.ActivityWatch;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace AudioWallpaper.ActivityWatch {
internal class ActivityAnalyzer {
private readonly ActivityAnalyzerConfig _config;
private readonly TimeZoneInfo _timeZone;
public ActivityAnalyzer(ActivityAnalyzerConfig config = null) {
_config = config ?? new ActivityAnalyzerConfig();
// 初始化时区
try {
_timeZone = TimeZoneInfo.FindSystemTimeZoneById(GetWindowsTimeZoneId(_config.Timezone));
} catch (TimeZoneNotFoundException) {
Console.WriteLine($"警告:未知时区 '{_config.Timezone}',使用默认时区 'China Standard Time'");
_timeZone = TimeZoneInfo.FindSystemTimeZoneById("China Standard Time");
}
}
private string GetWindowsTimeZoneId(string timezone) {
// 映射一些常见的时区名称到Windows时区ID
var timeZoneMap = new Dictionary<string, string>
{
{ "Asia/Shanghai", "China Standard Time" },
{ "UTC", "UTC" },
{ "US/Eastern", "Eastern Standard Time" },
{ "US/Pacific", "Pacific Standard Time" },
{ "Europe/London", "GMT Standard Time" },
{ "Europe/Paris", "W. Europe Standard Time" }
};
return timeZoneMap.ContainsKey(timezone) ? timeZoneMap[timezone] : timezone;
}
private DateTime? ParseTimestamp(string timestampStr) {
if (string.IsNullOrEmpty(timestampStr))
return null;
try {
// 方法1直接解析ISO格式
if (DateTime.TryParse(timestampStr, out DateTime dt)) {
// 转换到指定时区
if (dt.Kind == DateTimeKind.Utc) {
return TimeZoneInfo.ConvertTimeFromUtc(dt, _timeZone);
} else if (dt.Kind == DateTimeKind.Unspecified) {
return TimeZoneInfo.ConvertTime(dt, _timeZone);
}
return dt;
}
// 方法2手动解析
string cleanTimestamp = timestampStr.Replace('T', ' ');
if (cleanTimestamp.Contains('+'))
cleanTimestamp = cleanTimestamp.Split('+')[0];
else if (cleanTimestamp.Contains('Z'))
cleanTimestamp = cleanTimestamp.Replace("Z", "");
// 移除毫秒部分
cleanTimestamp = Regex.Replace(cleanTimestamp, @"\.\d+", "");
if (DateTime.TryParseExact(cleanTimestamp, "yyyy-MM-dd HH:mm:ss",
CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime parsedDt)) {
return TimeZoneInfo.ConvertTime(parsedDt, _timeZone);
}
return null;
} catch (Exception ex) {
Console.WriteLine($"警告:无法解析时间戳 '{timestampStr}': {ex.Message}");
return null;
}
}
/// <summary>
/// 解析从ActivityWatchClient获取的原始活动数据
/// </summary>
/// <param name="rawData">ActivityWatchClient.GetEventsAsync()返回的数据</param>
/// <returns>解析后的活动条目列表</returns>
public List<ActivityEntry> ParseActivityData(List<Dictionary<string, object>> rawData) {
var activities = new List<ActivityEntry>();
if (rawData == null || !rawData.Any())
return activities;
foreach (var entry in rawData) {
var processed = ProcessDictionaryEntry(entry);
if (processed != null)
activities.AddRange(processed);
}
return activities.OrderBy(a => a.Timestamp).ToList();
}
/// <summary>
/// 从JSON字符串解析活动数据兼容原有功能
/// </summary>
/// <param name="jsonString">JSON格式的活动数据</param>
/// <returns>解析后的活动条目列表</returns>
public List<ActivityEntry> ParseActivityDataFromJson(string jsonString) {
var activities = new List<ActivityEntry>();
if (string.IsNullOrEmpty(jsonString))
return activities;
try {
// 尝试解析为JSON数组
var jsonArray = JsonSerializer.Deserialize<List<JsonElement>>(jsonString);
foreach (var entry in jsonArray) {
var processed = ProcessEntry(entry);
if (processed != null)
activities.AddRange(processed);
}
} catch (JsonException) {
// 逐行解析
foreach (var line in jsonString.Split('\n')) {
var trimmedLine = line.Trim();
if (string.IsNullOrEmpty(trimmedLine))
continue;
try {
var entry = JsonSerializer.Deserialize<JsonElement>(trimmedLine);
var processed = ProcessEntry(entry);
if (processed != null)
activities.AddRange(processed);
} catch (JsonException) {
// 忽略无法解析的行
}
}
}
return activities.OrderBy(a => a.Timestamp).ToList();
}
private List<ActivityEntry> ProcessEntry(JsonElement entry) {
try {
var data = entry.TryGetProperty("data", out var dataElement) ? dataElement : entry;
if (!data.TryGetProperty("app", out var appElement) ||
!data.TryGetProperty("title", out var titleElement))
return null;
var app = appElement.GetString();
var title = titleElement.GetString();
if (string.IsNullOrEmpty(app) || string.IsNullOrEmpty(title))
return null;
var duration = entry.TryGetProperty("duration", out var durationElement)
? durationElement.GetDouble() : 0.0;
if (duration < _config.MinDuration ||
_config.ExcludedApps.Contains(app) ||
_config.ExcludedTitles.Any(keyword => title.Contains(keyword)))
return null;
var timestampStr = entry.TryGetProperty("timestamp", out var timestampElement)
? timestampElement.GetString() : "";
var parsedTimestamp = ParseTimestamp(timestampStr);
if (!parsedTimestamp.HasValue)
return null;
return new List<ActivityEntry>
{
new ActivityEntry
{
Timestamp = parsedTimestamp.Value,
Duration = duration,
App = app,
Title = title,
RawTitle = title
}
};
} catch (Exception) {
return null;
}
}
private List<ActivityEntry> ProcessDictionaryEntry(Dictionary<string, object> entry) {
try {
Dictionary<string, object> data;
if (entry.ContainsKey("data")) {
if (entry["data"] is JsonElement je && je.ValueKind == JsonValueKind.Object) {
// ✅ 将 JsonElement 转为 Dictionary<string, object>
data = JsonSerializer.Deserialize<Dictionary<string, object>>(je.GetRawText());
} else if (entry["data"] is Dictionary<string, object> dict) {
data = dict;
} else {
return null;
}
} else {
data = entry;
}
if (!data.ContainsKey("app") || !data.ContainsKey("title"))
return null;
var app = data["app"].ToString();
var title = data["title"].ToString();
if (string.IsNullOrEmpty(app) || string.IsNullOrEmpty(title))
return null;
var duration = entry.ContainsKey("duration") ? GetValue<double>(entry["duration"]) : 0.0;
if (duration < _config.MinDuration ||
_config.ExcludedApps.Contains(app) ||
_config.ExcludedTitles.Any(keyword => title.Contains(keyword)))
return null;
var timestampStr = entry.ContainsKey("timestamp") ? entry["timestamp"].ToString() : "";
var parsedTimestamp = ParseTimestamp(timestampStr);
if (!parsedTimestamp.HasValue)
return null;
return new List<ActivityEntry>
{
new ActivityEntry
{
Timestamp = parsedTimestamp.Value,
Duration = duration,
App = app,
Title = title,
RawTitle = title
}
};
} catch (Exception e) {
return null;
}
}
private T GetValue<T>(object value, T defaultValue = default) {
try {
if (value == null || value is DBNull)
return defaultValue;
// 处理 JsonElement 特殊情况
if (value is JsonElement je) {
if (typeof(T) == typeof(string)) return (T)(object)je.ToString();
if (typeof(T) == typeof(int) && je.TryGetInt32(out var i)) return (T)(object)i;
if (typeof(T) == typeof(long) && je.TryGetInt64(out var l)) return (T)(object)l;
if (typeof(T) == typeof(double) && je.TryGetDouble(out var d)) return (T)(object)d;
if (typeof(T) == typeof(float) && je.TryGetSingle(out var f)) return (T)(object)f;
if (typeof(T) == typeof(DateTime) && je.TryGetDateTime(out var dt)) return (T)(object)dt;
// fallback: 尝试反序列化整个对象
return JsonSerializer.Deserialize<T>(je.GetRawText());
}
// 如果已经是目标类型,直接返回
if (value is T tVal)
return tVal;
// 尝试 Convert.ChangeType
return (T)Convert.ChangeType(value, typeof(T));
} catch {
return defaultValue;
}
}
private List<ActivityEntry> SplitActivityAcrossSlices(ActivityEntry activity, int sliceDurationMinutes) {
var startTime = activity.Timestamp;
var duration = activity.Duration;
var endTime = startTime.AddSeconds(duration);
var sliceInterval = TimeSpan.FromMinutes(sliceDurationMinutes);
// 计算开始时间片的边界
var startSlice = new DateTime(startTime.Year, startTime.Month, startTime.Day,
startTime.Hour, (startTime.Minute / sliceDurationMinutes) * sliceDurationMinutes, 0);
// 如果活动完全在一个时间片内,直接返回
var nextSliceBoundary = startSlice.Add(sliceInterval);
if (endTime <= nextSliceBoundary)
return new List<ActivityEntry> { activity };
// 将活动分割到多个时间片
var splitActivities = new List<ActivityEntry>();
var currentSliceStart = startSlice;
var remainingDuration = duration;
while (remainingDuration > 0 && currentSliceStart < endTime) {
var currentSliceEnd = currentSliceStart.Add(sliceInterval);
// 计算当前时间片中的活动开始时间
var activityStartInSlice = startTime > currentSliceStart ? startTime : currentSliceStart;
var activityEndInSlice = endTime < currentSliceEnd ? endTime : currentSliceEnd;
// 计算在当前时间片中的持续时间
var durationInSlice = (activityEndInSlice - activityStartInSlice).TotalSeconds;
if (durationInSlice > 0) {
splitActivities.Add(new ActivityEntry {
Timestamp = activityStartInSlice,
Duration = durationInSlice,
App = activity.App,
Title = activity.Title,
RawTitle = activity.RawTitle
});
}
currentSliceStart = currentSliceEnd;
remainingDuration -= durationInSlice;
}
return splitActivities;
}
/// <summary>
/// 分析活动数据并生成时间片结果
/// </summary>
/// <param name="activities">活动条目列表</param>
/// <returns>时间片分析结果</returns>
public List<TimeSliceResult> AnalyzeTimeSlices(List<ActivityEntry> activities) {
if (!activities.Any())
return new List<TimeSliceResult>();
var startTime = activities.First().Timestamp;
var endTime = activities.Last().Timestamp;
var interval = _config.TimeSliceMinutes;
var sliceDict = new Dictionary<DateTime, TimeSliceData>();
// 分配活动到时间片
foreach (var activity in activities) {
var splitActivities = SplitActivityAcrossSlices(activity, interval);
foreach (var splitActivity in splitActivities) {
var sliceKey = new DateTime(splitActivity.Timestamp.Year, splitActivity.Timestamp.Month,
splitActivity.Timestamp.Day, splitActivity.Timestamp.Hour,
(splitActivity.Timestamp.Minute / interval) * interval, 0);
if (!sliceDict.ContainsKey(sliceKey))
sliceDict[sliceKey] = new TimeSliceData();
var sliceData = sliceDict[sliceKey];
sliceData.ActiveSeconds += splitActivity.Duration;
if (!sliceData.AppUsage.ContainsKey(splitActivity.App))
sliceData.AppUsage[splitActivity.App] = 0;
sliceData.AppUsage[splitActivity.App] += splitActivity.Duration;
sliceData.WindowTitles.Add(splitActivity.RawTitle);
sliceData.Events.Add(splitActivity);
// 关键词分析
var titleLower = splitActivity.Title.ToLower();
if (_config.WorkKeywords.Any(kw => titleLower.Contains(kw.ToLower())))
sliceData.KeywordsMatched++;
if (_config.DistractionKeywords.Any(kw => titleLower.Contains(kw.ToLower())))
sliceData.DistractionCount++;
}
}
// 计算切换次数和主导应用
foreach (var kvp in sliceDict) {
var data = kvp.Value;
// 计算应用切换次数
if (data.Events.Count > 1) {
var prevApp = data.Events[0].App;
for (int i = 1; i < data.Events.Count; i++) {
if (data.Events[i].App != prevApp)
data.SwitchCount++;
prevApp = data.Events[i].App;
}
}
// 确定主导应用
if (data.AppUsage.Any()) {
data.DominantProcess = data.AppUsage.OrderByDescending(x => x.Value).First().Key;
}
}
// 构建最终输出
var results = new List<TimeSliceResult>();
var current = new DateTime(startTime.Year, startTime.Month, startTime.Day,
startTime.Hour, (startTime.Minute / interval) * interval, 0);
while (current <= endTime) {
if (sliceDict.ContainsKey(current)) {
var data = sliceDict[current];
// 计算专注分数
var maxSeconds = interval * 60;
var timeUtilization = Math.Min(1.0, data.ActiveSeconds / maxSeconds);
var switchPenalty = Math.Min(1.0, data.SwitchCount / 10.0);
var keywordBoost = Math.Min(0.3, data.KeywordsMatched * 0.1);
var distractionPenalty = Math.Min(0.5, data.DistractionCount * 0.2);
var focusScore = (timeUtilization * 0.7 + keywordBoost) * (1 - switchPenalty) - distractionPenalty;
focusScore = Math.Max(0.0, Math.Min(1.0, focusScore));
// 根据阈值确定专注状态
var focusState = "无活动";
if (data.ActiveSeconds > 0) {
var sortedThresholds = _config.FocusThresholds.OrderByDescending(x => x.Value);
foreach (var threshold in sortedThresholds) {
if (focusScore >= threshold.Value) {
focusState = threshold.Key;
break;
}
}
}
var result = new TimeSliceResult {
TimeSlice = current.ToString("yyyy-MM-dd HH:mm"),
Timezone = _config.Timezone,
DominantProcess = data.DominantProcess ?? "none",
ActiveSeconds = Math.Round(data.ActiveSeconds, 1),
SwitchCount = data.SwitchCount,
KeywordsMatched = data.KeywordsMatched,
DistractionEvents = data.DistractionCount,
WindowTitles = data.WindowTitles.ToList(),
FocusScore = Math.Round(focusScore, 2),
FocusState = focusState,
ProductivityScore = Math.Round(focusScore * timeUtilization * 100, 1)
};
if (_config.OutputVerbose) {
result.AppUtilization = data.AppUsage.ToDictionary(
kvp => kvp.Key,
kvp => Math.Round(kvp.Value, 1)
);
}
results.Add(result);
} else {
// 无活动的空时间片
results.Add(new TimeSliceResult {
TimeSlice = current.ToString("yyyy-MM-dd HH:mm"),
Timezone = _config.Timezone,
DominantProcess = "none",
ActiveSeconds = 0,
SwitchCount = 0,
KeywordsMatched = 0,
DistractionEvents = 0,
WindowTitles = new List<string>(),
FocusScore = 0.0,
FocusState = "无活动",
ProductivityScore = 0.0
});
}
current = current.AddMinutes(interval);
}
return results;
}
/// <summary>
/// 完整的分析流程从ActivityWatchClient数据到时间片结果
/// </summary>
/// <param name="rawData">ActivityWatchClient.GetEventsAsync()返回的数据</param>
/// <returns>时间片分析结果</returns>
public List<TimeSliceResult> AnalyzeFromRawData(List<Dictionary<string, object>> rawData) {
var activities = ParseActivityData(rawData);
return AnalyzeTimeSlices(activities);
}
/// <summary>
/// 将结果保存为JSON文件
/// </summary>
/// <param name="results">分析结果</param>
/// <param name="filePath">输出文件路径</param>
public async Task SaveResultsToFileAsync(List<TimeSliceResult> results, string filePath) {
var options = new JsonSerializerOptions {
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
var jsonString = JsonSerializer.Serialize(results, options);
await File.WriteAllTextAsync(filePath, jsonString);
}
/// <summary>
/// 从配置文件加载配置
/// </summary>
/// <param name="configFilePath">配置文件路径</param>
/// <returns>配置对象</returns>
public static async Task<ActivityAnalyzerConfig> LoadConfigFromFileAsync(string configFilePath) {
try {
var configJson = await File.ReadAllTextAsync(configFilePath);
return JsonSerializer.Deserialize<ActivityAnalyzerConfig>(configJson);
} catch (Exception) {
return new ActivityAnalyzerConfig();
}
}
}
}

View File

@@ -0,0 +1,32 @@
namespace AudioWallpaper.ActivityWatch {
public class ActivityAnalyzerConfig {
public int TimeSliceMinutes { get; set; } = 5;
public double MinDuration { get; set; } = 0.1;
public string Timezone { get; set; } = "Asia/Shanghai";
public List<string> WorkKeywords { get; set; } = new List<string>
{
"work", "code", "develop", "programming", "research",
"writing", "study", "test", "开发", "编程", "测试", "脚本", "分析", "pycharm"
};
public List<string> DistractionKeywords { get; set; } = new List<string>
{
"social", "media", "video", "game", "shopping",
"news", "娱乐", "视频", "购物", "游戏", "聊天", "微博"
};
public List<string> ExcludedApps { get; set; } = new List<string>
{
"explorer.exe", "StartMenuExperienceHost.exe", "SearchApp.exe"
};
public List<string> ExcludedTitles { get; set; } = new List<string>
{
"任务切换", "任务管理器"
};
public bool OutputVerbose { get; set; } = true;
public Dictionary<string, double> FocusThresholds { get; set; } = new Dictionary<string, double>
{
{ "高专注", 0.75 },
{ "中专注", 0.5 },
{ "低专注", 0.0 }
};
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AudioWallpaper.ActivityWatch {
public class ActivityEntry {
public DateTime Timestamp { get; set; }
public double Duration { get; set; }
public string App { get; set; }
public string Title { get; set; }
public string RawTitle { get; set; }
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Web;
namespace AudioWallpaper.ActivityWatch {
public class ActivityWatchClient {
private readonly HttpClient _httpClient;
private readonly string _baseUrl;
private readonly string _bucket;
public ActivityWatchClient(string bucketName, string host = "http://localhost:5600") {
_httpClient = new HttpClient();
_baseUrl = $"{host}/api/0";
_bucket = bucketName;
}
public async Task<List<Dictionary<string, object>>> GetEventsAsync(string start, string end) {
var url = $"{_baseUrl}/buckets/{_bucket}/events";
var uriBuilder = new UriBuilder(url);
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
query["start"] = start;
query["end"] = end;
uriBuilder.Query = query.ToString();
var response = await _httpClient.GetAsync(uriBuilder.ToString());
response.EnsureSuccessStatusCode();
var jsonString = await response.Content.ReadAsStringAsync();
var events = JsonSerializer.Deserialize<List<Dictionary<string, object>>>(jsonString);
return events;
}
public static Dictionary<string, Dictionary<string, string>> GenerateTimeRanges(DateTime? now = null) {
var currentTime = now ?? DateTime.UtcNow;
return new Dictionary<string, Dictionary<string, string>> {
["last_5_minutes"] = new Dictionary<string, string> {
["start"] = currentTime.AddMinutes(-5).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
["end"] = currentTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
},
["last_30_minutes"] = new Dictionary<string, string> {
["start"] = currentTime.AddMinutes(-30).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
["end"] = currentTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
},
["last_6_hours"] = new Dictionary<string, string> {
["start"] = currentTime.AddHours(-6).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
["end"] = currentTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
},
["last_24_hours"] = new Dictionary<string, string> {
["start"] = currentTime.AddHours(-24).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
["end"] = currentTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
},
["last_3_days"] = new Dictionary<string, string> {
["start"] = currentTime.AddDays(-3).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
["end"] = currentTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
}
};
}
public void Dispose() {
_httpClient?.Dispose();
}
}
}

View File

@@ -0,0 +1,66 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace AudioWallpaper.ActivityWatch {
// 定义数据模型
public class ApiResponse {
[JsonPropertyName("code")]
public int Code { get; set; }
[JsonPropertyName("data")]
public DataItem[] Data { get; set; }
}
public class DataItem {
[JsonPropertyName("message")]
public MessageContent Message { get; set; }
}
public class MessageContent {
[JsonPropertyName("content")]
public string Content { get; set; }
}
public class FocusStatusRecord {
[JsonPropertyName("time_slice")]
public string TimeSlice { get; set; }
[JsonPropertyName("focus_state")]
public string FocusState { get; set; }
[JsonPropertyName("productivity_score")]
public double ProductivityScore { get; set; }
[JsonPropertyName("message")]
public string Message { get; set; }
}
public class FocusStatusParser {
public FocusStatusRecord GetLatestRecord(string jsonResponse) {
// 解析外层响应
var apiResponse = JsonSerializer.Deserialize<ApiResponse>(jsonResponse);
if (apiResponse?.Data == null || apiResponse.Data.Length == 0)
throw new InvalidOperationException("无效的API响应数据");
// 提取内容字符串包含Markdown代码块
string content = apiResponse.Data[0].Message.Content;
// 去除Markdown代码块标记
string cleanJson = content
.Replace("```json", "")
.Replace("```", "")
.Trim();
// 解析内部JSON数组
var records = JsonSerializer.Deserialize<FocusStatusRecord[]>(cleanJson);
if (records == null || records.Length == 0)
throw new InvalidOperationException("未找到专注状态记录");
// 返回最新的记录(数组的第一个元素)
return records[0];
}
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AudioWallpaper.ActivityWatch {
public class TimeSliceData {
public double ActiveSeconds { get; set; }
public Dictionary<string, double> AppUsage { get; set; } = new Dictionary<string, double>();
public HashSet<string> WindowTitles { get; set; } = new HashSet<string>();
public List<ActivityEntry> Events { get; set; } = new List<ActivityEntry>();
public int SwitchCount { get; set; }
public int KeywordsMatched { get; set; }
public int DistractionCount { get; set; }
public string DominantProcess { get; set; }
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AudioWallpaper.ActivityWatch {
public class TimeSliceResult {
public string TimeSlice { get; set; }
public string Timezone { get; set; }
public string DominantProcess { get; set; }
public double ActiveSeconds { get; set; }
public int SwitchCount { get; set; }
public int KeywordsMatched { get; set; }
public int DistractionEvents { get; set; }
public List<string> WindowTitles { get; set; }
public double FocusScore { get; set; }
public string FocusState { get; set; }
public double ProductivityScore { get; set; }
public Dictionary<string, double> AppUtilization { get; set; }
#if DEBUG
public override string ToString() {
var windowTitles = WindowTitles != null ? string.Join(", ", WindowTitles) : "null";
var appUtilization = AppUtilization != null
? string.Join(", ", AppUtilization.Select(kv => $"{kv.Key}:{kv.Value:F2}"))
: "null";
return $"TimeSlice: {TimeSlice}, " +
$"Timezone: {Timezone}, " +
$"DominantProcess: {DominantProcess}, " +
$"ActiveSeconds: {ActiveSeconds:F2}, " +
$"SwitchCount: {SwitchCount}, " +
$"KeywordsMatched: {KeywordsMatched}, " +
$"DistractionEvents: {DistractionEvents}, " +
$"WindowTitles: [{windowTitles}], " +
$"FocusScore: {FocusScore:F2}, " +
$"FocusState: {FocusState}, " +
$"ProductivityScore: {ProductivityScore:F2}, " +
$"AppUtilization: [{appUtilization}]";
}
}
#endif
}

View File

@@ -1,5 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Globals">
<WebView2EnableCsWinRTProjection>False</WebView2EnableCsWinRTProjection>
</PropertyGroup>
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows10.0.17763.0</TargetFramework> <TargetFramework>net8.0-windows10.0.17763.0</TargetFramework>
@@ -19,18 +23,42 @@
<SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion> <SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="FakesAssemblies\**" />
<Content Remove="FakesAssemblies\**" />
<EmbeddedResource Remove="FakesAssemblies\**" />
<None Remove="FakesAssemblies\**" />
<Page Remove="FakesAssemblies\**" />
<PRIResource Remove="FakesAssemblies\**" />
</ItemGroup>
<ItemGroup>
<COMReference Include="AxSHDocVw">
<WrapperTool>aximp</WrapperTool>
<VersionMinor>1</VersionMinor>
<VersionMajor>1</VersionMajor>
<Guid>eab22ac0-30c1-11cf-a7eb-0000c05bae0b</Guid>
<Lcid>0</Lcid>
<Isolated>false</Isolated>
</COMReference>
</ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="FM-channel.ico" /> <Content Include="FM-channel.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="LibVLCSharp.WinForms" Version="3.8.2" /> <PackageReference Include="LibVLCSharp.WinForms" Version="3.8.2" />
<PackageReference Include="Microsoft.QualityTools.Testing.Fakes" Version="17.14.1" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3344-prerelease" />
<PackageReference Include="NAudio" Version="2.2.1" /> <PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="Sgnl.IdentityModel.OidcClient" Version="6.0.2" />
<PackageReference Include="System.Management" Version="8.0.0" /> <PackageReference Include="System.Management" Version="8.0.0" />
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.20" /> <PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.20" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\AccompanyingAssistant\AccompanyingAssistant.csproj" />
<ProjectReference Include="..\LibAudioVisualizer\LibAudioVisualizer.csproj" /> <ProjectReference Include="..\LibAudioVisualizer\LibAudioVisualizer.csproj" />
</ItemGroup> </ItemGroup>
@@ -49,4 +77,8 @@
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup>
<AdditionalDesignTimeBuildInput Remove="FakesAssemblies\**" />
</ItemGroup>
</Project> </Project>

View File

@@ -3,6 +3,7 @@
public class ConfigurationObject { public class ConfigurationObject {
private GeneralConfigurationObjects? generalConfigurationObjects; private GeneralConfigurationObjects? generalConfigurationObjects;
private VideoWallpaperConfigObject? videoWallpaperConfigObject; private VideoWallpaperConfigObject? videoWallpaperConfigObject;
private OtherConfigObjects? otherConfigObjects;
public bool DeviceStateChange = false; public bool DeviceStateChange = false;
public bool SignRenderingStatus = false; public bool SignRenderingStatus = false;
public bool RenderingStatus = true; public bool RenderingStatus = true;
@@ -38,6 +39,21 @@
} }
} }
} }
public OtherConfigObjects OtherConfigObjects {
get {
if (otherConfigObjects != null) {
return otherConfigObjects;
}
return new OtherConfigObjects();
}
set {
if (value != null) {
otherConfigObjects = value;
} else {
otherConfigObjects = new OtherConfigObjects();
}
}
}
} }
} }

View File

@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AudioWallpaper.Entity {
[Serializable]
public class OtherConfigObjects {
/// <summary>
/// 配置名称
/// </summary>
private const string CONFIG_NAME = "OtherConfig";
/// <summary>
/// 是否使用陪伴助手
/// </summary>
public bool UseAccompanyingAssistant = false;
/// <summary>
/// Bucket名称
/// </summary>
public string? BucketName = null;
/// <summary>
/// 数据获取范围时间
/// </summary>
public string? RangeTime = null;
/// <summary>
/// 数据获取间隔时间,单位秒,默认五分钟
/// </summary>
public int Interval = 60 * 5;
public string DisplayName = "Unknown";
/// <summary>
/// 该配置是否为全局配置
/// </summary>
private const bool OVERALL_SITUATION = true;
public bool SaveConfig(string configFilePath) {
ConfigurationTools configurationTools = new ConfigurationTools(configFilePath);
string fullConfigName = OVERALL_SITUATION ? CONFIG_NAME : DisplayName + "_" + CONFIG_NAME;
configurationTools.AddSetting(fullConfigName, "UseAccompanyingAssistant", UseAccompanyingAssistant.ToString());
configurationTools.AddSetting(fullConfigName, "BucketName", BucketName ?? string.Empty);
configurationTools.AddSetting(fullConfigName, "RangeTime", RangeTime ?? string.Empty);
configurationTools.AddSetting(fullConfigName, "Interval", Interval.ToString());
configurationTools.SaveSettings();
return true;
}
public OtherConfigObjects LoadConfig(string configFilePath, string name) {
try {
ConfigurationTools configurationTools = new ConfigurationTools(configFilePath);
string fullConfigName = OVERALL_SITUATION ? CONFIG_NAME : name + "_" + CONFIG_NAME;
UseAccompanyingAssistant = Convert.ToBoolean(configurationTools.GetSetting(fullConfigName, "UseAccompanyingAssistant"));
BucketName = configurationTools.GetSetting(fullConfigName, "BucketName");
RangeTime = configurationTools.GetSetting(fullConfigName, "RangeTime");
Interval = Convert.ToInt32(configurationTools.GetSetting(fullConfigName, "Interval"));
return this;
} catch (Exception) {
return this;
}
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AudioWallpaper.Entity {
public class UIModification {
public string? ControlName { get; set; }
public string? PropertyName { get; set; }
public object? Value { get; set; }
}
}

View File

@@ -62,7 +62,7 @@ namespace AudioWallpaper {
} }
return; return;
} }
Console.WriteLine("重新加载配置");
GeneralConfigurationObjects generalConfigurationObjects = configurationObject.GeneralConfigurationObjects; GeneralConfigurationObjects generalConfigurationObjects = configurationObject.GeneralConfigurationObjects;
MainWindowReLoadConfig(generalConfigurationObjects); MainWindowReLoadConfig(generalConfigurationObjects);
@@ -141,7 +141,7 @@ namespace AudioWallpaper {
} }
public void LoadWindowSet() { public void LoadWindowSet() {
//加载窗体设置 //加载窗体设置
//Console.WriteLine("窗体管理器正在初始化"); Console.WriteLine("窗体管理器正在初始化");
ReCreateWindow(); ReCreateWindow();
} }

View File

@@ -1,4 +1,6 @@
using AudioWallpaper.ActivityWatch;
using AudioWallpaper.Entity; using AudioWallpaper.Entity;
using AudioWallpaper.Tools;
using LibAudioVisualizer; using LibAudioVisualizer;
using NAudio.CoreAudioApi; using NAudio.CoreAudioApi;
using NAudio.Wave; using NAudio.Wave;
@@ -26,6 +28,8 @@ namespace AudioWallpaper {
public delegate void FullScreenDetected(bool status); public delegate void FullScreenDetected(bool status);
public event FullScreenDetected FullScreenDetectedEvent; public event FullScreenDetected FullScreenDetectedEvent;
private string DrawText = null;
public MainWindow(GeneralConfigurationObjects configuration) { public MainWindow(GeneralConfigurationObjects configuration) {
appBarManager = new AppBarManager(Handle); appBarManager = new AppBarManager(Handle);
generalConfigurationObjects = configuration; generalConfigurationObjects = configuration;
@@ -43,6 +47,8 @@ namespace AudioWallpaper {
Init(); Init();
Win32.SetParent(this.Handle, programIntPtr);//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E1B4A5>DisplayChange<67>¼<EFBFBD><C2BC><EFBFBD><EFBFBD>е<EFBFBD><D0B5><EFBFBD>ѧ Win32.SetParent(this.Handle, programIntPtr);//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><E1B4A5>DisplayChange<67>¼<EFBFBD><C2BC><EFBFBD><EFBFBD>е<EFBFBD><D0B5><EFBFBD>ѧ
} }
/// <summary> /// <summary>
/// <20><>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ /// <20><>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ
/// </summary> /// </summary>
@@ -460,6 +466,68 @@ namespace AudioWallpaper {
g.FillRectangle(leftB, left); g.FillRectangle(leftB, left);
g.FillRectangle(rightB, right); g.FillRectangle(rightB, right);
} }
/// <summary>
/// <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>ı<EFBFBD><C4B1><EFBFBD>ָ<EFBFBD><D6B8>λ<EFBFBD><CEBB>
/// </summary>
/// <param name="g"><3E><><EFBFBD><EFBFBD></param>
/// <param name="text"><3E>ı<EFBFBD></param>
/// <param name="x">x<><78></param>
/// <param name="y">y<><79></param>
/// <param name="isVertical"><3E>Ƿ<EFBFBD><C7B7><EFBFBD>ֱ</param>
/// <param name="wordWrap"><3E>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="style"><3E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ</param>
/// <param name="fontName"><3E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="fontSize"><3E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>С</param>
/// <param name="textColor"><3E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ</param>
private void DrawingText(Graphics g, string text, int x, int y,
bool isVertical = false, bool wordWrap = false,
FontStyle style = FontStyle.Bold,
string fontName = "Arial", float fontSize = 16,
Color? textColor = null) {
if (string.IsNullOrEmpty(text)) text = string.Empty;
textColor ??= Color.White; // Ĭ<>ϰ<EFBFBD>ɫ<EFBFBD>ı<EFBFBD>
using Font font = new Font(fontName, fontSize, style);
using Brush brush = new SolidBrush(textColor.Value);
// <20><><EFBFBD><EFBFBD><EFBFBD>ı<EFBFBD><C4B1><EFBFBD><EFBFBD><EFBFBD>
if (!isVertical) {
if (wordWrap) {
// <20>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD>ģʽ<C4A3><CABD>ʹ<EFBFBD>þ<EFBFBD><C3BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
TextFormatFlags flags = TextFormatFlags.WordBreak;
TextRenderer.DrawText(g, text, font, new Rectangle(x, y, 600, 600), textColor.Value, flags);
} else {
// <20><><EFBFBD><EFBFBD>ģʽ
g.DrawString(text, font, brush, x, y);
}
}
// <20><><EFBFBD><EFBFBD><EFBFBD>ı<EFBFBD><C4B1><EFBFBD><EFBFBD><EFBFBD>
else {
float charHeight = font.Height;
float charSpacing = charHeight * 0.2f; // <20>ַ<EFBFBD><D6B7><EFBFBD><EFBFBD><EFBFBD>
float currentY = y;
float maxWidth = 0;
// Ԥ<><D4A4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ַ<EFBFBD><D6B7><EFBFBD><EFBFBD><EFBFBD>
foreach (char c in text) {
float charWidth = g.MeasureString(c.ToString(), font).Width;
if (charWidth > maxWidth) maxWidth = charWidth;
}
// <20><><EFBFBD>ַ<EFBFBD><D6B7><EFBFBD><EFBFBD><EFBFBD>
foreach (char c in text) {
// <20>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD>д<EFBFBD><D0B4><EFBFBD>
if (wordWrap && currentY > g.VisibleClipBounds.Bottom - charHeight) {
x += (int)maxWidth + 2; // <20><><EFBFBD><EFBFBD>
currentY = y;
}
// <20><><EFBFBD>Ƶ<EFBFBD><C6B5><EFBFBD><EFBFBD>ַ<EFBFBD>
g.DrawString(c.ToString(), font, brush, x, currentY);
currentY += charHeight + charSpacing;
}
}
}
int colorIndex = 0; int colorIndex = 0;
double rotation = 0; double rotation = 0;
@@ -508,6 +576,7 @@ namespace AudioWallpaper {
if (generalConfigurationObjects.WavyLine) { if (generalConfigurationObjects.WavyLine) {
DrawCurve(g, pen, visualizer.SampleData, visualizer.SampleData.Length, drawingPanel.Width, 0, drawingPanel.Height / 2, MathF.Min(drawingPanel.Height / 10, 100)); DrawCurve(g, pen, visualizer.SampleData, visualizer.SampleData.Length, drawingPanel.Width, 0, drawingPanel.Height / 2, MathF.Min(drawingPanel.Height / 10, 100));
} }
DrawingText(g, DrawText, 700, 50, false, true);
buffer.Render(); buffer.Render();
oldBuffer = buffer; // <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB> buffer (֮<><D6AE><EFBFBD>Բ<EFBFBD>ȫ<EFBFBD><C8AB>ֻʹ<D6BB><CAB9>һ<EFBFBD><D2BB> Buffer <20><><EFBFBD><EFBFBD>Ϊ,,, <20>û<EFBFBD><C3BB><EFBFBD><EFBFBD>ܵ<EFBFBD><DCB5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD>С, <20><><EFBFBD><EFBFBD>ÿһ֡<D2BB><D6A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӧ) oldBuffer = buffer; // <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB> buffer (֮<><D6AE><EFBFBD>Բ<EFBFBD>ȫ<EFBFBD><C8AB>ֻʹ<D6BB><CAB9>һ<EFBFBD><D2BB> Buffer <20><><EFBFBD><EFBFBD>Ϊ,,, <20>û<EFBFBD><C3BB><EFBFBD><EFBFBD>ܵ<EFBFBD><DCB5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڴ<EFBFBD>С, <20><><EFBFBD><EFBFBD>ÿһ֡<D2BB><D6A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӧ)
} }

View File

@@ -0,0 +1,101 @@
using AudioWallpaper.Tools;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AudioWallpaper.OtherWallpaper {
public class ExecutableWallpaper {
public IntPtr Handle = IntPtr.Zero;
public IntPtr MainWindowHandle = IntPtr.Zero;
public String Url;
public Process WallpaperProcess;
private nint programIntPtr;
public ExecutableWallpaper(String url) {
this.Url = url;
}
public Process Start() {
WallpaperProcess = new Process();
WallpaperProcess.StartInfo = new ProcessStartInfo(Url);
WallpaperProcess.Start();
return WallpaperProcess;
}
public void ReShow(int x, int y, int w, int h) {
}
public IntPtr GetHandle() {
if (Handle == IntPtr.Zero) {
//获取Handle
if (WallpaperProcess == null) {
throw new NullReferenceException("WallpaperProcess Is Null Or Close");
}
Handle = WallpaperProcess.Handle;
Console.WriteLine(Handle);
} else {
return Handle;
}
return MainWindowHandle;
}
public IntPtr GetMainWindowHandle() {
if (MainWindowHandle == IntPtr.Zero) {
if (WallpaperProcess == null || WallpaperProcess.HasExited) {
throw new NullReferenceException("WallpaperProcess Is Null Or Close");
}
MainWindowHandle = WallpaperProcess.MainWindowHandle;
} else {
return MainWindowHandle;
}
Console.WriteLine(MainWindowHandle);
return MainWindowHandle;
}
public void SetShowPatternWallpaper() {
GetMainWindowHandle();
GetHandle();
Console.WriteLine("请输入句柄");
int temph = int.Parse(Console.ReadLine());
Console.WriteLine("..........");
Console.WriteLine(temph);
// 通过类名查找一个窗口,返回窗口句柄。
programIntPtr = Win32.FindWindow("Progman", null);
// 窗口句柄有效
if (programIntPtr != IntPtr.Zero) {
IntPtr result = IntPtr.Zero;
// 向 Program Manager 窗口发送 0x52c 的一个消息超时设置为0x3e81秒
Win32.SendMessageTimeout(programIntPtr, 0x52c, IntPtr.Zero, IntPtr.Zero, 0, 0x3e8, result);
// 遍历顶级窗口
Win32.EnumWindows((hwnd, lParam) => {
// 找到包含 SHELLDLL_DefView 这个窗口句柄的 WorkerW
if (Win32.FindWindowEx(hwnd, IntPtr.Zero, "SHELLDLL_DefView", null) != IntPtr.Zero) {
// 找到当前 WorkerW 窗口的,后一个 WorkerW 窗口。
IntPtr tempHwnd = Win32.FindWindowEx(IntPtr.Zero, hwnd, "WorkerW", null);
// 隐藏这个窗口
Win32.ShowWindow(tempHwnd, 0);
}
return true;
}, IntPtr.Zero);
}
Win32.SetParent(temph, programIntPtr);//这里会触发DisplayChange事件有点玄学
Win32.SetWindowPos(temph, IntPtr.Zero, 1920, 0, 1920, 1080, WindowPositionConstants.SWP_SHOWWINDOW);
}
/// <summary>
/// 结束壁纸
/// </summary>
/// <returns>true 结束成功 flase 结束失败</returns>
public bool Close() {
if (WallpaperProcess != null && !WallpaperProcess.HasExited) {
WallpaperProcess.Kill();
} else {
return true;
}
return WallpaperProcess.HasExited;
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AudioWallpaper.OtherWallpaper {
public class OtherWallpaperManager {
public List<ExecutableWallpaper> ExecutableWallpapers = new List<ExecutableWallpaper>();
public OtherWallpaperManager() {
ExecutableWallpaper executableWallpaper = new ExecutableWallpaper("C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe");
executableWallpaper.Start();
executableWallpaper.SetShowPatternWallpaper();
}
}
}

View File

@@ -1,3 +1,7 @@
using AccompanyingAssistant;
using AudioWallpaper.IPC;
using AudioWallpaper.OtherWallpaper;
using AudioWallpaper.WebWallpaper;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace AudioWallpaper { namespace AudioWallpaper {
@@ -15,6 +19,14 @@ namespace AudioWallpaper {
public static extern bool AllocConsole(); public static extern bool AllocConsole();
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
public static extern bool FreeConsole();//<2F>ͷŹ<CDB7><C5B9><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD>̨<EFBFBD><CCA8><EFBFBD><EFBFBD> public static extern bool FreeConsole();//<2F>ͷŹ<CDB7><C5B9><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD>̨<EFBFBD><CCA8><EFBFBD><EFBFBD>
private static SSOManager _ssoManager = new SSOManager();
//public static SSOManager SSOM { get; private set; };
public static SSOManager SSOM {
get {
return _ssoManager;
}
}
public static WindowManager WindowManager { get; private set; } = new WindowManager();
[STAThread] [STAThread]
static void Main(String[] args) { static void Main(String[] args) {
@@ -24,6 +36,9 @@ namespace AudioWallpaper {
bool DoesTheGuardianExist = false; bool DoesTheGuardianExist = false;
// <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB>Ψһ<CEA8>Ļ<EFBFBD><C4BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƣ<EFBFBD>ȷ<EFBFBD><C8B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ψһ<CEA8><D2BB> // <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB>Ψһ<CEA8>Ļ<EFBFBD><C4BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƣ<EFBFBD>ȷ<EFBFBD><C8B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ψһ<CEA8><D2BB>
using (Mutex mutex = new Mutex(true, "{C8F8E5D9-6F05-4F92-BE10-DF7A4F5F97FB}", out createdNew)) { using (Mutex mutex = new Mutex(true, "{C8F8E5D9-6F05-4F92-BE10-DF7A4F5F97FB}", out createdNew)) {
#if DEBUG
createdNew = true;
#endif
if (createdNew) { if (createdNew) {
//<2F>ж<EFBFBD><D0B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><C7B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> //<2F>ж<EFBFBD><D0B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><C7B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
//if (args.Length < 1) { //if (args.Length < 1) {
@@ -55,9 +70,18 @@ namespace AudioWallpaper {
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
ApplicationConfiguration.Initialize(); ApplicationConfiguration.Initialize();
Task.Run(() => SSOM.Initialize()); // Run SSOManager initialization in a separate task
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> //<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
FormManager formManager = new FormManager(); FormManager formManager = new FormManager();
//<2F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֹ<EFBFBD><D6B9><EFBFBD><EFBFBD><EFBFBD>
AccAssManage accAssManage = new AccAssManage();
IPCClientExample.RunAsync(); // <20><><EFBFBD><EFBFBD>IPC<50>ͻ<EFBFBD><CDBB><EFBFBD>ʾ<EFBFBD><CABE>
//WebWallpaperManager webWallpaperManager = new WebWallpaperManager("https://blog.ysit.top");
//OtherWallpaperManager otherWallpaperManager = new OtherWallpaperManager();
Application.Run(); Application.Run();
//<2F>ͷſ<CDB7><C5BF><EFBFBD>̨ //<2F>ͷſ<CDB7><C5BF><EFBFBD>̨
FreeConsole(); FreeConsole();

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Formats.Asn1.AsnWriter;
namespace AudioWallpaper.SSO {
public class AuthConfig {
public String clientId;
public Uri redirectUri;
public String scope;
public Uri discoveryUri;
public String authorityUrl = "http://localhost:8080/realms/ysit";
public String tokenEndpoint = "http://localhost:8080/realms/ysit/protocol/openid-connect/token";
public String authorizationEndpoint = "http://localhost:8080/realms/ysit/protocol/openid-connect/auth";
public String revocationEndpoint = "http://localhost:8080/realms/ysit/protocol/openid-connect/revoke";
public String userInfoEndpoint = "http://localhost:8080/realms/ysit/protocol/openid-connect/userinfo";
//访问令牌失效后最大刷新次数
public int maxRefreshCount = 3;
//获取用户信息失败后最大尝试次数
public int maxGetUserInfoCount = 3;
public AuthConfig() {
this.clientId = "yiconnect";
this.redirectUri = new Uri("http://127.0.0.1:5000/");
this.scope = "openid email profile offline_access";
this.discoveryUri = new Uri("http://localhost:8080/realms/ysit/.well-known/openid-configuration");
}
public AuthConfig(String clientId,Uri redirectUri) {
this.clientId = clientId;
this.redirectUri = redirectUri;
this.scope = "openid email profile offline_access";
this.discoveryUri = new Uri("http://localhost:8080/realms/ysit/.well-known/openid-configuration");
}
}
}

View File

@@ -0,0 +1,280 @@
using IdentityModel.OidcClient;
using IdentityModel.OidcClient.Results;
using System.Net.Http.Headers;
using System.Text.Json;
using AudioWallpaper.Sso.Exceptions;
namespace AudioWallpaper.SSO {
public class AuthManager {
private OidcClient _oidcClient;
private readonly AuthConfig authConfig = new();
public static readonly AuthManager Instance = new AuthManager();
private AuthStatus authStatus = AuthStatus.Unknown;
//登录成功回调
public Action<TokenSet>? OnLoginSuccess;
//登录失败回调
public Action<string>? OnLoginFailed;
//刷新令牌成功回调
public Action<TokenSet>? OnRefreshTokenSuccess;
//刷新令牌失败回调
public Action<string>? OnRefreshTokenFailed;
//获取用户信息成功回调
public Action<UserInfoSet>? OnGetUserInfoSuccess;
//获取用户信息失败回调
public Action<string>? OnGetUserInfoFailed;
//撤销令牌成功回调
public Action? OnRevokeTokenSuccess;
//撤销令牌失败回调
public Action<string>? OnRevokeTokenFailed;
//注销登录成功回调
public Action? OnLogoutSuccess;
//注销登录失败回调
public Action<string>? OnLogoutFailed;
//登录状态改变回调
public Action<AuthStatus> OnAuthStatusChanged;
public AuthStatus AuthStatus {
get => authStatus; set {
authStatus = value;
OnAuthStatusChanged?.Invoke(value);
}
}
public AuthManager() {
var options = new OidcClientOptions {
Authority = authConfig.authorityUrl,
ClientId = authConfig.clientId,
RedirectUri = authConfig.redirectUri.ToString(),
Scope = authConfig.scope,
Policy = new Policy {
RequireAccessTokenHash = false,
},
LoadProfile = true
};
_oidcClient = new OidcClient(options);
Console.WriteLine("OIDC 客户端初始化");
}
public async void Login(Form? from = null) {
if (from == null) {
OnLoginFailed?.Invoke("登录窗口未初始化");
return;
}
if (_oidcClient == null) {
OnLoginFailed?.Invoke("OIDC客户端未初始化");
return;
}
if (AuthStatus == AuthStatus.Success) {
OnLoginFailed?.Invoke("已登录,请先注销");
return;
}
AuthStatus = AuthStatus.InProgress;
_oidcClient.Options.Browser = new WebView2Browser(from);
try {
var result = await _oidcClient.LoginAsync(new LoginRequest());
if (result.IsError) {
AuthStatus = AuthStatus.Failed;
OnLoginFailed?.Invoke(result.Error);
} else {
TokenSet tokenSet = new(
result.AccessToken,
result.RefreshToken,
result.IdentityToken
);
SsoException? exception = CheckToken(tokenSet);
if (exception != null) {
throw exception;
}
AuthStatus = AuthStatus.Success;
OnLoginSuccess?.Invoke(tokenSet);
}
} catch (Exception ex) {
AuthStatus = AuthStatus.Failed;
OnLoginFailed?.Invoke("登录异常:" + ex.Message);
}
}
/// <summary>
/// 获取用户信息
/// </summary>
/// <param name="accessToken">访问令牌</param>
/// <returns>用户信息</returns>
public UserInfoSet? GetUserInfo(string? accessToken) {
try {
if (string.IsNullOrWhiteSpace(accessToken)) {
throw new SsoException("AccessToken 为空");
}
using var httpClient = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, authConfig.userInfoEndpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = httpClient.Send(request);
if (!response.IsSuccessStatusCode) {
OnGetUserInfoFailed?.Invoke($"获取用户信息失败,状态码: {response.StatusCode}");
return null;
}
var content = response.Content.ReadAsStringAsync().Result;
var userInfoSet = JsonSerializer.Deserialize<UserInfoSet>(content);
if (userInfoSet == null) {
throw new SsoException("获取用户信息失败,内容解析失败");
}
OnGetUserInfoSuccess?.Invoke(userInfoSet);
return userInfoSet;
} catch (Exception ex) {
OnGetUserInfoFailed?.Invoke($"获取用户信息异常: {ex.Message}");
return null;
}
}
/// <summary>
/// 刷新令牌
/// </summary>
/// <param name="refreshToken">刷新令牌</param>
/// <returns>令牌信息</returns>
public async Task<TokenSet?> RefreshToken(string? refreshToken) {
try {
if (_oidcClient == null) {
OnRefreshTokenFailed?.Invoke("OidcClient 未初始化");
return null;
}
if (string.IsNullOrWhiteSpace(refreshToken)) {
OnRefreshTokenFailed?.Invoke("刷新令牌失败refreshToken 不能为空");
return null;
}
// 调用 OidcClient 的刷新令牌方法
RefreshTokenResult result = await _oidcClient.RefreshTokenAsync(refreshToken);
if (result.IsError) {
OnRefreshTokenFailed?.Invoke($"刷新令牌失败: {result.Error}");
return null;
}
var tokenSet = new TokenSet(
result.AccessToken,
result.RefreshToken,
result.IdentityToken
);
SsoException? exception = CheckToken(tokenSet);
if (exception != null) {
throw exception;
}
AuthStatus = AuthStatus.Success;
OnRefreshTokenSuccess?.Invoke(tokenSet);
return tokenSet;
} catch (Exception ex) {
OnRefreshTokenFailed?.Invoke($"刷新令牌异常: {ex.Message}");
return null;
}
}
/// <summary>
/// 撤销令牌
/// </summary>
/// <param name="token">token</param>
/// <param name="tokenTypeHint">token id</param>
public async void RevokeToken(string? token, string? tokenTypeHint, bool form = false) {
if (string.IsNullOrWhiteSpace(token)) {
OnRevokeTokenFailed?.Invoke("撤销令牌失败token 不能为空");
if (form) {
OnLogoutFailed?.Invoke("token 不能为空");
}
return;
}
if (string.IsNullOrWhiteSpace(tokenTypeHint)) {
OnRevokeTokenFailed?.Invoke("撤销令牌失败tokenTypeHint 不能为空");
if (form) {
OnLogoutFailed?.Invoke("tokenTypeHint 不能为空");
}
return;
}
try {
using var httpClient = new HttpClient();
var parameters = new Dictionary<string, string>
{
{ "token", token }
};
if (!string.IsNullOrWhiteSpace(tokenTypeHint)) {
parameters.Add("token_type_hint", tokenTypeHint);
}
parameters.Add("client_id", authConfig.clientId);
// 如果有 client_secret说明是 confidential client
var clientSecretProperty = authConfig.GetType().GetProperty("clientSecret");
if (clientSecretProperty != null) {
var clientSecret = clientSecretProperty.GetValue(authConfig) as string;
if (!string.IsNullOrWhiteSpace(clientSecret)) {
parameters.Add("client_secret", clientSecret);
}
}
var content = new FormUrlEncodedContent(parameters);
var response = await httpClient.PostAsync(authConfig.revocationEndpoint, content);
var respContent = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode) {
// 撤销令牌成功,修改登录状态
AuthStatus = AuthStatus.LoginOut;
OnRevokeTokenSuccess?.Invoke();
if (form) {
OnLogoutSuccess?.Invoke();
}
return;
} else {
OnRevokeTokenFailed?.Invoke($"撤销令牌失败,状态码: {response.StatusCode},内容: {respContent}");
if (form) {
OnLogoutFailed?.Invoke($"撤销令牌失败,状态码: {response.StatusCode},内容: {respContent}");
}
return;
}
} catch (Exception ex) {
OnRevokeTokenFailed?.Invoke($"撤销令牌异常: {ex.Message}");
if (form) {
OnLogoutFailed?.Invoke($"撤销令牌异常: {ex.Message}");
}
}
}
/// <summary>
/// 注销登录
/// </summary>
public void Logout(TokenSet? set) {
SsoException? exception = CheckToken(set);
if (exception != null) {
OnLogoutFailed?.Invoke(exception.Message);
return;
}
#pragma warning disable CS8602 // 解引用可能出现空引用。
RevokeToken(set.RefreshToken, set.IdToken, true);
#pragma warning restore CS8602 // 解引用可能出现空引用。
//TokenManager
}
/// <summary>
/// 检查 TokenSet 是否有效
/// </summary>
/// <param name="tokenSet">TokenSet 对象</param>
/// <returns>如果有效返回Null如果无效返回对应的Exception</returns>
public static SsoException? CheckToken(TokenSet? tokenSet) {
if (tokenSet == null) {
return new SsoException("TokenSet 为空");
}
if (string.IsNullOrWhiteSpace(tokenSet.AccessToken)) {
return new SsoException("AccessToken 为空");
}
if (string.IsNullOrWhiteSpace(tokenSet.RefreshToken)) {
return new SsoException("RefreshToken 为空");
}
if (string.IsNullOrWhiteSpace(tokenSet.IdToken)) {
return new SsoException("IdToken 为空");
}
return null;
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AudioWallpaper.SSO {
public enum AuthStatus : int {
Unknown = 0x0000,//未知,一般是没有登录或程序刚启动
InProgress = 0x0001,//正在进行中,表示正在进行登录操作
Success = 0x0002,//登录成功
Cancelled = 0x0003,//用户取消登录
Failed = 0x0004,//登录失败
Timeout = 0x0005,//登录超时
LockedOut = 0x0006,//用户被锁定
RequiresVerify = 0x0007,//需要验证
LoginOut = 0x0100,//已经退出登录
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AudioWallpaper.Sso.Exceptions {
public class SsoException:Exception {
public SsoException() {
}
public SsoException(string message) : base(message) {
}
public SsoException(string message, Exception innerException) : base(message, innerException) {
}
public SsoException(string message, string? errorCode) : base(message) {
ErrorCode = errorCode;
}
public string? ErrorCode { get; set; } = null;
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AudioWallpaper.SSO {
public class LoginMeta {
public bool IsLoggedIn { get; set; }
public DateTime LastLoginTime { get; set; } = DateTime.Now;
public UserInfoSet? UserInfo { get; set; }
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace AudioWallpaper.SSO {
public class LoopbackHttpListener {
private readonly string _url;
private readonly string _redirectUri;
public LoopbackHttpListener(int port, string redirectUri) {
_redirectUri = redirectUri;
_url = $"http://127.0.0.1:{port}/";
}
public async Task<string> WaitForCallbackAsync() {
using var listener = new HttpListener();
listener.Prefixes.Add(_url);
listener.Start();
var context = await listener.GetContextAsync();
var request = context.Request;
var response = context.Response;
response.Headers.Add("Content-Type", "text/html; charset=UTF-8");
string responseString = "<html><body><h1>登录成功!请返回应用程序。</h1></body></html>";
var buffer = Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
response.OutputStream.Write(buffer, 0, buffer.Length);
response.OutputStream.Close();
listener.Stop();
return request.Url.ToString();
}
}
}

View File

@@ -0,0 +1,44 @@
using IdentityModel.OidcClient.Browser;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AudioWallpaper.SSO {
public class SystemBrowser : IBrowser {
private readonly int _port;
public SystemBrowser(int port) {
_port = port;
}
public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default) {
var listener = new LoopbackHttpListener(_port, options.EndUrl);
Process.Start(new ProcessStartInfo {
FileName = options.StartUrl,
UseShellExecute = true
});
try {
var result = await listener.WaitForCallbackAsync();
return new BrowserResult {
Response = result,
ResultType = BrowserResultType.Success
};
} catch (TaskCanceledException) {
return new BrowserResult {
ResultType = BrowserResultType.Timeout,
Error = "Login timeout"
};
} catch (Exception ex) {
return new BrowserResult {
ResultType = BrowserResultType.UnknownError,
Error = ex.Message
};
}
}
}
}

View File

@@ -0,0 +1,160 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
namespace AudioWallpaper.SSO {
public interface ITokenStore {
void Save(string tokenName, string token);
string? Get(string tokenName);
void Clear(string tokenName);
}
public class EncryptedFileTokenStore : ITokenStore {
private readonly string _storagePath;
private readonly object _lock = new();
public EncryptedFileTokenStore(string storageDirectory) {
_storagePath = storageDirectory;
Directory.CreateDirectory(_storagePath);
}
private string GetTokenFilePath(string tokenName) =>
Path.Combine(_storagePath, $"{tokenName}.token");
public void Save(string tokenName, string token) {
var data = Encoding.UTF8.GetBytes(token);
var encrypted = ProtectedData.Protect(data, null, DataProtectionScope.CurrentUser);
lock (_lock) {
File.WriteAllBytes(GetTokenFilePath(tokenName), encrypted);
}
}
public string? Get(string tokenName) {
var path = GetTokenFilePath(tokenName);
if (!File.Exists(path))
return null;
try {
lock (_lock) {
var encrypted = File.ReadAllBytes(path);
var decrypted = ProtectedData.Unprotect(encrypted, null, DataProtectionScope.CurrentUser);
return Encoding.UTF8.GetString(decrypted);
}
} catch {
return null; // 可选:记录日志
}
}
public void Clear(string tokenName) {
var path = GetTokenFilePath(tokenName);
lock (_lock) {
if (File.Exists(path))
File.Delete(path);
}
}
}
public class TokenManager {
private readonly ITokenStore _store;
private const string AccessTokenKey = "access_token";
private const string RefreshTokenKey = "refresh_token";
private const string IdTokenKey = "id_token";
private const string LoginMetaKey = "login_state";
public TokenManager(ITokenStore store) {
_store = store ?? throw new ArgumentNullException(nameof(store));
}
// 保存 TokenSet
public void SaveToken(TokenSet tokens) {
SaveToken(tokens, null);
}
public void SaveToken(TokenSet tokens, UserInfoSet? userInfoSet) {
if (tokens == null) throw new ArgumentNullException(nameof(tokens));
if (string.IsNullOrWhiteSpace(tokens.AccessToken))
throw new ArgumentException("Access token is required.");
if (string.IsNullOrWhiteSpace(tokens.RefreshToken))
throw new ArgumentException("Refresh token is required.");
if (string.IsNullOrWhiteSpace(tokens.IdToken))
throw new ArgumentException("ID token is required.");
_store.Save(AccessTokenKey, tokens.AccessToken);
_store.Save(RefreshTokenKey, tokens.RefreshToken);
_store.Save(IdTokenKey, tokens.IdToken);
if (userInfoSet != null) {
LoginMeta loginMeta = new LoginMeta {
IsLoggedIn = true,
UserInfo = userInfoSet
};
SaveLoginMeta(loginMeta);
}
}
public void SaveLoginMeta(LoginMeta? loginMeta) {
if (loginMeta == null)
throw new ArgumentNullException(nameof(loginMeta));
string loginMetaJson = JsonSerializer.Serialize(loginMeta);
_store.Save(LoginMetaKey, loginMetaJson);
}
// 保存单个 token
public void SaveToken(string name, string token) {
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Token name is required.");
if (string.IsNullOrWhiteSpace(token)) throw new ArgumentException("Token value is required.");
_store.Save(name, token);
}
// 获取 TokenSet
public TokenSet? GetToken() {
var accessToken = _store.Get(AccessTokenKey);
var refreshToken = _store.Get(RefreshTokenKey);
var idToken = _store.Get(IdTokenKey);
if (string.IsNullOrWhiteSpace(accessToken) && string.IsNullOrWhiteSpace(refreshToken) && string.IsNullOrWhiteSpace(idToken))
return null;
return new TokenSet(accessToken ?? string.Empty, refreshToken ?? string.Empty, idToken ?? string.Empty);
}
// 获取指定 token
public string? GetToken(string name) {
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Token name is required.");
return _store.Get(name);
}
// 获取登录状态
public LoginMeta? GetLoginMeta() {
var loginMetaJson = _store.Get(LoginMetaKey);
if (string.IsNullOrWhiteSpace(loginMetaJson))
return null;
try {
return JsonSerializer.Deserialize<LoginMeta>(loginMetaJson);
} catch (JsonException) {
return null; // 可选:记录日志
}
}
// 清除全部 token
public void ClearToken() {
_store.Clear(AccessTokenKey);
_store.Clear(RefreshTokenKey);
_store.Clear(IdTokenKey);
_store.Clear(LoginMetaKey);
}
// 清除指定 token
public void ClearToken(string name) {
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Token name is required.");
_store.Clear(name);
}
}
}

Some files were not shown because too many files have changed in this diff Show More