del /AudioWallpaper.1.2.1.nupkg
5
.gitattributes
vendored
@@ -2,7 +2,6 @@
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
@@ -11,7 +10,6 @@
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
@@ -34,7 +32,6 @@
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
@@ -43,7 +40,6 @@
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
@@ -61,3 +57,4 @@
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
AudioVisualizer/bin/Release/AudioWallpaper.1.2.1.nupkg filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
4
.gitignore
vendored
@@ -360,4 +360,6 @@ MigrationBackup/
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
FodyWeavers.xsd
|
||||
|
||||
**/*.nupkg
|
||||
188
AccompanyingAssistant/AccompanyingAssistant.csproj
Normal 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>
|
||||
130
AccompanyingAssistant/BubbleForm.cs
Normal 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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
120
AccompanyingAssistant/BubbleForm.resx
Normal 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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
342
AccompanyingAssistant/MainForm.cs
Normal 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>");
|
||||
}
|
||||
}
|
||||
}
|
||||
120
AccompanyingAssistant/MainForm.resx
Normal 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>
|
||||
14
AccompanyingAssistant/Program.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
67
AccompanyingAssistant/Win32.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
51
AccompanyingAssistant/WindowManager.cs
Normal 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
AccompanyingAssistant/shell/surface0000.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
AccompanyingAssistant/shell/surface0000_2001.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
AccompanyingAssistant/shell/surface0000_2002.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
AccompanyingAssistant/shell/surface0000_2004.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
AccompanyingAssistant/shell/surface0001.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
AccompanyingAssistant/shell/surface0002.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
AccompanyingAssistant/shell/surface0003.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
AccompanyingAssistant/shell/surface0004.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
AccompanyingAssistant/shell/surface0005.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
AccompanyingAssistant/shell/surface0006.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
AccompanyingAssistant/shell/surface0007.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
AccompanyingAssistant/shell/surface0008.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
AccompanyingAssistant/shell/surface0009.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
AccompanyingAssistant/shell/surface0010.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
AccompanyingAssistant/shell/surface0020.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
AccompanyingAssistant/shell/surface0025.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
AccompanyingAssistant/shell/surface0029.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
AccompanyingAssistant/shell/surface0030.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
AccompanyingAssistant/shell/surface0032.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
AccompanyingAssistant/shell/surface0033.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
AccompanyingAssistant/shell/surface0034.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
AccompanyingAssistant/shell/surface0035.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
AccompanyingAssistant/shell/surface0036.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
AccompanyingAssistant/shell/surface0037.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
AccompanyingAssistant/shell/surface0038.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
AccompanyingAssistant/shell/surface0042.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
AccompanyingAssistant/shell/surface0043.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
AccompanyingAssistant/shell/surface0044.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
AccompanyingAssistant/shell/surface0091.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
AccompanyingAssistant/shell/surface0092.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
AccompanyingAssistant/shell/surface0093.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
AccompanyingAssistant/shell/surface1001.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
AccompanyingAssistant/shell/surface1002.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
AccompanyingAssistant/shell/surface1003.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
AccompanyingAssistant/shell/surface1004.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
AccompanyingAssistant/shell/surface2001.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
AccompanyingAssistant/shell/surface2002.png
Normal file
|
After Width: | Height: | Size: 249 B |
BIN
AccompanyingAssistant/shell/surface2003.png
Normal file
|
After Width: | Height: | Size: 305 B |
BIN
AccompanyingAssistant/shell/surface2004.png
Normal file
|
After Width: | Height: | Size: 329 B |
BIN
AccompanyingAssistant/shell/surface3000.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
AccompanyingAssistant/shell/surface3001.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
AccompanyingAssistant/shell/surface3002.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
AccompanyingAssistant/shell/surface3003.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
AccompanyingAssistant/shell/surface3004.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
AccompanyingAssistant/shell/surface3005.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
AccompanyingAssistant/shell/surface3006.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
AccompanyingAssistant/shell/surface3007.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
AccompanyingAssistant/shell/surface3100.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
AccompanyingAssistant/shell/surface3101.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
AccompanyingAssistant/shell/surface3102.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
AccompanyingAssistant/shell/surface3103.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
AccompanyingAssistant/shell/surface3104.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
AccompanyingAssistant/shell/surface3200.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
AccompanyingAssistant/shell/surface3201.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
AccompanyingAssistant/shell/surface3202.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
AccompanyingAssistant/shell/surface3203.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
AccompanyingAssistant/shell/surface3204.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
AccompanyingAssistant/shell/surface9999.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
@@ -25,78 +25,126 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioWallpaperManager", "Au
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioWallpaper", "AudioVisualizer\AudioWallpaper.csproj", "{4DFEE2A6-2CE7-4A35-A2A9-0EF33D32F6FE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccompanyingAssistant", "AccompanyingAssistant\AccompanyingAssistant.csproj", "{9C088677-7177-4D46-801F-426575401B1B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{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|x64.ActiveCfg = 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.Build.0 = Release|Any CPU
|
||||
{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|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.Build.0 = Debug|Any CPU
|
||||
{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|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.Build.0 = Release|Any CPU
|
||||
{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|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.Build.0 = Debug|Any CPU
|
||||
{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|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.Build.0 = Release|Any CPU
|
||||
{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|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.Build.0 = Debug|Any CPU
|
||||
{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|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.Build.0 = Release|Any CPU
|
||||
{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|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.Build.0 = Debug|Any CPU
|
||||
{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|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.Build.0 = Release|Any CPU
|
||||
{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|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.Build.0 = Debug|Any CPU
|
||||
{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|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.Build.0 = Release|Any CPU
|
||||
{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|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.Build.0 = Debug|Any CPU
|
||||
{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|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.Build.0 = Release|Any CPU
|
||||
{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|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.Build.0 = Debug|Any CPU
|
||||
{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|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.Build.0 = Release|Any CPU
|
||||
{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|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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
53
AudioVisualizer/AccAssManage.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
497
AudioVisualizer/ActivityWatch/ActivityAnalyzer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
AudioVisualizer/ActivityWatch/ActivityAnalyzerConfig.cs
Normal 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 }
|
||||
};
|
||||
}
|
||||
}
|
||||
15
AudioVisualizer/ActivityWatch/ActivityEntry.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
68
AudioVisualizer/ActivityWatch/ActivityWatchClient.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
66
AudioVisualizer/ActivityWatch/FocusStatusResponseParser.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
18
AudioVisualizer/ActivityWatch/TimeSliceData.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
45
AudioVisualizer/ActivityWatch/TimeSliceResult.cs
Normal 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
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup Label="Globals">
|
||||
<WebView2EnableCsWinRTProjection>False</WebView2EnableCsWinRTProjection>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows10.0.17763.0</TargetFramework>
|
||||
@@ -19,18 +23,42 @@
|
||||
<SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion>
|
||||
</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>
|
||||
<Content Include="FM-channel.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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="Sgnl.IdentityModel.OidcClient" Version="6.0.2" />
|
||||
<PackageReference Include="System.Management" Version="8.0.0" />
|
||||
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.20" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AccompanyingAssistant\AccompanyingAssistant.csproj" />
|
||||
<ProjectReference Include="..\LibAudioVisualizer\LibAudioVisualizer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -49,4 +77,8 @@
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalDesignTimeBuildInput Remove="FakesAssemblies\**" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -3,6 +3,7 @@
|
||||
public class ConfigurationObject {
|
||||
private GeneralConfigurationObjects? generalConfigurationObjects;
|
||||
private VideoWallpaperConfigObject? videoWallpaperConfigObject;
|
||||
private OtherConfigObjects? otherConfigObjects;
|
||||
public bool DeviceStateChange = false;
|
||||
public bool SignRenderingStatus = false;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
68
AudioVisualizer/Entity/OtherConfigObjects.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
AudioVisualizer/Entity/UIModification.cs
Normal 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; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -62,7 +62,7 @@ namespace AudioWallpaper {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine("重新加载配置");
|
||||
GeneralConfigurationObjects generalConfigurationObjects = configurationObject.GeneralConfigurationObjects;
|
||||
MainWindowReLoadConfig(generalConfigurationObjects);
|
||||
|
||||
@@ -141,7 +141,7 @@ namespace AudioWallpaper {
|
||||
}
|
||||
public void LoadWindowSet() {
|
||||
//加载窗体设置
|
||||
//Console.WriteLine("窗体管理器正在初始化");
|
||||
Console.WriteLine("窗体管理器正在初始化");
|
||||
ReCreateWindow();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using AudioWallpaper.ActivityWatch;
|
||||
using AudioWallpaper.Entity;
|
||||
using AudioWallpaper.Tools;
|
||||
using LibAudioVisualizer;
|
||||
using NAudio.CoreAudioApi;
|
||||
using NAudio.Wave;
|
||||
@@ -26,6 +28,8 @@ namespace AudioWallpaper {
|
||||
public delegate void FullScreenDetected(bool status);
|
||||
public event FullScreenDetected FullScreenDetectedEvent;
|
||||
|
||||
private string DrawText = null;
|
||||
|
||||
public MainWindow(GeneralConfigurationObjects configuration) {
|
||||
appBarManager = new AppBarManager(Handle);
|
||||
generalConfigurationObjects = configuration;
|
||||
@@ -43,6 +47,8 @@ namespace AudioWallpaper {
|
||||
Init();
|
||||
Win32.SetParent(this.Handle, programIntPtr);//<2F><><EFBFBD><EFBFBD><EFBFBD>ᴥ<EFBFBD><E1B4A5>DisplayChange<67>¼<EFBFBD><C2BC><EFBFBD><EFBFBD>е<EFBFBD><D0B5><EFBFBD>ѧ
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <20><>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ɫ
|
||||
/// </summary>
|
||||
@@ -122,11 +128,11 @@ namespace AudioWallpaper {
|
||||
public void Capture_DataAvailable(object? sender, WaveInEventArgs e) {
|
||||
int length = e.BytesRecorded / 4; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (ÿһ<C3BF><D2BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 4 <20>ֽ<EFBFBD>)
|
||||
double[] result = new double[length]; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
|
||||
result[i] = BitConverter.ToSingle(e.Buffer, i * 4)*generalConfigurationObjects.DefaultRadical; // ȡ<><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ
|
||||
|
||||
result[i] = BitConverter.ToSingle(e.Buffer, i * 4) * generalConfigurationObjects.DefaultRadical; // ȡ<><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ
|
||||
|
||||
//result[i] = db;
|
||||
|
||||
}
|
||||
@@ -460,6 +466,68 @@ namespace AudioWallpaper {
|
||||
g.FillRectangle(leftB, left);
|
||||
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;
|
||||
double rotation = 0;
|
||||
@@ -508,6 +576,7 @@ namespace AudioWallpaper {
|
||||
if (generalConfigurationObjects.WavyLine) {
|
||||
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();
|
||||
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>Ӧ)
|
||||
}
|
||||
|
||||
101
AudioVisualizer/OtherWallpaper/ExecutableWallpaper.cs
Normal 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 的一个消息,超时设置为0x3e8(1秒)。
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
17
AudioVisualizer/OtherWallpaper/OtherWallpaperManager.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
using AccompanyingAssistant;
|
||||
using AudioWallpaper.IPC;
|
||||
using AudioWallpaper.OtherWallpaper;
|
||||
using AudioWallpaper.WebWallpaper;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace AudioWallpaper {
|
||||
@@ -15,6 +19,14 @@ namespace AudioWallpaper {
|
||||
public static extern bool AllocConsole();
|
||||
[DllImport("kernel32.dll")]
|
||||
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]
|
||||
static void Main(String[] args) {
|
||||
@@ -24,6 +36,9 @@ namespace AudioWallpaper {
|
||||
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>
|
||||
using (Mutex mutex = new Mutex(true, "{C8F8E5D9-6F05-4F92-BE10-DF7A4F5F97FB}", out createdNew)) {
|
||||
#if DEBUG
|
||||
createdNew = true;
|
||||
#endif
|
||||
if (createdNew) {
|
||||
//<2F>ж<EFBFBD><D0B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD><C7B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
//if (args.Length < 1) {
|
||||
@@ -55,9 +70,18 @@ namespace AudioWallpaper {
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
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>
|
||||
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();
|
||||
//<2F>ͷſ<CDB7><C5BF><EFBFBD>̨
|
||||
FreeConsole();
|
||||
|
||||
39
AudioVisualizer/SSO/AuthConfig.cs
Normal 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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
280
AudioVisualizer/SSO/AuthManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
AudioVisualizer/SSO/AuthStatus.cs
Normal 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,//已经退出登录
|
||||
}
|
||||
}
|
||||
20
AudioVisualizer/SSO/Exceptions/SsoException.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
14
AudioVisualizer/SSO/LoginMeta.cs
Normal 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; }
|
||||
}
|
||||
|
||||
}
|
||||
38
AudioVisualizer/SSO/LoopbackHttpListener.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
44
AudioVisualizer/SSO/SystemBrowser.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
160
AudioVisualizer/SSO/TokenManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||