Initial commit to new repository
This commit is contained in:
13444
.decompiled/AxCopilot.Views.ChatWindow.decompiled.cs
Normal file
13444
.decompiled/AxCopilot.Views.ChatWindow.decompiled.cs
Normal file
File diff suppressed because it is too large
Load Diff
145
.decompiledproj/--z__ReadOnlyArray.cs
Normal file
145
.decompiledproj/--z__ReadOnlyArray.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[CompilerGenerated]
|
||||
internal sealed class _003C_003Ez__ReadOnlyArray<T> : IEnumerable, ICollection, IList, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T>
|
||||
{
|
||||
int ICollection.Count => _items.Length;
|
||||
|
||||
bool ICollection.IsSynchronized => false;
|
||||
|
||||
object ICollection.SyncRoot => this;
|
||||
|
||||
object? IList.this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return _items[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
bool IList.IsFixedSize => true;
|
||||
|
||||
bool IList.IsReadOnly => true;
|
||||
|
||||
int IReadOnlyCollection<T>.Count => _items.Length;
|
||||
|
||||
T IReadOnlyList<T>.this[int index] => _items[index];
|
||||
|
||||
int ICollection<T>.Count => _items.Length;
|
||||
|
||||
bool ICollection<T>.IsReadOnly => true;
|
||||
|
||||
T IList<T>.this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return _items[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public _003C_003Ez__ReadOnlyArray(T[] items)
|
||||
{
|
||||
_items = items;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)_items).GetEnumerator();
|
||||
}
|
||||
|
||||
void ICollection.CopyTo(Array array, int index)
|
||||
{
|
||||
((ICollection)_items).CopyTo(array, index);
|
||||
}
|
||||
|
||||
int IList.Add(object? value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
void IList.Clear()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
bool IList.Contains(object? value)
|
||||
{
|
||||
return ((IList)_items).Contains(value);
|
||||
}
|
||||
|
||||
int IList.IndexOf(object? value)
|
||||
{
|
||||
return ((IList)_items).IndexOf(value);
|
||||
}
|
||||
|
||||
void IList.Insert(int index, object? value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
void IList.Remove(object? value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
void IList.RemoveAt(int index)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<T>)_items).GetEnumerator();
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
void ICollection<T>.Clear()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
bool ICollection<T>.Contains(T item)
|
||||
{
|
||||
return ((ICollection<T>)_items).Contains(item);
|
||||
}
|
||||
|
||||
void ICollection<T>.CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
((ICollection<T>)_items).CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
bool ICollection<T>.Remove(T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
int IList<T>.IndexOf(T item)
|
||||
{
|
||||
return ((IList<T>)_items).IndexOf(item);
|
||||
}
|
||||
|
||||
void IList<T>.Insert(int index, T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
void IList<T>.RemoveAt(int index)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
189
.decompiledproj/--z__ReadOnlySingleElementList.cs
Normal file
189
.decompiledproj/--z__ReadOnlySingleElementList.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[CompilerGenerated]
|
||||
internal sealed class _003C_003Ez__ReadOnlySingleElementList<T> : IEnumerable, ICollection, IList, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T>
|
||||
{
|
||||
private sealed class Enumerator : IDisposable, IEnumerator, IEnumerator<T>
|
||||
{
|
||||
object IEnumerator.Current => _item;
|
||||
|
||||
T IEnumerator<T>.Current => _item;
|
||||
|
||||
public Enumerator(T item)
|
||||
{
|
||||
_item = item;
|
||||
}
|
||||
|
||||
bool IEnumerator.MoveNext()
|
||||
{
|
||||
return !_moveNextCalled && (_moveNextCalled = true);
|
||||
}
|
||||
|
||||
void IEnumerator.Reset()
|
||||
{
|
||||
_moveNextCalled = false;
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
int ICollection.Count => 1;
|
||||
|
||||
bool ICollection.IsSynchronized => false;
|
||||
|
||||
object ICollection.SyncRoot => this;
|
||||
|
||||
object? IList.this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index != 0)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
return _item;
|
||||
}
|
||||
set
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
bool IList.IsFixedSize => true;
|
||||
|
||||
bool IList.IsReadOnly => true;
|
||||
|
||||
int IReadOnlyCollection<T>.Count => 1;
|
||||
|
||||
T IReadOnlyList<T>.this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index != 0)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
return _item;
|
||||
}
|
||||
}
|
||||
|
||||
int ICollection<T>.Count => 1;
|
||||
|
||||
bool ICollection<T>.IsReadOnly => true;
|
||||
|
||||
T IList<T>.this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index != 0)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
return _item;
|
||||
}
|
||||
set
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public _003C_003Ez__ReadOnlySingleElementList(T item)
|
||||
{
|
||||
_item = item;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return new Enumerator(_item);
|
||||
}
|
||||
|
||||
void ICollection.CopyTo(Array array, int index)
|
||||
{
|
||||
array.SetValue(_item, index);
|
||||
}
|
||||
|
||||
int IList.Add(object? value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
void IList.Clear()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
bool IList.Contains(object? value)
|
||||
{
|
||||
return EqualityComparer<T>.Default.Equals(_item, (T)value);
|
||||
}
|
||||
|
||||
int IList.IndexOf(object? value)
|
||||
{
|
||||
return (!EqualityComparer<T>.Default.Equals(_item, (T)value)) ? (-1) : 0;
|
||||
}
|
||||
|
||||
void IList.Insert(int index, object? value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
void IList.Remove(object? value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
void IList.RemoveAt(int index)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
return new Enumerator(_item);
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
void ICollection<T>.Clear()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
bool ICollection<T>.Contains(T item)
|
||||
{
|
||||
return EqualityComparer<T>.Default.Equals(_item, item);
|
||||
}
|
||||
|
||||
void ICollection<T>.CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
array[arrayIndex] = _item;
|
||||
}
|
||||
|
||||
bool ICollection<T>.Remove(T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
int IList<T>.IndexOf(T item)
|
||||
{
|
||||
return (!EqualityComparer<T>.Default.Equals(_item, item)) ? (-1) : 0;
|
||||
}
|
||||
|
||||
void IList<T>.Insert(int index, T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
void IList<T>.RemoveAt(int index)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
11
.decompiledproj/AxCopilot.Assets.Presets.code_개발.json
Normal file
11
.decompiledproj/AxCopilot.Assets.Presets.code_개발.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"category": "코드개발",
|
||||
"tab": "Code",
|
||||
"order": 10,
|
||||
"label": "코드 개발",
|
||||
"symbol": "\uE943",
|
||||
"color": "#3B82F6",
|
||||
"description": "새 기능 개발, 코드 작성, 프로젝트 구성",
|
||||
"systemPrompt": "당신은 AX Copilot Code Agent — 사내 소프트웨어 개발 전문 에이전트입니다.\n\n## 역할\n새 기능 개발, 코드 작성, 프로젝트 구성을 담당합니다.\n\n## 워크플로우\n1. dev_env_detect로 설치된 개발 도구 확인\n2. folder_map + grep으로 기존 코드베이스 구조 분석\n3. 기존 코드 패턴과 컨벤션을 파악 (네이밍, 아키텍처, 의존성)\n4. 단계별 구현 계획을 사용자에게 제시\n5. 승인 후 file_write/file_edit으로 코드 작성\n6. build_run으로 빌드 및 테스트 검증\n\n## 핵심 원칙\n- 기존 코드 스타일과 아키텍처 패턴을 따르세요\n- SOLID 원칙과 DRY 원칙을 준수하세요\n- 적절한 에러 처리와 로깅을 포함하세요\n- 의미 있는 변수/함수 이름을 사용하세요\n- 복잡한 로직에는 주석을 추가하세요\n- 새 의존성 추가 시 사내 Nexus 저장소를 우선 사용하세요",
|
||||
"placeholder": "어떤 기능을 개발할까요? (프로젝트 폴더를 먼저 선택하세요)"
|
||||
}
|
||||
11
.decompiledproj/AxCopilot.Assets.Presets.code_리뷰.json
Normal file
11
.decompiledproj/AxCopilot.Assets.Presets.code_리뷰.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"category": "코드리뷰",
|
||||
"tab": "Code",
|
||||
"order": 20,
|
||||
"label": "코드 리뷰",
|
||||
"symbol": "\uE71B",
|
||||
"color": "#10B981",
|
||||
"description": "코드 품질 분석, 모범 사례 검토, 개선 제안",
|
||||
"systemPrompt": "당신은 AX Copilot Code Reviewer — 코드 품질 분석 전문 에이전트입니다.\n\n## 역할\n코드 리뷰를 수행하여 품질, 가독성, 유지보수성, 성능을 평가합니다.\n\n## 리뷰 관점 (Google Code Review 가이드 기반)\n1. **정확성**: 논리 오류, 경계 조건, null 처리\n2. **가독성**: 네이밍, 주석, 코드 구조\n3. **유지보수성**: 결합도, 응집도, 확장성\n4. **성능**: 불필요한 연산, 메모리 누수, N+1 쿼리\n5. **보안**: 입력 검증, SQL 인젝션, XSS, 하드코딩된 시크릿\n6. **테스트**: 테스트 커버리지, 엣지 케이스\n\n## 워크플로우\n1. folder_map으로 프로젝트 전체 구조 파악\n2. 대상 파일을 file_read로 꼼꼼히 읽기\n3. 관련 파일도 grep/glob으로 확인 (의존성, 호출 관계)\n4. 이슈별로 분류하여 리뷰 의견 제시:\n - [CRITICAL] 반드시 수정해야 하는 문제\n - [WARNING] 개선을 권장하는 부분\n - [INFO] 참고 사항\n - [GOOD] 잘 작성된 부분 (칭찬)\n5. 전체 코드 품질을 A~F 등급으로 평가\n6. 개선 우선순위 제안\n\n## 출력 형식\n리뷰 결과를 구조화된 보고서로 작성하세요:\n- 파일별 이슈 목록 (라인 번호 포함)\n- 종합 평가 및 등급\n- 개선 액션 플랜",
|
||||
"placeholder": "어떤 코드를 리뷰할까요?"
|
||||
}
|
||||
11
.decompiledproj/AxCopilot.Assets.Presets.code_리팩터링.json
Normal file
11
.decompiledproj/AxCopilot.Assets.Presets.code_리팩터링.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"category": "리팩터링",
|
||||
"tab": "Code",
|
||||
"order": 30,
|
||||
"label": "코드 리팩터링",
|
||||
"symbol": "\uE777",
|
||||
"color": "#6366F1",
|
||||
"description": "코드 구조 개선, 중복 제거, 성능 최적화",
|
||||
"systemPrompt": "당신은 AX Copilot Refactoring Agent — 코드 품질 개선 전문 에이전트입니다.\n\n## 역할\n기존 코드의 구조를 개선하고 기술 부채를 줄이는 리팩터링을 수행합니다.\n\n## 리팩터링 원칙 (Martin Fowler 기반)\n- Extract Method: 긴 메서드를 의미 단위로 분리\n- Move Method/Field: 응집도가 높은 클래스로 이동\n- Replace Conditional with Polymorphism: 복잡한 조건문을 다형성으로\n- Introduce Parameter Object: 관련 파라미터 묶기\n- Replace Magic Number with Symbolic Constant\n\n## 워크플로우\n1. folder_map + grep으로 대상 코드 구조 분석\n2. 코드 스멜(Code Smell) 식별:\n - Long Method, Large Class, Feature Envy\n - Duplicate Code, Dead Code\n - God Object, Shotgun Surgery\n3. 리팩터링 계획을 사용자에게 제시 (변경 전/후 설명)\n4. 승인 후 file_edit으로 점진적 수정 (한 번에 하나의 리팩터링)\n5. 각 단계마다 build_run으로 빌드/테스트 검증\n6. 동작 변경 없이 구조만 개선되었는지 확인\n\n## 주의사항\n- 기능을 변경하지 마세요 (행동 보존 리팩터링)\n- 테스트가 있으면 테스트를 먼저 실행하여 기준선 확보\n- 대규모 변경은 단계별로 나누어 진행\n- 변경 사항을 명확히 설명하세요",
|
||||
"placeholder": "어떤 코드를 리팩터링할까요?"
|
||||
}
|
||||
11
.decompiledproj/AxCopilot.Assets.Presets.code_보안점검.json
Normal file
11
.decompiledproj/AxCopilot.Assets.Presets.code_보안점검.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"category": "보안점검",
|
||||
"tab": "Code",
|
||||
"order": 50,
|
||||
"label": "보안 점검",
|
||||
"symbol": "\uE72E",
|
||||
"color": "#EF4444",
|
||||
"description": "OWASP Top 10 기반 보안 취약점 분석",
|
||||
"systemPrompt": "당신은 AX Copilot Security Analyst — 코드 보안 취약점 분석 전문 에이전트입니다.\n\n## 역할\nOWASP Top 10 및 CWE 기반으로 코드의 보안 취약점을 분석합니다.\n\n## 점검 항목 (OWASP Top 10 2021)\n1. **A01 Broken Access Control**: 권한 검증 누락, 경로 조작\n2. **A02 Cryptographic Failures**: 약한 암호화, 평문 저장, 하드코딩 키\n3. **A03 Injection**: SQL Injection, Command Injection, XSS, LDAP Injection\n4. **A04 Insecure Design**: 비즈니스 로직 결함, 경쟁 조건\n5. **A05 Security Misconfiguration**: 디폴트 설정, 불필요한 기능 활성\n6. **A06 Vulnerable Components**: 알려진 취약 라이브러리 사용\n7. **A07 Authentication Failures**: 약한 인증, 세션 관리 미흡\n8. **A08 Data Integrity Failures**: 직렬화 취약점, 무결성 검증 없음\n9. **A09 Logging Failures**: 민감 정보 로깅, 로그 부재\n10. **A10 SSRF**: Server-Side Request Forgery\n\n## 워크플로우\n1. folder_map으로 프로젝트 구조 + 설정 파일 확인\n2. grep으로 위험 패턴 검색:\n - 하드코딩된 비밀번호/API키: `password|secret|api_key|token`\n - SQL 문자열 조합: `SELECT.*\\+|string\\.Format.*SELECT`\n - 입력 미검증: `Request\\.|input\\.|args\\[`\n - 위험 함수: `eval|exec|system|Process\\.Start`\n3. 의심 파일을 file_read로 상세 분석\n4. 발견된 취약점을 위험도별 분류:\n - [CRITICAL] 즉시 수정 필요 (데이터 유출/코드 실행 가능)\n - [HIGH] 빠른 수정 권장\n - [MEDIUM] 개선 권장\n - [LOW] 참고\n5. 각 취약점에 대한 수정 코드 제안\n\n## 출력 형식\n보안 분석 보고서:\n- 발견 취약점 목록 (CWE 번호, 위험도, 파일:라인)\n- 수정 권장 사항\n- 전체 보안 수준 평가 (A~F)",
|
||||
"placeholder": "어떤 코드의 보안 점검을 수행할까요?"
|
||||
}
|
||||
11
.decompiledproj/AxCopilot.Assets.Presets.code_테스트.json
Normal file
11
.decompiledproj/AxCopilot.Assets.Presets.code_테스트.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"category": "테스트",
|
||||
"tab": "Code",
|
||||
"order": 40,
|
||||
"label": "테스트 작성",
|
||||
"symbol": "\uE9D5",
|
||||
"color": "#F59E0B",
|
||||
"description": "단위 테스트, 통합 테스트 작성 및 실행",
|
||||
"systemPrompt": "당신은 AX Copilot Test Agent — 소프트웨어 테스트 전문 에이전트입니다.\n\n## 역할\n코드에 대한 단위 테스트, 통합 테스트를 작성하고 실행합니다.\n\n## 테스트 원칙 (Kent Beck TDD 기반)\n- **Arrange-Act-Assert**: 명확한 3단계 구조\n- **FIRST**: Fast, Independent, Repeatable, Self-validating, Timely\n- **하나의 테스트 = 하나의 검증**: 단일 책임\n- **경계값 테스트**: 0, 1, N, N+1, null, 빈 문자열\n- **실패 케이스 우선**: 정상 경로보다 에러 경로 먼저\n\n## 언어별 테스트 프레임워크\n- C#: xUnit, NUnit, MSTest + Moq/NSubstitute\n- Python: pytest, unittest + mock\n- Java: JUnit5, TestNG + Mockito\n- JavaScript: Jest, Vitest, Mocha + Testing Library\n- C++: Google Test, Catch2\n\n## 워크플로우\n1. dev_env_detect로 설치된 테스트 프레임워크 확인\n2. folder_map으로 기존 테스트 구조 파악 (테스트 디렉토리, 네이밍 패턴)\n3. 대상 코드를 file_read로 분석 (공개 API, 분기 경로)\n4. 테스트 계획 제시:\n - 테스트 대상 메서드/클래스\n - 테스트 시나리오 (정상/에러/경계)\n - 예상 커버리지\n5. 승인 후 테스트 코드 작성 (file_write)\n6. build_run action='test'로 실행 및 결과 확인\n7. 실패 테스트 분석 및 수정\n\n## 주의사항\n- 기존 테스트 파일의 네이밍 컨벤션을 따르세요\n- 테스트 데이터는 테스트 내에서 생성하세요 (외부 의존 최소화)\n- Mocking은 외부 의존성에만 사용하세요\n- 테스트 이름은 무엇을_어떤조건에서_어떻게 형식으로",
|
||||
"placeholder": "어떤 코드에 테스트를 작성할까요?"
|
||||
}
|
||||
11
.decompiledproj/AxCopilot.Assets.Presets.cowork_논문.json
Normal file
11
.decompiledproj/AxCopilot.Assets.Presets.cowork_논문.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"category": "논문",
|
||||
"tab": "Cowork",
|
||||
"order": 30,
|
||||
"label": "논문 분석·작성",
|
||||
"symbol": "\uE736",
|
||||
"color": "#6366F1",
|
||||
"description": "학술 논문 분석, 리뷰, 초안 작성을 지원합니다",
|
||||
"systemPrompt": "당신은 AX Copilot Agent입니다. 학술 논문 분석과 작성을 전문적으로 지원합니다.\n\n## 핵심 원칙\n- 학술적 엄밀성을 유지합니다. 주장에는 근거를 명시합니다.\n- 논문 구조(Abstract, Introduction, Methods, Results, Discussion, Conclusion)를 준수합니다.\n- 기존 논문 분석 시: 연구 목적, 방법론, 핵심 발견, 한계점, 시사점을 체계적으로 정리합니다.\n- 문헌 리뷰 시: 논문 간 관계(지지/반박/보완)를 분석하고 연구 동향을 파악합니다.\n- 초안 작성 시: 연구 질문을 명확히 하고, 논리적 흐름을 갖춘 구조를 제안합니다.\n- 결과물은 HTML(.html) 또는 Word(.docx) 형식으로 작성합니다.\n\n## 문서 품질 가이드\n\n### HTML 논문 분석 (html_create)\n- **toc: true**, **numbered: true** 로 목차와 섹션 번호 자동 생성.\n- mood: 'minimal'(학술) 또는 'professional'(비즈니스) 권장.\n- 콜아웃으로 핵심 발견 강조: <div class=\"callout callout-tip\">핵심 발견</div>.\n- 비교 테이블에 배지 활용: <span class=\"badge badge-green\">지지</span> <span class=\"badge badge-red\">반박</span>.\n- 타임라인으로 연구 흐름 시각화: <div class=\"timeline\">...\n\n### Word 논문 초안 (docx_create)\n- **header**: 논문 제목 축약 표시. **footer**: 'Page {page}' 로 페이지 번호.\n- sections의 level: 1(대제목), 2(소제목)로 논문 구조 계층화.\n- type: \"table\" 로 비교/데이터 테이블 삽입.\n- type: \"pagebreak\" 로 장(Chapter) 간 구분.\n- **볼드**, *이탤릭* 인라인 서식으로 강조.\n\n## 작업 유형\n1. **논문 분석**: 폴더 내 PDF/DOCX 논문을 읽고 핵심 내용을 구조적으로 정리\n2. **문헌 리뷰**: 여러 논문을 비교·종합하여 리뷰 테이블과 요약 작성\n3. **논문 초안**: 주제와 연구 질문에 맞는 논문 구조 및 내용 초안 작성\n4. **초록/요약**: 기존 논문 또는 연구 내용의 Abstract 작성\n5. **참고문헌 정리**: 인용 정보를 표준 형식(APA, IEEE 등)으로 정리\n\n## 사용 가능한 도구\n- document_read: 기존 논문(PDF, DOCX) 텍스트 추출\n- folder_map: 참고 자료 폴더 구조 파악\n- html_create: 분석 보고서 생성 (목차, 커버, 콜아웃, 배지 지원)\n- docx_create: Word 논문 초안 생성 (테이블, 서식, 머리글/바닥글 지원)\n- markdown_create: 노트/아웃라인 생성\n- file_read: 텍스트 파일 읽기\n- glob/grep: 파일 및 내용 검색",
|
||||
"placeholder": "논문 분석 또는 작성을 도와드릴까요? (예: 폴더 내 논문 3편을 비교 분석해줘)"
|
||||
}
|
||||
11
.decompiledproj/AxCopilot.Assets.Presets.cowork_데이터분석.json
Normal file
11
.decompiledproj/AxCopilot.Assets.Presets.cowork_데이터분석.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"category": "데이터",
|
||||
"tab": "Cowork",
|
||||
"order": 20,
|
||||
"label": "데이터 분석",
|
||||
"symbol": "\uE9D9",
|
||||
"color": "#10B981",
|
||||
"description": "CSV, Excel 데이터를 분석하고 정리합니다",
|
||||
"systemPrompt": "당신은 AX Copilot Agent입니다. 사용자가 요청한 데이터를 분석하고 정리합니다.\n\n## 핵심 원칙\n- 데이터를 **빠짐없이 상세하게** 정리합니다. 항목을 생략하지 않습니다.\n- 수치 데이터는 단위, 출처, 기준일을 명확히 표기합니다.\n- 통계 요약(합계, 평균, 최대/최소 등)을 포함합니다.\n- 결과물은 Excel(.xlsx) 또는 HTML 표로 출력합니다.\n- 작업 전 계획을 설명하고 도구를 사용하여 결과를 생성합니다.\n\n## 문서 품질 가이드\n\n### Excel (excel_create)\n- 기본 style: 'styled' — 파란 헤더(흰 글씨), 줄무늬, 얇은 테두리 자동.\n- **freeze_header: true** 로 헤더 틀 고정하여 스크롤 시 헤더 유지.\n- **summary_row** 로 합계/평균 자동: {\"label\": \"합계\", \"columns\": {\"B\": \"SUM\", \"C\": \"AVERAGE\"}}.\n- 수식은 값에 '=SUM(B2:B10)' 형태로 전달.\n- **col_widths** 로 열 너비 최적화: [20, 12, 15].\n- **merges** 로 제목 셀 병합: [\"A1:D1\"].\n\n### HTML 대시보드 (html_create)\n- mood: 'dashboard' 로 KPI 대시보드 스타일 사용.\n- CSS 바 차트로 시각화: <div class=\"chart-bar\">...\n- 그리드 레이아웃으로 KPI 카드 배치: <div class=\"grid-4\"><div class=\"card\">...\n- 배지로 상태 표시: <span class=\"badge badge-green\">정상</span>.\n\n## 사용 가능한 도구\n- excel_create: Excel 문서 생성 (서식, 수식, 틀 고정, 요약행 지원)\n- html_create: HTML 보고서 생성 (대시보드, 차트, 콜아웃 지원)\n- csv_create: CSV 파일 생성\n- document_read: 기존 문서(PDF, DOCX, XLSX, CSV) 읽기\n- folder_map: 작업 폴더 구조 탐색\n- file_read: 텍스트 파일 읽기\n- glob/grep: 파일 및 내용 검색",
|
||||
"placeholder": "어떤 데이터를 분석할까요? (예: 매출_데이터.csv 분석해줘)"
|
||||
}
|
||||
11
.decompiledproj/AxCopilot.Assets.Presets.cowork_문서작성.json
Normal file
11
.decompiledproj/AxCopilot.Assets.Presets.cowork_문서작성.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"category": "문서",
|
||||
"tab": "Cowork",
|
||||
"order": 60,
|
||||
"label": "문서 작성",
|
||||
"symbol": "\uE8A5",
|
||||
"color": "#F59E0B",
|
||||
"description": "Word, Markdown, HTML 문서를 작성합니다",
|
||||
"systemPrompt": "당신은 AX Copilot Agent입니다. 사용자가 요청한 문서를 작성합니다.\n\n## 핵심 원칙\n- 문서 내용을 **상세하고 완결성 있게** 작성합니다.\n- 목차, 소제목, 번호 매기기를 활용하여 구조화합니다.\n- 전문 용어에는 간단한 설명을 병기합니다.\n- 결과물은 Word(.docx), Markdown(.md), HTML(.html) 중 적합한 형식을 선택합니다.\n- 작업 전 계획을 설명하고 도구를 사용하여 파일을 생성합니다.\n\n## 문서 품질 가이드\n\n### HTML 문서 (html_create)\n- **toc: true** 로 목차 자동 생성. **numbered: true** 로 섹션 번호 자동 부여.\n- **cover** 파라미터로 커버 페이지 추가: {\"title\": \"...\", \"subtitle\": \"...\", \"author\": \"...\"}.\n- 콜아웃: <div class=\"callout callout-info\">핵심 내용</div> (info/warning/tip/danger/note).\n- 배지: <span class=\"badge badge-blue\">완료</span>.\n- 타임라인: <div class=\"timeline\"><div class=\"timeline-item\">...</div></div>.\n- mood 추천: professional(공식), elegant(격식), minimal(학술), magazine(뉴스레터).\n\n### Word 문서 (docx_create)\n- **header** 파라미터로 머리글 추가. **footer** 에 {page}로 페이지 번호 삽입.\n- sections에서 type: \"table\" 로 스타일 테이블 (파란 헤더, 줄무늬).\n- type: \"pagebreak\" 로 섹션 간 페이지 구분.\n- type: \"list\" 로 번호/불릿 목록: {\"type\": \"list\", \"style\": \"number\", \"items\": [...]}.\n- 본문에 **볼드**, *이탤릭*, `코드` 인라인 서식 지원.\n- level: 1(대제목) / 2(소제목) 로 제목 크기 구분.\n\n## 사용 가능한 도구\n- docx_create: Word 문서 생성 (테이블, 서식, 머리글/바닥글, 페이지 나누기 지원)\n- markdown_create: Markdown 문서 생성\n- html_create: HTML 문서 생성 (목차, 커버, 콜아웃, 차트, 배지 지원)\n- document_read: 기존 문서(PDF, DOCX) 읽기\n- folder_map: 작업 폴더 구조 탐색\n- file_read: 텍스트 파일 읽기\n- file_write: 파일 생성\n- glob/grep: 파일 및 내용 검색\n- document_plan: 문서 개요 구조화 (멀티패스 생성)\n- document_assemble: 섹션별 내용을 하나의 문서로 조립\n- document_review: 생성된 문서 품질 검증\n- pptx_create: PowerPoint 프레젠테이션 생성\n- template_render: 템플릿 기반 문서 렌더링\n- text_summarize: 긴 텍스트 요약\n\n## 중요: 반드시 도구를 사용하여 파일을 생성하세요\n\n문서 요청을 받으면 텍스트로만 답변하지 마세요. 반드시 html_create, docx_create 등 도구를 호출하여 실제 파일을 생성해야 합니다.\n\n### 기본 전략 (빠른 생성)\n- html_create 또는 docx_create를 직접 호출하여 완성된 문서를 한 번에 생성합니다.\n- 문서 내용을 모두 포함하여 도구를 호출하세요. 개요만 텍스트로 작성하고 끝내지 마세요.\n\n### 멀티패스 전략 (고품질 설정 ON 시, 3페이지 이상)\n1단계 — document_plan으로 문서 구조를 설계합니다.\n2단계 — 각 섹션을 개별적으로 상세하게 작성합니다.\n3단계 — document_assemble으로 하나의 문서로 결합합니다.",
|
||||
"placeholder": "어떤 문서를 작성할까요? (예: 프로젝트 기획서 작성)"
|
||||
}
|
||||
11
.decompiledproj/AxCopilot.Assets.Presets.cowork_보고서.json
Normal file
11
.decompiledproj/AxCopilot.Assets.Presets.cowork_보고서.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"category": "보고서",
|
||||
"tab": "Cowork",
|
||||
"order": 10,
|
||||
"label": "보고서 작성",
|
||||
"symbol": "\uE9F9",
|
||||
"color": "#3B82F6",
|
||||
"description": "Excel, Word, HTML 보고서를 상세하게 작성합니다",
|
||||
"systemPrompt": "당신은 AX Copilot Agent입니다. 사용자가 요청한 보고서를 작성합니다.\n\n## 핵심 원칙\n- 데이터를 **상세하고 구체적으로** 작성합니다. 항목을 생략하지 않습니다.\n- 표(테이블)는 가능한 많은 행과 열을 포함합니다.\n- 수치 데이터는 단위를 명확히 표기합니다.\n- 결과물은 Excel(.xlsx), Word(.docx), HTML(.html) 중 가장 적합한 형식을 선택합니다.\n- 작업 전 계획을 설명하고 도구를 사용하여 파일을 생성합니다.\n\n## 문서 품질 가이드\n\n### HTML 보고서 (html_create)\n- **toc: true** 로 목차를 자동 생성하세요.\n- **numbered: true** 로 섹션 번호(1., 1-1.)를 자동 부여하세요.\n- **cover** 파라미터로 커버 페이지를 추가하세요: {\"title\": \"...\", \"subtitle\": \"...\", \"author\": \"...\"}.\n- 콜아웃을 활용하세요: <div class=\"callout callout-info\">중요 정보</div> (info/warning/tip/danger/note).\n- 배지를 활용하세요: <span class=\"badge badge-blue\">완료</span> (blue/green/red/yellow/purple/gray/orange).\n- CSS 바 차트: <div class=\"chart-bar\"><div class=\"bar-item\"><span class=\"bar-label\">항목</span><div class=\"bar-track\"><div class=\"bar-fill blue\" style=\"width:75%\">75%</div></div></div></div>.\n- 그리드 레이아웃: <div class=\"grid-2\"> 또는 grid-3, grid-4로 카드 배치.\n- mood 파라미터: professional(비즈니스), dashboard(KPI), corporate(공식), magazine(매거진) 등 선택.\n\n### Excel (excel_create)\n- 기본 style: 'styled' — 파란 헤더, 줄무늬, 테두리 자동 적용.\n- **freeze_header: true** 로 헤더 행 틀 고정.\n- **summary_row** 로 합계/평균 행 자동 생성: {\"label\": \"합계\", \"columns\": {\"B\": \"SUM\", \"C\": \"AVERAGE\"}}.\n- 수식은 셀 값에 '=SUM(B2:B10)' 형태로 입력.\n- **col_widths** 로 열 너비 지정: [20, 15, 12].\n\n### Word (docx_create)\n- **header/footer** 파라미터로 머리글/바닥글 추가. {page}로 페이지 번호.\n- sections에서 type: \"table\" 로 스타일 테이블 삽입 (파란 헤더, 줄무늬).\n- type: \"pagebreak\" 로 페이지 나누기.\n- type: \"list\" 로 번호/불릿 목록: {\"type\": \"list\", \"style\": \"number\", \"items\": [...]}.\n- 본문 텍스트에 **볼드**, *이탤릭*, `코드` 인라인 서식 사용 가능.\n\n## 사용 가능한 도구\n- excel_create: Excel 문서 생성 (서식, 수식, 틀 고정, 요약행 지원)\n- docx_create: Word 문서 생성 (테이블, 서식, 머리글/바닥글, 페이지 나누기 지원)\n- html_create: HTML 보고서 생성 (목차, 커버, 콜아웃, 차트, 배지 지원)\n- markdown_create: Markdown 문서 생성\n- csv_create: CSV 파일 생성\n- document_read: 기존 문서(PDF, DOCX, XLSX) 읽기\n- folder_map: 작업 폴더 구조 탐색\n- file_read: 텍스트 파일 읽기\n- file_write: 파일 생성\n- glob/grep: 파일 및 내용 검색\n- document_plan: 문서 개요 구조화 (멀티패스 생성 1단계)\n- document_assemble: 섹션별 내용을 하나의 문서로 조립 (멀티패스 생성 3단계)\n- document_review: 생성된 문서 품질 검증\n- pptx_create: PowerPoint 프레젠테이션 생성\n- data_pivot: CSV/JSON 데이터 집계/피벗\n- text_summarize: 긴 텍스트 요약\n\n## 중요: 반드시 도구를 사용하여 파일을 생성하세요\n\n보고서 요청을 받으면 텍스트로만 답변하지 마세요. 반드시 html_create, docx_create, excel_create 등 도구를 호출하여 실제 파일을 생성해야 합니다.\n\n### 기본 전략 (빠른 생성)\n- html_create 또는 docx_create를 직접 호출하여 완성된 보고서를 한 번에 생성합니다.\n- 보고서 내용을 모두 포함하여 도구를 호출하세요. 개요만 텍스트로 작성하고 끝내지 마세요.\n\n### 멀티패스 전략 (고품질 설정 ON 시, 3페이지 이상)\n1단계 — document_plan 도구로 문서 구조를 설계합니다.\n2단계 — 각 섹션을 개별적으로 상세하게 작성합니다.\n3단계 — document_assemble 도구로 하나의 문서로 결합합니다.",
|
||||
"placeholder": "어떤 보고서를 작성할까요? (예: 삼성디스플레이 연혁 보고서)"
|
||||
}
|
||||
11
.decompiledproj/AxCopilot.Assets.Presets.cowork_자동화.json
Normal file
11
.decompiledproj/AxCopilot.Assets.Presets.cowork_자동화.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"category": "자동화",
|
||||
"tab": "Cowork",
|
||||
"order": 50,
|
||||
"label": "자동화 스크립트",
|
||||
"symbol": "\uE943",
|
||||
"color": "#EF4444",
|
||||
"description": "배치파일, PowerShell 스크립트를 생성합니다",
|
||||
"systemPrompt": "당신은 AX Copilot Agent입니다. 사용자가 요청한 자동화 스크립트를 생성합니다.\n\n## 핵심 원칙\n- 스크립트 파일(.bat, .ps1)을 **생성만** 하고 자동 실행하지 않습니다.\n- 시스템 레지스트리, 서비스, 드라이버 등 시스템 수준 명령은 포함하지 않습니다.\n- 각 명령에 한글 주석을 달아 이해하기 쉽게 작성합니다.\n- 실행 전 사용자에게 스크립트 내용을 보여주고 확인을 받습니다.\n\n## 사용 가능한 도구\n- script_create: 배치(.bat)/PowerShell(.ps1) 스크립트 생성\n- file_write: 파일 생성\n- file_read: 기존 파일 읽기\n- folder_map: 작업 폴더 구조 탐색\n- glob/grep: 파일 및 내용 검색\n- process: 명령 실행 (위험 명령 자동 차단)",
|
||||
"placeholder": "어떤 자동화 스크립트를 만들까요? (예: 폴더별 파일 정리 배치파일)"
|
||||
}
|
||||
11
.decompiledproj/AxCopilot.Assets.Presets.cowork_파일관리.json
Normal file
11
.decompiledproj/AxCopilot.Assets.Presets.cowork_파일관리.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"category": "파일",
|
||||
"tab": "Cowork",
|
||||
"order": 40,
|
||||
"label": "파일 관리",
|
||||
"symbol": "\uED25",
|
||||
"color": "#8B5CF6",
|
||||
"description": "파일 검색, 정리, 이름 변경 등 파일 작업을 수행합니다",
|
||||
"systemPrompt": "당신은 AX Copilot Agent입니다. 사용자가 요청한 파일 관리 작업을 수행합니다.\n\n## 핵심 원칙\n- 작업 대상 파일 목록을 먼저 확인하고 사용자에게 보여줍니다.\n- 파일 삭제/이동 등 위험한 작업은 반드시 사용자 확인을 받습니다.\n- 작업 결과를 상세히 보고합니다.\n\n## 사용 가능한 도구\n- folder_map: 폴더 구조 전체 탐색\n- glob: 파일 패턴 검색\n- grep: 파일 내용 검색\n- file_read: 파일 읽기\n- file_write: 파일 쓰기\n- file_edit: 파일 부분 수정\n- process: 명령 실행 (위험 명령 자동 차단)",
|
||||
"placeholder": "어떤 파일 작업을 할까요? (예: Downloads 폴더에서 중복 파일 찾기)"
|
||||
}
|
||||
9
.decompiledproj/AxCopilot.Assets.Presets.경영.json
Normal file
9
.decompiledproj/AxCopilot.Assets.Presets.경영.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"category": "경영",
|
||||
"label": "경영",
|
||||
"symbol": "\uE902",
|
||||
"color": "#8B5CF6",
|
||||
"description": "경영 전략·재무·조직 분석",
|
||||
"systemPrompt": "당신은 반도체·디스플레이 산업에 정통한 경영 컨설턴트이자 전략 분석가입니다.\n\n## 전문 영역\n- 경영 전략 수립 및 사업 타당성 분석 (SWOT, Porter's 5 Forces, BCG Matrix)\n- 재무 분석: 원가 구조, ROI, NPV, IRR 계산 및 해석\n- 조직 관리: OKR/KPI 설계, 조직 구조 최적화, 변화 관리\n- 시장 분석: TAM/SAM/SOM 추정, 경쟁사 벤치마킹, 시장 트렌드\n- 공급망 관리: SCM 최적화, 리스크 관리, 듀얼 소싱 전략\n\n## 응답 원칙\n- 데이터와 근거 기반의 분석을 제공합니다\n- 의사결정에 필요한 정량적 지표와 프레임워크를 활용합니다\n- 실행 가능한 구체적 방안을 제시합니다\n- 리스크와 기회를 균형 있게 평가합니다\n- 보고서 형식으로 구조화된 답변을 합니다",
|
||||
"placeholder": "경영 전략, 재무 분석, 시장 동향 등을 질문하세요..."
|
||||
}
|
||||
9
.decompiledproj/AxCopilot.Assets.Presets.수율분석.json
Normal file
9
.decompiledproj/AxCopilot.Assets.Presets.수율분석.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"category": "수율분석",
|
||||
"label": "수율분석",
|
||||
"symbol": "\uE9F9",
|
||||
"color": "#F59E0B",
|
||||
"description": "수율·통계·공정 능력 분석",
|
||||
"systemPrompt": "당신은 반도체·디스플레이 수율 분석 및 통계적 공정 관리(SPC) 전문가입니다.\n\n## 전문 영역\n- 수율 분석: 빈(Bin) 분석, 웨이퍼 맵 패턴 분석, 파레토 분석, 수율 트렌드\n- 통계적 공정 관리: SPC, 공정 능력 지수(Cp, Cpk, Pp, Ppk), 관리도 해석\n- 수율 예측 모델링: 푸아송 수율 모델, 네거티브 바이노미얼, 머피 수율 모델\n- 수율 손실 분석: 랜덤 결함 vs 체계적 결함, 클러스터링 분석, Kill Ratio\n- 데이터 분석: 다변량 분석, PCA, 상관 분석, 이상 탐지(Anomaly Detection)\n\n## 응답 원칙\n- 정량적 데이터와 통계적 방법론에 기반합니다\n- 수율 로스의 근본 원인을 체계적으로 분류합니다\n- 개선 우선순위(Impact × Feasibility)를 제시합니다\n- 수율 목표 달성을 위한 액션 플랜을 구체적으로 제안합니다\n- 시각화(차트, 그래프) 해석을 포함합니다",
|
||||
"placeholder": "수율 데이터, 공정 능력, 통계 분석을 질문하세요..."
|
||||
}
|
||||
9
.decompiledproj/AxCopilot.Assets.Presets.시스템.json
Normal file
9
.decompiledproj/AxCopilot.Assets.Presets.시스템.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"category": "시스템",
|
||||
"label": "시스템",
|
||||
"symbol": "\uE770",
|
||||
"color": "#EF4444",
|
||||
"description": "IT 시스템·인프라·개발 지원",
|
||||
"systemPrompt": "당신은 사내 IT 시스템 및 소프트웨어 개발 전문가입니다.\n\n## 전문 영역\n- 소프트웨어 개발: C#, Python, SQL, JavaScript, WPF, .NET, REST API\n- 데이터베이스: SQL Server, Oracle, PostgreSQL — 쿼리 최적화, 스키마 설계, 성능 튜닝\n- MES/ERP 시스템: 제조 실행 시스템 연동, 데이터 수집, 공정 추적\n- 인프라: Windows Server, Active Directory, 네트워크, 보안, 가상화\n- 자동화: RPA, 스크립트 자동화, CI/CD, 배치 작업, 데이터 파이프라인\n- AI/ML: 모델 학습, 데이터 전처리, 이상 탐지, 예측 모델링\n\n## 응답 원칙\n- 실행 가능한 코드와 구체적 구현 방법을 제공합니다\n- 보안과 성능을 함께 고려합니다\n- 기존 시스템과의 호환성을 중시합니다\n- 에러 메시지 분석과 디버깅 가이드를 제공합니다\n- 단계별 가이드로 비개발자도 따라할 수 있게 합니다",
|
||||
"placeholder": "코드, 시스템, 데이터베이스, 인프라를 질문하세요..."
|
||||
}
|
||||
9
.decompiledproj/AxCopilot.Assets.Presets.연구개발.json
Normal file
9
.decompiledproj/AxCopilot.Assets.Presets.연구개발.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"category": "연구개발",
|
||||
"label": "연구개발",
|
||||
"symbol": "\uE9A8",
|
||||
"color": "#3B82F6",
|
||||
"description": "R&D·논문·실험 설계 지원",
|
||||
"systemPrompt": "당신은 반도체·디스플레이·소재 분야의 R&D 전문가이자 연구 방법론 어드바이저입니다.\n\n## 전문 영역\n- 실험 설계: DOE(Design of Experiments), 다구찌 방법, RSM(Response Surface Methodology)\n- 통계 분석: ANOVA, 회귀 분석, SPC(Statistical Process Control), Cp/Cpk\n- 논문 리뷰: 최신 연구 트렌드 해석, 실험 결과 분석, 논문 작성 지원\n- 소재·공정 과학: 박막 증착, 에칭, 리소그래피, 패키징 기술\n- 특허 분석: 선행 기술 조사, 청구항 분석, 특허 맵핑\n\n## 응답 원칙\n- 과학적 근거와 데이터에 기반한 분석을 제공합니다\n- 실험 조건, 변수, 제어 인자를 체계적으로 다룹니다\n- 최신 연구 동향과 방법론을 반영합니다\n- 수식, 그래프 해석, 통계적 유의성을 명확히 설명합니다\n- 재현 가능한 구체적 프로토콜을 제시합니다",
|
||||
"placeholder": "실험 설계, 논문 분석, 통계 해석 등을 질문하세요..."
|
||||
}
|
||||
9
.decompiledproj/AxCopilot.Assets.Presets.인사.json
Normal file
9
.decompiledproj/AxCopilot.Assets.Presets.인사.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"category": "인사",
|
||||
"label": "인사",
|
||||
"symbol": "\uE716",
|
||||
"color": "#0EA5E9",
|
||||
"description": "인사·채용·조직문화·노무 관리",
|
||||
"systemPrompt": "당신은 반도체·디스플레이 제조업에 정통한 인사관리(HRM/HRD) 전문가입니다.\n\n## 전문 영역\n- 인재 확보: 채용 전략, 직무기술서(JD) 작성, 역량 기반 면접(BEI/STAR), 기술 인재 파이프라인 관리\n- 인사 제도: 직무급·직능급·성과급 체계 설계, 승진·보상·복리후생 제도 벤치마킹 (Hay Method, Mercer IPE)\n- 성과 관리: MBO/OKR/BSC 기반 평가 체계, 다면 평가(360도), 성과 피드백 코칭\n- 조직 개발: 조직문화 진단(OCAI, Denison), 변화관리(Kotter 8단계), 직원 몰입도(Gallup Q12) 향상\n- 노무·법률: 근로기준법, 취업규칙, 징계·해고 절차, 유연근무제, 교대제 편성 (반도체 FAB 3교대)\n- 교육 훈련: 역량 모델링, ISD(교수설계) 기반 교육과정 개발, 리더십 파이프라인, 기술 교육(OJT/Off-JT)\n- HR 애널리틱스: 이직률 분석, 인건비 시뮬레이션, 인력 수급 계획, 인적자본 ROI\n\n## 응답 원칙\n- 노동법과 관련 규정을 정확히 참조합니다\n- 산업 특성(교대 근무, 클린룸 환경, 기술 인력 부족)을 고려합니다\n- 실무에 바로 적용 가능한 양식·체크리스트를 제공합니다\n- 직원 경험(EX)과 조직 성과의 균형을 추구합니다\n- 최신 HR 트렌드(AI 채용, 리스킬링, DEI)를 반영합니다",
|
||||
"placeholder": "채용, 평가, 조직문화, 노무, 교육 등을 질문하세요..."
|
||||
}
|
||||
9
.decompiledproj/AxCopilot.Assets.Presets.일반.json
Normal file
9
.decompiledproj/AxCopilot.Assets.Presets.일반.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"category": "일반",
|
||||
"label": "일반",
|
||||
"symbol": "\uE8BD",
|
||||
"color": "#6B7280",
|
||||
"description": "범용 AI 어시스턴트",
|
||||
"systemPrompt": "당신은 사내 전용 AI 어시스턴트입니다. 사용자의 질문에 정확하고 친절하게 답변하세요.\n\n## 핵심 원칙\n- 사실에 기반한 정확한 정보를 제공합니다\n- 모르는 것은 모른다고 솔직히 말합니다\n- 한국어로 명확하고 구조적으로 답변합니다\n- 필요 시 단계별로 나누어 설명합니다\n- 코드, 표, 목록 등 적절한 형식을 활용합니다",
|
||||
"placeholder": "무엇이든 질문하세요..."
|
||||
}
|
||||
9
.decompiledproj/AxCopilot.Assets.Presets.재무.json
Normal file
9
.decompiledproj/AxCopilot.Assets.Presets.재무.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"category": "재무",
|
||||
"label": "재무",
|
||||
"symbol": "\uE8C7",
|
||||
"color": "#D97706",
|
||||
"description": "재무회계·관리회계·원가·투자 분석",
|
||||
"systemPrompt": "당신은 반도체·디스플레이 제조업에 정통한 재무·회계 전문가입니다.\n\n## 전문 영역\n- 재무회계: K-IFRS 기반 재무제표 분석, 연결재무제표, 감사 대응, 공시 실무\n- 관리회계: 원가 계산(표준원가·활동기준원가 ABC), 변동비/고정비 분석, CVP 분석, 손익분기점\n- 반도체 원가 구조: 웨이퍼 원가, 수율 영향 원가, 감가상각(FAB 장비), 재공품(WIP) 평가\n- 투자 분석: 설비투자 타당성(NPV, IRR, Payback), 용량 확장(CAPEX) 의사결정, DCF 밸류에이션\n- 예산 관리: 제로베이스 예산, 롤링 예산, 차이 분석(예산 vs 실적), 부문별 손익\n- 세무: 법인세, 이전가격(TP), R&D 세액공제, 관세·FTA 원산지 관리\n- 자금 관리: 현금흐름 예측, 운전자본 최적화, 환위험 헤지(FX), 유동성 관리\n- 재무 비율: ROE, ROA, ROIC, EBITDA 마진, 부채비율, 유동비율, 재고자산회전율 해석\n\n## 응답 원칙\n- 회계 기준(K-IFRS/K-GAAP)을 명확히 구분하여 설명합니다\n- 숫자와 계산 과정을 투명하게 보여줍니다 (Excel 수식 형태 권장)\n- 의사결정에 필요한 민감도 분석·시나리오 분석을 포함합니다\n- 반도체 산업의 높은 고정비 구조와 대규모 설비투자 특성을 반영합니다\n- 세무·법률 사항은 전문가 확인을 권고하되, 실무 방향을 제시합니다",
|
||||
"placeholder": "원가 분석, 투자 타당성, 재무제표, 예산 등을 질문하세요..."
|
||||
}
|
||||
9
.decompiledproj/AxCopilot.Assets.Presets.제조기술.json
Normal file
9
.decompiledproj/AxCopilot.Assets.Presets.제조기술.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"category": "제조기술",
|
||||
"label": "제조기술",
|
||||
"symbol": "\uE90F",
|
||||
"color": "#10B981",
|
||||
"description": "공정·설비·생산 기술 지원",
|
||||
"systemPrompt": "당신은 반도체·디스플레이 제조 공정 및 설비 기술 전문가입니다.\n\n## 전문 영역\n- 반도체 공정: 증착(CVD/PVD/ALD), 에칭(Dry/Wet), 리소그래피, CMP, 이온주입\n- 디스플레이 공정: TFT 공정, 컬러 필터, 셀 공정, 모듈 공정, 봉지(Encapsulation)\n- 설비 관리: PM(Preventive Maintenance), 설비 효율(OEE), 챔버 관리, 파티클 제어\n- 공정 최적화: 레시피 개발, 공정 윈도우 확보, 공정 마진 분석\n- 생산 관리: 라인 밸런싱, 보틀넥 분석, 택트 타임 최적화, 자동화(FA)\n\n## 응답 원칙\n- 실제 제조 현장 경험에 기반한 실용적 솔루션을 제공합니다\n- 공정 파라미터와 물리·화학적 메커니즘을 연결하여 설명합니다\n- 트러블슈팅 시 체계적 접근(현상→원인→대책→검증)을 따릅니다\n- 설비 조건과 레시피를 구체적으로 다룹니다\n- 안전·환경 규정을 고려합니다",
|
||||
"placeholder": "공정 조건, 설비 이슈, 생산 기술을 질문하세요..."
|
||||
}
|
||||
9
.decompiledproj/AxCopilot.Assets.Presets.제품분석.json
Normal file
9
.decompiledproj/AxCopilot.Assets.Presets.제품분석.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"category": "제품분석",
|
||||
"label": "제품분석",
|
||||
"symbol": "\uE9D9",
|
||||
"color": "#EC4899",
|
||||
"description": "제품 품질·불량·신뢰성 분석",
|
||||
"systemPrompt": "당신은 반도체·디스플레이 제품의 품질 분석 및 신뢰성 공학 전문가입니다.\n\n## 전문 영역\n- 불량 분석: 8D Report, 5 Why, FTA(Fault Tree Analysis), FMEA\n- 품질 관리: QC 7 Tools, 6시그마(DMAIC), 관리도(X-bar, R chart)\n- 신뢰성 공학: 와이블 분석(Weibull), MTBF/MTTF, 가속 수명 시험(ALT)\n- 제품 특성 분석: 전기적 특성(I-V, C-V), 광학 특성, 기계적 특성\n- 불량 메커니즘: ESD, 마이그레이션, 디라미네이션, 크랙, 부식\n\n## 응답 원칙\n- 체계적인 불량 분석 프레임워크를 적용합니다\n- 근본 원인(Root Cause)까지 추적하는 분석을 제공합니다\n- 재발 방지 대책을 포함한 종합적 솔루션을 제시합니다\n- 데이터 기반의 정량적 분석을 우선합니다\n- 관련 규격(IPC, JEDEC, MIL-STD)을 참조합니다",
|
||||
"placeholder": "제품 불량, 품질 이슈, 신뢰성 분석을 질문하세요..."
|
||||
}
|
||||
183
.decompiledproj/AxCopilot.csproj
Normal file
183
.decompiledproj/AxCopilot.csproj
Normal file
@@ -0,0 +1,183 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
<PropertyGroup>
|
||||
<AssemblyName>AxCopilot</AssemblyName>
|
||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<UseWPF>True</UseWPF>
|
||||
<TargetFramework>netcoreapp8.0</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>app.ico</ApplicationIcon>
|
||||
<RootNamespace />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="views\largetypewindow.baml" />
|
||||
<None Remove="assets\quotes\display_semiconductor.json" />
|
||||
<None Remove="assets\quotes\greetings.json" />
|
||||
<None Remove="views\guideviewerwindow.baml" />
|
||||
<None Remove="views\agentstatsdashboardwindow.baml" />
|
||||
<None Remove="views\resourcemonitorwindow.baml" />
|
||||
<None Remove="assets\quotes\science.json" />
|
||||
<None Remove="themes\monokai.xaml" />
|
||||
<None Remove="views\workflowanalyzerwindow.baml" />
|
||||
<None Remove="themes\sepia.xaml" />
|
||||
<None Remove="themes\oled.xaml" />
|
||||
<None Remove="assets\quotes\english.json" />
|
||||
<None Remove="assets\quotes\motivational.json" />
|
||||
<None Remove="themes\catppuccin.xaml" />
|
||||
<None Remove="views\regionselectwindow.baml" />
|
||||
<None Remove="assets\quotes\history.json" />
|
||||
<None Remove="themes\codex.baml" />
|
||||
<None Remove="themes\alfredlight.xaml" />
|
||||
<None Remove="views\skillgallerywindow.baml" />
|
||||
<None Remove="assets\quotes\today_events.json" />
|
||||
<None Remove="views\commandpalettewindow.baml" />
|
||||
<None Remove="assets\mascot.png" />
|
||||
<None Remove="views\traymenuwindow.baml" />
|
||||
<None Remove="views\launcherwindow.baml" />
|
||||
<None Remove="assets\quotes\it_ai.json" />
|
||||
<None Remove="assets\quotes\movies.json" />
|
||||
<None Remove="themes\light.xaml" />
|
||||
<None Remove="assets\quotes\famous.json" />
|
||||
<None Remove="views\settingswindow.baml" />
|
||||
<None Remove="views\colorpickresultwindow.baml" />
|
||||
<None Remove="views\reminderpopupwindow.baml" />
|
||||
<None Remove="themes\alfred.xaml" />
|
||||
<None Remove="views\helpdetailwindow.baml" />
|
||||
<None Remove="themes\nord.xaml" />
|
||||
<None Remove="views\previewwindow.baml" />
|
||||
<None Remove="assets\icon.ico" />
|
||||
<None Remove="views\eyedropperwindow.baml" />
|
||||
<None Remove="views\skilleditorwindow.baml" />
|
||||
<None Remove="views\statisticswindow.baml" />
|
||||
<None Remove="views\chatwindow.baml" />
|
||||
<None Remove="views\shortcuthelpwindow.baml" />
|
||||
<None Remove="themes\dark.xaml" />
|
||||
<None Remove="assets\about.json" />
|
||||
<None Remove="app.baml" />
|
||||
<None Remove="views\textactionpopup.baml" />
|
||||
<None Remove="views\dockbarwindow.baml" />
|
||||
<None Remove="views\aboutwindow.baml" />
|
||||
<None Remove="AxCopilot.Assets.Presets.code_개발.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.code_리뷰.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.code_리팩터링.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.code_보안점검.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.code_테스트.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.cowork_논문.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.cowork_데이터분석.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.cowork_문서작성.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.cowork_보고서.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.cowork_자동화.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.cowork_파일관리.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.경영.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.수율분석.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.시스템.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.연구개발.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.인사.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.일반.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.재무.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.제조기술.json" />
|
||||
<None Remove="AxCopilot.Assets.Presets.제품분석.json" />
|
||||
<EmbeddedResource Include="views\largetypewindow.baml" LogicalName="views/largetypewindow.baml" />
|
||||
<EmbeddedResource Include="assets\quotes\display_semiconductor.json" LogicalName="assets/quotes/display_semiconductor.json" />
|
||||
<EmbeddedResource Include="assets\quotes\greetings.json" LogicalName="assets/quotes/greetings.json" />
|
||||
<EmbeddedResource Include="views\guideviewerwindow.baml" LogicalName="views/guideviewerwindow.baml" />
|
||||
<EmbeddedResource Include="views\agentstatsdashboardwindow.baml" LogicalName="views/agentstatsdashboardwindow.baml" />
|
||||
<EmbeddedResource Include="views\resourcemonitorwindow.baml" LogicalName="views/resourcemonitorwindow.baml" />
|
||||
<EmbeddedResource Include="assets\quotes\science.json" LogicalName="assets/quotes/science.json" />
|
||||
<EmbeddedResource Include="themes\monokai.xaml" LogicalName="themes/monokai.xaml" />
|
||||
<EmbeddedResource Include="views\workflowanalyzerwindow.baml" LogicalName="views/workflowanalyzerwindow.baml" />
|
||||
<EmbeddedResource Include="themes\sepia.xaml" LogicalName="themes/sepia.xaml" />
|
||||
<EmbeddedResource Include="themes\oled.xaml" LogicalName="themes/oled.xaml" />
|
||||
<EmbeddedResource Include="assets\quotes\english.json" LogicalName="assets/quotes/english.json" />
|
||||
<EmbeddedResource Include="assets\quotes\motivational.json" LogicalName="assets/quotes/motivational.json" />
|
||||
<EmbeddedResource Include="themes\catppuccin.xaml" LogicalName="themes/catppuccin.xaml" />
|
||||
<EmbeddedResource Include="views\regionselectwindow.baml" LogicalName="views/regionselectwindow.baml" />
|
||||
<EmbeddedResource Include="assets\quotes\history.json" LogicalName="assets/quotes/history.json" />
|
||||
<EmbeddedResource Include="themes\codex.baml" LogicalName="themes/codex.baml" />
|
||||
<EmbeddedResource Include="themes\alfredlight.xaml" LogicalName="themes/alfredlight.xaml" />
|
||||
<EmbeddedResource Include="views\skillgallerywindow.baml" LogicalName="views/skillgallerywindow.baml" />
|
||||
<EmbeddedResource Include="assets\quotes\today_events.json" LogicalName="assets/quotes/today_events.json" />
|
||||
<EmbeddedResource Include="views\commandpalettewindow.baml" LogicalName="views/commandpalettewindow.baml" />
|
||||
<EmbeddedResource Include="assets\mascot.png" LogicalName="assets/mascot.png" />
|
||||
<EmbeddedResource Include="views\traymenuwindow.baml" LogicalName="views/traymenuwindow.baml" />
|
||||
<EmbeddedResource Include="views\launcherwindow.baml" LogicalName="views/launcherwindow.baml" />
|
||||
<EmbeddedResource Include="assets\quotes\it_ai.json" LogicalName="assets/quotes/it_ai.json" />
|
||||
<EmbeddedResource Include="assets\quotes\movies.json" LogicalName="assets/quotes/movies.json" />
|
||||
<EmbeddedResource Include="themes\light.xaml" LogicalName="themes/light.xaml" />
|
||||
<EmbeddedResource Include="assets\quotes\famous.json" LogicalName="assets/quotes/famous.json" />
|
||||
<EmbeddedResource Include="views\settingswindow.baml" LogicalName="views/settingswindow.baml" />
|
||||
<EmbeddedResource Include="views\colorpickresultwindow.baml" LogicalName="views/colorpickresultwindow.baml" />
|
||||
<EmbeddedResource Include="views\reminderpopupwindow.baml" LogicalName="views/reminderpopupwindow.baml" />
|
||||
<EmbeddedResource Include="themes\alfred.xaml" LogicalName="themes/alfred.xaml" />
|
||||
<EmbeddedResource Include="views\helpdetailwindow.baml" LogicalName="views/helpdetailwindow.baml" />
|
||||
<EmbeddedResource Include="themes\nord.xaml" LogicalName="themes/nord.xaml" />
|
||||
<EmbeddedResource Include="views\previewwindow.baml" LogicalName="views/previewwindow.baml" />
|
||||
<EmbeddedResource Include="assets\icon.ico" LogicalName="assets/icon.ico" />
|
||||
<EmbeddedResource Include="views\eyedropperwindow.baml" LogicalName="views/eyedropperwindow.baml" />
|
||||
<EmbeddedResource Include="views\skilleditorwindow.baml" LogicalName="views/skilleditorwindow.baml" />
|
||||
<EmbeddedResource Include="views\statisticswindow.baml" LogicalName="views/statisticswindow.baml" />
|
||||
<EmbeddedResource Include="views\chatwindow.baml" LogicalName="views/chatwindow.baml" />
|
||||
<EmbeddedResource Include="views\shortcuthelpwindow.baml" LogicalName="views/shortcuthelpwindow.baml" />
|
||||
<EmbeddedResource Include="themes\dark.xaml" LogicalName="themes/dark.xaml" />
|
||||
<EmbeddedResource Include="assets\about.json" LogicalName="assets/about.json" />
|
||||
<EmbeddedResource Include="app.baml" LogicalName="app.baml" />
|
||||
<EmbeddedResource Include="views\textactionpopup.baml" LogicalName="views/textactionpopup.baml" />
|
||||
<EmbeddedResource Include="views\dockbarwindow.baml" LogicalName="views/dockbarwindow.baml" />
|
||||
<EmbeddedResource Include="views\aboutwindow.baml" LogicalName="views/aboutwindow.baml" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.code_개발.json" LogicalName="AxCopilot.Assets.Presets.code_개발.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.code_리뷰.json" LogicalName="AxCopilot.Assets.Presets.code_리뷰.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.code_리팩터링.json" LogicalName="AxCopilot.Assets.Presets.code_리팩터링.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.code_보안점검.json" LogicalName="AxCopilot.Assets.Presets.code_보안점검.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.code_테스트.json" LogicalName="AxCopilot.Assets.Presets.code_테스트.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.cowork_논문.json" LogicalName="AxCopilot.Assets.Presets.cowork_논문.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.cowork_데이터분석.json" LogicalName="AxCopilot.Assets.Presets.cowork_데이터분석.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.cowork_문서작성.json" LogicalName="AxCopilot.Assets.Presets.cowork_문서작성.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.cowork_보고서.json" LogicalName="AxCopilot.Assets.Presets.cowork_보고서.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.cowork_자동화.json" LogicalName="AxCopilot.Assets.Presets.cowork_자동화.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.cowork_파일관리.json" LogicalName="AxCopilot.Assets.Presets.cowork_파일관리.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.경영.json" LogicalName="AxCopilot.Assets.Presets.경영.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.수율분석.json" LogicalName="AxCopilot.Assets.Presets.수율분석.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.시스템.json" LogicalName="AxCopilot.Assets.Presets.시스템.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.연구개발.json" LogicalName="AxCopilot.Assets.Presets.연구개발.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.인사.json" LogicalName="AxCopilot.Assets.Presets.인사.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.일반.json" LogicalName="AxCopilot.Assets.Presets.일반.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.재무.json" LogicalName="AxCopilot.Assets.Presets.재무.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.제조기술.json" LogicalName="AxCopilot.Assets.Presets.제조기술.json" />
|
||||
<EmbeddedResource Include="AxCopilot.Assets.Presets.제품분석.json" LogicalName="AxCopilot.Assets.Presets.제품분석.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Web.WebView2.Wpf">
|
||||
<HintPath>src/AxCopilot/bin/Debug/net8.0-windows/win-x64/Microsoft.Web.WebView2.Wpf.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Web.WebView2.Core">
|
||||
<HintPath>src/AxCopilot/bin/Debug/net8.0-windows/win-x64/Microsoft.Web.WebView2.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="AxCopilot.SDK">
|
||||
<HintPath>src/AxCopilot/bin/Debug/net8.0-windows/win-x64/AxCopilot.SDK.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Data.Sqlite">
|
||||
<HintPath>src/AxCopilot/bin/Debug/net8.0-windows/win-x64/Microsoft.Data.Sqlite.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="DocumentFormat.OpenXml">
|
||||
<HintPath>src/AxCopilot/bin/Debug/net8.0-windows/win-x64/DocumentFormat.OpenXml.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UglyToad.PdfPig">
|
||||
<HintPath>src/AxCopilot/bin/Debug/net8.0-windows/win-x64/UglyToad.PdfPig.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="DocumentFormat.OpenXml.Framework">
|
||||
<HintPath>src/AxCopilot/bin/Debug/net8.0-windows/win-x64/DocumentFormat.OpenXml.Framework.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Markdig">
|
||||
<HintPath>src/AxCopilot/bin/Debug/net8.0-windows/win-x64/Markdig.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ServiceProcess.ServiceController">
|
||||
<HintPath>src/AxCopilot/bin/Debug/net8.0-windows/win-x64/System.ServiceProcess.ServiceController.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
943
.decompiledproj/AxCopilot/App.cs
Normal file
943
.decompiledproj/AxCopilot/App.cs
Normal file
@@ -0,0 +1,943 @@
|
||||
using System;
|
||||
using System.CodeDom.Compiler;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.Core;
|
||||
using AxCopilot.Handlers;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.Security;
|
||||
using AxCopilot.Services;
|
||||
using AxCopilot.ViewModels;
|
||||
using AxCopilot.Views;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace AxCopilot;
|
||||
|
||||
public class App : System.Windows.Application
|
||||
{
|
||||
private static class NativeMethods
|
||||
{
|
||||
public struct INPUT
|
||||
{
|
||||
public uint type;
|
||||
|
||||
public InputUnion u;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct InputUnion
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public KEYBDINPUT ki;
|
||||
}
|
||||
|
||||
public struct KEYBDINPUT
|
||||
{
|
||||
public ushort wVk;
|
||||
|
||||
public ushort wScan;
|
||||
|
||||
public uint dwFlags;
|
||||
|
||||
public uint time;
|
||||
|
||||
public nint dwExtraInfo;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
[CompilerGenerated]
|
||||
private sealed class _003C_003Ec
|
||||
{
|
||||
public static readonly _003C_003Ec _003C_003E9 = new _003C_003Ec();
|
||||
|
||||
public static DispatcherUnhandledExceptionEventHandler _003C_003E9__24_0;
|
||||
|
||||
public static Action _003C_003E9__31_10;
|
||||
|
||||
public static Action _003C_003E9__31_17;
|
||||
|
||||
public static Action _003C_003E9__31_18;
|
||||
|
||||
public static Action<bool> _003C_003E9__31_13;
|
||||
|
||||
public static Action _003C_003E9__31_19;
|
||||
|
||||
internal void _003COnStartup_003Eb__24_0(object _, DispatcherUnhandledExceptionEventArgs ex)
|
||||
{
|
||||
LogService.Error($"미처리 예외: {ex.Exception}");
|
||||
CustomMessageBox.Show("오류가 발생했습니다:\n" + ex.Exception.Message + "\n\n로그 폴더를 확인하세요.", "AX Copilot 오류", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||||
ex.Handled = true;
|
||||
}
|
||||
|
||||
internal void _003CSetupTrayIcon_003Eb__31_10()
|
||||
{
|
||||
string arguments = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "logs");
|
||||
Process.Start(new ProcessStartInfo("explorer.exe", arguments)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
|
||||
internal void _003CSetupTrayIcon_003Eb__31_17()
|
||||
{
|
||||
new StatisticsWindow().Show();
|
||||
}
|
||||
|
||||
internal void _003CSetupTrayIcon_003Eb__31_18()
|
||||
{
|
||||
new GuideViewerWindow().Show();
|
||||
}
|
||||
|
||||
internal void _003CSetupTrayIcon_003Eb__31_13(bool isChecked)
|
||||
{
|
||||
SetAutoStart(isChecked);
|
||||
}
|
||||
|
||||
internal void _003CSetupTrayIcon_003Eb__31_19()
|
||||
{
|
||||
new AboutWindow().Show();
|
||||
}
|
||||
}
|
||||
|
||||
private Mutex? _singleInstanceMutex;
|
||||
|
||||
private InputListener? _inputListener;
|
||||
|
||||
private LauncherWindow? _launcher;
|
||||
|
||||
private NotifyIcon? _trayIcon;
|
||||
|
||||
private TrayMenuWindow? _trayMenu;
|
||||
|
||||
private SettingsService? _settings;
|
||||
|
||||
private SettingsWindow? _settingsWindow;
|
||||
|
||||
private PluginHost? _pluginHost;
|
||||
|
||||
private ClipboardHistoryService? _clipboardHistory;
|
||||
|
||||
private DockBarWindow? _dockBar;
|
||||
|
||||
private FileDialogWatcher? _fileDialogWatcher;
|
||||
|
||||
private volatile IndexService? _indexService;
|
||||
|
||||
private SessionTrackingService? _sessionTracking;
|
||||
|
||||
private WorktimeReminderService? _worktimeReminder;
|
||||
|
||||
private ScreenCaptureHandler? _captureHandler;
|
||||
|
||||
private AgentMemoryService? _memoryService;
|
||||
|
||||
private ChatWindow? _chatWindow;
|
||||
|
||||
private const string AutoRunKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
||||
|
||||
private const string AutoRunName = "AxCopilot";
|
||||
|
||||
private bool _contentLoaded;
|
||||
|
||||
public IndexService? IndexService => _indexService;
|
||||
|
||||
public SettingsService? SettingsService => _settings;
|
||||
|
||||
public ClipboardHistoryService? ClipboardHistoryService => _clipboardHistory;
|
||||
|
||||
public AgentMemoryService? MemoryService => _memoryService;
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
//IL_0031: Unknown result type (might be due to invalid IL or missing references)
|
||||
//IL_0036: Unknown result type (might be due to invalid IL or missing references)
|
||||
//IL_003c: Expected O, but got Unknown
|
||||
base.OnStartup(e);
|
||||
AntiTamper.Check();
|
||||
object obj = _003C_003Ec._003C_003E9__24_0;
|
||||
if (obj == null)
|
||||
{
|
||||
DispatcherUnhandledExceptionEventHandler val = delegate(object _, DispatcherUnhandledExceptionEventArgs ex)
|
||||
{
|
||||
LogService.Error($"미처리 예외: {ex.Exception}");
|
||||
CustomMessageBox.Show("오류가 발생했습니다:\n" + ex.Exception.Message + "\n\n로그 폴더를 확인하세요.", "AX Copilot 오류", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||||
ex.Handled = true;
|
||||
};
|
||||
_003C_003Ec._003C_003E9__24_0 = val;
|
||||
obj = (object)val;
|
||||
}
|
||||
base.DispatcherUnhandledException += (DispatcherUnhandledExceptionEventHandler)obj;
|
||||
_singleInstanceMutex = new Mutex(initiallyOwned: true, "Global\\AXCopilot_SingleInstance", out var createdNew);
|
||||
if (!createdNew)
|
||||
{
|
||||
_singleInstanceMutex.Dispose();
|
||||
_singleInstanceMutex = null;
|
||||
CustomMessageBox.Show("AX Copilot가 이미 실행 중입니다.\n트레이 아이콘을 확인하세요.", "AX Copilot", MessageBoxButton.OK, MessageBoxImage.Asterisk);
|
||||
Shutdown();
|
||||
return;
|
||||
}
|
||||
LogService.Info("=== AX Copilot 시작 ===");
|
||||
_settings = new SettingsService();
|
||||
SettingsService settings = _settings;
|
||||
settings.Load();
|
||||
if (settings.MigrationSummary != null)
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.BeginInvoke((Delegate)(Action)delegate
|
||||
{
|
||||
CustomMessageBox.Show(settings.MigrationSummary, "AX Copilot — 설정 업데이트", MessageBoxButton.OK, MessageBoxImage.Asterisk);
|
||||
}, (DispatcherPriority)6, Array.Empty<object>());
|
||||
}
|
||||
L10n.SetLanguage(settings.Settings.Launcher.Language);
|
||||
if (!IsAutoStartEnabled())
|
||||
{
|
||||
SetAutoStart(enable: true);
|
||||
}
|
||||
_memoryService = new AgentMemoryService();
|
||||
_memoryService.Load(settings.Settings.Llm.WorkFolder);
|
||||
_indexService = new IndexService(settings);
|
||||
IndexService indexService = _indexService;
|
||||
FuzzyEngine fuzzy = new FuzzyEngine(indexService);
|
||||
CommandResolver commandResolver = new CommandResolver(fuzzy, settings);
|
||||
ContextManager context = new ContextManager(settings);
|
||||
_sessionTracking = new SessionTrackingService();
|
||||
_worktimeReminder = new WorktimeReminderService(settings, ((DispatcherObject)this).Dispatcher);
|
||||
_clipboardHistory = new ClipboardHistoryService(settings);
|
||||
commandResolver.RegisterHandler(new CalculatorHandler());
|
||||
commandResolver.RegisterHandler(new SystemCommandHandler(settings));
|
||||
commandResolver.RegisterHandler(new SnippetHandler(settings));
|
||||
commandResolver.RegisterHandler(new ClipboardHistoryHandler(_clipboardHistory));
|
||||
commandResolver.RegisterHandler(new WorkspaceHandler(context, settings));
|
||||
commandResolver.RegisterHandler(new UrlAliasHandler(settings));
|
||||
commandResolver.RegisterHandler(new FolderAliasHandler(settings));
|
||||
commandResolver.RegisterHandler(new BatchHandler(settings));
|
||||
commandResolver.RegisterHandler(new ClipboardHandler(settings));
|
||||
commandResolver.RegisterHandler(new BookmarkHandler());
|
||||
commandResolver.RegisterHandler(new WebSearchHandler(settings));
|
||||
commandResolver.RegisterHandler(new ProcessHandler());
|
||||
commandResolver.RegisterHandler(new MediaHandler());
|
||||
commandResolver.RegisterHandler(new SystemInfoHandler());
|
||||
commandResolver.RegisterHandler(new StarInfoHandler());
|
||||
commandResolver.RegisterHandler(new EmojiHandler());
|
||||
commandResolver.RegisterHandler(new ColorHandler());
|
||||
commandResolver.RegisterHandler(new RecentFilesHandler());
|
||||
commandResolver.RegisterHandler(new NoteHandler());
|
||||
commandResolver.RegisterHandler(new UninstallHandler());
|
||||
commandResolver.RegisterHandler(new PortHandler());
|
||||
commandResolver.RegisterHandler(new EnvHandler());
|
||||
commandResolver.RegisterHandler(new JsonHandler());
|
||||
commandResolver.RegisterHandler(new EncodeHandler());
|
||||
commandResolver.RegisterHandler(new SnapHandler());
|
||||
_captureHandler = new ScreenCaptureHandler(settings);
|
||||
commandResolver.RegisterHandler(_captureHandler);
|
||||
commandResolver.RegisterHandler(new ColorPickHandler());
|
||||
commandResolver.RegisterHandler(new DateCalcHandler());
|
||||
commandResolver.RegisterHandler(new ServiceHandler(_clipboardHistory));
|
||||
commandResolver.RegisterHandler(new ClipboardPipeHandler());
|
||||
commandResolver.RegisterHandler(new JournalHandler());
|
||||
commandResolver.RegisterHandler(new RoutineHandler());
|
||||
commandResolver.RegisterHandler(new BatchTextHandler());
|
||||
commandResolver.RegisterHandler(new DiffHandler(_clipboardHistory));
|
||||
commandResolver.RegisterHandler(new WindowSwitchHandler());
|
||||
commandResolver.RegisterHandler(new RunHandler());
|
||||
commandResolver.RegisterHandler(new TextStatsHandler());
|
||||
commandResolver.RegisterHandler(new FavoriteHandler());
|
||||
commandResolver.RegisterHandler(new RenameHandler());
|
||||
commandResolver.RegisterHandler(new MonitorHandler());
|
||||
commandResolver.RegisterHandler(new ScaffoldHandler());
|
||||
commandResolver.RegisterHandler(new EverythingHandler());
|
||||
commandResolver.RegisterHandler(new HelpHandler(settings));
|
||||
commandResolver.RegisterHandler(new ChatHandler(settings));
|
||||
PluginHost pluginHost = new PluginHost(settings, commandResolver);
|
||||
pluginHost.LoadAll();
|
||||
_pluginHost = pluginHost;
|
||||
LauncherViewModel vm = new LauncherViewModel(commandResolver, settings);
|
||||
_launcher = new LauncherWindow(vm)
|
||||
{
|
||||
OpenSettingsAction = OpenSettings
|
||||
};
|
||||
((DispatcherObject)this).Dispatcher.BeginInvoke((Delegate)(Action)delegate
|
||||
{
|
||||
_clipboardHistory?.Initialize();
|
||||
}, (DispatcherPriority)2, Array.Empty<object>());
|
||||
((DispatcherObject)this).Dispatcher.BeginInvoke((Delegate)(Action)delegate
|
||||
{
|
||||
PrewarmChatWindow();
|
||||
}, (DispatcherPriority)1, Array.Empty<object>());
|
||||
indexService.BuildAsync().ContinueWith(delegate
|
||||
{
|
||||
indexService.StartWatchers();
|
||||
});
|
||||
_inputListener = new InputListener();
|
||||
_inputListener.HotkeyTriggered += OnHotkeyTriggered;
|
||||
_inputListener.CaptureHotkeyTriggered += OnCaptureHotkeyTriggered;
|
||||
_inputListener.HookFailed += OnHookFailed;
|
||||
_inputListener.UpdateHotkey(settings.Settings.Hotkey);
|
||||
_inputListener.UpdateCaptureHotkey(settings.Settings.ScreenCapture.GlobalHotkey, settings.Settings.ScreenCapture.GlobalHotkeyEnabled);
|
||||
SnippetExpander snippetExpander = new SnippetExpander(settings);
|
||||
_inputListener.KeyFilter = snippetExpander.HandleKey;
|
||||
_inputListener.Start();
|
||||
SetupTrayIcon(pluginHost, settings);
|
||||
_fileDialogWatcher = new FileDialogWatcher();
|
||||
_fileDialogWatcher.FileDialogOpened += delegate
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
if (_launcher != null && !_launcher.IsVisible)
|
||||
{
|
||||
SettingsService? settings2 = _settings;
|
||||
if (settings2 != null && settings2.Settings.Launcher.EnableFileDialogIntegration)
|
||||
{
|
||||
WindowTracker.Capture();
|
||||
_launcher.Show();
|
||||
((DispatcherObject)this).Dispatcher.BeginInvoke((DispatcherPriority)5, (Delegate)(Action)delegate
|
||||
{
|
||||
_launcher.SetInputText("cd ");
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
_fileDialogWatcher.Start();
|
||||
if (settings.Settings.Launcher.DockBarAutoShow)
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.BeginInvoke((DispatcherPriority)6, (Delegate)(Action)delegate
|
||||
{
|
||||
ToggleDockBar();
|
||||
});
|
||||
}
|
||||
LogService.Info("초기화 완료. " + settings.Settings.Hotkey + "로 실행하세요.");
|
||||
}
|
||||
|
||||
private void OnHotkeyTriggered(object? sender, EventArgs e)
|
||||
{
|
||||
WindowTracker.Capture();
|
||||
LauncherSettings launcherSettings = _settings?.Settings.Launcher;
|
||||
bool flag = launcherSettings?.EnableTextAction ?? false;
|
||||
bool isVisible = false;
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
isVisible = _launcher?.IsVisible ?? false;
|
||||
});
|
||||
if (isVisible)
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
_launcher?.Hide();
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!flag)
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
if (_launcher != null)
|
||||
{
|
||||
UsageStatisticsService.RecordLauncherOpen();
|
||||
_launcher.Show();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
string selectedText = TryGetSelectedText();
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
if (_launcher != null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(selectedText))
|
||||
{
|
||||
List<string> enabledCmds = launcherSettings?.TextActionCommands ?? new List<string>();
|
||||
if (enabledCmds.Count == 1 && !string.IsNullOrEmpty(TextActionPopup.AvailableCommands.FirstOrDefault<(string, string)>(((string Key, string Label) c) => c.Key == enabledCmds[0]).Item1))
|
||||
{
|
||||
string text = enabledCmds[0];
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
TextActionPopup.ActionResult actionResult = text switch
|
||||
{
|
||||
"translate" => TextActionPopup.ActionResult.Translate,
|
||||
"summarize" => TextActionPopup.ActionResult.Summarize,
|
||||
"grammar" => TextActionPopup.ActionResult.GrammarFix,
|
||||
"explain" => TextActionPopup.ActionResult.Explain,
|
||||
"rewrite" => TextActionPopup.ActionResult.Rewrite,
|
||||
_ => TextActionPopup.ActionResult.None,
|
||||
};
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
TextActionPopup.ActionResult actionResult2 = actionResult;
|
||||
if (actionResult2 != TextActionPopup.ActionResult.None)
|
||||
{
|
||||
ExecuteTextAction(actionResult2, selectedText);
|
||||
return;
|
||||
}
|
||||
}
|
||||
TextActionPopup popup = new TextActionPopup(selectedText, enabledCmds);
|
||||
popup.Closed += delegate
|
||||
{
|
||||
switch (popup.SelectedAction)
|
||||
{
|
||||
case TextActionPopup.ActionResult.OpenLauncher:
|
||||
UsageStatisticsService.RecordLauncherOpen();
|
||||
_launcher.Show();
|
||||
break;
|
||||
case TextActionPopup.ActionResult.None:
|
||||
break;
|
||||
default:
|
||||
ExecuteTextAction(popup.SelectedAction, popup.SelectedText);
|
||||
break;
|
||||
}
|
||||
};
|
||||
popup.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
UsageStatisticsService.RecordLauncherOpen();
|
||||
_launcher.Show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private string? TryGetSelectedText()
|
||||
{
|
||||
try
|
||||
{
|
||||
string prevText = null;
|
||||
bool hadText = false;
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
hadText = System.Windows.Clipboard.ContainsText();
|
||||
prevText = (hadText ? System.Windows.Clipboard.GetText() : null);
|
||||
System.Windows.Clipboard.Clear();
|
||||
});
|
||||
Thread.Sleep(30);
|
||||
NativeMethods.INPUT[] array = new NativeMethods.INPUT[4]
|
||||
{
|
||||
new NativeMethods.INPUT
|
||||
{
|
||||
type = 1u,
|
||||
u = new NativeMethods.InputUnion
|
||||
{
|
||||
ki = new NativeMethods.KEYBDINPUT
|
||||
{
|
||||
wVk = 17
|
||||
}
|
||||
}
|
||||
},
|
||||
new NativeMethods.INPUT
|
||||
{
|
||||
type = 1u,
|
||||
u = new NativeMethods.InputUnion
|
||||
{
|
||||
ki = new NativeMethods.KEYBDINPUT
|
||||
{
|
||||
wVk = 67
|
||||
}
|
||||
}
|
||||
},
|
||||
new NativeMethods.INPUT
|
||||
{
|
||||
type = 1u,
|
||||
u = new NativeMethods.InputUnion
|
||||
{
|
||||
ki = new NativeMethods.KEYBDINPUT
|
||||
{
|
||||
wVk = 67,
|
||||
dwFlags = 2u
|
||||
}
|
||||
}
|
||||
},
|
||||
new NativeMethods.INPUT
|
||||
{
|
||||
type = 1u,
|
||||
u = new NativeMethods.InputUnion
|
||||
{
|
||||
ki = new NativeMethods.KEYBDINPUT
|
||||
{
|
||||
wVk = 17,
|
||||
dwFlags = 2u
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
NativeMethods.SendInput((uint)array.Length, array, Marshal.SizeOf<NativeMethods.INPUT>());
|
||||
Thread.Sleep(80);
|
||||
string selected = null;
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
if (System.Windows.Clipboard.ContainsText())
|
||||
{
|
||||
string text = System.Windows.Clipboard.GetText();
|
||||
if (text != prevText && !string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
selected = text;
|
||||
}
|
||||
}
|
||||
if (selected != null && prevText != null)
|
||||
{
|
||||
System.Windows.Clipboard.SetText(prevText);
|
||||
}
|
||||
else if (selected != null && !hadText)
|
||||
{
|
||||
System.Windows.Clipboard.Clear();
|
||||
}
|
||||
});
|
||||
return selected;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteTextAction(TextActionPopup.ActionResult action, string text)
|
||||
{
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string text2 = action switch
|
||||
{
|
||||
TextActionPopup.ActionResult.Translate => "다음 텍스트를 번역해줘:\n\n" + text,
|
||||
TextActionPopup.ActionResult.Summarize => "다음 텍스트를 요약해줘:\n\n" + text,
|
||||
TextActionPopup.ActionResult.GrammarFix => "다음 텍스트의 문법을 교정해줘:\n\n" + text,
|
||||
TextActionPopup.ActionResult.Explain => "다음 텍스트를 쉽게 설명해줘:\n\n" + text,
|
||||
TextActionPopup.ActionResult.Rewrite => "다음 텍스트를 다시 작성해줘:\n\n" + text,
|
||||
_ => text,
|
||||
};
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string text3 = text2;
|
||||
_launcher?.Show();
|
||||
_launcher?.SetInputText("! " + text3);
|
||||
}
|
||||
|
||||
private void OnCaptureHotkeyTriggered(object? sender, EventArgs e)
|
||||
{
|
||||
WindowTracker.Capture();
|
||||
string mode = _settings?.Settings.ScreenCapture.GlobalHotkeyMode ?? "screen";
|
||||
((DispatcherObject)this).Dispatcher.Invoke<Task>((Func<Task>)async delegate
|
||||
{
|
||||
if (_captureHandler == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
await _captureHandler.CaptureDirectAsync(mode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Error("글로벌 캡처 실패: " + ex.Message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void OnHookFailed(object? sender, EventArgs e)
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
_trayIcon?.ShowBalloonTip(3000, "AX Copilot", "글로벌 단축키 등록에 실패했습니다.\n다른 앱과 " + (_settings?.Settings.Hotkey ?? "단축키") + "가 충돌하고 있을 수 있습니다.", ToolTipIcon.Warning);
|
||||
});
|
||||
}
|
||||
|
||||
private void SetupTrayIcon(PluginHost pluginHost, SettingsService settings)
|
||||
{
|
||||
_trayIcon = new NotifyIcon
|
||||
{
|
||||
Text = "AX Copilot",
|
||||
Visible = true,
|
||||
Icon = LoadAppIcon()
|
||||
};
|
||||
_trayMenu = new TrayMenuWindow();
|
||||
_trayMenu.AddItem("\ue7c5", "AX Commander 호출하기", delegate
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
_launcher?.Show();
|
||||
});
|
||||
}).AddItem("\ue8bd", "AX Agent 대화하기", delegate
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
OpenAiChat();
|
||||
});
|
||||
}, out Border aiTrayItem).AddItem("\ue8a7", "독 바 표시", delegate
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
ToggleDockBar();
|
||||
});
|
||||
})
|
||||
.AddSeparator()
|
||||
.AddItem("\ue72c", "플러그인 재로드", delegate
|
||||
{
|
||||
pluginHost.Reload();
|
||||
_trayIcon.ShowBalloonTip(2000, "AX Copilot", "플러그인이 재로드되었습니다.", ToolTipIcon.None);
|
||||
})
|
||||
.AddItem("\ue838", "로그 폴더 열기", delegate
|
||||
{
|
||||
string arguments = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "logs");
|
||||
Process.Start(new ProcessStartInfo("explorer.exe", arguments)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
})
|
||||
.AddItem("\ue9d9", "사용 통계", delegate
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
new StatisticsWindow().Show();
|
||||
});
|
||||
})
|
||||
.AddItem("\ue736", "사용 가이드 문서보기", delegate
|
||||
{
|
||||
try
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
new GuideViewerWindow().Show();
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Error("사용 가이드 열기 실패: " + ex.Message);
|
||||
}
|
||||
})
|
||||
.AddSeparator()
|
||||
.AddToggleItem("\ue82f", "Windows 시작 시 자동 실행", IsAutoStartEnabled(), delegate(bool isChecked)
|
||||
{
|
||||
SetAutoStart(isChecked);
|
||||
}, out Func<bool> _, out Action<string> _)
|
||||
.AddSeparator()
|
||||
.AddItem("\ue713", "설정", delegate
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)OpenSettings);
|
||||
})
|
||||
.AddItem("\ue946", "개발 정보", delegate
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
new AboutWindow().Show();
|
||||
});
|
||||
})
|
||||
.AddItem("\ue711", "종료", delegate
|
||||
{
|
||||
_inputListener?.Dispose();
|
||||
_trayIcon?.Dispose();
|
||||
Shutdown();
|
||||
});
|
||||
_trayMenu.Opening += delegate
|
||||
{
|
||||
aiTrayItem.Visibility = ((!settings.Settings.AiEnabled) ? Visibility.Collapsed : Visibility.Visible);
|
||||
};
|
||||
_trayIcon.MouseClick += delegate(object? _, MouseEventArgs e)
|
||||
{
|
||||
if (e.Button == MouseButtons.Left)
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
_launcher?.Show();
|
||||
});
|
||||
}
|
||||
else if (e.Button == MouseButtons.Right)
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
_trayMenu?.ShowWithUpdate();
|
||||
});
|
||||
}
|
||||
};
|
||||
NotificationService.Initialize(delegate(string title, string msg)
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
_trayIcon?.ShowBalloonTip(4000, title, msg, ToolTipIcon.None);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void OpenSettingsFromChat()
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.Invoke((Action)OpenSettings);
|
||||
}
|
||||
|
||||
internal void PrewarmChatWindow()
|
||||
{
|
||||
if (_chatWindow == null && _settings != null)
|
||||
{
|
||||
_chatWindow = new ChatWindow(_settings);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenAiChat()
|
||||
{
|
||||
if (_settings != null)
|
||||
{
|
||||
if (_chatWindow == null)
|
||||
{
|
||||
_chatWindow = new ChatWindow(_settings);
|
||||
}
|
||||
_chatWindow.Show();
|
||||
_chatWindow.Activate();
|
||||
}
|
||||
}
|
||||
|
||||
public void ToggleDockBar()
|
||||
{
|
||||
if (_dockBar != null && _dockBar.IsVisible)
|
||||
{
|
||||
_dockBar.Hide();
|
||||
return;
|
||||
}
|
||||
if (_dockBar == null)
|
||||
{
|
||||
_dockBar = new DockBarWindow();
|
||||
_dockBar.OnQuickSearch = delegate(string query)
|
||||
{
|
||||
if (_launcher != null)
|
||||
{
|
||||
_launcher.Show();
|
||||
_launcher.Activate();
|
||||
if (!string.IsNullOrEmpty(query))
|
||||
{
|
||||
((DispatcherObject)this).Dispatcher.BeginInvoke((DispatcherPriority)5, (Delegate)(Action)delegate
|
||||
{
|
||||
_launcher.SetInputText(query);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
_dockBar.OnCapture = async delegate
|
||||
{
|
||||
WindowTracker.Capture();
|
||||
if (_captureHandler != null)
|
||||
{
|
||||
await _captureHandler.CaptureDirectAsync("region");
|
||||
}
|
||||
};
|
||||
_dockBar.OnOpenAgent = delegate
|
||||
{
|
||||
if (_launcher != null)
|
||||
{
|
||||
_launcher.Show();
|
||||
((DispatcherObject)this).Dispatcher.BeginInvoke((DispatcherPriority)5, (Delegate)(Action)delegate
|
||||
{
|
||||
_launcher.SetInputText("!");
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
LauncherSettings launcherSettings = _settings?.Settings.Launcher;
|
||||
List<string> itemKeys = launcherSettings?.DockBarItems ?? new List<string> { "launcher", "clipboard", "capture", "agent", "clock", "cpu" };
|
||||
_dockBar.BuildFromSettings(itemKeys);
|
||||
_dockBar.OnPositionChanged = delegate(double left, double top)
|
||||
{
|
||||
if (_settings != null)
|
||||
{
|
||||
_settings.Settings.Launcher.DockBarLeft = left;
|
||||
_settings.Settings.Launcher.DockBarTop = top;
|
||||
_settings.Save();
|
||||
}
|
||||
};
|
||||
_dockBar.Show();
|
||||
_dockBar.ApplySettings(launcherSettings?.DockBarOpacity ?? 0.92, launcherSettings?.DockBarLeft ?? (-1.0), launcherSettings?.DockBarTop ?? (-1.0), launcherSettings?.DockBarRainbowGlow ?? false);
|
||||
}
|
||||
|
||||
public void RefreshDockBar()
|
||||
{
|
||||
if (_dockBar != null && _dockBar.IsVisible)
|
||||
{
|
||||
LauncherSettings launcherSettings = _settings?.Settings.Launcher;
|
||||
List<string> itemKeys = launcherSettings?.DockBarItems ?? new List<string> { "launcher", "clipboard", "capture", "agent", "clock", "cpu" };
|
||||
_dockBar.BuildFromSettings(itemKeys);
|
||||
_dockBar.ApplySettings(launcherSettings?.DockBarOpacity ?? 0.92, launcherSettings?.DockBarLeft ?? (-1.0), launcherSettings?.DockBarTop ?? (-1.0), launcherSettings?.DockBarRainbowGlow ?? false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenSettings()
|
||||
{
|
||||
SettingsViewModel vm;
|
||||
if (_settingsWindow != null && _settingsWindow.IsVisible)
|
||||
{
|
||||
_settingsWindow.Activate();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_settings == null || _launcher == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
vm = new SettingsViewModel(_settings);
|
||||
_settingsWindow = new SettingsWindow(vm, PreviewCallback, RevertCallback)
|
||||
{
|
||||
SuspendHotkeyCallback = delegate(bool suspend)
|
||||
{
|
||||
if (_inputListener != null)
|
||||
{
|
||||
_inputListener.SuspendHotkey = suspend;
|
||||
}
|
||||
}
|
||||
};
|
||||
vm.SaveCompleted += delegate
|
||||
{
|
||||
if (_inputListener != null && _settings != null)
|
||||
{
|
||||
_inputListener.UpdateHotkey(_settings.Settings.Hotkey);
|
||||
_inputListener.UpdateCaptureHotkey(_settings.Settings.ScreenCapture.GlobalHotkey, _settings.Settings.ScreenCapture.GlobalHotkeyEnabled);
|
||||
}
|
||||
_worktimeReminder?.RestartTimer();
|
||||
};
|
||||
_settingsWindow.Show();
|
||||
}
|
||||
void PreviewCallback(string themeKey)
|
||||
{
|
||||
if (themeKey == "custom")
|
||||
{
|
||||
CustomThemeColors customThemeColors = new CustomThemeColors();
|
||||
foreach (ColorRowModel colorRow in vm.ColorRows)
|
||||
{
|
||||
typeof(CustomThemeColors).GetProperty(colorRow.Property)?.SetValue(customThemeColors, colorRow.Hex);
|
||||
}
|
||||
_launcher.ApplyTheme(themeKey, customThemeColors);
|
||||
}
|
||||
else
|
||||
{
|
||||
_launcher.ApplyTheme(themeKey, _settings.Settings.Launcher.CustomTheme);
|
||||
}
|
||||
}
|
||||
void RevertCallback()
|
||||
{
|
||||
_launcher.ApplyTheme(_settings.Settings.Launcher.Theme ?? "system", _settings.Settings.Launcher.CustomTheme);
|
||||
}
|
||||
}
|
||||
|
||||
private static Icon LoadAppIcon()
|
||||
{
|
||||
Size smallIconSize = SystemInformation.SmallIconSize;
|
||||
try
|
||||
{
|
||||
string path = Path.GetDirectoryName(Environment.ProcessPath) ?? AppContext.BaseDirectory;
|
||||
string text = Path.Combine(path, "Assets", "icon.ico");
|
||||
if (File.Exists(text))
|
||||
{
|
||||
return new Icon(text, smallIconSize);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
try
|
||||
{
|
||||
Uri uriResource = new Uri("pack://application:,,,/Assets/icon.ico");
|
||||
Stream stream = System.Windows.Application.GetResourceStream(uriResource)?.Stream;
|
||||
if (stream != null)
|
||||
{
|
||||
return new Icon(stream, smallIconSize);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return SystemIcons.Application;
|
||||
}
|
||||
|
||||
private static bool IsAutoStartEnabled()
|
||||
{
|
||||
try
|
||||
{
|
||||
using RegistryKey registryKey = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Run", writable: false);
|
||||
return registryKey?.GetValue("AxCopilot") != null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetAutoStart(bool enable)
|
||||
{
|
||||
try
|
||||
{
|
||||
using RegistryKey registryKey = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Run", writable: true);
|
||||
if (registryKey == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (enable)
|
||||
{
|
||||
string text = Environment.ProcessPath ?? Process.GetCurrentProcess().MainModule?.FileName ?? string.Empty;
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
registryKey.SetValue("AxCopilot", "\"" + text + "\"");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
registryKey.DeleteValue("AxCopilot", throwOnMissingValue: false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("자동 시작 레지스트리 설정 실패: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnExit(ExitEventArgs e)
|
||||
{
|
||||
_chatWindow?.ForceClose();
|
||||
_inputListener?.Dispose();
|
||||
_clipboardHistory?.Dispose();
|
||||
_indexService?.Dispose();
|
||||
_sessionTracking?.Dispose();
|
||||
_worktimeReminder?.Dispose();
|
||||
_trayIcon?.Dispose();
|
||||
try
|
||||
{
|
||||
_singleInstanceMutex?.ReleaseMutex();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
_singleInstanceMutex?.Dispose();
|
||||
LogService.Info("=== AX Copilot 종료 ===");
|
||||
base.OnExit(e);
|
||||
}
|
||||
|
||||
[DebuggerNonUserCode]
|
||||
[GeneratedCode("PresentationBuildTasks", "10.0.5.0")]
|
||||
public void InitializeComponent()
|
||||
{
|
||||
if (!_contentLoaded)
|
||||
{
|
||||
_contentLoaded = true;
|
||||
Uri resourceLocator = new Uri("/AxCopilot;component/app.xaml", UriKind.Relative);
|
||||
System.Windows.Application.LoadComponent(this, resourceLocator);
|
||||
}
|
||||
}
|
||||
|
||||
[STAThread]
|
||||
[DebuggerNonUserCode]
|
||||
[GeneratedCode("PresentationBuildTasks", "10.0.5.0")]
|
||||
public static void Main()
|
||||
{
|
||||
App app = new App();
|
||||
app.InitializeComponent();
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
47
.decompiledproj/AxCopilot/Core/BulkObservableCollection.cs
Normal file
47
.decompiledproj/AxCopilot/Core/BulkObservableCollection.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace AxCopilot.Core;
|
||||
|
||||
public sealed class BulkObservableCollection<T> : ObservableCollection<T>
|
||||
{
|
||||
private bool _suppressNotification;
|
||||
|
||||
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (!_suppressNotification)
|
||||
{
|
||||
base.OnCollectionChanged(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
|
||||
{
|
||||
if (!_suppressNotification)
|
||||
{
|
||||
base.OnPropertyChanged(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReplaceAll(IEnumerable<T> items)
|
||||
{
|
||||
_suppressNotification = true;
|
||||
try
|
||||
{
|
||||
base.Items.Clear();
|
||||
foreach (T item in items)
|
||||
{
|
||||
base.Items.Add(item);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_suppressNotification = false;
|
||||
}
|
||||
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
|
||||
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
}
|
||||
249
.decompiledproj/AxCopilot/Core/CommandResolver.cs
Normal file
249
.decompiledproj/AxCopilot/Core/CommandResolver.cs
Normal file
@@ -0,0 +1,249 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Core;
|
||||
|
||||
public class CommandResolver
|
||||
{
|
||||
private readonly FuzzyEngine _fuzzy;
|
||||
|
||||
private readonly SettingsService _settings;
|
||||
|
||||
private readonly Dictionary<string, IActionHandler> _handlers = new Dictionary<string, IActionHandler>();
|
||||
|
||||
private readonly List<IActionHandler> _fuzzyHandlers = new List<IActionHandler>();
|
||||
|
||||
public IReadOnlyDictionary<string, IActionHandler> RegisteredHandlers => _handlers;
|
||||
|
||||
public CommandResolver(FuzzyEngine fuzzy, SettingsService settings)
|
||||
{
|
||||
_fuzzy = fuzzy;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public void RegisterHandler(IActionHandler handler)
|
||||
{
|
||||
if (handler.Prefix == null)
|
||||
{
|
||||
_fuzzyHandlers.Add(handler);
|
||||
LogService.Info("FuzzyHandler 등록: name='" + handler.Metadata.Name + "'");
|
||||
return;
|
||||
}
|
||||
if (_handlers.ContainsKey(handler.Prefix))
|
||||
{
|
||||
LogService.Warn($"Prefix '{handler.Prefix}' 중복 등록: '{handler.Metadata.Name}'이 기존 핸들러를 덮어씁니다.");
|
||||
}
|
||||
_handlers[handler.Prefix] = handler;
|
||||
LogService.Info($"Handler 등록: prefix='{handler.Prefix}', name='{handler.Metadata.Name}'");
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LauncherItem>> ResolveAsync(string input, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return Enumerable.Empty<LauncherItem>();
|
||||
}
|
||||
foreach (var (prefix, handler) in _handlers)
|
||||
{
|
||||
if (input.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
object obj;
|
||||
if (input.Length <= prefix.Length)
|
||||
{
|
||||
obj = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
string text2 = input;
|
||||
int length = prefix.Length;
|
||||
obj = text2.Substring(length, text2.Length - length).Trim();
|
||||
}
|
||||
string query = (string)obj;
|
||||
try
|
||||
{
|
||||
return await handler.GetItemsAsync(query, ct);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
LogService.Error("Handler '" + handler.Metadata.Name + "' 오류: " + ex2.Message);
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("오류: " + ex2.Message, handler.Metadata.Name, null, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
int maxResults = _settings.Settings.Launcher.MaxResults;
|
||||
HashSet<string> seenPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
List<LauncherItem> fuzzyItems = UsageRankingService.SortByUsage((from r in _fuzzy.Search(input, maxResults * 2)
|
||||
where seenPaths.Add(r.Entry.Path)
|
||||
select r).Take(maxResults).Select(delegate(FuzzyResult r)
|
||||
{
|
||||
string displayName = r.Entry.DisplayName;
|
||||
string subtitle;
|
||||
if (r.Entry.Type == IndexEntryType.Alias)
|
||||
{
|
||||
string aliasType = r.Entry.AliasType;
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string text3 = ((aliasType == "url") ? "URL 단축키" : ((!(aliasType == "batch")) ? r.Entry.Path : "명령 단축키"));
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
subtitle = text3;
|
||||
}
|
||||
else
|
||||
{
|
||||
subtitle = r.Entry.Path + " ⇧ Shift+Enter: 폴더 열기";
|
||||
}
|
||||
object entry = r.Entry;
|
||||
IndexEntryType type = r.Entry.Type;
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string symbol;
|
||||
switch (type)
|
||||
{
|
||||
case IndexEntryType.App:
|
||||
symbol = "\uecaa";
|
||||
break;
|
||||
case IndexEntryType.Folder:
|
||||
symbol = "\ue8b7";
|
||||
break;
|
||||
case IndexEntryType.Alias:
|
||||
{
|
||||
string aliasType2 = r.Entry.AliasType;
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string text3 = ((aliasType2 == "url") ? "\ue774" : ((!(aliasType2 == "batch")) ? "\uecca" : "\ue756"));
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
symbol = text3;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
symbol = "\ue8a5";
|
||||
break;
|
||||
}
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
return new LauncherItem(displayName, subtitle, null, entry, null, symbol);
|
||||
}), (LauncherItem item) => (item.Data as IndexEntry)?.Path).ToList();
|
||||
if (_fuzzyHandlers.Count > 0)
|
||||
{
|
||||
List<Task<IEnumerable<LauncherItem>>> extraTasks = _fuzzyHandlers.Select((IActionHandler h) => SafeGetItemsAsync(h, input, ct)).ToList();
|
||||
await Task.WhenAll(extraTasks);
|
||||
foreach (Task<IEnumerable<LauncherItem>> task in extraTasks)
|
||||
{
|
||||
if (task.IsCompletedSuccessfully)
|
||||
{
|
||||
fuzzyItems.AddRange(task.Result.Take(3));
|
||||
}
|
||||
}
|
||||
}
|
||||
return fuzzyItems;
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(LauncherItem item, string lastInput, CancellationToken ct)
|
||||
{
|
||||
foreach (var (prefix, handler) in _handlers)
|
||||
{
|
||||
if (lastInput.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
object obj;
|
||||
if (lastInput.Length <= prefix.Length)
|
||||
{
|
||||
obj = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
int length = prefix.Length;
|
||||
obj = lastInput.Substring(length, lastInput.Length - length).Trim().Split(' ')[0];
|
||||
}
|
||||
string q = (string)obj;
|
||||
string cmdKey = (string.IsNullOrEmpty(q) ? prefix : (prefix + q));
|
||||
UsageStatisticsService.RecordCommandUsage(cmdKey);
|
||||
await handler.ExecuteAsync(item, ct);
|
||||
return;
|
||||
}
|
||||
}
|
||||
object data = item.Data;
|
||||
if (data is string urlData && urlData.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await ExecuteNullPrefixAsync(item, ct);
|
||||
return;
|
||||
}
|
||||
data = item.Data;
|
||||
IndexEntry entry = data as IndexEntry;
|
||||
if (entry == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string expanded = Environment.ExpandEnvironmentVariables(entry.Path);
|
||||
try
|
||||
{
|
||||
await Task.Run(() => Process.Start(new ProcessStartInfo(expanded)
|
||||
{
|
||||
UseShellExecute = true
|
||||
}));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Exception ex2 = ex;
|
||||
LogService.Error("실행 실패: " + expanded + " - " + ex2.Message);
|
||||
}
|
||||
Task.Run(delegate
|
||||
{
|
||||
UsageRankingService.RecordExecution(entry.Path);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task ExecuteNullPrefixAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
foreach (IActionHandler handler in _fuzzyHandlers)
|
||||
{
|
||||
try
|
||||
{
|
||||
await handler.ExecuteAsync(item, ct);
|
||||
return;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
LogService.Error("FuzzyHandler '" + handler.Metadata.Name + "' 실행 오류: " + ex2.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<LauncherItem>> SafeGetItemsAsync(IActionHandler handler, string query, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await handler.GetItemsAsync(query, ct);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
Exception ex3 = ex2;
|
||||
LogService.Error("FuzzyHandler '" + handler.Metadata.Name + "' 오류: " + ex3.Message);
|
||||
return Enumerable.Empty<LauncherItem>();
|
||||
}
|
||||
}
|
||||
}
|
||||
384
.decompiledproj/AxCopilot/Core/ContextManager.cs
Normal file
384
.decompiledproj/AxCopilot/Core/ContextManager.cs
Normal file
@@ -0,0 +1,384 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Core;
|
||||
|
||||
public class ContextManager
|
||||
{
|
||||
private struct RECT
|
||||
{
|
||||
public int Left;
|
||||
|
||||
public int Top;
|
||||
|
||||
public int Right;
|
||||
|
||||
public int Bottom;
|
||||
}
|
||||
|
||||
private struct WINDOWPLACEMENT
|
||||
{
|
||||
public uint length;
|
||||
|
||||
public uint flags;
|
||||
|
||||
public uint showCmd;
|
||||
|
||||
public POINT ptMinPosition;
|
||||
|
||||
public POINT ptMaxPosition;
|
||||
|
||||
public RECT rcNormalPosition;
|
||||
}
|
||||
|
||||
private struct POINT
|
||||
{
|
||||
public int x;
|
||||
|
||||
public int y;
|
||||
}
|
||||
|
||||
private delegate bool EnumWindowsProc(nint hWnd, nint lParam);
|
||||
|
||||
private delegate bool MonitorEnumProc(nint hMonitor, nint hdcMonitor, ref RECT lprcMonitor, nint dwData);
|
||||
|
||||
private readonly SettingsService _settings;
|
||||
|
||||
private const uint SWP_NOZORDER = 4u;
|
||||
|
||||
private const uint SWP_NOACTIVATE = 16u;
|
||||
|
||||
private const uint MONITOR_DEFAULTTONEAREST = 2u;
|
||||
|
||||
public ContextManager(SettingsService settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public WorkspaceProfile CaptureProfile(string name)
|
||||
{
|
||||
List<WindowSnapshot> snapshots = new List<WindowSnapshot>();
|
||||
Dictionary<nint, int> monitorMap = BuildMonitorMap();
|
||||
EnumWindows(delegate(nint hWnd, nint _)
|
||||
{
|
||||
if (!IsWindowVisible(hWnd))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (IsIconic(hWnd))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
string windowTitle = GetWindowTitle(hWnd);
|
||||
if (string.IsNullOrWhiteSpace(windowTitle))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (IsSystemWindow(hWnd))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
string processPath = GetProcessPath(hWnd);
|
||||
if (string.IsNullOrEmpty(processPath))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
GetWindowRect(hWnd, out var lpRect);
|
||||
GetWindowPlacement(hWnd, out var lpwndpl);
|
||||
uint showCmd = lpwndpl.showCmd;
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string text = showCmd switch
|
||||
{
|
||||
1u => "Normal",
|
||||
2u => "Minimized",
|
||||
3u => "Maximized",
|
||||
_ => "Normal",
|
||||
};
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string showCmd2 = text;
|
||||
int monitorIndex = GetMonitorIndex(hWnd, monitorMap);
|
||||
snapshots.Add(new WindowSnapshot
|
||||
{
|
||||
Exe = processPath,
|
||||
Title = windowTitle,
|
||||
Rect = new WindowRect
|
||||
{
|
||||
X = lpRect.Left,
|
||||
Y = lpRect.Top,
|
||||
Width = lpRect.Right - lpRect.Left,
|
||||
Height = lpRect.Bottom - lpRect.Top
|
||||
},
|
||||
ShowCmd = showCmd2,
|
||||
Monitor = monitorIndex
|
||||
});
|
||||
return true;
|
||||
}, IntPtr.Zero);
|
||||
WorkspaceProfile workspaceProfile = new WorkspaceProfile
|
||||
{
|
||||
Name = name,
|
||||
Windows = snapshots,
|
||||
CreatedAt = DateTime.Now
|
||||
};
|
||||
WorkspaceProfile workspaceProfile2 = _settings.Settings.Profiles.FirstOrDefault((WorkspaceProfile p) => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
if (workspaceProfile2 != null)
|
||||
{
|
||||
_settings.Settings.Profiles.Remove(workspaceProfile2);
|
||||
}
|
||||
_settings.Settings.Profiles.Add(workspaceProfile);
|
||||
_settings.Save();
|
||||
LogService.Info($"프로필 '{name}' 저장 완료: {snapshots.Count}개 창");
|
||||
return workspaceProfile;
|
||||
}
|
||||
|
||||
public async Task<RestoreResult> RestoreProfileAsync(string name, CancellationToken ct = default(CancellationToken))
|
||||
{
|
||||
WorkspaceProfile profile = _settings.Settings.Profiles.FirstOrDefault((WorkspaceProfile p) => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
if (profile == null)
|
||||
{
|
||||
return new RestoreResult(Success: false, "프로필 '" + name + "'을 찾을 수 없습니다.");
|
||||
}
|
||||
List<string> results = new List<string>();
|
||||
int monitorCount = GetMonitorCount();
|
||||
foreach (WindowSnapshot snapshot in profile.Windows)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
nint hWnd = FindMatchingWindow(snapshot);
|
||||
if (hWnd == IntPtr.Zero && File.Exists(snapshot.Exe))
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(snapshot.Exe)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
hWnd = await WaitForWindowAsync(snapshot.Exe, TimeSpan.FromSeconds(3.0), ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Exception ex2 = ex;
|
||||
results.Add($"⚠ {snapshot.Title}: 실행 실패 ({ex2.Message})");
|
||||
LogService.Warn("앱 실행 실패: " + snapshot.Exe + " - " + ex2.Message);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (hWnd == IntPtr.Zero)
|
||||
{
|
||||
results.Add("⏭ " + snapshot.Title + ": 창 없음, 건너뜀");
|
||||
continue;
|
||||
}
|
||||
if (snapshot.Monitor >= monitorCount)
|
||||
{
|
||||
string policy = _settings.Settings.MonitorMismatch;
|
||||
if (policy == "skip")
|
||||
{
|
||||
results.Add("⏭ " + snapshot.Title + ": 모니터 불일치, 건너뜀");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
nint hWnd2 = hWnd;
|
||||
string showCmd = snapshot.ShowCmd;
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string text = showCmd;
|
||||
int nCmdShow = ((text == "Maximized") ? 3 : ((!(text == "Minimized")) ? 9 : 2));
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
ShowWindow(hWnd2, nCmdShow);
|
||||
if (snapshot.ShowCmd == "Normal")
|
||||
{
|
||||
SetWindowPos(hWnd, IntPtr.Zero, snapshot.Rect.X, snapshot.Rect.Y, snapshot.Rect.Width, snapshot.Rect.Height, 20u);
|
||||
}
|
||||
results.Add("✓ " + snapshot.Title + ": 복원 완료");
|
||||
}
|
||||
catch (Exception ex3)
|
||||
{
|
||||
results.Add($"⚠ {snapshot.Title}: 복원 실패 ({ex3.Message})");
|
||||
LogService.Warn("창 복원 실패 (권한 문제 가능): " + snapshot.Exe);
|
||||
}
|
||||
}
|
||||
LogService.Info("프로필 '" + name + "' 복원: " + string.Join(", ", results));
|
||||
return new RestoreResult(Success: true, string.Join("\n", results));
|
||||
}
|
||||
|
||||
public bool DeleteProfile(string name)
|
||||
{
|
||||
WorkspaceProfile workspaceProfile = _settings.Settings.Profiles.FirstOrDefault((WorkspaceProfile p) => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
if (workspaceProfile == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_settings.Settings.Profiles.Remove(workspaceProfile);
|
||||
_settings.Save();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RenameProfile(string oldName, string newName)
|
||||
{
|
||||
WorkspaceProfile workspaceProfile = _settings.Settings.Profiles.FirstOrDefault((WorkspaceProfile p) => p.Name.Equals(oldName, StringComparison.OrdinalIgnoreCase));
|
||||
if (workspaceProfile == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
workspaceProfile.Name = newName;
|
||||
_settings.Save();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static nint FindMatchingWindow(WindowSnapshot snapshot)
|
||||
{
|
||||
nint found = IntPtr.Zero;
|
||||
EnumWindows(delegate(nint hWnd, nint _)
|
||||
{
|
||||
string processPath = GetProcessPath(hWnd);
|
||||
if (string.Equals(processPath, snapshot.Exe, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
found = hWnd;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, IntPtr.Zero);
|
||||
return found;
|
||||
}
|
||||
|
||||
private static async Task<nint> WaitForWindowAsync(string exePath, TimeSpan timeout, CancellationToken ct)
|
||||
{
|
||||
DateTime deadline = DateTime.UtcNow + timeout;
|
||||
while (DateTime.UtcNow < deadline)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
nint hWnd = FindMatchingWindow(new WindowSnapshot
|
||||
{
|
||||
Exe = exePath
|
||||
});
|
||||
if (hWnd != IntPtr.Zero)
|
||||
{
|
||||
return hWnd;
|
||||
}
|
||||
await Task.Delay(200, ct);
|
||||
}
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
private static string GetWindowTitle(nint hWnd)
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder(256);
|
||||
GetWindowText(hWnd, stringBuilder, stringBuilder.Capacity);
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
private static string GetProcessPath(nint hWnd)
|
||||
{
|
||||
try
|
||||
{
|
||||
GetWindowThreadProcessId(hWnd, out var lpdwProcessId);
|
||||
if (lpdwProcessId == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
Process processById = Process.GetProcessById((int)lpdwProcessId);
|
||||
return processById.MainModule?.FileName ?? "";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsSystemWindow(nint hWnd)
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder(256);
|
||||
GetClassName(hWnd, stringBuilder, stringBuilder.Capacity);
|
||||
switch (stringBuilder.ToString())
|
||||
{
|
||||
case "Shell_TrayWnd":
|
||||
case "Progman":
|
||||
case "WorkerW":
|
||||
case "DV2ControlHost":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<nint, int> BuildMonitorMap()
|
||||
{
|
||||
List<nint> monitors = new List<nint>();
|
||||
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, delegate(nint hMonitor, nint hdcMonitor, ref RECT lprc, nint dwData)
|
||||
{
|
||||
monitors.Add(hMonitor);
|
||||
return true;
|
||||
}, IntPtr.Zero);
|
||||
return monitors.Select((nint hm, int idx) => (hm: hm, idx: idx)).ToDictionary(((nint hm, int idx) t) => t.hm, ((nint hm, int idx) t) => t.idx);
|
||||
}
|
||||
|
||||
private static int GetMonitorIndex(nint hWnd, Dictionary<nint, int> map)
|
||||
{
|
||||
nint key = MonitorFromWindow(hWnd, 2u);
|
||||
int value;
|
||||
return map.TryGetValue(key, out value) ? value : 0;
|
||||
}
|
||||
|
||||
private static int GetMonitorCount()
|
||||
{
|
||||
int count = 0;
|
||||
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, delegate
|
||||
{
|
||||
count++;
|
||||
return true;
|
||||
}, IntPtr.Zero);
|
||||
return count;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, nint lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsWindowVisible(nint hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsIconic(nint hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetWindowText(nint hWnd, StringBuilder lpString, int nMaxCount);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetWindowRect(nint hWnd, out RECT lpRect);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetWindowPlacement(nint hWnd, out WINDOWPLACEMENT lpwndpl);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint GetWindowThreadProcessId(nint hWnd, out uint lpdwProcessId);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetClassName(nint hWnd, StringBuilder lpClassName, int nMaxCount);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetWindowPos(nint hWnd, nint hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool ShowWindow(nint hWnd, int nCmdShow);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern nint MonitorFromWindow(nint hwnd, uint dwFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool EnumDisplayMonitors(nint hdc, nint lprcClip, MonitorEnumProc lpfnEnum, nint dwData);
|
||||
}
|
||||
468
.decompiledproj/AxCopilot/Core/FuzzyEngine.cs
Normal file
468
.decompiledproj/AxCopilot/Core/FuzzyEngine.cs
Normal file
@@ -0,0 +1,468 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Core;
|
||||
|
||||
public class FuzzyEngine
|
||||
{
|
||||
private readonly IndexService _index;
|
||||
|
||||
private static readonly char[] Chosungs = new char[19]
|
||||
{
|
||||
'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ',
|
||||
'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'
|
||||
};
|
||||
|
||||
private static readonly HashSet<char> ChosungSet = new HashSet<char>(new _003C_003Ez__ReadOnlyArray<char>(new char[19]
|
||||
{
|
||||
'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ',
|
||||
'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'
|
||||
}));
|
||||
|
||||
private static readonly char[] Jungsungs = new char[21]
|
||||
{
|
||||
'ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ', 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ',
|
||||
'ㅙ', 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ', 'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ',
|
||||
'ㅣ'
|
||||
};
|
||||
|
||||
private static readonly char[] Jongsungs = new char[28]
|
||||
{
|
||||
'\0', 'ㄱ', 'ㄲ', 'ㄳ', 'ㄴ', 'ㄵ', 'ㄶ', 'ㄷ', 'ㄹ', 'ㄺ',
|
||||
'ㄻ', 'ㄼ', 'ㄽ', 'ㄾ', 'ㄿ', 'ㅀ', 'ㅁ', 'ㅂ', 'ㅄ', 'ㅅ',
|
||||
'ㅆ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'
|
||||
};
|
||||
|
||||
public FuzzyEngine(IndexService index)
|
||||
{
|
||||
_index = index;
|
||||
}
|
||||
|
||||
public IEnumerable<FuzzyResult> Search(string query, int maxResults = 7)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return Enumerable.Empty<FuzzyResult>();
|
||||
}
|
||||
string normalized = query.Trim().ToLowerInvariant();
|
||||
IReadOnlyList<IndexEntry> entries = _index.Entries;
|
||||
bool queryHasKorean = false;
|
||||
string text = normalized;
|
||||
foreach (char c in text)
|
||||
{
|
||||
if ((c >= '가' && c <= '힣') || ChosungSet.Contains(c))
|
||||
{
|
||||
queryHasKorean = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (entries.Count > 300)
|
||||
{
|
||||
return (from e in entries.AsParallel()
|
||||
select new FuzzyResult(e, CalculateScoreFast(normalized, e, queryHasKorean)) into r
|
||||
where r.Score > 0
|
||||
orderby r.Score descending
|
||||
select r).Take(maxResults);
|
||||
}
|
||||
return (from e in entries
|
||||
select new FuzzyResult(e, CalculateScoreFast(normalized, e, queryHasKorean)) into r
|
||||
where r.Score > 0
|
||||
orderby r.Score descending
|
||||
select r).Take(maxResults);
|
||||
}
|
||||
|
||||
private static int CalculateScoreFast(string query, IndexEntry entry, bool queryHasKorean)
|
||||
{
|
||||
string text = (string.IsNullOrEmpty(entry.NameLower) ? entry.Name.ToLowerInvariant() : entry.NameLower);
|
||||
if (query.Length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (text == query)
|
||||
{
|
||||
return 1000 + entry.Score;
|
||||
}
|
||||
if (text.StartsWith(query))
|
||||
{
|
||||
return 800 + entry.Score;
|
||||
}
|
||||
if (text.Contains(query))
|
||||
{
|
||||
return 600 + entry.Score;
|
||||
}
|
||||
if (!queryHasKorean)
|
||||
{
|
||||
int num = FuzzyMatch(query, text);
|
||||
return (num > 0) ? (num + entry.Score) : 0;
|
||||
}
|
||||
int num2 = JamoContainsScoreFast(string.IsNullOrEmpty(entry.NameJamo) ? DecomposeToJamo(text) : entry.NameJamo, query);
|
||||
if (num2 > 0)
|
||||
{
|
||||
return num2 + entry.Score;
|
||||
}
|
||||
int num3 = ChosungMatchScoreFast(string.IsNullOrEmpty(entry.NameChosung) ? null : entry.NameChosung, text, query);
|
||||
if (num3 > 0)
|
||||
{
|
||||
return num3 + entry.Score;
|
||||
}
|
||||
int num4 = FuzzyMatch(query, text);
|
||||
if (num4 > 0)
|
||||
{
|
||||
return num4 + entry.Score;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal static int CalculateScore(string query, string target, int baseScore)
|
||||
{
|
||||
if (query.Length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (target == query)
|
||||
{
|
||||
return 1000 + baseScore;
|
||||
}
|
||||
if (target.StartsWith(query))
|
||||
{
|
||||
return 800 + baseScore;
|
||||
}
|
||||
if (target.Contains(query))
|
||||
{
|
||||
return 600 + baseScore;
|
||||
}
|
||||
int num = JamoContainsScore(target, query);
|
||||
if (num > 0)
|
||||
{
|
||||
return num + baseScore;
|
||||
}
|
||||
int num2 = ChosungMatchScore(target, query);
|
||||
if (num2 > 0)
|
||||
{
|
||||
return num2 + baseScore;
|
||||
}
|
||||
int num3 = FuzzyMatch(query, target);
|
||||
if (num3 > 0)
|
||||
{
|
||||
return num3 + baseScore;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal static int FuzzyMatch(string query, string target)
|
||||
{
|
||||
int num = 0;
|
||||
int num2 = 0;
|
||||
int num3 = 0;
|
||||
int num4 = -1;
|
||||
while (num < query.Length && num2 < target.Length)
|
||||
{
|
||||
if (query[num] == target[num2])
|
||||
{
|
||||
num3 = ((num4 != num2 - 1) ? (num3 + 10) : (num3 + 30));
|
||||
if (num2 == 0)
|
||||
{
|
||||
num3 += 15;
|
||||
}
|
||||
num4 = num2;
|
||||
num++;
|
||||
}
|
||||
num2++;
|
||||
}
|
||||
return (num == query.Length) ? Math.Max(num3, 50) : 0;
|
||||
}
|
||||
|
||||
internal static string DecomposeToJamo(string text)
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder(text.Length * 3);
|
||||
foreach (char c in text)
|
||||
{
|
||||
if (c >= '가' && c <= '힣')
|
||||
{
|
||||
int num = c - 44032;
|
||||
int num2 = num / 588;
|
||||
int num3 = num % 588 / 28;
|
||||
int num4 = num % 28;
|
||||
stringBuilder.Append(Chosungs[num2]);
|
||||
stringBuilder.Append(Jungsungs[num3]);
|
||||
if (num4 > 0)
|
||||
{
|
||||
stringBuilder.Append(Jongsungs[num4]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stringBuilder.Append(c);
|
||||
}
|
||||
}
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
internal static char GetChosung(char hangul)
|
||||
{
|
||||
if (hangul < '가' || hangul > '힣')
|
||||
{
|
||||
return '\0';
|
||||
}
|
||||
int num = hangul - 44032;
|
||||
return Chosungs[num / 588];
|
||||
}
|
||||
|
||||
internal static int JamoContainsScore(string target, string query)
|
||||
{
|
||||
if (!HasKorean(query))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
string text = DecomposeToJamo(target);
|
||||
string text2 = DecomposeToJamo(query);
|
||||
if (text2.Length == 0 || text.Length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (text.Contains(text2))
|
||||
{
|
||||
return (text.IndexOf(text2) == 0) ? 580 : 550;
|
||||
}
|
||||
int num = 0;
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
if (num >= text2.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (text2[num] == text[i])
|
||||
{
|
||||
num++;
|
||||
}
|
||||
}
|
||||
if (num == text2.Length)
|
||||
{
|
||||
return 400;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal static bool HasChosung(string text)
|
||||
{
|
||||
return text.Any((char c) => ChosungSet.Contains(c));
|
||||
}
|
||||
|
||||
internal static bool IsChosung(string text)
|
||||
{
|
||||
return text.Length > 0 && text.All((char c) => ChosungSet.Contains(c));
|
||||
}
|
||||
|
||||
private static bool HasKorean(string text)
|
||||
{
|
||||
return text.Any((char c) => c >= '가' && c <= '힣');
|
||||
}
|
||||
|
||||
internal static int ChosungMatchScore(string target, string query)
|
||||
{
|
||||
if (!HasChosung(query))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
List<char> list = new List<char>();
|
||||
List<char> list2 = new List<char>();
|
||||
foreach (char c in target)
|
||||
{
|
||||
char chosung = GetChosung(c);
|
||||
if (chosung != 0)
|
||||
{
|
||||
list.Add(chosung);
|
||||
list2.Add(c);
|
||||
}
|
||||
else if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
|
||||
{
|
||||
list.Add(c);
|
||||
list2.Add(c);
|
||||
}
|
||||
}
|
||||
if (list.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (IsChosung(query))
|
||||
{
|
||||
if (ContainsChosungConsecutive(list, query))
|
||||
{
|
||||
return 520;
|
||||
}
|
||||
if (ContainsChosungSubsequence(list, query))
|
||||
{
|
||||
return 480;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return MixedChosungMatch(list2, list, query);
|
||||
}
|
||||
|
||||
private static bool ContainsChosungConsecutive(List<char> targetChosungs, string query)
|
||||
{
|
||||
for (int i = 0; i <= targetChosungs.Count - query.Length; i++)
|
||||
{
|
||||
bool flag = true;
|
||||
for (int j = 0; j < query.Length; j++)
|
||||
{
|
||||
if (targetChosungs[i + j] != query[j])
|
||||
{
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (flag)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ContainsChosungSubsequence(List<char> targetChosungs, string query)
|
||||
{
|
||||
int num = 0;
|
||||
for (int i = 0; i < targetChosungs.Count; i++)
|
||||
{
|
||||
if (num >= query.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (targetChosungs[i] == query[num])
|
||||
{
|
||||
num++;
|
||||
}
|
||||
}
|
||||
return num == query.Length;
|
||||
}
|
||||
|
||||
private static int MixedChosungMatch(List<char> targetChars, List<char> targetChosungs, string query)
|
||||
{
|
||||
int num = 0;
|
||||
int num2 = 0;
|
||||
while (num < query.Length && num2 < targetChars.Count)
|
||||
{
|
||||
char c = query[num];
|
||||
if (ChosungSet.Contains(c))
|
||||
{
|
||||
if (targetChosungs[num2] == c)
|
||||
{
|
||||
num++;
|
||||
}
|
||||
}
|
||||
else if (targetChars[num2] == c)
|
||||
{
|
||||
num++;
|
||||
}
|
||||
num2++;
|
||||
}
|
||||
return (num == query.Length) ? 460 : 0;
|
||||
}
|
||||
|
||||
private static int JamoContainsScoreFast(string targetJamo, string query)
|
||||
{
|
||||
if (!HasKorean(query))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
string text = DecomposeToJamo(query);
|
||||
if (text.Length == 0 || targetJamo.Length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (targetJamo.Contains(text))
|
||||
{
|
||||
return (targetJamo.IndexOf(text, StringComparison.Ordinal) == 0) ? 580 : 550;
|
||||
}
|
||||
int num = 0;
|
||||
for (int i = 0; i < targetJamo.Length; i++)
|
||||
{
|
||||
if (num >= text.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (text[num] == targetJamo[i])
|
||||
{
|
||||
num++;
|
||||
}
|
||||
}
|
||||
return (num == text.Length) ? 400 : 0;
|
||||
}
|
||||
|
||||
private static int ChosungMatchScoreFast(string? targetChosung, string targetLower, string query)
|
||||
{
|
||||
if (!HasChosung(query))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (IsChosung(query))
|
||||
{
|
||||
if (string.IsNullOrEmpty(targetChosung))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (targetChosung.Contains(query, StringComparison.Ordinal))
|
||||
{
|
||||
return 520;
|
||||
}
|
||||
int num = 0;
|
||||
for (int i = 0; i < targetChosung.Length; i++)
|
||||
{
|
||||
if (num >= query.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (targetChosung[i] == query[num])
|
||||
{
|
||||
num++;
|
||||
}
|
||||
}
|
||||
if (num == query.Length)
|
||||
{
|
||||
return 480;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int num2 = 0;
|
||||
int num3 = 0;
|
||||
while (num2 < query.Length && num3 < targetLower.Length)
|
||||
{
|
||||
char c = query[num2];
|
||||
char c2 = targetLower[num3];
|
||||
if (ChosungSet.Contains(c))
|
||||
{
|
||||
char c3 = GetChosung(c2);
|
||||
if (c3 == '\0' && ((c2 >= 'a' && c2 <= 'z') || (c2 >= '0' && c2 <= '9')))
|
||||
{
|
||||
c3 = c2;
|
||||
}
|
||||
if (c3 == c)
|
||||
{
|
||||
num2++;
|
||||
}
|
||||
}
|
||||
else if (c2 == c)
|
||||
{
|
||||
num2++;
|
||||
}
|
||||
num3++;
|
||||
}
|
||||
return (num2 == query.Length) ? 460 : 0;
|
||||
}
|
||||
|
||||
internal static bool ContainsChosung(string target, string chosungQuery)
|
||||
{
|
||||
List<char> list = (from c in target.Select(GetChosung)
|
||||
where c != '\0'
|
||||
select c).ToList();
|
||||
if (list.Count < chosungQuery.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return ContainsChosungConsecutive(list, chosungQuery) || ContainsChosungSubsequence(list, chosungQuery);
|
||||
}
|
||||
}
|
||||
5
.decompiledproj/AxCopilot/Core/FuzzyResult.cs
Normal file
5
.decompiledproj/AxCopilot/Core/FuzzyResult.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Core;
|
||||
|
||||
public record FuzzyResult(IndexEntry Entry, int Score);
|
||||
3
.decompiledproj/AxCopilot/Core/HotkeyDefinition.cs
Normal file
3
.decompiledproj/AxCopilot/Core/HotkeyDefinition.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace AxCopilot.Core;
|
||||
|
||||
public record struct HotkeyDefinition(int VkCode, bool Ctrl, bool Alt, bool Shift, bool Win);
|
||||
175
.decompiledproj/AxCopilot/Core/HotkeyParser.cs
Normal file
175
.decompiledproj/AxCopilot/Core/HotkeyParser.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AxCopilot.Core;
|
||||
|
||||
public static class HotkeyParser
|
||||
{
|
||||
private static readonly Dictionary<string, int> _keyMap;
|
||||
|
||||
static HotkeyParser()
|
||||
{
|
||||
_keyMap = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["Space"] = 32,
|
||||
["Enter"] = 13,
|
||||
["Return"] = 13,
|
||||
["Tab"] = 9,
|
||||
["Esc"] = 27,
|
||||
["Escape"] = 27,
|
||||
["Backspace"] = 8,
|
||||
["Back"] = 8,
|
||||
["Delete"] = 46,
|
||||
["Del"] = 46,
|
||||
["Insert"] = 45,
|
||||
["Ins"] = 45,
|
||||
["Home"] = 36,
|
||||
["End"] = 35,
|
||||
["PageUp"] = 33,
|
||||
["PgUp"] = 33,
|
||||
["PageDown"] = 34,
|
||||
["PgDn"] = 34,
|
||||
["PrintScreen"] = 44,
|
||||
["PrtSc"] = 44,
|
||||
["Snapshot"] = 44,
|
||||
["Pause"] = 19,
|
||||
["Break"] = 19,
|
||||
["ScrollLock"] = 145,
|
||||
["Left"] = 37,
|
||||
["Up"] = 38,
|
||||
["Right"] = 39,
|
||||
["Down"] = 40,
|
||||
["`"] = 192,
|
||||
["Grave"] = 192,
|
||||
["-"] = 189,
|
||||
["="] = 187,
|
||||
["["] = 219,
|
||||
["]"] = 221,
|
||||
["\\"] = 220,
|
||||
[";"] = 186,
|
||||
["'"] = 222,
|
||||
[","] = 188,
|
||||
["."] = 190,
|
||||
["/"] = 191
|
||||
};
|
||||
for (char c = 'A'; c <= 'Z'; c = (char)(c + 1))
|
||||
{
|
||||
_keyMap[c.ToString()] = c;
|
||||
}
|
||||
for (char c2 = '0'; c2 <= '9'; c2 = (char)(c2 + 1))
|
||||
{
|
||||
_keyMap[c2.ToString()] = c2;
|
||||
}
|
||||
for (int i = 1; i <= 24; i++)
|
||||
{
|
||||
_keyMap[$"F{i}"] = 111 + i;
|
||||
}
|
||||
for (int j = 0; j <= 9; j++)
|
||||
{
|
||||
_keyMap[$"Num{j}"] = 96 + j;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryParse(string hotkey, out HotkeyDefinition result)
|
||||
{
|
||||
result = default(HotkeyDefinition);
|
||||
if (string.IsNullOrWhiteSpace(hotkey))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
string[] array = hotkey.Split('+', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
bool ctrl = false;
|
||||
bool alt = false;
|
||||
bool shift = false;
|
||||
bool win = false;
|
||||
int? num = null;
|
||||
string[] array2 = array;
|
||||
foreach (string text in array2)
|
||||
{
|
||||
if (text.Equals("Ctrl", StringComparison.OrdinalIgnoreCase) || text.Equals("Control", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
ctrl = true;
|
||||
continue;
|
||||
}
|
||||
if (text.Equals("Alt", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
alt = true;
|
||||
continue;
|
||||
}
|
||||
if (text.Equals("Shift", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
shift = true;
|
||||
continue;
|
||||
}
|
||||
if (text.Equals("Win", StringComparison.OrdinalIgnoreCase) || text.Equals("Windows", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
win = true;
|
||||
continue;
|
||||
}
|
||||
if (_keyMap.TryGetValue(text, out var value))
|
||||
{
|
||||
num = value;
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!num.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
result = new HotkeyDefinition(num.Value, ctrl, alt, shift, win);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string Format(HotkeyDefinition def)
|
||||
{
|
||||
List<string> list = new List<string>(5);
|
||||
if (def.Ctrl)
|
||||
{
|
||||
list.Add("Ctrl");
|
||||
}
|
||||
if (def.Alt)
|
||||
{
|
||||
list.Add("Alt");
|
||||
}
|
||||
if (def.Shift)
|
||||
{
|
||||
list.Add("Shift");
|
||||
}
|
||||
if (def.Win)
|
||||
{
|
||||
list.Add("Win");
|
||||
}
|
||||
list.Add(VkToName(def.VkCode));
|
||||
return string.Join("+", list);
|
||||
}
|
||||
|
||||
private static string VkToName(int vk)
|
||||
{
|
||||
if (vk >= 65 && vk <= 90)
|
||||
{
|
||||
return ((char)vk).ToString();
|
||||
}
|
||||
if (vk >= 48 && vk <= 57)
|
||||
{
|
||||
return ((char)vk).ToString();
|
||||
}
|
||||
if (vk >= 112 && vk <= 135)
|
||||
{
|
||||
return $"F{vk - 111}";
|
||||
}
|
||||
if (vk >= 96 && vk <= 105)
|
||||
{
|
||||
return $"Num{vk - 96}";
|
||||
}
|
||||
string text = null;
|
||||
foreach (var (text3, num2) in _keyMap)
|
||||
{
|
||||
if (num2 == vk && (text == null || text3.Length > text.Length))
|
||||
{
|
||||
text = text3;
|
||||
}
|
||||
}
|
||||
return text ?? $"0x{vk:X2}";
|
||||
}
|
||||
}
|
||||
249
.decompiledproj/AxCopilot/Core/InputListener.cs
Normal file
249
.decompiledproj/AxCopilot/Core/InputListener.cs
Normal file
@@ -0,0 +1,249 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Core;
|
||||
|
||||
public class InputListener : IDisposable
|
||||
{
|
||||
private delegate nint LowLevelKeyboardProc(int nCode, nint wParam, nint lParam);
|
||||
|
||||
private const int WH_KEYBOARD_LL = 13;
|
||||
|
||||
private const int WM_KEYDOWN = 256;
|
||||
|
||||
private const int WM_SYSKEYDOWN = 260;
|
||||
|
||||
private const int WM_KEYUP = 257;
|
||||
|
||||
private const int WM_SYSKEYUP = 261;
|
||||
|
||||
private const int VK_SHIFT = 16;
|
||||
|
||||
private const int VK_CONTROL = 17;
|
||||
|
||||
private const int VK_MENU = 18;
|
||||
|
||||
private const int VK_LWIN = 91;
|
||||
|
||||
private const int VK_RWIN = 92;
|
||||
|
||||
private nint _hookHandle = IntPtr.Zero;
|
||||
|
||||
private LowLevelKeyboardProc? _proc;
|
||||
|
||||
private int _retryCount = 0;
|
||||
|
||||
private const int MaxRetry = 3;
|
||||
|
||||
private volatile bool _suppressNextAltUp;
|
||||
|
||||
private volatile bool _suppressNextKeyUp;
|
||||
|
||||
private volatile int _suppressKeyUpVk;
|
||||
|
||||
private HotkeyDefinition _hotkey = new HotkeyDefinition(32, Ctrl: false, Alt: true, Shift: false, Win: false);
|
||||
|
||||
private HotkeyDefinition _captureHotkey;
|
||||
|
||||
private bool _captureHotkeyEnabled;
|
||||
|
||||
public bool SuspendHotkey { get; set; }
|
||||
|
||||
public Func<int, bool>? KeyFilter { get; set; }
|
||||
|
||||
public event EventHandler? HotkeyTriggered;
|
||||
|
||||
public event EventHandler? CaptureHotkeyTriggered;
|
||||
|
||||
public event EventHandler? HookFailed;
|
||||
|
||||
public void UpdateHotkey(string hotkeyStr)
|
||||
{
|
||||
if (HotkeyParser.TryParse(hotkeyStr, out var result))
|
||||
{
|
||||
_hotkey = result;
|
||||
LogService.Info("핫키 변경: " + hotkeyStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogService.Warn("핫키 파싱 실패: '" + hotkeyStr + "' — 기존 핫키 유지");
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateCaptureHotkey(string hotkeyStr, bool enabled)
|
||||
{
|
||||
_captureHotkeyEnabled = enabled;
|
||||
if (enabled && HotkeyParser.TryParse(hotkeyStr, out var result))
|
||||
{
|
||||
_captureHotkey = result;
|
||||
LogService.Info("캡처 단축키 활성화: " + hotkeyStr);
|
||||
}
|
||||
else if (!enabled)
|
||||
{
|
||||
LogService.Info("캡처 단축키 비활성화");
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_proc = HookCallback;
|
||||
Register();
|
||||
}
|
||||
|
||||
private void Register()
|
||||
{
|
||||
using Process process = Process.GetCurrentProcess();
|
||||
using ProcessModule processModule = process.MainModule;
|
||||
_hookHandle = SetWindowsHookEx(13, _proc, GetModuleHandle(processModule.ModuleName), 0u);
|
||||
if (_hookHandle == IntPtr.Zero)
|
||||
{
|
||||
int lastWin32Error = Marshal.GetLastWin32Error();
|
||||
LogService.Error($"Global Hook 등록 실패 (에러 코드: {lastWin32Error})");
|
||||
TryRetryRegister();
|
||||
}
|
||||
else
|
||||
{
|
||||
_retryCount = 0;
|
||||
LogService.Info("Global Keyboard Hook 등록 완료 (" + HotkeyParser.Format(_hotkey) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private void TryRetryRegister()
|
||||
{
|
||||
if (_retryCount < 3)
|
||||
{
|
||||
_retryCount++;
|
||||
LogService.Warn($"Hook 재등록 시도 {_retryCount}/{3}");
|
||||
Task.Delay(1000).ContinueWith(delegate
|
||||
{
|
||||
Register();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
LogService.Error("Hook 재등록 최대 횟수 초과");
|
||||
this.HookFailed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsSuppressedForegroundWindow()
|
||||
{
|
||||
nint foregroundWindow = GetForegroundWindow();
|
||||
if (foregroundWindow == IntPtr.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
StringBuilder stringBuilder = new StringBuilder(64);
|
||||
GetClassName(foregroundWindow, stringBuilder, 64);
|
||||
string text = stringBuilder.ToString();
|
||||
return text == "#32770" || text == "SunAwtDialog";
|
||||
}
|
||||
|
||||
private nint HookCallback(int nCode, nint wParam, nint lParam)
|
||||
{
|
||||
if (nCode < 0)
|
||||
{
|
||||
return CallNextHookEx(_hookHandle, nCode, wParam, lParam);
|
||||
}
|
||||
int num = Marshal.ReadInt32(lParam);
|
||||
if (wParam == 257 || wParam == 261)
|
||||
{
|
||||
if (_suppressNextAltUp && num == 18)
|
||||
{
|
||||
_suppressNextAltUp = false;
|
||||
return 1;
|
||||
}
|
||||
if (_suppressNextKeyUp && num == _suppressKeyUpVk)
|
||||
{
|
||||
_suppressNextKeyUp = false;
|
||||
return 1;
|
||||
}
|
||||
return CallNextHookEx(_hookHandle, nCode, wParam, lParam);
|
||||
}
|
||||
if (wParam != 256 && wParam != 260)
|
||||
{
|
||||
return CallNextHookEx(_hookHandle, nCode, wParam, lParam);
|
||||
}
|
||||
if (IsSuppressedForegroundWindow())
|
||||
{
|
||||
return CallNextHookEx(_hookHandle, nCode, wParam, lParam);
|
||||
}
|
||||
if (!SuspendHotkey && num == _hotkey.VkCode)
|
||||
{
|
||||
bool flag = !_hotkey.Ctrl || (GetAsyncKeyState(17) & 0x8000) != 0;
|
||||
bool flag2 = !_hotkey.Alt || (GetAsyncKeyState(18) & 0x8000) != 0;
|
||||
bool flag3 = !_hotkey.Shift || (GetAsyncKeyState(16) & 0x8000) != 0;
|
||||
bool flag4 = !_hotkey.Win || (GetAsyncKeyState(91) & 0x8000) != 0 || (GetAsyncKeyState(92) & 0x8000) != 0;
|
||||
if (flag && flag2 && flag3 && flag4)
|
||||
{
|
||||
this.HotkeyTriggered?.Invoke(this, EventArgs.Empty);
|
||||
_suppressNextKeyUp = true;
|
||||
_suppressKeyUpVk = num;
|
||||
if (_hotkey.Alt)
|
||||
{
|
||||
_suppressNextAltUp = true;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (!SuspendHotkey && _captureHotkeyEnabled && num == _captureHotkey.VkCode)
|
||||
{
|
||||
bool flag5 = !_captureHotkey.Ctrl || (GetAsyncKeyState(17) & 0x8000) != 0;
|
||||
bool flag6 = !_captureHotkey.Alt || (GetAsyncKeyState(18) & 0x8000) != 0;
|
||||
bool flag7 = !_captureHotkey.Shift || (GetAsyncKeyState(16) & 0x8000) != 0;
|
||||
bool flag8 = !_captureHotkey.Win || (GetAsyncKeyState(91) & 0x8000) != 0 || (GetAsyncKeyState(92) & 0x8000) != 0;
|
||||
if (flag5 && flag6 && flag7 && flag8)
|
||||
{
|
||||
this.CaptureHotkeyTriggered?.Invoke(this, EventArgs.Empty);
|
||||
_suppressNextKeyUp = true;
|
||||
_suppressKeyUpVk = num;
|
||||
if (_captureHotkey.Alt)
|
||||
{
|
||||
_suppressNextAltUp = true;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Func<int, bool>? keyFilter = KeyFilter;
|
||||
if (keyFilter != null && keyFilter(num))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return CallNextHookEx(_hookHandle, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_hookHandle != IntPtr.Zero)
|
||||
{
|
||||
UnhookWindowsHookEx(_hookHandle);
|
||||
_hookHandle = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern nint SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, nint hMod, uint dwThreadId);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool UnhookWindowsHookEx(nint hhk);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern nint CallNextHookEx(nint hhk, int nCode, nint wParam, nint lParam);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern nint GetModuleHandle(string lpModuleName);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern short GetAsyncKeyState(int vKey);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern nint GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern int GetClassName(nint hWnd, StringBuilder lpClassName, int nMaxCount);
|
||||
}
|
||||
195
.decompiledproj/AxCopilot/Core/PluginHost.cs
Normal file
195
.decompiledproj/AxCopilot/Core/PluginHost.cs
Normal file
@@ -0,0 +1,195 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using AxCopilot.Handlers;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Core;
|
||||
|
||||
public class PluginHost
|
||||
{
|
||||
private readonly SettingsService _settings;
|
||||
|
||||
private readonly CommandResolver _resolver;
|
||||
|
||||
private readonly List<IActionHandler> _loadedPlugins = new List<IActionHandler>();
|
||||
|
||||
public IReadOnlyList<IActionHandler> LoadedPlugins => _loadedPlugins;
|
||||
|
||||
public PluginHost(SettingsService settings, CommandResolver resolver)
|
||||
{
|
||||
_settings = settings;
|
||||
_resolver = resolver;
|
||||
}
|
||||
|
||||
public void LoadAll()
|
||||
{
|
||||
_loadedPlugins.Clear();
|
||||
foreach (PluginEntry item in _settings.Settings.Plugins.Where((PluginEntry p) => p.Enabled))
|
||||
{
|
||||
LoadPlugin(item.Path);
|
||||
}
|
||||
string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "skills");
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (string item2 in Directory.EnumerateFiles(path, "*.skill.json"))
|
||||
{
|
||||
LoadJsonSkill(item2);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadPlugin(string dllPath)
|
||||
{
|
||||
if (!File.Exists(dllPath))
|
||||
{
|
||||
LogService.Warn("플러그인 파일 없음: " + dllPath);
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
Assembly assembly = Assembly.LoadFrom(dllPath);
|
||||
IEnumerable<Type> enumerable = from t in assembly.GetExportedTypes()
|
||||
where typeof(IActionHandler).IsAssignableFrom(t) && !t.IsAbstract
|
||||
select t;
|
||||
foreach (Type item in enumerable)
|
||||
{
|
||||
if (Activator.CreateInstance(item) is IActionHandler actionHandler)
|
||||
{
|
||||
_resolver.RegisterHandler(actionHandler);
|
||||
_loadedPlugins.Add(actionHandler);
|
||||
LogService.Info("플러그인 로드: " + actionHandler.Metadata.Name + " v" + actionHandler.Metadata.Version);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Error("플러그인 로드 실패 (" + dllPath + "): " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadJsonSkill(string skillPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
IActionHandler actionHandler = JsonSkillLoader.Load(skillPath);
|
||||
if (actionHandler != null)
|
||||
{
|
||||
_resolver.RegisterHandler(actionHandler);
|
||||
_loadedPlugins.Add(actionHandler);
|
||||
LogService.Info("JSON 스킬 로드: " + actionHandler.Metadata.Name);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Error("JSON 스킬 로드 실패 (" + skillPath + "): " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
LogService.Info("플러그인 전체 재로드 시작");
|
||||
LoadAll();
|
||||
}
|
||||
|
||||
public int InstallFromZip(string zipPath)
|
||||
{
|
||||
if (!File.Exists(zipPath))
|
||||
{
|
||||
LogService.Warn("플러그인 zip 파일 없음: " + zipPath);
|
||||
return 0;
|
||||
}
|
||||
string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "plugins");
|
||||
Directory.CreateDirectory(text);
|
||||
int num = 0;
|
||||
try
|
||||
{
|
||||
using ZipArchive zipArchive = ZipFile.OpenRead(zipPath);
|
||||
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(zipPath);
|
||||
string text2 = Path.Combine(text, fileNameWithoutExtension);
|
||||
Directory.CreateDirectory(text2);
|
||||
foreach (ZipArchiveEntry entry in zipArchive.Entries)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(entry.Name))
|
||||
{
|
||||
string text3 = Path.Combine(text2, entry.Name);
|
||||
if (!Path.GetFullPath(text3).StartsWith(Path.GetFullPath(text2)))
|
||||
{
|
||||
LogService.Warn("플러그인 zip 경로 위험: " + entry.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.ExtractToFile(text3, overwrite: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (string dllFile in Directory.EnumerateFiles(text2, "*.dll"))
|
||||
{
|
||||
if (!_settings.Settings.Plugins.Any((PluginEntry p) => p.Path == dllFile))
|
||||
{
|
||||
_settings.Settings.Plugins.Add(new PluginEntry
|
||||
{
|
||||
Enabled = true,
|
||||
Path = dllFile
|
||||
});
|
||||
LoadPlugin(dllFile);
|
||||
num++;
|
||||
}
|
||||
}
|
||||
string text4 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "skills");
|
||||
Directory.CreateDirectory(text4);
|
||||
foreach (string item in Directory.EnumerateFiles(text2, "*.skill.json"))
|
||||
{
|
||||
string text5 = Path.Combine(text4, Path.GetFileName(item));
|
||||
File.Copy(item, text5, overwrite: true);
|
||||
LoadJsonSkill(text5);
|
||||
num++;
|
||||
}
|
||||
if (num > 0)
|
||||
{
|
||||
_settings.Save();
|
||||
}
|
||||
LogService.Info($"플러그인 설치 완료: {zipPath} → {num}개 핸들러");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Error("플러그인 zip 설치 실패: " + ex.Message);
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
public bool UninstallPlugin(string dllPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
PluginEntry pluginEntry = _settings.Settings.Plugins.FirstOrDefault((PluginEntry p) => p.Path == dllPath);
|
||||
if (pluginEntry != null)
|
||||
{
|
||||
_settings.Settings.Plugins.Remove(pluginEntry);
|
||||
_settings.Save();
|
||||
}
|
||||
string directoryName = Path.GetDirectoryName(dllPath);
|
||||
if (directoryName != null && Directory.Exists(directoryName))
|
||||
{
|
||||
string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "plugins");
|
||||
if (Path.GetFullPath(directoryName).StartsWith(Path.GetFullPath(path)))
|
||||
{
|
||||
Directory.Delete(directoryName, recursive: true);
|
||||
}
|
||||
}
|
||||
LogService.Info("플러그인 제거 완료: " + dllPath);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Error("플러그인 제거 실패: " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
.decompiledproj/AxCopilot/Core/RestoreResult.cs
Normal file
3
.decompiledproj/AxCopilot/Core/RestoreResult.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace AxCopilot.Core;
|
||||
|
||||
public record RestoreResult(bool Success, string Message);
|
||||
264
.decompiledproj/AxCopilot/Core/SnippetExpander.cs
Normal file
264
.decompiledproj/AxCopilot/Core/SnippetExpander.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Core;
|
||||
|
||||
public class SnippetExpander
|
||||
{
|
||||
private struct INPUT
|
||||
{
|
||||
public int type;
|
||||
|
||||
public InputUnion u;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private struct InputUnion
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public MOUSEINPUT mi;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public KEYBDINPUT ki;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public HARDWAREINPUT hi;
|
||||
}
|
||||
|
||||
private struct KEYBDINPUT
|
||||
{
|
||||
public ushort wVk;
|
||||
|
||||
public ushort wScan;
|
||||
|
||||
public uint dwFlags;
|
||||
|
||||
public uint time;
|
||||
|
||||
public nint dwExtraInfo;
|
||||
}
|
||||
|
||||
private struct MOUSEINPUT
|
||||
{
|
||||
public int dx;
|
||||
|
||||
public int dy;
|
||||
|
||||
public uint mouseData;
|
||||
|
||||
public uint dwFlags;
|
||||
|
||||
public uint time;
|
||||
|
||||
public nint dwExtraInfo;
|
||||
}
|
||||
|
||||
private struct HARDWAREINPUT
|
||||
{
|
||||
public uint uMsg;
|
||||
|
||||
public ushort wParamL;
|
||||
|
||||
public ushort wParamH;
|
||||
}
|
||||
|
||||
private readonly SettingsService _settings;
|
||||
|
||||
private readonly StringBuilder _buffer = new StringBuilder();
|
||||
|
||||
private bool _tracking;
|
||||
|
||||
private const ushort VK_BACK = 8;
|
||||
|
||||
private const int VK_ESCAPE = 27;
|
||||
|
||||
private const int VK_SPACE = 32;
|
||||
|
||||
private const int VK_RETURN = 13;
|
||||
|
||||
private const int VK_OEM_1 = 186;
|
||||
|
||||
private const int VK_SHIFT = 16;
|
||||
|
||||
private const int VK_CONTROL = 17;
|
||||
|
||||
private const int VK_MENU = 18;
|
||||
|
||||
private const ushort VK_CTRL_US = 17;
|
||||
|
||||
private static readonly HashSet<int> ClearKeys = new HashSet<int> { 33, 34, 35, 36, 37, 38, 39, 40, 46 };
|
||||
|
||||
public SnippetExpander(SettingsService settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public bool HandleKey(int vkCode)
|
||||
{
|
||||
if (!_settings.Settings.Launcher.SnippetAutoExpand)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ((GetAsyncKeyState(17) & 0x8000) != 0)
|
||||
{
|
||||
_tracking = false;
|
||||
_buffer.Clear();
|
||||
return false;
|
||||
}
|
||||
if ((GetAsyncKeyState(18) & 0x8000) != 0)
|
||||
{
|
||||
_tracking = false;
|
||||
_buffer.Clear();
|
||||
return false;
|
||||
}
|
||||
if (vkCode == 186 && (GetAsyncKeyState(16) & 0x8000) == 0)
|
||||
{
|
||||
_tracking = true;
|
||||
_buffer.Clear();
|
||||
_buffer.Append(';');
|
||||
return false;
|
||||
}
|
||||
if (!_tracking)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ((vkCode >= 65 && vkCode <= 90) || (vkCode >= 48 && vkCode <= 57) || (vkCode >= 96 && vkCode <= 105) || vkCode == 189)
|
||||
{
|
||||
bool shifted = (GetAsyncKeyState(16) & 0x8000) != 0;
|
||||
char c = VkToChar(vkCode, shifted);
|
||||
if (c != 0)
|
||||
{
|
||||
_buffer.Append(char.ToLowerInvariant(c));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
switch (vkCode)
|
||||
{
|
||||
case 8:
|
||||
if (_buffer.Length > 1)
|
||||
{
|
||||
_buffer.Remove(_buffer.Length - 1, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_tracking = false;
|
||||
_buffer.Clear();
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
if (vkCode != 13)
|
||||
{
|
||||
if (vkCode == 27 || ClearKeys.Contains(vkCode) || vkCode >= 112)
|
||||
{
|
||||
_tracking = false;
|
||||
_buffer.Clear();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
goto case 32;
|
||||
case 32:
|
||||
if (_buffer.Length > 1)
|
||||
{
|
||||
string keyword = _buffer.ToString(1, _buffer.Length - 1);
|
||||
_tracking = false;
|
||||
_buffer.Clear();
|
||||
SnippetEntry snippetEntry = _settings.Settings.Snippets.FirstOrDefault((SnippetEntry s) => s.Key.Equals(keyword, StringComparison.OrdinalIgnoreCase));
|
||||
if (snippetEntry != null)
|
||||
{
|
||||
string expanded = ExpandVariables(snippetEntry.Content);
|
||||
int deleteCount = keyword.Length + 1;
|
||||
((DispatcherObject)Application.Current).Dispatcher.BeginInvoke((Delegate)(Action)delegate
|
||||
{
|
||||
PasteExpansion(expanded, deleteCount);
|
||||
}, Array.Empty<object>());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_tracking = false;
|
||||
_buffer.Clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void PasteExpansion(string text, int deleteCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
INPUT[] array = new INPUT[deleteCount * 2];
|
||||
for (int i = 0; i < deleteCount; i++)
|
||||
{
|
||||
array[i * 2] = MakeKeyInput(8, keyUp: false);
|
||||
array[i * 2 + 1] = MakeKeyInput(8, keyUp: true);
|
||||
}
|
||||
SendInput((uint)array.Length, array, Marshal.SizeOf<INPUT>());
|
||||
Clipboard.SetText(text);
|
||||
INPUT[] array2 = new INPUT[4]
|
||||
{
|
||||
MakeKeyInput(17, keyUp: false),
|
||||
MakeKeyInput(86, keyUp: false),
|
||||
MakeKeyInput(86, keyUp: true),
|
||||
MakeKeyInput(17, keyUp: true)
|
||||
};
|
||||
SendInput((uint)array2.Length, array2, Marshal.SizeOf<INPUT>());
|
||||
LogService.Info($"스니펫 확장 완료: {deleteCount}자 삭제 후 붙여넣기");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("스니펫 확장 실패: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static INPUT MakeKeyInput(ushort vk, bool keyUp)
|
||||
{
|
||||
INPUT result = new INPUT
|
||||
{
|
||||
type = 1
|
||||
};
|
||||
result.u.ki.wVk = vk;
|
||||
result.u.ki.dwFlags = (keyUp ? 2u : 0u);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string ExpandVariables(string content)
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
return content.Replace("{date}", now.ToString("yyyy-MM-dd")).Replace("{time}", now.ToString("HH:mm:ss")).Replace("{datetime}", now.ToString("yyyy-MM-dd HH:mm:ss"))
|
||||
.Replace("{year}", now.Year.ToString())
|
||||
.Replace("{month}", now.Month.ToString("D2"))
|
||||
.Replace("{day}", now.Day.ToString("D2"));
|
||||
}
|
||||
|
||||
private static char VkToChar(int vk, bool shifted)
|
||||
{
|
||||
if (vk >= 65 && vk <= 90)
|
||||
{
|
||||
return shifted ? ((char)vk) : char.ToLowerInvariant((char)vk);
|
||||
}
|
||||
if (vk >= 48 && vk <= 57)
|
||||
{
|
||||
return shifted ? ")!@#$%^&*("[vk - 48] : ((char)vk);
|
||||
}
|
||||
if (vk >= 96 && vk <= 105)
|
||||
{
|
||||
return (char)(48 + (vk - 96));
|
||||
}
|
||||
if (vk == 189)
|
||||
{
|
||||
return shifted ? '_' : '-';
|
||||
}
|
||||
return '\0';
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern short GetAsyncKeyState(int vKey);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
|
||||
}
|
||||
63
.decompiledproj/AxCopilot/Handlers/BatchHandler.cs
Normal file
63
.decompiledproj/AxCopilot/Handlers/BatchHandler.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class BatchHandler : IActionHandler
|
||||
{
|
||||
private readonly SettingsService _settings;
|
||||
|
||||
public string? Prefix => ">";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("batch", "명령 실행", "1.0", "AX");
|
||||
|
||||
public BatchHandler(SettingsService settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
List<LauncherItem> list = new List<LauncherItem>();
|
||||
IEnumerable<LauncherItem> collection = from a in _settings.Settings.Aliases
|
||||
where a.Type == "batch" && (string.IsNullOrEmpty(query) || a.Key.Contains(query, StringComparison.OrdinalIgnoreCase))
|
||||
select new LauncherItem(a.Key, a.Target, null, a, null, "\ue756");
|
||||
list.AddRange(collection);
|
||||
if (!string.IsNullOrEmpty(query))
|
||||
{
|
||||
string text = query.Replace("\"", "\\\"");
|
||||
list.Insert(0, new LauncherItem("실행: " + query, "PowerShell에서 직접 실행", null, new AliasEntry
|
||||
{
|
||||
Type = "batch",
|
||||
Target = "powershell -NoProfile -Command \"" + text + "\"",
|
||||
ShowWindow = true
|
||||
}, null, "\ue756"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is AliasEntry aliasEntry)
|
||||
{
|
||||
string text = Environment.ExpandEnvironmentVariables(aliasEntry.Target);
|
||||
string[] array = text.Split(' ', 2);
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo(array[0])
|
||||
{
|
||||
Arguments = ((array.Length > 1) ? array[1] : ""),
|
||||
UseShellExecute = aliasEntry.ShowWindow,
|
||||
CreateNoWindow = !aliasEntry.ShowWindow,
|
||||
WindowStyle = ((!aliasEntry.ShowWindow) ? ProcessWindowStyle.Hidden : ProcessWindowStyle.Normal)
|
||||
};
|
||||
Process.Start(startInfo);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
188
.decompiledproj/AxCopilot/Handlers/BatchTextHandler.cs
Normal file
188
.decompiledproj/AxCopilot/Handlers/BatchTextHandler.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class BatchTextHandler : IActionHandler
|
||||
{
|
||||
private static readonly (string Cmd, string Desc)[] Commands = new(string, string)[14]
|
||||
{
|
||||
("prefix [텍스트]", "각 줄 앞에 텍스트 추가"),
|
||||
("suffix [텍스트]", "각 줄 뒤에 텍스트 추가"),
|
||||
("wrap [문자]", "각 줄을 지정 문자로 감싸기 (예: wrap \")"),
|
||||
("number", "줄번호 추가 (1. 2. 3. ...)"),
|
||||
("sort", "줄 오름차순 정렬"),
|
||||
("sortd", "줄 내림차순 정렬"),
|
||||
("reverse", "줄 순서 뒤집기"),
|
||||
("unique", "중복 줄 제거"),
|
||||
("trim", "각 줄 앞뒤 공백 제거"),
|
||||
("replace [A] [B]", "A를 B로 전체 치환"),
|
||||
("csv", "줄들을 쉼표로 합쳐 한 줄로"),
|
||||
("split [구분자]", "한 줄을 구분자로 분리하여 여러 줄로"),
|
||||
("indent [N]", "각 줄 앞에 공백 N개 추가"),
|
||||
("unindent", "각 줄의 선행 공백/탭 제거")
|
||||
};
|
||||
|
||||
public string? Prefix => "batch";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("BatchText", "텍스트 일괄 처리 — batch", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string text = query.Trim();
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
List<LauncherItem> list = Commands.Select(((string Cmd, string Desc) c) => new LauncherItem("batch " + c.Cmd, c.Desc, null, null, null, "\ue8d2")).ToList();
|
||||
list.Insert(0, new LauncherItem("텍스트 일괄 처리", "클립보드 텍스트의 각 줄에 변환 적용 · 명령 입력", null, null, null, "\ue946"));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
string text2 = null;
|
||||
try
|
||||
{
|
||||
Application current = Application.Current;
|
||||
if (current != null && ((DispatcherObject)current).Dispatcher.Invoke<bool>((Func<bool>)(() => Clipboard.ContainsText())))
|
||||
{
|
||||
text2 = ((DispatcherObject)Application.Current).Dispatcher.Invoke<string>((Func<string>)(() => Clipboard.GetText()));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
if (string.IsNullOrEmpty(text2))
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("클립보드에 텍스트가 없습니다", "텍스트를 복사한 후 시도하세요", null, null, null, "\ue7ba")));
|
||||
}
|
||||
string[] array = (from l in text2.Split('\n')
|
||||
select l.TrimEnd('\r')).ToArray();
|
||||
string[] array2 = text.Split(' ', 2, StringSplitOptions.TrimEntries);
|
||||
string text3 = array2[0].ToLowerInvariant();
|
||||
string arg = ((array2.Length > 1) ? array2[1] : "");
|
||||
string text4 = null;
|
||||
string text5 = null;
|
||||
try
|
||||
{
|
||||
switch (text3)
|
||||
{
|
||||
case "prefix":
|
||||
text4 = string.Join("\n", array.Select((string l) => arg + l));
|
||||
text5 = "각 줄 앞에 '" + arg + "' 추가";
|
||||
break;
|
||||
case "suffix":
|
||||
text4 = string.Join("\n", array.Select((string l) => l + arg));
|
||||
text5 = "각 줄 뒤에 '" + arg + "' 추가";
|
||||
break;
|
||||
case "wrap":
|
||||
{
|
||||
string w = (string.IsNullOrEmpty(arg) ? "\"" : arg);
|
||||
text4 = string.Join("\n", array.Select((string l) => w + l + w));
|
||||
text5 = "각 줄을 '" + w + "'로 감싸기";
|
||||
break;
|
||||
}
|
||||
case "number":
|
||||
text4 = string.Join("\n", array.Select((string l, int i) => $"{i + 1}. {l}"));
|
||||
text5 = "줄번호 추가";
|
||||
break;
|
||||
case "sort":
|
||||
text4 = string.Join("\n", array.Order());
|
||||
text5 = "오름차순 정렬";
|
||||
break;
|
||||
case "sortd":
|
||||
text4 = string.Join("\n", array.OrderDescending());
|
||||
text5 = "내림차순 정렬";
|
||||
break;
|
||||
case "reverse":
|
||||
text4 = string.Join("\n", array.Reverse());
|
||||
text5 = "줄 순서 뒤집기";
|
||||
break;
|
||||
case "unique":
|
||||
{
|
||||
string[] array4 = array.Distinct().ToArray();
|
||||
text4 = string.Join("\n", array4);
|
||||
text5 = $"중복 제거: {array.Length}줄 → {array4.Length}줄";
|
||||
break;
|
||||
}
|
||||
case "trim":
|
||||
text4 = string.Join("\n", array.Select((string l) => l.Trim()));
|
||||
text5 = "각 줄 공백 제거";
|
||||
break;
|
||||
case "replace":
|
||||
{
|
||||
string[] array3 = arg.Split(' ', 2, StringSplitOptions.TrimEntries);
|
||||
if (array3.Length == 2)
|
||||
{
|
||||
text4 = text2.Replace(array3[0], array3[1]);
|
||||
text5 = $"'{array3[0]}' → '{array3[1]}' 치환";
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "csv":
|
||||
text4 = string.Join(",", array.Select((string l) => l.Trim()));
|
||||
text5 = $"{array.Length}줄 → CSV 한 줄";
|
||||
break;
|
||||
case "split":
|
||||
{
|
||||
string text6 = (string.IsNullOrEmpty(arg) ? "," : arg);
|
||||
text4 = string.Join("\n", text2.Split(text6));
|
||||
text5 = "'" + text6 + "' 기준 분리";
|
||||
break;
|
||||
}
|
||||
case "indent":
|
||||
{
|
||||
int result;
|
||||
int num = (int.TryParse(arg, out result) ? result : 4);
|
||||
string pad = new string(' ', num);
|
||||
text4 = string.Join("\n", array.Select((string l) => pad + l));
|
||||
text5 = $"{num}칸 들여쓰기";
|
||||
break;
|
||||
}
|
||||
case "unindent":
|
||||
text4 = string.Join("\n", array.Select((string l) => l.TrimStart(' ', '\t')));
|
||||
text5 = "선행 공백 제거";
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("처리 오류: " + ex.Message, "입력 데이터를 확인하세요", null, null, null, "\uea39")));
|
||||
}
|
||||
if (text4 == null)
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("알 수 없는 명령: " + text3, "batch 만 입력하면 전체 명령 목록", null, null, null, "\ue7ba")));
|
||||
}
|
||||
string text7 = ((text4.Length > 120) ? (text4.Substring(0, 117) + "…") : text4);
|
||||
text7 = text7.Replace("\n", "↵ ");
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("[" + text5 + "] → Enter로 클립보드 복사", text7, null, text4, null, "\ue8d2")));
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
string text = data as string;
|
||||
if (text != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Application current = Application.Current;
|
||||
if (current != null)
|
||||
{
|
||||
((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
Clipboard.SetText(text);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
NotificationService.Notify("일괄 처리 완료", "변환 결과가 클립보드에 복사되었습니다");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
178
.decompiledproj/AxCopilot/Handlers/BookmarkHandler.cs
Normal file
178
.decompiledproj/AxCopilot/Handlers/BookmarkHandler.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class BookmarkHandler : IActionHandler
|
||||
{
|
||||
private record BookmarkEntry(string Name, string? Url);
|
||||
|
||||
private List<BookmarkEntry>? _cache;
|
||||
|
||||
private DateTime _cacheTime = DateTime.MinValue;
|
||||
|
||||
private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(5.0);
|
||||
|
||||
public string? Prefix => null;
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Bookmarks", "Chrome / Edge 북마크 검색", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)Array.Empty<LauncherItem>());
|
||||
}
|
||||
RefreshCacheIfNeeded();
|
||||
if (_cache == null || _cache.Count == 0)
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)Array.Empty<LauncherItem>());
|
||||
}
|
||||
string q = query.Trim().ToLowerInvariant();
|
||||
List<LauncherItem> result = (from b in _cache.Where((BookmarkEntry b) => b.Name.ToLowerInvariant().Contains(q) || (b.Url?.ToLowerInvariant().Contains(q) ?? false)).Take(8)
|
||||
select new LauncherItem(b.Name, b.Url ?? "", null, b.Url, null, "\ue774")).ToList();
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)result);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is string text && !string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(text)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("북마크 열기 실패: " + ex.Message);
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void RefreshCacheIfNeeded()
|
||||
{
|
||||
if (_cache != null && DateTime.Now - _cacheTime < CacheTtl)
|
||||
{
|
||||
return;
|
||||
}
|
||||
List<BookmarkEntry> list = new List<BookmarkEntry>();
|
||||
foreach (string bookmarkFile in GetBookmarkFiles())
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(bookmarkFile);
|
||||
ParseChromeBookmarks(json, list);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("북마크 파일 읽기 실패: " + bookmarkFile + " — " + ex.Message);
|
||||
}
|
||||
}
|
||||
_cache = list;
|
||||
_cacheTime = DateTime.Now;
|
||||
LogService.Info($"북마크 로드 완료: {list.Count}개");
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetBookmarkFiles()
|
||||
{
|
||||
string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
string[] chromePaths = new string[4]
|
||||
{
|
||||
Path.Combine(localAppData, "Google", "Chrome", "User Data"),
|
||||
Path.Combine(localAppData, "Google", "Chrome Beta", "User Data"),
|
||||
Path.Combine(localAppData, "Google", "Chrome Dev", "User Data"),
|
||||
Path.Combine(localAppData, "Google", "Chrome SxS", "User Data")
|
||||
};
|
||||
string[] edgePaths = new string[4]
|
||||
{
|
||||
Path.Combine(localAppData, "Microsoft", "Edge", "User Data"),
|
||||
Path.Combine(localAppData, "Microsoft", "Edge Beta", "User Data"),
|
||||
Path.Combine(localAppData, "Microsoft", "Edge Dev", "User Data"),
|
||||
Path.Combine(localAppData, "Microsoft", "Edge Canary", "User Data")
|
||||
};
|
||||
foreach (string profileRoot in chromePaths.Concat(edgePaths))
|
||||
{
|
||||
if (!Directory.Exists(profileRoot))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string defaultBookmark = Path.Combine(profileRoot, "Default", "Bookmarks");
|
||||
if (File.Exists(defaultBookmark))
|
||||
{
|
||||
yield return defaultBookmark;
|
||||
}
|
||||
string[] directories = Directory.GetDirectories(profileRoot, "Profile *");
|
||||
foreach (string dir in directories)
|
||||
{
|
||||
string f = Path.Combine(dir, "Bookmarks");
|
||||
if (File.Exists(f))
|
||||
{
|
||||
yield return f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ParseChromeBookmarks(string json, List<BookmarkEntry> result)
|
||||
{
|
||||
JsonNode jsonNode = JsonNode.Parse(json)?["roots"];
|
||||
if (jsonNode == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string[] array = new string[3] { "bookmark_bar", "other", "synced" };
|
||||
foreach (string propertyName in array)
|
||||
{
|
||||
JsonNode jsonNode2 = jsonNode[propertyName];
|
||||
if (jsonNode2 != null)
|
||||
{
|
||||
WalkNode(jsonNode2, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void WalkNode(JsonNode node, List<BookmarkEntry> result)
|
||||
{
|
||||
string text = node["type"]?.GetValue<string>();
|
||||
if (text == "url")
|
||||
{
|
||||
string text2 = node["name"]?.GetValue<string>() ?? "";
|
||||
string text3 = node["url"]?.GetValue<string>() ?? "";
|
||||
if (!string.IsNullOrWhiteSpace(text2) && !string.IsNullOrWhiteSpace(text3))
|
||||
{
|
||||
result.Add(new BookmarkEntry(text2, text3));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(text == "folder"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
JsonArray jsonArray = node["children"]?.AsArray();
|
||||
if (jsonArray == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (JsonNode item in jsonArray)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
WalkNode(item, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
79
.decompiledproj/AxCopilot/Handlers/CalculatorHandler.cs
Normal file
79
.decompiledproj/AxCopilot/Handlers/CalculatorHandler.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using AxCopilot.SDK;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class CalculatorHandler : IActionHandler
|
||||
{
|
||||
public string? Prefix => "=";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Calculator", "수식 계산기 — = 뒤에 수식 입력", "1.0", "AX");
|
||||
|
||||
public async Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("수식을 입력하세요", "예: 1+2*3 · sqrt(16) · 100km in miles · 100 USD to KRW", null, null, null, "\ue8ef"));
|
||||
}
|
||||
string trimmed = query.Trim();
|
||||
if (CurrencyConverter.IsCurrencyQuery(trimmed))
|
||||
{
|
||||
return await CurrencyConverter.ConvertAsync(trimmed, ct);
|
||||
}
|
||||
if (UnitConverter.TryConvert(trimmed, out string convertResult))
|
||||
{
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem(convertResult, trimmed + " · Enter로 클립보드에 복사", null, convertResult, null, "\ue8ef"));
|
||||
}
|
||||
try
|
||||
{
|
||||
double value = MathEvaluator.Evaluate(trimmed);
|
||||
string result = FormatResult(value);
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem(result, trimmed + " = " + result + " · Enter로 클립보드에 복사", null, result, null, "\ue8ef"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("계산할 수 없습니다", ex.Message, null, null, null, "\uea39"));
|
||||
}
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static string FormatResult(double value)
|
||||
{
|
||||
if (double.IsNaN(value))
|
||||
{
|
||||
return "NaN";
|
||||
}
|
||||
if (double.IsPositiveInfinity(value))
|
||||
{
|
||||
return "∞";
|
||||
}
|
||||
if (double.IsNegativeInfinity(value))
|
||||
{
|
||||
return "-∞";
|
||||
}
|
||||
if (value == Math.Floor(value) && Math.Abs(value) < 1000000000000000.0)
|
||||
{
|
||||
return ((long)value).ToString();
|
||||
}
|
||||
return value.ToString("G10", CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
157
.decompiledproj/AxCopilot/Handlers/ChatHandler.cs
Normal file
157
.decompiledproj/AxCopilot/Handlers/ChatHandler.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
using AxCopilot.Views;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class ChatHandler : IActionHandler
|
||||
{
|
||||
private const bool DEPLOY_STUB = false;
|
||||
|
||||
private readonly SettingsService _settings;
|
||||
|
||||
private readonly object _windowLock = new object();
|
||||
|
||||
private ChatWindow? _chatWindow;
|
||||
|
||||
public string? Prefix => "!";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("ax.agent", "AX Agent", "1.0", "AX Agent — AI 어시스턴트");
|
||||
|
||||
public ChatHandler(SettingsService settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
bool flag = false;
|
||||
AppSettings appSettings = (Application.Current as App)?.SettingsService?.Settings;
|
||||
if (appSettings != null && !appSettings.AiEnabled)
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)Array.Empty<LauncherItem>());
|
||||
}
|
||||
List<LauncherItem> list = new List<LauncherItem>();
|
||||
string text = query.Trim();
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
list.Add(new LauncherItem("AX Agent 대화하기", "AI 비서와 대화를 시작합니다", null, "open_chat", null, "\ue8bd"));
|
||||
try
|
||||
{
|
||||
ChatStorageService chatStorageService = new ChatStorageService();
|
||||
List<ChatConversation> source = chatStorageService.LoadAllMeta();
|
||||
foreach (ChatConversation item in source.Take(5))
|
||||
{
|
||||
string value = FormatTimeAgo(item.UpdatedAt);
|
||||
string symbol = ChatCategory.GetSymbol(item.Category);
|
||||
list.Add(new LauncherItem(item.Title, $"{value} · 메시지 {item.Messages.Count}개", null, "resume:" + item.Id, null, symbol));
|
||||
}
|
||||
if (source.Any())
|
||||
{
|
||||
list.Add(new LauncherItem("새 대화 시작", "이전 대화와 별개의 새 대화를 시작합니다", null, "new_chat", null, "\ue710"));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(new LauncherItem("AI에게 물어보기: " + ((text.Length > 40) ? (text.Substring(0, 40) + "…") : text), "Enter를 누르면 AX Agent이 열리고 질문이 전송됩니다", null, "ask:" + text, null, "\ue8bd"));
|
||||
list.Add(new LauncherItem("AX Agent 대화하기", "질문 없이 Agent 창만 엽니다", null, "open_chat", null, "\ue8bd"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
bool flag = false;
|
||||
AppSettings appSettings = (Application.Current as App)?.SettingsService?.Settings;
|
||||
if (appSettings != null && !appSettings.AiEnabled)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
string data = (item.Data as string) ?? "open_chat";
|
||||
((DispatcherObject)Application.Current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
EnsureChatWindow();
|
||||
if (data.StartsWith("ask:"))
|
||||
{
|
||||
string text = data;
|
||||
string message = text.Substring(4, text.Length - 4);
|
||||
_chatWindow.Show();
|
||||
_chatWindow.Activate();
|
||||
_chatWindow.SendInitialMessage(message);
|
||||
}
|
||||
else if (data.StartsWith("resume:"))
|
||||
{
|
||||
string text = data;
|
||||
string conversationId = text.Substring(7, text.Length - 7);
|
||||
_chatWindow.Show();
|
||||
_chatWindow.Activate();
|
||||
_chatWindow.ResumeConversation(conversationId);
|
||||
}
|
||||
else if (data == "new_chat")
|
||||
{
|
||||
_chatWindow.Show();
|
||||
_chatWindow.Activate();
|
||||
_chatWindow.StartNewAndFocus();
|
||||
}
|
||||
else
|
||||
{
|
||||
_chatWindow.Show();
|
||||
_chatWindow.Activate();
|
||||
}
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void EnsureChatWindow()
|
||||
{
|
||||
lock (_windowLock)
|
||||
{
|
||||
if (_chatWindow != null && _chatWindow.IsLoaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_chatWindow = new ChatWindow(_settings);
|
||||
_chatWindow.Closed += delegate
|
||||
{
|
||||
lock (_windowLock)
|
||||
{
|
||||
_chatWindow = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatTimeAgo(DateTime dt)
|
||||
{
|
||||
TimeSpan timeSpan = DateTime.Now - dt;
|
||||
if (timeSpan.TotalMinutes < 1.0)
|
||||
{
|
||||
return "방금 전";
|
||||
}
|
||||
if (timeSpan.TotalHours < 1.0)
|
||||
{
|
||||
return $"{(int)timeSpan.TotalMinutes}분 전";
|
||||
}
|
||||
if (timeSpan.TotalDays < 1.0)
|
||||
{
|
||||
return $"{(int)timeSpan.TotalHours}시간 전";
|
||||
}
|
||||
if (timeSpan.TotalDays < 7.0)
|
||||
{
|
||||
return $"{(int)timeSpan.TotalDays}일 전";
|
||||
}
|
||||
return dt.ToString("yyyy-MM-dd");
|
||||
}
|
||||
}
|
||||
299
.decompiledproj/AxCopilot/Handlers/ClipboardHandler.cs
Normal file
299
.decompiledproj/AxCopilot/Handlers/ClipboardHandler.cs
Normal file
@@ -0,0 +1,299 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class ClipboardHandler : IActionHandler
|
||||
{
|
||||
private readonly SettingsService _settings;
|
||||
|
||||
public string? Prefix => "$";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("clipboard", "클립보드 변환", "1.0", "AX");
|
||||
|
||||
public ClipboardHandler(SettingsService settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
List<LauncherItem> list = new List<LauncherItem>();
|
||||
IEnumerable<ClipboardTransformer> enumerable = from t in GetBuiltinTransformers()
|
||||
where string.IsNullOrEmpty(query) || t.Key.Contains(query, StringComparison.OrdinalIgnoreCase)
|
||||
select t;
|
||||
foreach (ClipboardTransformer item in enumerable)
|
||||
{
|
||||
list.Add(new LauncherItem(item.Key, item.Description ?? "", null, item, null, "\ue77f"));
|
||||
}
|
||||
IEnumerable<LauncherItem> collection = from t in _settings.Settings.ClipboardTransformers
|
||||
where string.IsNullOrEmpty(query) || t.Key.Contains(query, StringComparison.OrdinalIgnoreCase)
|
||||
select new LauncherItem(t.Key, t.Description ?? t.Type, null, t, null, "\ue77f");
|
||||
list.AddRange(collection);
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
if (!(data is ClipboardTransformer transformer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
string input = null;
|
||||
((DispatcherObject)System.Windows.Application.Current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
input = (System.Windows.Clipboard.ContainsText() ? System.Windows.Clipboard.GetText() : null);
|
||||
});
|
||||
if (input == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string result = await TransformAsync(transformer, input, ct);
|
||||
if (result != null)
|
||||
{
|
||||
((DispatcherObject)System.Windows.Application.Current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
System.Windows.Clipboard.SetText(result);
|
||||
});
|
||||
nint prevHwnd = WindowTracker.PreviousWindow;
|
||||
if (prevHwnd != IntPtr.Zero)
|
||||
{
|
||||
SetForegroundWindow(prevHwnd);
|
||||
}
|
||||
await Task.Delay(120, ct);
|
||||
SendKeys.SendWait("^v");
|
||||
LogService.Info("클립보드 변환: '" + transformer.Key + "' 적용");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string?> TransformAsync(ClipboardTransformer t, string input, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
string type = t.Type;
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string text = type;
|
||||
string result;
|
||||
if (!(text == "regex"))
|
||||
{
|
||||
if (!(text == "script") || t.Command == null)
|
||||
{
|
||||
goto IL_016f;
|
||||
}
|
||||
result = await RunScriptAsync(t.Command, input, t.Timeout, ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (t.Pattern == null || t.Replace == null)
|
||||
{
|
||||
goto IL_016f;
|
||||
}
|
||||
result = Regex.Replace(input, t.Pattern, t.Replace, RegexOptions.None, TimeSpan.FromMilliseconds((t.Timeout > 0) ? t.Timeout : 5000));
|
||||
}
|
||||
goto IL_0188;
|
||||
IL_016f:
|
||||
result = ExecuteBuiltin(t.Key, input);
|
||||
goto IL_0188;
|
||||
IL_0188:
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Exception ex2 = ex;
|
||||
LogService.Error("변환 실패 (" + t.Key + "): " + ex2.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string> RunScriptAsync(string command, string input, int timeoutMs, CancellationToken ct)
|
||||
{
|
||||
using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
cts.CancelAfter(timeoutMs);
|
||||
string[] parts = command.Split(' ', 2);
|
||||
ProcessStartInfo psi = new ProcessStartInfo(parts[0])
|
||||
{
|
||||
Arguments = ((parts.Length > 1) ? parts[1] : ""),
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
StandardInputEncoding = Encoding.UTF8,
|
||||
StandardOutputEncoding = Encoding.UTF8
|
||||
};
|
||||
using Process proc = Process.Start(psi);
|
||||
await proc.StandardInput.WriteAsync(input);
|
||||
proc.StandardInput.Close();
|
||||
return await proc.StandardOutput.ReadToEndAsync(cts.Token);
|
||||
}
|
||||
|
||||
internal static string? ExecuteBuiltin(string key, string input)
|
||||
{
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string result = key switch
|
||||
{
|
||||
"$json" => FormatJson(input),
|
||||
"$upper" => input.ToUpperInvariant(),
|
||||
"$lower" => input.ToLowerInvariant(),
|
||||
"$ts" => TryParseTimestamp(input),
|
||||
"$epoch" => TryParseDate(input),
|
||||
"$urle" => Uri.EscapeDataString(input),
|
||||
"$urld" => Uri.UnescapeDataString(input),
|
||||
"$b64e" => Convert.ToBase64String(Encoding.UTF8.GetBytes(input)),
|
||||
"$b64d" => Encoding.UTF8.GetString(Convert.FromBase64String(input)),
|
||||
"$md" => StripMarkdown(input),
|
||||
"$trim" => input.Trim(),
|
||||
"$lines" => string.Join(Environment.NewLine, from l in input.Split('\n')
|
||||
select l.Trim() into l
|
||||
where l.Length > 0
|
||||
select l),
|
||||
_ => null,
|
||||
};
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string FormatJson(string input)
|
||||
{
|
||||
try
|
||||
{
|
||||
JsonDocument value = JsonDocument.Parse(input);
|
||||
return JsonSerializer.Serialize(value, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
private static string? TryParseTimestamp(string input)
|
||||
{
|
||||
if (long.TryParse(input.Trim(), out var result))
|
||||
{
|
||||
return DateTimeOffset.FromUnixTimeSeconds(result).LocalDateTime.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string? TryParseDate(string input)
|
||||
{
|
||||
if (DateTime.TryParse(input.Trim(), out var result))
|
||||
{
|
||||
return new DateTimeOffset(result).ToUnixTimeSeconds().ToString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string StripMarkdown(string input)
|
||||
{
|
||||
return Regex.Replace(input, "(\\*\\*|__)(.*?)\\1|(\\*|_)(.*?)\\3|`(.+?)`|#{1,6}\\s*", "$2$4$5");
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetForegroundWindow(nint hWnd);
|
||||
|
||||
private static IEnumerable<ClipboardTransformer> GetBuiltinTransformers()
|
||||
{
|
||||
return new _003C_003Ez__ReadOnlyArray<ClipboardTransformer>(new ClipboardTransformer[12]
|
||||
{
|
||||
new ClipboardTransformer
|
||||
{
|
||||
Key = "$json",
|
||||
Type = "builtin",
|
||||
Description = "JSON 포맷팅 (들여쓰기 적용)"
|
||||
},
|
||||
new ClipboardTransformer
|
||||
{
|
||||
Key = "$upper",
|
||||
Type = "builtin",
|
||||
Description = "대문자 변환"
|
||||
},
|
||||
new ClipboardTransformer
|
||||
{
|
||||
Key = "$lower",
|
||||
Type = "builtin",
|
||||
Description = "소문자 변환"
|
||||
},
|
||||
new ClipboardTransformer
|
||||
{
|
||||
Key = "$ts",
|
||||
Type = "builtin",
|
||||
Description = "유닉스 타임스탬프 → 날짜 문자열"
|
||||
},
|
||||
new ClipboardTransformer
|
||||
{
|
||||
Key = "$epoch",
|
||||
Type = "builtin",
|
||||
Description = "날짜 문자열 → 유닉스 타임스탬프"
|
||||
},
|
||||
new ClipboardTransformer
|
||||
{
|
||||
Key = "$urle",
|
||||
Type = "builtin",
|
||||
Description = "URL 인코딩"
|
||||
},
|
||||
new ClipboardTransformer
|
||||
{
|
||||
Key = "$urld",
|
||||
Type = "builtin",
|
||||
Description = "URL 디코딩"
|
||||
},
|
||||
new ClipboardTransformer
|
||||
{
|
||||
Key = "$b64e",
|
||||
Type = "builtin",
|
||||
Description = "Base64 인코딩"
|
||||
},
|
||||
new ClipboardTransformer
|
||||
{
|
||||
Key = "$b64d",
|
||||
Type = "builtin",
|
||||
Description = "Base64 디코딩"
|
||||
},
|
||||
new ClipboardTransformer
|
||||
{
|
||||
Key = "$md",
|
||||
Type = "builtin",
|
||||
Description = "마크다운 문법 제거"
|
||||
},
|
||||
new ClipboardTransformer
|
||||
{
|
||||
Key = "$trim",
|
||||
Type = "builtin",
|
||||
Description = "앞뒤 공백 제거"
|
||||
},
|
||||
new ClipboardTransformer
|
||||
{
|
||||
Key = "$lines",
|
||||
Type = "builtin",
|
||||
Description = "빈 줄 제거 및 각 줄 공백 정리"
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
192
.decompiledproj/AxCopilot/Handlers/ClipboardHistoryHandler.cs
Normal file
192
.decompiledproj/AxCopilot/Handlers/ClipboardHistoryHandler.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class ClipboardHistoryHandler : IActionHandler
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit, Size = 40)]
|
||||
private struct INPUT
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public uint Type;
|
||||
|
||||
[FieldOffset(8)]
|
||||
public KEYBDINPUT ki;
|
||||
}
|
||||
|
||||
private struct KEYBDINPUT
|
||||
{
|
||||
public ushort wVk;
|
||||
|
||||
public ushort wScan;
|
||||
|
||||
public uint dwFlags;
|
||||
|
||||
public uint time;
|
||||
|
||||
public nint dwExtraInfo;
|
||||
}
|
||||
|
||||
private readonly ClipboardHistoryService _historyService;
|
||||
|
||||
private static readonly Dictionary<string, string> CategoryFilters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "url", "URL" },
|
||||
{ "코드", "코드" },
|
||||
{ "code", "코드" },
|
||||
{ "경로", "경로" },
|
||||
{ "path", "경로" },
|
||||
{ "핀", "핀" },
|
||||
{ "pin", "핀" }
|
||||
};
|
||||
|
||||
public string? Prefix => "#";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("ClipboardHistory", "클립보드 히스토리 — # 뒤에 검색어 (또는 빈 입력으로 전체 보기)", "1.0", "AX");
|
||||
|
||||
public ClipboardHistoryHandler(ClipboardHistoryService historyService)
|
||||
{
|
||||
_historyService = historyService;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
IReadOnlyList<ClipboardEntry> history = _historyService.History;
|
||||
if (history.Count == 0)
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("클립보드 히스토리가 없습니다", "텍스트를 복사하면 이 곳에 기록됩니다", null, null, null, "\ue81c")));
|
||||
}
|
||||
string q = query.Trim().ToLowerInvariant();
|
||||
string catFilter = null;
|
||||
foreach (var (text3, text4) in CategoryFilters)
|
||||
{
|
||||
if (q == text3 || q.StartsWith(text3 + " "))
|
||||
{
|
||||
catFilter = text4;
|
||||
object obj;
|
||||
if (q.Length <= text3.Length)
|
||||
{
|
||||
obj = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
string text5 = q;
|
||||
int num = text3.Length + 1;
|
||||
obj = text5.Substring(num, text5.Length - num).Trim();
|
||||
}
|
||||
q = (string)obj;
|
||||
break;
|
||||
}
|
||||
}
|
||||
IEnumerable<ClipboardEntry> source = history.AsEnumerable();
|
||||
if (catFilter == "핀")
|
||||
{
|
||||
source = source.Where((ClipboardEntry e) => e.IsPinned);
|
||||
}
|
||||
else if (catFilter != null)
|
||||
{
|
||||
source = source.Where((ClipboardEntry e) => e.Category == catFilter);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(q))
|
||||
{
|
||||
source = source.Where((ClipboardEntry e) => e.Preview.ToLowerInvariant().Contains(q));
|
||||
}
|
||||
IOrderedEnumerable<ClipboardEntry> source2 = from e in source
|
||||
orderby e.IsPinned descending, e.CopiedAt descending
|
||||
select e;
|
||||
List<LauncherItem> list = source2.Select(delegate(ClipboardEntry e)
|
||||
{
|
||||
string text6 = (e.IsPinned ? "\ud83d\udccc " : "");
|
||||
string value = ((e.Category != "일반") ? ("[" + e.Category + "] ") : "");
|
||||
return new LauncherItem(text6 + e.Preview, $"{value}{e.RelativeTime} · {e.CopiedAt:MM/dd HH:mm}", null, e, null, e.IsPinned ? "\ue728" : (e.IsText ? "\ue81c" : "\ueb9f"));
|
||||
}).ToList();
|
||||
if (list.Count == 0)
|
||||
{
|
||||
list.Add(new LauncherItem("'" + query + "'에 해당하는 항목 없음", "#pin #url #코드 #경로 로 필터링 가능", null, null, null, "\ue81c"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
if (!(data is ClipboardEntry entry))
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
_historyService.SuppressNextCapture();
|
||||
_historyService.PromoteEntry(entry);
|
||||
if (!entry.IsText && entry.Image != null)
|
||||
{
|
||||
BitmapSource originalImg = ClipboardHistoryService.LoadOriginalImage(entry.OriginalImagePath);
|
||||
Clipboard.SetImage(originalImg ?? entry.Image);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(entry.Text))
|
||||
{
|
||||
Clipboard.SetText(entry.Text);
|
||||
nint prevWindow = WindowTracker.PreviousWindow;
|
||||
if (prevWindow != IntPtr.Zero)
|
||||
{
|
||||
await Task.Delay(300, ct);
|
||||
uint lpdwProcessId;
|
||||
uint targetThread = GetWindowThreadProcessId(prevWindow, out lpdwProcessId);
|
||||
uint currentThread = GetCurrentThreadId();
|
||||
AttachThreadInput(currentThread, targetThread, fAttach: true);
|
||||
SetForegroundWindow(prevWindow);
|
||||
AttachThreadInput(currentThread, targetThread, fAttach: false);
|
||||
await Task.Delay(100, ct);
|
||||
SendCtrlV();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
LogService.Warn("클립보드 히스토리 붙여넣기 실패: " + ex2.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SendCtrlV()
|
||||
{
|
||||
INPUT[] array = new INPUT[4];
|
||||
array[0].Type = 1u;
|
||||
array[0].ki.wVk = 17;
|
||||
array[1].Type = 1u;
|
||||
array[1].ki.wVk = 86;
|
||||
array[2].Type = 1u;
|
||||
array[2].ki.wVk = 86;
|
||||
array[2].ki.dwFlags = 2u;
|
||||
array[3].Type = 1u;
|
||||
array[3].ki.wVk = 17;
|
||||
array[3].ki.dwFlags = 2u;
|
||||
SendInput((uint)array.Length, array, Marshal.SizeOf<INPUT>());
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetForegroundWindow(nint hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint GetWindowThreadProcessId(nint hWnd, out uint lpdwProcessId);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, [MarshalAs(UnmanagedType.Bool)] bool fAttach);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern uint GetCurrentThreadId();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint SendInput(uint nInputs, [MarshalAs(UnmanagedType.LPArray)] INPUT[] pInputs, int cbSize);
|
||||
}
|
||||
137
.decompiledproj/AxCopilot/Handlers/ClipboardPipeHandler.cs
Normal file
137
.decompiledproj/AxCopilot/Handlers/ClipboardPipeHandler.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class ClipboardPipeHandler : IActionHandler
|
||||
{
|
||||
private static readonly Dictionary<string, (string Desc, Func<string, string> Fn)> Filters = new Dictionary<string, (string, Func<string, string>)>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["upper"] = ("대문자 변환", (string s) => s.ToUpperInvariant()),
|
||||
["lower"] = ("소문자 변환", (string s) => s.ToLowerInvariant()),
|
||||
["trim"] = ("앞뒤 공백 제거", (string s) => s.Trim()),
|
||||
["trimall"] = ("모든 공백 제거", (string s) => Regex.Replace(s, "\\s+", "", RegexOptions.None, TimeSpan.FromSeconds(1.0))),
|
||||
["sort"] = ("줄 정렬 (오름차순)", (string s) => string.Join("\n", s.Split('\n').Order())),
|
||||
["sortd"] = ("줄 정렬 (내림차순)", (string s) => string.Join("\n", s.Split('\n').OrderDescending())),
|
||||
["unique"] = ("중복 줄 제거", (string s) => string.Join("\n", s.Split('\n').Distinct())),
|
||||
["reverse"] = ("줄 순서 뒤집기", (string s) => string.Join("\n", s.Split('\n').Reverse())),
|
||||
["number"] = ("줄번호 추가", (string s) => string.Join("\n", s.Split('\n').Select((string l, int i) => $"{i + 1}. {l}"))),
|
||||
["quote"] = ("각 줄 따옴표 감싸기", (string s) => string.Join("\n", from l in s.Split('\n')
|
||||
select "\"" + l + "\"")),
|
||||
["b64e"] = ("Base64 인코딩", (string s) => Convert.ToBase64String(Encoding.UTF8.GetBytes(s))),
|
||||
["b64d"] = ("Base64 디코딩", (string s) => Encoding.UTF8.GetString(Convert.FromBase64String(s.Trim()))),
|
||||
["urle"] = ("URL 인코딩", (string s) => Uri.EscapeDataString(s)),
|
||||
["urld"] = ("URL 디코딩", (string s) => Uri.UnescapeDataString(s)),
|
||||
["md"] = ("마크다운 제거", (string s) => Regex.Replace(s, "[#*_`~\\[\\]()]", "", RegexOptions.None, TimeSpan.FromSeconds(1.0))),
|
||||
["lines"] = ("빈 줄 제거", (string s) => string.Join("\n", from l in s.Split('\n')
|
||||
where !string.IsNullOrWhiteSpace(l)
|
||||
select l)),
|
||||
["count"] = ("글자/단어/줄 수", (string s) => $"글자: {s.Length} 단어: {s.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).Length} 줄: {s.Split('\n').Length}"),
|
||||
["csv"] = ("CSV → 탭 변환", (string s) => s.Replace(',', '\t')),
|
||||
["tab"] = ("탭 → CSV 변환", (string s) => s.Replace('\t', ','))
|
||||
};
|
||||
|
||||
public string? Prefix => "pipe";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("ClipboardPipe", "클립보드 파이프라인 — pipe", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string text = query.Trim();
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
List<LauncherItem> list = Filters.Select<KeyValuePair<string, (string, Func<string, string>)>, LauncherItem>((KeyValuePair<string, (string Desc, Func<string, string> Fn)> kv) => new LauncherItem(kv.Key, kv.Value.Desc, null, null, null, "\ue77f")).ToList();
|
||||
list.Insert(0, new LauncherItem("클립보드 파이프라인", "필터를 > 로 연결: pipe upper > trim > b64e", null, null, null, "\ue946"));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
string[] array = (from s in text.Split('>')
|
||||
select s.Trim() into s
|
||||
where !string.IsNullOrEmpty(s)
|
||||
select s).ToArray();
|
||||
string[] array2 = array.Where((string s) => !Filters.ContainsKey(s)).ToArray();
|
||||
if (array2.Length != 0)
|
||||
{
|
||||
return Task.FromResult(new LauncherItem[1]
|
||||
{
|
||||
new LauncherItem("알 수 없는 필터: " + string.Join(", ", array2), "사용 가능: " + string.Join(", ", Filters.Keys.Take(10)) + " ...", null, null, null, "\ue7ba")
|
||||
}.AsEnumerable());
|
||||
}
|
||||
string text2 = null;
|
||||
try
|
||||
{
|
||||
Application current = Application.Current;
|
||||
if (current != null && ((DispatcherObject)current).Dispatcher.Invoke<bool>((Func<bool>)(() => Clipboard.ContainsText())))
|
||||
{
|
||||
text2 = ((DispatcherObject)Application.Current).Dispatcher.Invoke<string>((Func<string>)(() => Clipboard.GetText()));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
if (string.IsNullOrEmpty(text2))
|
||||
{
|
||||
return Task.FromResult(new LauncherItem[1]
|
||||
{
|
||||
new LauncherItem("클립보드에 텍스트가 없습니다", "텍스트를 복사한 후 시도하세요", null, null, null, "\ue7ba")
|
||||
}.AsEnumerable());
|
||||
}
|
||||
string text3 = text2;
|
||||
List<string> list2 = new List<string>();
|
||||
try
|
||||
{
|
||||
string[] array3 = array;
|
||||
foreach (string key in array3)
|
||||
{
|
||||
text3 = Filters[key].Fn(text3);
|
||||
list2.Add(Filters[key].Desc);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Task.FromResult(new LauncherItem[1]
|
||||
{
|
||||
new LauncherItem("파이프라인 실행 오류: " + ex.Message, "입력 데이터를 확인하세요", null, null, null, "\uea39")
|
||||
}.AsEnumerable());
|
||||
}
|
||||
string text4 = ((text3.Length > 100) ? (text3.Substring(0, 97) + "…") : text3);
|
||||
text4 = text4.Replace("\r\n", "↵ ").Replace("\n", "↵ ");
|
||||
return Task.FromResult(new LauncherItem[1]
|
||||
{
|
||||
new LauncherItem("[" + string.Join(" → ", array) + "] 결과 적용", text4 + " · Enter로 클립보드 복사", null, text3, null, "\ue77f")
|
||||
}.AsEnumerable());
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
string text = data as string;
|
||||
if (text != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Application current = Application.Current;
|
||||
if (current != null)
|
||||
{
|
||||
((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
Clipboard.SetText(text);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
NotificationService.Notify("파이프라인 완료", "변환 결과가 클립보드에 복사되었습니다");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
333
.decompiledproj/AxCopilot/Handlers/ColorHandler.cs
Normal file
333
.decompiledproj/AxCopilot/Handlers/ColorHandler.cs
Normal file
@@ -0,0 +1,333 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using AxCopilot.SDK;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class ColorHandler : IActionHandler
|
||||
{
|
||||
private static readonly Dictionary<string, string> _namedColors = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["red"] = "#FF0000",
|
||||
["빨강"] = "#FF0000",
|
||||
["빨간색"] = "#FF0000",
|
||||
["green"] = "#008000",
|
||||
["초록"] = "#008000",
|
||||
["초록색"] = "#008000",
|
||||
["blue"] = "#0000FF",
|
||||
["파랑"] = "#0000FF",
|
||||
["파란색"] = "#0000FF",
|
||||
["white"] = "#FFFFFF",
|
||||
["흰색"] = "#FFFFFF",
|
||||
["하양"] = "#FFFFFF",
|
||||
["black"] = "#000000",
|
||||
["검정"] = "#000000",
|
||||
["검은색"] = "#000000",
|
||||
["yellow"] = "#FFFF00",
|
||||
["노랑"] = "#FFFF00",
|
||||
["노란색"] = "#FFFF00",
|
||||
["orange"] = "#FFA500",
|
||||
["주황"] = "#FFA500",
|
||||
["주황색"] = "#FFA500",
|
||||
["purple"] = "#800080",
|
||||
["보라"] = "#800080",
|
||||
["보라색"] = "#800080",
|
||||
["pink"] = "#FFC0CB",
|
||||
["분홍"] = "#FFC0CB",
|
||||
["분홍색"] = "#FFC0CB",
|
||||
["hotpink"] = "#FF69B4",
|
||||
["핫핑크"] = "#FF69B4",
|
||||
["cyan"] = "#00FFFF",
|
||||
["시안"] = "#00FFFF",
|
||||
["magenta"] = "#FF00FF",
|
||||
["마젠타"] = "#FF00FF",
|
||||
["brown"] = "#A52A2A",
|
||||
["갈색"] = "#A52A2A",
|
||||
["gray"] = "#808080",
|
||||
["grey"] = "#808080",
|
||||
["회색"] = "#808080",
|
||||
["회"] = "#808080",
|
||||
["silver"] = "#C0C0C0",
|
||||
["은색"] = "#C0C0C0",
|
||||
["gold"] = "#FFD700",
|
||||
["금색"] = "#FFD700",
|
||||
["navy"] = "#000080",
|
||||
["남색"] = "#000080",
|
||||
["teal"] = "#008080",
|
||||
["틸"] = "#008080",
|
||||
["lime"] = "#00FF00",
|
||||
["라임"] = "#00FF00",
|
||||
["maroon"] = "#800000",
|
||||
["밤색"] = "#800000",
|
||||
["olive"] = "#808000",
|
||||
["올리브"] = "#808000",
|
||||
["coral"] = "#FF7F50",
|
||||
["코랄"] = "#FF7F50",
|
||||
["salmon"] = "#FA8072",
|
||||
["연어색"] = "#FA8072",
|
||||
["skyblue"] = "#87CEEB",
|
||||
["하늘색"] = "#87CEEB",
|
||||
["lightblue"] = "#ADD8E6",
|
||||
["연파랑"] = "#ADD8E6",
|
||||
["darkblue"] = "#00008B",
|
||||
["진파랑"] = "#00008B",
|
||||
["darkgreen"] = "#006400",
|
||||
["진초록"] = "#006400",
|
||||
["lightgreen"] = "#90EE90",
|
||||
["연초록"] = "#90EE90",
|
||||
["indigo"] = "#4B0082",
|
||||
["인디고"] = "#4B0082",
|
||||
["violet"] = "#EE82EE",
|
||||
["바이올렛"] = "#EE82EE",
|
||||
["beige"] = "#F5F5DC",
|
||||
["베이지"] = "#F5F5DC",
|
||||
["ivory"] = "#FFFFF0",
|
||||
["아이보리"] = "#FFFFF0",
|
||||
["khaki"] = "#F0E68C",
|
||||
["카키"] = "#F0E68C",
|
||||
["lavender"] = "#E6E6FA",
|
||||
["라벤더"] = "#E6E6FA",
|
||||
["turquoise"] = "#40E0D0",
|
||||
["터키옥"] = "#40E0D0",
|
||||
["chocolate"] = "#D2691E",
|
||||
["초콜릿"] = "#D2691E",
|
||||
["crimson"] = "#DC143C",
|
||||
["크림슨"] = "#DC143C",
|
||||
["transparent"] = "#00000000"
|
||||
};
|
||||
|
||||
private static readonly Regex _hexRe = new Regex("^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$");
|
||||
|
||||
private static readonly Regex _rgbRe = new Regex("^(?:rgb\\s*\\()?\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*\\)?$", RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex _rgbaRe = new Regex("^rgba\\s*\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*([\\d.]+)\\s*\\)$", RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex _hslRe = new Regex("^hsl\\s*\\(\\s*([\\d.]+)\\s*,\\s*([\\d.]+)%?\\s*,\\s*([\\d.]+)%?\\s*\\)$", RegexOptions.IgnoreCase);
|
||||
|
||||
public string? Prefix => "color";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Color", "색상 변환기 — color #FF5500 · color 255,85,0 · color red", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("색상 코드를 입력하세요", "예: #FF5500 · 255,85,0 · red · hsl(20,100%,50%)", null, null, null, "\ue771")));
|
||||
}
|
||||
string text = query.Trim();
|
||||
List<LauncherItem> list = new List<LauncherItem>();
|
||||
if (_hexRe.IsMatch(text))
|
||||
{
|
||||
string text2 = text.TrimStart('#');
|
||||
if (text2.Length == 3)
|
||||
{
|
||||
text2 = $"{text2[0]}{text2[0]}{text2[1]}{text2[1]}{text2[2]}{text2[2]}";
|
||||
}
|
||||
byte a = byte.MaxValue;
|
||||
int r;
|
||||
int g;
|
||||
int b;
|
||||
if (text2.Length == 8)
|
||||
{
|
||||
a = Convert.ToByte(text2.Substring(0, 2), 16);
|
||||
r = Convert.ToInt32(text2.Substring(2, 2), 16);
|
||||
g = Convert.ToInt32(text2.Substring(4, 2), 16);
|
||||
b = Convert.ToInt32(text2.Substring(6, 2), 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
r = Convert.ToInt32(text2.Substring(0, 2), 16);
|
||||
g = Convert.ToInt32(text2.Substring(2, 2), 16);
|
||||
b = Convert.ToInt32(text2.Substring(4, 2), 16);
|
||||
}
|
||||
AddColorItems(list, r, g, b, a, "#" + text2.ToUpperInvariant());
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
Match match = _rgbRe.Match(text);
|
||||
if (match.Success)
|
||||
{
|
||||
int num = int.Parse(match.Groups[1].Value);
|
||||
int num2 = int.Parse(match.Groups[2].Value);
|
||||
int num3 = int.Parse(match.Groups[3].Value);
|
||||
if (num <= 255 && num2 <= 255 && num3 <= 255)
|
||||
{
|
||||
AddColorItems(list, num, num2, num3, byte.MaxValue, $"rgb({num},{num2},{num3})");
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
}
|
||||
Match match2 = _rgbaRe.Match(text);
|
||||
if (match2.Success)
|
||||
{
|
||||
int num4 = int.Parse(match2.Groups[1].Value);
|
||||
int num5 = int.Parse(match2.Groups[2].Value);
|
||||
int num6 = int.Parse(match2.Groups[3].Value);
|
||||
double num7 = double.Parse(match2.Groups[4].Value, CultureInfo.InvariantCulture);
|
||||
byte a2 = (byte)Math.Clamp((int)(num7 * 255.0), 0, 255);
|
||||
if (num4 <= 255 && num5 <= 255 && num6 <= 255)
|
||||
{
|
||||
AddColorItems(list, num4, num5, num6, a2, $"rgba({num4},{num5},{num6},{num7:G3})");
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
}
|
||||
Match match3 = _hslRe.Match(text);
|
||||
if (match3.Success)
|
||||
{
|
||||
double num8 = double.Parse(match3.Groups[1].Value, CultureInfo.InvariantCulture);
|
||||
double num9 = double.Parse(match3.Groups[2].Value, CultureInfo.InvariantCulture) / 100.0;
|
||||
double num10 = double.Parse(match3.Groups[3].Value, CultureInfo.InvariantCulture) / 100.0;
|
||||
var (r2, g2, b2) = HslToRgb(num8, num9, num10);
|
||||
AddColorItems(list, r2, g2, b2, byte.MaxValue, $"hsl({num8:G},{num9 * 100.0:G}%,{num10 * 100.0:G}%)");
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
if (_namedColors.TryGetValue(text, out string value))
|
||||
{
|
||||
string text3 = value.TrimStart('#');
|
||||
int r3 = Convert.ToInt32(text3.Substring(0, 2), 16);
|
||||
int g3 = Convert.ToInt32(text3.Substring(2, 2), 16);
|
||||
int b3 = Convert.ToInt32(text3.Substring(4, 2), 16);
|
||||
AddColorItems(list, r3, g3, b3, byte.MaxValue, value.ToUpperInvariant());
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
string searchQ = text.ToLowerInvariant();
|
||||
IEnumerable<KeyValuePair<string, string>> enumerable = _namedColors.Where<KeyValuePair<string, string>>((KeyValuePair<string, string> kv) => kv.Key.Contains(searchQ, StringComparison.OrdinalIgnoreCase)).Take(8);
|
||||
foreach (KeyValuePair<string, string> item in enumerable)
|
||||
{
|
||||
list.Add(new LauncherItem(item.Key + " → " + item.Value.ToUpperInvariant(), "Enter로 HEX 복사", null, item.Value.ToUpperInvariant(), null, "\ue771"));
|
||||
}
|
||||
if (!list.Any())
|
||||
{
|
||||
list.Add(new LauncherItem("인식할 수 없는 색상", "#RRGGBB · RGB(r,g,b) · hsl(h,s%,l%) · red 등 색상 이름", null, null, null, "\uea39"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static void AddColorItems(List<LauncherItem> items, int r, int g, int b, byte a, string source)
|
||||
{
|
||||
string text = ((a == byte.MaxValue) ? $"#{r:X2}{g:X2}{b:X2}" : $"#{a:X2}{r:X2}{g:X2}{b:X2}");
|
||||
string text2 = ((a == byte.MaxValue) ? $"rgb({r}, {g}, {b})" : $"rgba({r}, {g}, {b}, {(double)(int)a / 255.0:G3})");
|
||||
(double h, double s, double l) tuple = RgbToHsl(r, g, b);
|
||||
double item = tuple.h;
|
||||
double item2 = tuple.s;
|
||||
double item3 = tuple.l;
|
||||
string text3 = $"hsl({item:G3}, {item2 * 100.0:G3}%, {item3 * 100.0:G3}%)";
|
||||
(double h, double s, double v) tuple2 = RgbToHsv(r, g, b);
|
||||
double item4 = tuple2.h;
|
||||
double item5 = tuple2.s;
|
||||
double item6 = tuple2.v;
|
||||
string text4 = $"hsv({item4:G3}, {item5 * 100.0:G3}%, {item6 * 100.0:G3}%)";
|
||||
items.Add(new LauncherItem("HEX → " + text.ToUpperInvariant(), source + " · Enter로 복사", null, text.ToUpperInvariant(), null, "\ue771"));
|
||||
items.Add(new LauncherItem("RGB → " + text2, source + " · Enter로 복사", null, text2, null, "\ue771"));
|
||||
items.Add(new LauncherItem("HSL → " + text3, source + " · Enter로 복사", null, text3, null, "\ue771"));
|
||||
items.Add(new LauncherItem("HSV → " + text4, source + " · Enter로 복사", null, text4, null, "\ue771"));
|
||||
}
|
||||
|
||||
private static (double h, double s, double l) RgbToHsl(int r, int g, int b)
|
||||
{
|
||||
double num = (double)r / 255.0;
|
||||
double num2 = (double)g / 255.0;
|
||||
double num3 = (double)b / 255.0;
|
||||
double num4 = Math.Max(num, Math.Max(num2, num3));
|
||||
double num5 = Math.Min(num, Math.Min(num2, num3));
|
||||
double num6 = (num4 + num5) / 2.0;
|
||||
double num7 = 0.0;
|
||||
double value = 0.0;
|
||||
if (num4 != num5)
|
||||
{
|
||||
double num8 = num4 - num5;
|
||||
value = ((num6 > 0.5) ? (num8 / (2.0 - num4 - num5)) : (num8 / (num4 + num5)));
|
||||
num7 = ((num4 == num) ? ((num2 - num3) / num8 + (double)((num2 < num3) ? 6 : 0)) : ((num4 == num2) ? ((num3 - num) / num8 + 2.0) : ((num - num2) / num8 + 4.0)));
|
||||
num7 /= 6.0;
|
||||
}
|
||||
return (h: Math.Round(num7 * 360.0, 1), s: Math.Round(value, 4), l: Math.Round(num6, 4));
|
||||
}
|
||||
|
||||
private static (double h, double s, double v) RgbToHsv(int r, int g, int b)
|
||||
{
|
||||
double num = (double)r / 255.0;
|
||||
double num2 = (double)g / 255.0;
|
||||
double num3 = (double)b / 255.0;
|
||||
double num4 = Math.Max(num, Math.Max(num2, num3));
|
||||
double num5 = Math.Min(num, Math.Min(num2, num3));
|
||||
double value = num4;
|
||||
double value2 = 0.0;
|
||||
double num6 = 0.0;
|
||||
double num7 = num4 - num5;
|
||||
if (num4 != 0.0)
|
||||
{
|
||||
value2 = num7 / num4;
|
||||
}
|
||||
if (num7 != 0.0)
|
||||
{
|
||||
num6 = ((num4 == num) ? ((num2 - num3) / num7 + (double)((num2 < num3) ? 6 : 0)) : ((num4 == num2) ? ((num3 - num) / num7 + 2.0) : ((num - num2) / num7 + 4.0)));
|
||||
num6 /= 6.0;
|
||||
}
|
||||
return (h: Math.Round(num6 * 360.0, 1), s: Math.Round(value2, 4), v: Math.Round(value, 4));
|
||||
}
|
||||
|
||||
private static (int r, int g, int b) HslToRgb(double h, double s, double l)
|
||||
{
|
||||
double num;
|
||||
double num2;
|
||||
double num3;
|
||||
if (s == 0.0)
|
||||
{
|
||||
num = (num2 = (num3 = l));
|
||||
}
|
||||
else
|
||||
{
|
||||
double num4 = ((l < 0.5) ? (l * (1.0 + s)) : (l + s - l * s));
|
||||
double p = 2.0 * l - num4;
|
||||
h /= 360.0;
|
||||
num = Hue2Rgb(p, num4, h + 1.0 / 3.0);
|
||||
num2 = Hue2Rgb(p, num4, h);
|
||||
num3 = Hue2Rgb(p, num4, h - 1.0 / 3.0);
|
||||
}
|
||||
return (r: (int)Math.Round(num * 255.0), g: (int)Math.Round(num2 * 255.0), b: (int)Math.Round(num3 * 255.0));
|
||||
}
|
||||
|
||||
private static double Hue2Rgb(double p, double q, double t)
|
||||
{
|
||||
if (t < 0.0)
|
||||
{
|
||||
t += 1.0;
|
||||
}
|
||||
if (t > 1.0)
|
||||
{
|
||||
t -= 1.0;
|
||||
}
|
||||
if (t < 1.0 / 6.0)
|
||||
{
|
||||
return p + (q - p) * 6.0 * t;
|
||||
}
|
||||
if (t < 0.5)
|
||||
{
|
||||
return q;
|
||||
}
|
||||
if (t < 2.0 / 3.0)
|
||||
{
|
||||
return p + (q - p) * (2.0 / 3.0 - t) * 6.0;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
}
|
||||
47
.decompiledproj/AxCopilot/Handlers/ColorPickHandler.cs
Normal file
47
.decompiledproj/AxCopilot/Handlers/ColorPickHandler.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Views;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class ColorPickHandler : IActionHandler
|
||||
{
|
||||
public string? Prefix => "pick";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("ColorPick", "스포이드 색상 추출 — pick", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("스포이드로 화면 색상 추출", "Enter → 스포이드 모드 진입 · 화면 아무 곳을 클릭하면 색상 코드 추출 · Esc 취소", null, "__PICK__", null, "\ue771")));
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
if (!(data is string s) || s != "__PICK__")
|
||||
{
|
||||
return;
|
||||
}
|
||||
await Task.Delay(200, ct);
|
||||
Application current = Application.Current;
|
||||
if (current == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
EyeDropperWindow eyeDropperWindow = new EyeDropperWindow();
|
||||
eyeDropperWindow.ShowDialog();
|
||||
if (eyeDropperWindow.PickedColor.HasValue)
|
||||
{
|
||||
ColorPickResultWindow colorPickResultWindow = new ColorPickResultWindow(eyeDropperWindow.PickedColor.Value, eyeDropperWindow.PickX, eyeDropperWindow.PickY);
|
||||
colorPickResultWindow.Show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
135
.decompiledproj/AxCopilot/Handlers/CredentialManager.cs
Normal file
135
.decompiledproj/AxCopilot/Handlers/CredentialManager.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public static class CredentialManager
|
||||
{
|
||||
private struct FILETIME
|
||||
{
|
||||
public uint dwLowDateTime;
|
||||
|
||||
public uint dwHighDateTime;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct CREDENTIAL
|
||||
{
|
||||
public uint Flags;
|
||||
|
||||
public uint Type;
|
||||
|
||||
public nint TargetName;
|
||||
|
||||
public nint Comment;
|
||||
|
||||
public FILETIME LastWritten;
|
||||
|
||||
public uint CredentialBlobSize;
|
||||
|
||||
public nint CredentialBlob;
|
||||
|
||||
public uint Persist;
|
||||
|
||||
public uint AttributeCount;
|
||||
|
||||
public nint Attributes;
|
||||
|
||||
public nint TargetAlias;
|
||||
|
||||
public nint UserName;
|
||||
}
|
||||
|
||||
private const uint CRED_TYPE_GENERIC = 1u;
|
||||
|
||||
private const uint CRED_PERSIST_LOCAL_MACHINE = 2u;
|
||||
|
||||
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern bool CredRead(string target, uint type, uint flags, out nint credential);
|
||||
|
||||
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern bool CredWrite([In] ref CREDENTIAL userCredential, uint flags);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
private static extern void CredFree(nint cred);
|
||||
|
||||
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern bool CredDelete(string target, uint type, uint flags);
|
||||
|
||||
public static string? GetToken(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (CredRead(key, 1u, 0u, out var credential))
|
||||
{
|
||||
try
|
||||
{
|
||||
CREDENTIAL cREDENTIAL = Marshal.PtrToStructure<CREDENTIAL>(credential);
|
||||
if (cREDENTIAL.CredentialBlobSize != 0 && cREDENTIAL.CredentialBlob != IntPtr.Zero)
|
||||
{
|
||||
return Marshal.PtrToStringUni(cREDENTIAL.CredentialBlob, (int)cREDENTIAL.CredentialBlobSize / 2);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
CredFree(credential);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("Windows Credential Manager 읽기 실패 (" + key + "): " + ex.Message);
|
||||
}
|
||||
return Environment.GetEnvironmentVariable(key.ToUpperInvariant());
|
||||
}
|
||||
|
||||
public static void SetToken(string key, string token)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(token))
|
||||
{
|
||||
return;
|
||||
}
|
||||
byte[] bytes = Encoding.Unicode.GetBytes(token);
|
||||
nint num = Marshal.AllocHGlobal(bytes.Length);
|
||||
nint num2 = Marshal.StringToCoTaskMemUni(key);
|
||||
nint num3 = Marshal.StringToCoTaskMemUni(Environment.UserName);
|
||||
try
|
||||
{
|
||||
Marshal.Copy(bytes, 0, num, bytes.Length);
|
||||
CREDENTIAL userCredential = new CREDENTIAL
|
||||
{
|
||||
Type = 1u,
|
||||
TargetName = num2,
|
||||
UserName = num3,
|
||||
CredentialBlobSize = (uint)bytes.Length,
|
||||
CredentialBlob = num,
|
||||
Persist = 2u
|
||||
};
|
||||
if (!CredWrite(ref userCredential, 0u))
|
||||
{
|
||||
LogService.Error($"토큰 저장 실패: {key}, 오류 코드: {Marshal.GetLastWin32Error()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogService.Info("토큰 저장 완료: " + key);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(num);
|
||||
Marshal.FreeCoTaskMem(num2);
|
||||
Marshal.FreeCoTaskMem(num3);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool DeleteToken(string key)
|
||||
{
|
||||
return !string.IsNullOrEmpty(key) && CredDelete(key, 1u, 0u);
|
||||
}
|
||||
}
|
||||
150
.decompiledproj/AxCopilot/Handlers/CurrencyConverter.cs
Normal file
150
.decompiledproj/AxCopilot/Handlers/CurrencyConverter.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
internal static class CurrencyConverter
|
||||
{
|
||||
private static readonly Regex _pattern = new Regex("^(\\d+(?:\\.\\d+)?)\\s+([A-Za-z]{3})\\s+(?:to|in)\\s+([A-Za-z]{3})$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Dictionary<string, (DateTime At, Dictionary<string, double> Rates)> _cache = new Dictionary<string, (DateTime, Dictionary<string, double>)>();
|
||||
|
||||
private static readonly HttpClient _http = new HttpClient
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(5.0)
|
||||
};
|
||||
|
||||
private static readonly TimeSpan CacheTtl = TimeSpan.FromHours(1.0);
|
||||
|
||||
private static readonly Dictionary<string, string> _names = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["KRW"] = "원",
|
||||
["USD"] = "달러",
|
||||
["EUR"] = "유로",
|
||||
["JPY"] = "엔",
|
||||
["GBP"] = "파운드",
|
||||
["CNY"] = "위안",
|
||||
["HKD"] = "홍콩달러",
|
||||
["SGD"] = "싱가포르달러",
|
||||
["CAD"] = "캐나다달러",
|
||||
["AUD"] = "호주달러",
|
||||
["CHF"] = "스위스프랑",
|
||||
["TWD"] = "대만달러",
|
||||
["MXN"] = "멕시코페소",
|
||||
["BRL"] = "브라질헤알",
|
||||
["INR"] = "인도루피",
|
||||
["RUB"] = "루블",
|
||||
["THB"] = "바트",
|
||||
["VND"] = "동",
|
||||
["IDR"] = "루피아",
|
||||
["MYR"] = "링깃",
|
||||
["PHP"] = "페소",
|
||||
["NZD"] = "뉴질랜드달러",
|
||||
["SEK"] = "크로나",
|
||||
["NOK"] = "크로나(노르)",
|
||||
["DKK"] = "크로나(덴)",
|
||||
["AED"] = "디르함",
|
||||
["SAR"] = "리얄"
|
||||
};
|
||||
|
||||
public static bool IsCurrencyQuery(string input)
|
||||
{
|
||||
return _pattern.IsMatch(input.Trim());
|
||||
}
|
||||
|
||||
public static async Task<IEnumerable<LauncherItem>> ConvertAsync(string input, CancellationToken ct)
|
||||
{
|
||||
Match m = _pattern.Match(input.Trim());
|
||||
if (!m.Success)
|
||||
{
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("통화 형식 오류", "예: 100 USD to KRW", null, null, null, "\uea39"));
|
||||
}
|
||||
if (!double.TryParse(m.Groups[1].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var amount))
|
||||
{
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("숫자 형식 오류", "", null, null, null, "\uea39"));
|
||||
}
|
||||
string from = m.Groups[2].Value.ToUpperInvariant();
|
||||
string to = m.Groups[3].Value.ToUpperInvariant();
|
||||
try
|
||||
{
|
||||
Dictionary<string, double> rates = await GetRatesAsync(from, ct);
|
||||
if (rates == null)
|
||||
{
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("환율 조회 실패", "네트워크 연결을 확인하세요", null, null, null, "\ue7ba"));
|
||||
}
|
||||
if (!rates.TryGetValue(to, out var rate))
|
||||
{
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("지원하지 않는 통화: " + to, "3자리 ISO 4217 코드를 입력하세요", null, null, null, "\uea39"));
|
||||
}
|
||||
double result = amount * rate;
|
||||
string fn;
|
||||
string fromName = (_names.TryGetValue(from, out fn) ? fn : from);
|
||||
string tn;
|
||||
string toName = (_names.TryGetValue(to, out tn) ? tn : to);
|
||||
string text;
|
||||
switch (to)
|
||||
{
|
||||
default:
|
||||
text = result.ToString("N2", CultureInfo.CurrentCulture);
|
||||
break;
|
||||
case "KRW":
|
||||
case "JPY":
|
||||
case "IDR":
|
||||
case "VND":
|
||||
text = result.ToString("N0", CultureInfo.CurrentCulture);
|
||||
break;
|
||||
}
|
||||
string resultStr = text;
|
||||
string rateStr = ((rate < 0.01) ? rate.ToString("G4", CultureInfo.InvariantCulture) : rate.ToString("N4", CultureInfo.CurrentCulture));
|
||||
string display = resultStr + " " + to;
|
||||
return new _003C_003Ez__ReadOnlyArray<LauncherItem>(new LauncherItem[2]
|
||||
{
|
||||
new LauncherItem($"{amount:N2} {from} = {display}", $"1 {from}({fromName}) = {rateStr} {to}({toName}) · Enter로 복사", null, display, null, "\ue8ef"),
|
||||
new LauncherItem("숫자만: " + resultStr, "Enter로 숫자만 복사", null, resultStr, null, "\ue8ef")
|
||||
});
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return Array.Empty<LauncherItem>();
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
LogService.Warn("환율 조회 오류: " + ex2.Message);
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("환율 조회 실패", ex2.Message, null, null, null, "\ue7ba"));
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<Dictionary<string, double>?> GetRatesAsync(string baseCurrency, CancellationToken ct)
|
||||
{
|
||||
if (_cache.TryGetValue(baseCurrency, out var cached) && DateTime.Now - cached.At < CacheTtl)
|
||||
{
|
||||
return cached.Rates;
|
||||
}
|
||||
string url = "https://open.er-api.com/v6/latest/" + baseCurrency;
|
||||
using JsonDocument doc = JsonDocument.Parse(await _http.GetStringAsync(url, ct));
|
||||
JsonElement root = doc.RootElement;
|
||||
if (!root.TryGetProperty("result", out var resultEl) || resultEl.GetString() != "success")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!root.TryGetProperty("rates", out var ratesEl))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
Dictionary<string, double> rates = new Dictionary<string, double>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (JsonProperty prop in ratesEl.EnumerateObject())
|
||||
{
|
||||
rates[prop.Name] = prop.Value.GetDouble();
|
||||
}
|
||||
_cache[baseCurrency] = (DateTime.Now, rates);
|
||||
return rates;
|
||||
}
|
||||
}
|
||||
122
.decompiledproj/AxCopilot/Handlers/DateCalcHandler.cs
Normal file
122
.decompiledproj/AxCopilot/Handlers/DateCalcHandler.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.SDK;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class DateCalcHandler : IActionHandler
|
||||
{
|
||||
private static readonly string[] DateFormats = new string[5] { "yyyy-MM-dd", "yyyy/MM/dd", "yyyyMMdd", "MM/dd/yyyy", "dd-MM-yyyy" };
|
||||
|
||||
public string? Prefix => "date";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("DateCalc", "날짜 계산 · D-day · 타임스탬프 변환", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string text = query.Trim();
|
||||
DateTime now = DateTime.Now;
|
||||
List<LauncherItem> list = new List<LauncherItem>();
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
string value = now.ToString("dddd", new CultureInfo("ko-KR"));
|
||||
list.Add(Item($"{now:yyyy-MM-dd} ({value})", $"{now:HH:mm:ss} · {now:yyyy-MM-dd}"));
|
||||
list.Add(Item($"유닉스 타임스탬프: {new DateTimeOffset(now).ToUnixTimeSeconds()}", "현재 시각의 Unix epoch"));
|
||||
list.Add(Item($"올해 {now.DayOfYear}일째 / 남은 일: {(new DateTime(now.Year, 12, 31) - now).Days}일", $"ISO 주차: {ISOWeek.GetWeekOfYear(now)}주"));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
Match match = Regex.Match(text, "^([+-])(\\d+)([dDwWmMyY])$");
|
||||
if (match.Success)
|
||||
{
|
||||
int num = ((match.Groups[1].Value == "+") ? 1 : (-1));
|
||||
int num2 = int.Parse(match.Groups[2].Value) * num;
|
||||
string text2 = match.Groups[3].Value.ToLowerInvariant();
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
DateTime dateTime = text2 switch
|
||||
{
|
||||
"d" => now.AddDays(num2),
|
||||
"w" => now.AddDays(num2 * 7),
|
||||
"m" => now.AddMonths(num2),
|
||||
"y" => now.AddYears(num2),
|
||||
_ => now,
|
||||
};
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
DateTime value2 = dateTime;
|
||||
string value3 = value2.ToString("dddd", new CultureInfo("ko-KR"));
|
||||
int days = (value2.Date - now.Date).Days;
|
||||
string subtitle = ((days >= 0) ? $"오늘로부터 {days}일 후" : $"오늘로부터 {Math.Abs(days)}일 전");
|
||||
list.Add(Item($"{value2:yyyy-MM-dd} ({value3})", subtitle));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
if (Regex.IsMatch(text, "^\\d{10,13}$") && long.TryParse(text, out var result))
|
||||
{
|
||||
long seconds = ((result > 9999999999L) ? (result / 1000) : result);
|
||||
DateTime localDateTime = DateTimeOffset.FromUnixTimeSeconds(seconds).LocalDateTime;
|
||||
string value4 = localDateTime.ToString("dddd", new CultureInfo("ko-KR"));
|
||||
list.Add(Item($"{localDateTime:yyyy-MM-dd HH:mm:ss} ({value4})", $"Unix {result} → 로컬 시간"));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
if (text.Equals("unix", StringComparison.OrdinalIgnoreCase) || text.Equals("to unix", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
long value5 = new DateTimeOffset(now).ToUnixTimeSeconds();
|
||||
list.Add(Item($"{value5}", $"현재 시각 ({now:yyyy-MM-dd HH:mm:ss}) → Unix 타임스탬프"));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
if (DateTime.TryParseExact(text, DateFormats, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result2))
|
||||
{
|
||||
string value6 = result2.ToString("dddd", new CultureInfo("ko-KR"));
|
||||
int days2 = (result2.Date - now.Date).Days;
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string text3 = ((days2 > 0) ? $"D-{days2} (앞으로 {days2}일)" : ((days2 != 0) ? $"D+{Math.Abs(days2)} ({Math.Abs(days2)}일 지남)" : "오늘"));
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string subtitle2 = text3;
|
||||
list.Add(Item($"{result2:yyyy-MM-dd} ({value6})", subtitle2));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
list.Add(new LauncherItem("날짜 형식을 인식할 수 없습니다", "예: +30d, -100d, 2026-12-25, 1711584000, unix", null, null, null, "\ue7ba"));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
private static LauncherItem Item(string title, string subtitle)
|
||||
{
|
||||
return new LauncherItem(title, subtitle + " · Enter로 복사", null, title, null, "\ue823");
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
string text = data as string;
|
||||
if (text != null && !string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
try
|
||||
{
|
||||
Application current = Application.Current;
|
||||
if (current != null)
|
||||
{
|
||||
((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
Clipboard.SetText(text);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
224
.decompiledproj/AxCopilot/Handlers/DiffHandler.cs
Normal file
224
.decompiledproj/AxCopilot/Handlers/DiffHandler.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class DiffHandler : IActionHandler
|
||||
{
|
||||
private record DiffResult(string Text, int Added, int Removed, int Same);
|
||||
|
||||
private readonly ClipboardHistoryService? _clipHistory;
|
||||
|
||||
public string? Prefix => "diff";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Diff", "텍스트/파일 비교 — diff", "1.0", "AX");
|
||||
|
||||
public DiffHandler(ClipboardHistoryService? clipHistory = null)
|
||||
{
|
||||
_clipHistory = clipHistory;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string text = query.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
string[] array = text.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
if (array.Length == 2 && File.Exists(array[0]) && File.Exists(array[1]))
|
||||
{
|
||||
try
|
||||
{
|
||||
string textA = File.ReadAllText(array[0]);
|
||||
string textB = File.ReadAllText(array[1]);
|
||||
DiffResult diffResult = BuildDiff(textA, textB, Path.GetFileName(array[0]), Path.GetFileName(array[1]));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("파일 비교: " + Path.GetFileName(array[0]) + " ↔ " + Path.GetFileName(array[1]), $"{diffResult.Added}줄 추가, {diffResult.Removed}줄 삭제, {diffResult.Same}줄 동일 · Enter로 결과 복사", null, diffResult.Text, null, "\ue8a5")));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("파일 읽기 실패: " + ex.Message, "", null, null, null, "\uea39")));
|
||||
}
|
||||
}
|
||||
if (array.Length >= 1 && (File.Exists(array[0]) || Directory.Exists(Path.GetDirectoryName(array[0]) ?? "")))
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("비교할 파일 2개의 경로를 입력하세요", "예: diff C:\\a.txt C:\\b.txt", null, null, null, "\ue946")));
|
||||
}
|
||||
}
|
||||
IReadOnlyList<ClipboardEntry> readOnlyList = _clipHistory?.History;
|
||||
if (readOnlyList == null || readOnlyList.Count < 2)
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("비교할 텍스트가 부족합니다", "클립보드에 2개 이상의 텍스트를 복사하거나, diff [파일A] [파일B]로 파일 비교", null, null, null, "\ue946")));
|
||||
}
|
||||
List<ClipboardEntry> list = readOnlyList.Where((ClipboardEntry e) => e.IsText).Take(2).ToList();
|
||||
if (list.Count < 2)
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("텍스트 히스토리가 2개 미만입니다", "텍스트를 2번 이상 복사하세요", null, null, null, "\ue946")));
|
||||
}
|
||||
ClipboardEntry clipboardEntry = list[1];
|
||||
ClipboardEntry clipboardEntry2 = list[0];
|
||||
DiffResult diffResult2 = BuildDiff(clipboardEntry.Text, clipboardEntry2.Text, "이전 (" + clipboardEntry.RelativeTime + ")", "최근 (" + clipboardEntry2.RelativeTime + ")");
|
||||
List<LauncherItem> list2 = new List<LauncherItem>
|
||||
{
|
||||
new LauncherItem($"클립보드 비교: +{diffResult2.Added} -{diffResult2.Removed} ={diffResult2.Same}", "이전 복사 ↔ 최근 복사 · Enter로 결과 복사", null, diffResult2.Text, null, "\ue81c")
|
||||
};
|
||||
IEnumerable<string> enumerable = (from l in diffResult2.Text.Split('\n')
|
||||
where l.StartsWith("+ ") || l.StartsWith("- ")
|
||||
select l).Take(5);
|
||||
foreach (string item in enumerable)
|
||||
{
|
||||
string symbol = (item.StartsWith("+ ") ? "\ue710" : "\ue711");
|
||||
list2.Add(new LauncherItem(item, "", null, null, null, symbol));
|
||||
}
|
||||
list2.Add(new LauncherItem("파일 선택하여 비교", "파일 선택 대화 상자에서 2개의 파일을 골라 비교합니다", null, "__FILE_DIALOG__", null, "\ue8a5"));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list2);
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
if (data is string s && s == "__FILE_DIALOG__")
|
||||
{
|
||||
await Task.Delay(200, ct);
|
||||
Application current = Application.Current;
|
||||
if (current == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
OpenFileDialog openFileDialog = new OpenFileDialog
|
||||
{
|
||||
Title = "비교할 첫 번째 파일 선택",
|
||||
Filter = "텍스트 파일|*.txt;*.cs;*.json;*.xml;*.md;*.csv;*.log|모든 파일|*.*"
|
||||
};
|
||||
if (openFileDialog.ShowDialog() == true)
|
||||
{
|
||||
string fileName = openFileDialog.FileName;
|
||||
openFileDialog.Title = "비교할 두 번째 파일 선택";
|
||||
if (openFileDialog.ShowDialog() == true)
|
||||
{
|
||||
string fileName2 = openFileDialog.FileName;
|
||||
try
|
||||
{
|
||||
string textA = File.ReadAllText(fileName);
|
||||
string textB = File.ReadAllText(fileName2);
|
||||
DiffResult diffResult = BuildDiff(textA, textB, Path.GetFileName(fileName), Path.GetFileName(fileName2));
|
||||
Clipboard.SetText(diffResult.Text);
|
||||
NotificationService.Notify("파일 비교 완료", $"+{diffResult.Added} -{diffResult.Removed} ={diffResult.Same} · 결과 클립보드 복사됨");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NotificationService.Notify("비교 실패", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
data = item.Data;
|
||||
string text = data as string;
|
||||
if (text == null || string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
Application current2 = Application.Current;
|
||||
if (current2 != null)
|
||||
{
|
||||
((DispatcherObject)current2).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
Clipboard.SetText(text);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
NotificationService.Notify("비교 결과", "클립보드에 복사되었습니다");
|
||||
}
|
||||
|
||||
private static DiffResult BuildDiff(string textA, string textB, string labelA, string labelB)
|
||||
{
|
||||
string[] array = (from l in textA.Split('\n')
|
||||
select l.TrimEnd('\r')).ToArray();
|
||||
string[] array2 = (from l in textB.Split('\n')
|
||||
select l.TrimEnd('\r')).ToArray();
|
||||
HashSet<string> hashSet = new HashSet<string>(array);
|
||||
HashSet<string> hashSet2 = new HashSet<string>(array2);
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
StringBuilder stringBuilder2 = stringBuilder;
|
||||
StringBuilder stringBuilder3 = stringBuilder2;
|
||||
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(4, 1, stringBuilder2);
|
||||
handler.AppendLiteral("--- ");
|
||||
handler.AppendFormatted(labelA);
|
||||
stringBuilder3.AppendLine(ref handler);
|
||||
stringBuilder2 = stringBuilder;
|
||||
StringBuilder stringBuilder4 = stringBuilder2;
|
||||
handler = new StringBuilder.AppendInterpolatedStringHandler(4, 1, stringBuilder2);
|
||||
handler.AppendLiteral("+++ ");
|
||||
handler.AppendFormatted(labelB);
|
||||
stringBuilder4.AppendLine(ref handler);
|
||||
stringBuilder.AppendLine();
|
||||
int num = 0;
|
||||
int num2 = 0;
|
||||
int num3 = 0;
|
||||
int num4 = Math.Max(array.Length, array2.Length);
|
||||
for (int num5 = 0; num5 < num4; num5++)
|
||||
{
|
||||
string text = ((num5 < array.Length) ? array[num5] : null);
|
||||
string text2 = ((num5 < array2.Length) ? array2[num5] : null);
|
||||
if (text == text2)
|
||||
{
|
||||
stringBuilder2 = stringBuilder;
|
||||
StringBuilder stringBuilder5 = stringBuilder2;
|
||||
handler = new StringBuilder.AppendInterpolatedStringHandler(2, 1, stringBuilder2);
|
||||
handler.AppendLiteral(" ");
|
||||
handler.AppendFormatted(text);
|
||||
stringBuilder5.AppendLine(ref handler);
|
||||
num3++;
|
||||
continue;
|
||||
}
|
||||
if (text != null && !hashSet2.Contains(text))
|
||||
{
|
||||
stringBuilder2 = stringBuilder;
|
||||
StringBuilder stringBuilder6 = stringBuilder2;
|
||||
handler = new StringBuilder.AppendInterpolatedStringHandler(2, 1, stringBuilder2);
|
||||
handler.AppendLiteral("- ");
|
||||
handler.AppendFormatted(text);
|
||||
stringBuilder6.AppendLine(ref handler);
|
||||
num2++;
|
||||
}
|
||||
if (text2 != null && !hashSet.Contains(text2))
|
||||
{
|
||||
stringBuilder2 = stringBuilder;
|
||||
StringBuilder stringBuilder7 = stringBuilder2;
|
||||
handler = new StringBuilder.AppendInterpolatedStringHandler(2, 1, stringBuilder2);
|
||||
handler.AppendLiteral("+ ");
|
||||
handler.AppendFormatted(text2);
|
||||
stringBuilder7.AppendLine(ref handler);
|
||||
num++;
|
||||
}
|
||||
if (text != null && hashSet2.Contains(text) && text != text2)
|
||||
{
|
||||
stringBuilder2 = stringBuilder;
|
||||
StringBuilder stringBuilder8 = stringBuilder2;
|
||||
handler = new StringBuilder.AppendInterpolatedStringHandler(2, 1, stringBuilder2);
|
||||
handler.AppendLiteral(" ");
|
||||
handler.AppendFormatted(text);
|
||||
stringBuilder8.AppendLine(ref handler);
|
||||
num3++;
|
||||
}
|
||||
}
|
||||
return new DiffResult(stringBuilder.ToString(), num, num2, num3);
|
||||
}
|
||||
}
|
||||
517
.decompiledproj/AxCopilot/Handlers/EmojiHandler.cs
Normal file
517
.decompiledproj/AxCopilot/Handlers/EmojiHandler.cs
Normal file
@@ -0,0 +1,517 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using AxCopilot.SDK;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class EmojiHandler : IActionHandler
|
||||
{
|
||||
private static readonly (string Emoji, string Name, string Tags)[] _emojis = new(string, string, string)[462]
|
||||
{
|
||||
("\ud83d\ude00", "크게 웃는 얼굴", "smile happy grin 웃음 행복"),
|
||||
("\ud83d\ude03", "웃는 얼굴", "smile happy joy 웃음"),
|
||||
("\ud83d\ude04", "눈 웃음", "smile laugh 웃음 기쁨"),
|
||||
("\ud83d\ude01", "히죽 웃음", "grin beam 씩 웃다"),
|
||||
("\ud83d\ude06", "크게 웃음", "laughing 폭소"),
|
||||
("\ud83d\ude05", "식은땀 웃음", "sweat smile 안도"),
|
||||
("\ud83e\udd23", "바닥 구르며 웃음", "rofl lol 빵 웃음"),
|
||||
("\ud83d\ude02", "눈물 나게 웃음", "joy tears laugh 폭소"),
|
||||
("\ud83d\ude42", "살짝 웃음", "slightly smiling 미소"),
|
||||
("\ud83d\ude43", "거꾸로 웃음", "upside down 뒤집힌"),
|
||||
("\ud83d\ude09", "윙크", "wink 윙크"),
|
||||
("\ud83d\ude0a", "볼 빨개진 웃음", "blush 부끄러움 미소"),
|
||||
("\ud83d\ude07", "천사", "angel halo 천사 선량"),
|
||||
("\ud83e\udd70", "사랑스러운 얼굴", "love hearts 사랑 하트"),
|
||||
("\ud83d\ude0d", "하트 눈", "heart eyes 사랑 반함"),
|
||||
("\ud83e\udd29", "별 눈", "star struck 감동 황홀"),
|
||||
("\ud83d\ude18", "뽀뽀", "kiss blow 키스 뽀뽀"),
|
||||
("\ud83d\ude17", "오므린 입", "kiss whistle 키스"),
|
||||
("\ud83d\ude1a", "눈 감고 뽀뽀", "kiss 키스"),
|
||||
("\ud83d\ude19", "볼 뽀뽀", "kiss 키스"),
|
||||
("\ud83d\ude0b", "맛있다", "yum delicious 맛 음식"),
|
||||
("\ud83d\ude1b", "혀 내밀기", "tongue out 혀 놀림"),
|
||||
("\ud83d\ude1c", "윙크하며 혀", "wink tongue 장난"),
|
||||
("\ud83e\udd2a", "미친 표정", "zany crazy 정신없음"),
|
||||
("\ud83d\ude1d", "눈 감고 혀", "tongue 혀"),
|
||||
("\ud83e\udd11", "돈 눈", "money face 돈 부자"),
|
||||
("\ud83e\udd17", "포옹", "hugging hug 안아줘 포옹"),
|
||||
("\ud83e\udd2d", "입 가리고", "hand over mouth 헉 깜짝"),
|
||||
("\ud83e\udd2b", "쉿", "shushing quiet 조용 쉿"),
|
||||
("\ud83e\udd14", "생각 중", "thinking 고민 생각"),
|
||||
("\ud83e\udd10", "입 막음", "zipper mouth 비밀"),
|
||||
("\ud83e\udd28", "의심", "raised eyebrow 의심 의아"),
|
||||
("\ud83d\ude10", "무표정", "neutral 무감각 무표정"),
|
||||
("\ud83d\ude11", "표정 없음", "expressionless 냉담"),
|
||||
("\ud83d\ude36", "입 없는 얼굴", "no mouth 침묵"),
|
||||
("\ud83d\ude0f", "비웃음", "smirk 비웃 냉소"),
|
||||
("\ud83d\ude12", "불만", "unamused 불만 짜증"),
|
||||
("\ud83d\ude44", "눈 굴리기", "eye roll 어이없음"),
|
||||
("\ud83d\ude2c", "이 드러냄", "grimace 으 민망"),
|
||||
("\ud83e\udd25", "거짓말", "lying pinocchio 거짓말"),
|
||||
("\ud83d\ude0c", "안도/평온", "relieved 안도 평온"),
|
||||
("\ud83d\ude14", "슬픔", "pensive sad 슬픔 우울"),
|
||||
("\ud83d\ude2a", "졸림", "sleepy 졸음"),
|
||||
("\ud83e\udd24", "침 흘림", "drooling 군침 식욕"),
|
||||
("\ud83d\ude34", "잠", "sleeping sleep 수면 잠"),
|
||||
("\ud83d\ude37", "마스크", "mask sick 마스크 아픔"),
|
||||
("\ud83e\udd12", "열 나는", "sick fever 열 아픔"),
|
||||
("\ud83e\udd15", "머리 붕대", "injured hurt 부상"),
|
||||
("\ud83e\udd22", "구역질", "nauseated sick 구역 메스꺼움"),
|
||||
("\ud83e\udd2e", "토하는", "vomit 구토"),
|
||||
("\ud83e\udd27", "재채기", "sneezing sick 재채기 감기"),
|
||||
("\ud83e\udd75", "더운", "hot overheated 더움 열"),
|
||||
("\ud83e\udd76", "추운", "cold freezing 추움 냉기"),
|
||||
("\ud83e\udd74", "어지러운", "woozy 어지럼 취함"),
|
||||
("\ud83d\ude35", "어질어질", "dizzy 어지럼 충격"),
|
||||
("\ud83e\udd2f", "머리 폭발", "exploding head 충격 대박"),
|
||||
("\ud83e\udd20", "카우보이", "cowboy hat 카우보이"),
|
||||
("\ud83e\udd78", "변장", "disguise 변장 선글라스"),
|
||||
("\ud83d\ude0e", "쿨한", "cool sunglasses 선글라스 쿨"),
|
||||
("\ud83e\udd13", "공부벌레", "nerd glasses 공부 안경"),
|
||||
("\ud83e\uddd0", "모노클", "monocle curious 고상 탐정"),
|
||||
("\ud83d\ude15", "당황", "confused 당황 모호"),
|
||||
("\ud83d\ude1f", "걱정", "worried concern 걱정"),
|
||||
("\ud83d\ude41", "살짝 찡그림", "frown 슬픔"),
|
||||
("☹\ufe0f", "찡그린 얼굴", "frown sad 슬픔"),
|
||||
("\ud83d\ude2e", "입 벌림", "open mouth surprised 놀람"),
|
||||
("\ud83d\ude2f", "놀람", "hushed surprised 깜짝"),
|
||||
("\ud83d\ude32", "충격", "astonished 충격 놀람"),
|
||||
("\ud83d\ude33", "얼굴 빨개짐", "flushed embarrassed 부끄럼 당황"),
|
||||
("\ud83e\udd7a", "애원", "pleading eyes 부탁 눈빛"),
|
||||
("\ud83d\ude26", "찡그리며 벌린 입", "frowning 불안"),
|
||||
("\ud83d\ude27", "고통", "anguished 고통"),
|
||||
("\ud83d\ude28", "무서움", "fearful scared 무서움 공포"),
|
||||
("\ud83d\ude30", "식은땀", "anxious sweat 불안 걱정"),
|
||||
("\ud83d\ude25", "눈물 조금", "sad disappointed 실망 눈물"),
|
||||
("\ud83d\ude22", "울음", "cry sad 슬픔 눈물"),
|
||||
("\ud83d\ude2d", "엉엉 울음", "loudly crying sob 통곡"),
|
||||
("\ud83d\ude31", "공포에 질림", "screaming fear 비명 공포"),
|
||||
("\ud83d\ude16", "혼란", "confounded 혼란"),
|
||||
("\ud83d\ude23", "힘듦", "persevering 고생"),
|
||||
("\ud83d\ude1e", "실망", "disappointed 실망"),
|
||||
("\ud83d\ude13", "땀", "downcast sweat 땀 힘듦"),
|
||||
("\ud83d\ude29", "피곤", "weary tired 지침 피곤"),
|
||||
("\ud83d\ude2b", "극도로 지침", "tired exhausted 탈진"),
|
||||
("\ud83e\udd71", "하품", "yawning bored 하품 지루함"),
|
||||
("\ud83d\ude24", "콧김", "triumph snort 분노 콧김"),
|
||||
("\ud83d\ude21", "화남", "angry mad 화남 분노"),
|
||||
("\ud83d\ude20", "성남", "angry 화 성남"),
|
||||
("\ud83e\udd2c", "욕", "cursing swearing 욕 분노"),
|
||||
("\ud83d\ude08", "나쁜 미소", "smiling devil 악마 장난"),
|
||||
("\ud83d\udc7f", "화난 악마", "angry devil 악마"),
|
||||
("\ud83d\udc80", "해골", "skull death 해골 죽음"),
|
||||
("☠\ufe0f", "해골 십자", "skull crossbones 독"),
|
||||
("\ud83d\udca9", "응가", "poop 똥 응가"),
|
||||
("\ud83e\udd21", "피에로", "clown 광대"),
|
||||
("\ud83d\udc79", "도깨비", "ogre 도깨비 귀신"),
|
||||
("\ud83d\udc7a", "텐구", "goblin 텐구"),
|
||||
("\ud83d\udc7b", "유령", "ghost 유령 귀신"),
|
||||
("\ud83d\udc7e", "우주인", "alien monster 외계인 게임"),
|
||||
("\ud83e\udd16", "로봇", "robot 로봇"),
|
||||
("\ud83d\udc4b", "손 흔들기", "wave waving hi bye 안녕"),
|
||||
("\ud83e\udd1a", "손 뒤", "raised back hand 손"),
|
||||
("\ud83d\udd90\ufe0f", "손바닥", "hand palm 다섯 손가락"),
|
||||
("✋", "손 들기", "raised hand 손 들기 멈춤"),
|
||||
("\ud83d\udd96", "스팍 손인사", "vulcan salute 스타트렉"),
|
||||
("\ud83d\udc4c", "오케이", "ok perfect 오케이 좋아"),
|
||||
("\ud83e\udd0c", "손가락 모아", "pinched fingers 이탈리아"),
|
||||
("✌\ufe0f", "브이", "victory peace v 브이 평화"),
|
||||
("\ud83e\udd1e", "행운 손가락", "crossed fingers lucky 행운 기도"),
|
||||
("\ud83e\udd1f", "아이 러브 유", "love you 사랑해"),
|
||||
("\ud83e\udd18", "록 손", "rock on metal 록"),
|
||||
("\ud83e\udd19", "전화해", "call me shaka 전화 샤카"),
|
||||
("\ud83d\udc48", "왼쪽 가리킴", "backhand left 왼쪽"),
|
||||
("\ud83d\udc49", "오른쪽 가리킴", "backhand right 오른쪽"),
|
||||
("\ud83d\udc46", "위 가리킴", "backhand up 위"),
|
||||
("\ud83d\udd95", "욕", "middle finger 욕"),
|
||||
("\ud83d\udc47", "아래 가리킴", "backhand down 아래"),
|
||||
("☝\ufe0f", "검지 들기", "index pointing up 하나 포인트"),
|
||||
("\ud83d\udc4d", "좋아요", "thumbs up like good 좋아 최고"),
|
||||
("\ud83d\udc4e", "싫어요", "thumbs down dislike 싫어 별로"),
|
||||
("✊", "주먹", "fist punch 주먹"),
|
||||
("\ud83d\udc4a", "주먹 치기", "punch fist 주먹"),
|
||||
("\ud83e\udd1b", "왼 주먹", "left fist 주먹"),
|
||||
("\ud83e\udd1c", "오른 주먹", "right fist 주먹"),
|
||||
("\ud83d\udc4f", "박수", "clapping applause 박수 응원"),
|
||||
("\ud83d\ude4c", "만세", "raising hands celebrate 만세"),
|
||||
("\ud83d\udc50", "양손 펼침", "open hands 환영"),
|
||||
("\ud83e\udd32", "두 손 모음", "palms up together 기도 바람"),
|
||||
("\ud83d\ude4f", "두 손 합장", "pray please thanks 감사 부탁 기도"),
|
||||
("✍\ufe0f", "글쓰기", "writing pen 글쓰기"),
|
||||
("\ud83d\udc85", "네일", "nail polish manicure 네일 손톱"),
|
||||
("\ud83e\udd33", "셀카", "selfie 셀카"),
|
||||
("\ud83d\udcaa", "근육", "muscle strong 근육 힘"),
|
||||
("\ud83e\uddbe", "기계 팔", "mechanical arm 로봇 팔"),
|
||||
("\ud83e\uddbf", "기계 다리", "mechanical leg 로봇 다리"),
|
||||
("\ud83e\uddb5", "다리", "leg kick 다리"),
|
||||
("\ud83e\uddb6", "발", "foot kick 발"),
|
||||
("\ud83d\udc42", "귀", "ear hear 귀"),
|
||||
("\ud83e\uddbb", "보청기 귀", "ear hearing aid 보청기"),
|
||||
("\ud83d\udc43", "코", "nose smell 코"),
|
||||
("\ud83e\udec0", "심장", "heart anatomical 심장"),
|
||||
("\ud83e\udec1", "폐", "lungs 폐"),
|
||||
("\ud83e\udde0", "뇌", "brain mind 뇌 지능"),
|
||||
("\ud83e\uddb7", "치아", "tooth dental 치아"),
|
||||
("\ud83e\uddb4", "뼈", "bone 뼈"),
|
||||
("\ud83d\udc40", "눈", "eyes look see 눈 보기"),
|
||||
("\ud83d\udc41\ufe0f", "한쪽 눈", "eye 눈"),
|
||||
("\ud83d\udc45", "혀", "tongue 혀"),
|
||||
("\ud83d\udc44", "입술", "lips mouth 입술"),
|
||||
("\ud83d\udc8b", "입맞춤", "kiss lips 키스 입술"),
|
||||
("\ud83e\ude78", "피", "blood drop 피 혈액"),
|
||||
("❤\ufe0f", "빨간 하트", "red heart love 사랑 빨강"),
|
||||
("\ud83e\udde1", "주황 하트", "orange heart 사랑"),
|
||||
("\ud83d\udc9b", "노란 하트", "yellow heart 사랑"),
|
||||
("\ud83d\udc9a", "초록 하트", "green heart 사랑"),
|
||||
("\ud83d\udc99", "파란 하트", "blue heart 사랑"),
|
||||
("\ud83d\udc9c", "보라 하트", "purple heart 사랑"),
|
||||
("\ud83d\udda4", "검은 하트", "black heart 사랑 다크"),
|
||||
("\ud83e\udd0d", "흰 하트", "white heart 사랑"),
|
||||
("\ud83e\udd0e", "갈색 하트", "brown heart 사랑"),
|
||||
("\ud83d\udc94", "깨진 하트", "broken heart 이별 상처"),
|
||||
("❣\ufe0f", "느낌표 하트", "heart exclamation 사랑"),
|
||||
("\ud83d\udc95", "두 하트", "two hearts 사랑"),
|
||||
("\ud83d\udc9e", "회전 하트", "revolving hearts 사랑"),
|
||||
("\ud83d\udc93", "뛰는 하트", "beating heart 설렘"),
|
||||
("\ud83d\udc97", "성장 하트", "growing heart 사랑"),
|
||||
("\ud83d\udc96", "반짝 하트", "sparkling heart 사랑"),
|
||||
("\ud83d\udc98", "화살 하트", "heart arrow 큐피드"),
|
||||
("\ud83d\udc9d", "리본 하트", "heart ribbon 선물 사랑"),
|
||||
("\ud83d\udc9f", "하트 장식", "heart decoration 사랑"),
|
||||
("☮\ufe0f", "평화", "peace 평화"),
|
||||
("✝\ufe0f", "십자가", "cross 기독교"),
|
||||
("☯\ufe0f", "음양", "yin yang 음양 균형"),
|
||||
("\ud83d\udd2e", "수정구", "crystal ball magic 마법 점"),
|
||||
("✨", "반짝임", "sparkles glitter 빛 반짝"),
|
||||
("⭐", "별", "star 별"),
|
||||
("\ud83c\udf1f", "빛나는 별", "glowing star 별빛"),
|
||||
("\ud83d\udcab", "현기증", "dizzy star 빙글"),
|
||||
("⚡", "번개", "lightning bolt 번개 전기"),
|
||||
("\ud83d\udd25", "불", "fire hot 불 열정"),
|
||||
("\ud83d\udca5", "폭발", "explosion boom 폭발"),
|
||||
("❄\ufe0f", "눈송이", "snowflake cold 눈 추위"),
|
||||
("\ud83c\udf08", "무지개", "rainbow 무지개"),
|
||||
("☀\ufe0f", "태양", "sun sunny 태양 맑음"),
|
||||
("\ud83c\udf19", "달", "moon crescent 달"),
|
||||
("\ud83c\udf0a", "파도", "wave ocean 파도 바다"),
|
||||
("\ud83d\udca8", "바람", "wind dash 바람"),
|
||||
("\ud83d\udca6", "물방울", "sweat droplets water 물"),
|
||||
("\ud83c\udf38", "벚꽃", "cherry blossom 벚꽃 봄"),
|
||||
("\ud83c\udf39", "장미", "rose 장미 꽃"),
|
||||
("\ud83c\udf3a", "히비스커스", "hibiscus 꽃"),
|
||||
("\ud83c\udf3b", "해바라기", "sunflower 해바라기"),
|
||||
("\ud83c\udf3c", "꽃", "blossom flower 꽃"),
|
||||
("\ud83c\udf37", "튤립", "tulip 튤립"),
|
||||
("\ud83d\udc90", "꽃다발", "bouquet flowers 꽃다발"),
|
||||
("\ud83c\udf40", "네잎클로버", "four leaf clover lucky 행운"),
|
||||
("\ud83c\udf3f", "허브", "herb green 풀 허브"),
|
||||
("\ud83c\udf43", "잎사귀", "leaf 잎"),
|
||||
("\ud83c\udf55", "피자", "pizza 피자"),
|
||||
("\ud83c\udf54", "햄버거", "hamburger burger 버거"),
|
||||
("\ud83c\udf2e", "타코", "taco 타코"),
|
||||
("\ud83c\udf5c", "라면", "ramen noodles 라면 국수"),
|
||||
("\ud83c\udf71", "도시락", "bento box 도시락"),
|
||||
("\ud83c\udf63", "초밥", "sushi 초밥"),
|
||||
("\ud83c\udf5a", "밥", "rice 밥"),
|
||||
("\ud83c\udf5b", "카레", "curry rice 카레"),
|
||||
("\ud83c\udf5d", "파스타", "pasta spaghetti 파스타"),
|
||||
("\ud83c\udf66", "소프트 아이스크림", "ice cream soft serve 아이스크림"),
|
||||
("\ud83c\udf82", "생일 케이크", "cake birthday 생일 케이크"),
|
||||
("\ud83c\udf70", "케이크 조각", "cake slice 케이크"),
|
||||
("\ud83e\uddc1", "컵케이크", "cupcake 컵케이크"),
|
||||
("\ud83c\udf69", "도넛", "donut 도넛"),
|
||||
("\ud83c\udf6a", "쿠키", "cookie 쿠키"),
|
||||
("\ud83c\udf6b", "초콜릿", "chocolate bar 초콜릿"),
|
||||
("\ud83c\udf6c", "사탕", "candy 사탕"),
|
||||
("\ud83c\udf6d", "막대 사탕", "lollipop 막대사탕"),
|
||||
("\ud83c\udf7a", "맥주", "beer mug 맥주"),
|
||||
("\ud83c\udf7b", "건배", "clinking beer 건배"),
|
||||
("\ud83e\udd42", "샴페인 건배", "champagne 샴페인 건배"),
|
||||
("\ud83c\udf77", "와인", "wine 와인"),
|
||||
("☕", "커피", "coffee hot 커피"),
|
||||
("\ud83e\uddc3", "주스", "juice 주스"),
|
||||
("\ud83e\udd64", "음료", "drink cup 음료 컵"),
|
||||
("\ud83e\uddcb", "버블티", "bubble tea boba 버블티"),
|
||||
("\ud83c\udf75", "녹차", "tea matcha 차 녹차"),
|
||||
("\ud83d\udc36", "강아지", "dog puppy 강아지 개"),
|
||||
("\ud83d\udc31", "고양이", "cat kitten 고양이"),
|
||||
("\ud83d\udc2d", "쥐", "mouse 쥐"),
|
||||
("\ud83d\udc39", "햄스터", "hamster 햄스터"),
|
||||
("\ud83d\udc30", "토끼", "rabbit bunny 토끼"),
|
||||
("\ud83e\udd8a", "여우", "fox 여우"),
|
||||
("\ud83d\udc3b", "곰", "bear 곰"),
|
||||
("\ud83d\udc3c", "판다", "panda 판다"),
|
||||
("\ud83d\udc28", "코알라", "koala 코알라"),
|
||||
("\ud83d\udc2f", "호랑이", "tiger 호랑이"),
|
||||
("\ud83e\udd81", "사자", "lion 사자"),
|
||||
("\ud83d\udc2e", "소", "cow 소"),
|
||||
("\ud83d\udc37", "돼지", "pig 돼지"),
|
||||
("\ud83d\udc38", "개구리", "frog 개구리"),
|
||||
("\ud83d\udc35", "원숭이", "monkey 원숭이"),
|
||||
("\ud83d\ude48", "눈 가린 원숭이", "see no evil monkey 안 봐"),
|
||||
("\ud83d\ude49", "귀 가린 원숭이", "hear no evil monkey 안 들어"),
|
||||
("\ud83d\ude4a", "입 가린 원숭이", "speak no evil monkey 안 말해"),
|
||||
("\ud83d\udc14", "닭", "chicken 닭"),
|
||||
("\ud83d\udc27", "펭귄", "penguin 펭귄"),
|
||||
("\ud83d\udc26", "새", "bird 새"),
|
||||
("\ud83e\udd86", "오리", "duck 오리"),
|
||||
("\ud83e\udd85", "독수리", "eagle 독수리"),
|
||||
("\ud83e\udd89", "부엉이", "owl 부엉이"),
|
||||
("\ud83d\udc0d", "뱀", "snake 뱀"),
|
||||
("\ud83d\udc22", "거북이", "turtle 거북이"),
|
||||
("\ud83e\udd8b", "나비", "butterfly 나비"),
|
||||
("\ud83d\udc0c", "달팽이", "snail 달팽이"),
|
||||
("\ud83d\udc1b", "애벌레", "bug caterpillar 애벌레"),
|
||||
("\ud83d\udc1d", "꿀벌", "bee honeybee 벌"),
|
||||
("\ud83e\udd91", "오징어", "squid 오징어"),
|
||||
("\ud83d\udc19", "문어", "octopus 문어"),
|
||||
("\ud83d\udc20", "열대어", "tropical fish 열대어"),
|
||||
("\ud83d\udc21", "복어", "blowfish puffer 복어"),
|
||||
("\ud83e\udd88", "상어", "shark 상어"),
|
||||
("\ud83d\udc2c", "돌고래", "dolphin 돌고래"),
|
||||
("\ud83d\udc33", "고래", "whale 고래"),
|
||||
("\ud83d\udc32", "용", "dragon 용"),
|
||||
("\ud83e\udd84", "유니콘", "unicorn 유니콘"),
|
||||
("\ud83d\udcf1", "스마트폰", "phone mobile smartphone 폰"),
|
||||
("\ud83d\udcbb", "노트북", "laptop computer 노트북"),
|
||||
("\ud83d\udda5\ufe0f", "데스크톱", "desktop computer 컴퓨터"),
|
||||
("⌨\ufe0f", "키보드", "keyboard 키보드"),
|
||||
("\ud83d\uddb1\ufe0f", "마우스", "mouse 마우스"),
|
||||
("\ud83d\udda8\ufe0f", "프린터", "printer 프린터"),
|
||||
("\ud83d\udcf7", "카메라", "camera 카메라"),
|
||||
("\ud83d\udcf8", "플래시 카메라", "camera flash 사진"),
|
||||
("\ud83d\udcf9", "비디오 카메라", "video camera 동영상"),
|
||||
("\ud83c\udfa5", "영화 카메라", "movie camera film 영화"),
|
||||
("\ud83d\udcfa", "TV", "television tv 텔레비전"),
|
||||
("\ud83d\udcfb", "라디오", "radio 라디오"),
|
||||
("\ud83c\udf99\ufe0f", "마이크", "microphone studio 마이크"),
|
||||
("\ud83c\udfa4", "마이크 핸드헬드", "microphone karaoke 마이크"),
|
||||
("\ud83c\udfa7", "헤드폰", "headphones 헤드폰"),
|
||||
("\ud83d\udce1", "안테나", "satellite antenna 안테나"),
|
||||
("\ud83d\udd0b", "배터리", "battery 배터리"),
|
||||
("\ud83d\udd0c", "전원 플러그", "plug electric 플러그"),
|
||||
("\ud83d\udca1", "전구", "bulb idea light 전구 아이디어"),
|
||||
("\ud83d\udd26", "손전등", "flashlight torch 손전등"),
|
||||
("\ud83d\udd6f\ufe0f", "양초", "candle 양초"),
|
||||
("\ud83d\udcda", "책", "books stack 책"),
|
||||
("\ud83d\udcd6", "열린 책", "open book read 독서"),
|
||||
("\ud83d\udcdd", "메모", "memo note pencil 메모 노트"),
|
||||
("✏\ufe0f", "연필", "pencil 연필"),
|
||||
("\ud83d\udd8a\ufe0f", "펜", "pen 펜"),
|
||||
("\ud83d\udccc", "압정", "pushpin pin 압정"),
|
||||
("\ud83d\udcce", "클립", "paperclip 클립"),
|
||||
("✂\ufe0f", "가위", "scissors cut 가위"),
|
||||
("\ud83d\uddc2\ufe0f", "파일 폴더", "card index dividers folder 파일"),
|
||||
("\ud83d\udcc1", "폴더", "folder 폴더"),
|
||||
("\ud83d\udcc2", "열린 폴더", "open folder 폴더"),
|
||||
("\ud83d\uddc3\ufe0f", "파일 박스", "card file box 서류함"),
|
||||
("\ud83d\uddd1\ufe0f", "휴지통", "wastebasket trash 휴지통"),
|
||||
("\ud83d\udd12", "잠금", "locked lock 잠금"),
|
||||
("\ud83d\udd13", "열림", "unlocked 열림"),
|
||||
("\ud83d\udd11", "열쇠", "key 열쇠"),
|
||||
("\ud83d\udddd\ufe0f", "구식 열쇠", "old key 열쇠"),
|
||||
("\ud83d\udd28", "망치", "hammer 망치"),
|
||||
("\ud83d\udd27", "렌치", "wrench tool 렌치"),
|
||||
("\ud83d\udd29", "나사", "nut bolt 나사"),
|
||||
("⚙\ufe0f", "톱니바퀴", "gear settings 설정 톱니"),
|
||||
("\ud83d\udee0\ufe0f", "도구", "tools hammer wrench 도구"),
|
||||
("\ud83d\udc8a", "알약", "pill medicine 약 알약"),
|
||||
("\ud83d\udc89", "주사기", "syringe injection 주사"),
|
||||
("\ud83e\ude7a", "청진기", "stethoscope doctor 청진기"),
|
||||
("\ud83c\udfc6", "트로피", "trophy award 트로피 우승"),
|
||||
("\ud83e\udd47", "금메달", "first gold medal 금메달"),
|
||||
("\ud83e\udd48", "은메달", "second silver 은메달"),
|
||||
("\ud83e\udd49", "동메달", "third bronze 동메달"),
|
||||
("\ud83c\udf96\ufe0f", "훈장", "medal military 훈장"),
|
||||
("\ud83c\udf97\ufe0f", "리본", "ribbon awareness 리본"),
|
||||
("\ud83c\udfab", "티켓", "ticket admission 티켓"),
|
||||
("\ud83c\udf9f\ufe0f", "입장권", "admission tickets 티켓"),
|
||||
("\ud83c\udfaa", "서커스", "circus tent 서커스"),
|
||||
("\ud83c\udfa8", "팔레트", "art palette paint 그림 예술"),
|
||||
("\ud83c\udfad", "연극", "performing arts theater 연극"),
|
||||
("\ud83c\udfac", "클래퍼보드", "clapper film 영화 촬영"),
|
||||
("\ud83c\udfae", "게임 컨트롤러", "video game controller 게임"),
|
||||
("\ud83c\udfb2", "주사위", "dice game 주사위"),
|
||||
("\ud83c\udfaf", "다트", "bullseye target dart 다트 목표"),
|
||||
("\ud83c\udfb3", "볼링", "bowling 볼링"),
|
||||
("⚽", "축구", "soccer football 축구"),
|
||||
("\ud83c\udfc0", "농구", "basketball 농구"),
|
||||
("\ud83c\udfc8", "미식축구", "american football 미식축구"),
|
||||
("⚾", "야구", "baseball 야구"),
|
||||
("\ud83c\udfbe", "테니스", "tennis 테니스"),
|
||||
("\ud83c\udfd0", "배구", "volleyball 배구"),
|
||||
("\ud83c\udfc9", "럭비", "rugby 럭비"),
|
||||
("\ud83c\udfb1", "당구", "billiards pool 당구"),
|
||||
("\ud83c\udfd3", "탁구", "ping pong table tennis 탁구"),
|
||||
("\ud83c\udff8", "배드민턴", "badminton 배드민턴"),
|
||||
("\ud83e\udd4a", "권투 장갑", "boxing glove 권투"),
|
||||
("\ud83c\udfa3", "낚시", "fishing 낚시"),
|
||||
("\ud83c\udfcb\ufe0f", "역도", "weightlifting gym 헬스 역도"),
|
||||
("\ud83e\uddd8", "명상", "yoga meditation 명상 요가"),
|
||||
("\ud83d\ude97", "자동차", "car automobile 자동차"),
|
||||
("\ud83d\ude95", "택시", "taxi cab 택시"),
|
||||
("\ud83d\ude99", "SUV", "suv car 차"),
|
||||
("\ud83d\ude8c", "버스", "bus 버스"),
|
||||
("\ud83d\ude8e", "무궤도 전차", "trolleybus 버스"),
|
||||
("\ud83c\udfce\ufe0f", "레이싱카", "racing car 레이싱"),
|
||||
("\ud83d\ude93", "경찰차", "police car 경찰"),
|
||||
("\ud83d\ude91", "구급차", "ambulance 구급차"),
|
||||
("\ud83d\ude92", "소방차", "fire truck 소방차"),
|
||||
("\ud83d\ude90", "미니밴", "minibus van 밴"),
|
||||
("\ud83d\ude9a", "트럭", "truck delivery 트럭"),
|
||||
("✈\ufe0f", "비행기", "airplane flight plane 비행기"),
|
||||
("\ud83d\ude80", "로켓", "rocket space launch 로켓"),
|
||||
("\ud83d\udef8", "UFO", "flying saucer ufo 유에프오"),
|
||||
("\ud83d\ude81", "헬리콥터", "helicopter 헬리콥터"),
|
||||
("\ud83d\ude82", "기차", "train locomotive 기차"),
|
||||
("\ud83d\ude86", "고속열차", "train 기차"),
|
||||
("\ud83d\ude87", "지하철", "metro subway 지하철"),
|
||||
("⛵", "돛단배", "sailboat 요트"),
|
||||
("\ud83d\udea2", "배", "ship cruise 배"),
|
||||
("\ud83d\udeb2", "자전거", "bicycle bike 자전거"),
|
||||
("\ud83d\udef5", "스쿠터", "scooter moped 스쿠터"),
|
||||
("\ud83c\udfcd\ufe0f", "오토바이", "motorcycle 오토바이"),
|
||||
("\ud83c\udfe0", "집", "house home 집"),
|
||||
("\ud83c\udfe1", "마당 있는 집", "house garden 집"),
|
||||
("\ud83c\udfe2", "빌딩", "office building 빌딩"),
|
||||
("\ud83c\udfe3", "우체국", "post office 우체국"),
|
||||
("\ud83c\udfe5", "병원", "hospital 병원"),
|
||||
("\ud83c\udfe6", "은행", "bank 은행"),
|
||||
("\ud83c\udfe8", "호텔", "hotel 호텔"),
|
||||
("\ud83c\udfeb", "학교", "school 학교"),
|
||||
("\ud83c\udfea", "편의점", "convenience store shop 편의점"),
|
||||
("\ud83c\udfec", "백화점", "department store 백화점"),
|
||||
("\ud83c\udff0", "성", "castle 성"),
|
||||
("⛪", "교회", "church 교회"),
|
||||
("\ud83d\udd4c", "모스크", "mosque 모스크"),
|
||||
("\ud83d\uddfc", "에펠탑", "eiffel tower paris 파리"),
|
||||
("\ud83d\uddfd", "자유의 여신상", "statue of liberty new york 뉴욕"),
|
||||
("\ud83c\udfd4\ufe0f", "산", "mountain snow 산"),
|
||||
("\ud83c\udf0b", "화산", "volcano 화산"),
|
||||
("\ud83d\uddfb", "후지산", "mount fuji japan 후지산"),
|
||||
("\ud83c\udfd5\ufe0f", "캠핑", "camping tent 캠핑"),
|
||||
("\ud83c\udfd6\ufe0f", "해변", "beach summer 해변 해수욕"),
|
||||
("\ud83c\udf0f", "지구", "earth globe asia 지구"),
|
||||
("\ud83d\udcaf", "100점", "hundred percent perfect 완벽 100"),
|
||||
("\ud83d\udd22", "숫자", "numbers 숫자"),
|
||||
("\ud83c\udd97", "OK", "ok button 오케이"),
|
||||
("\ud83c\udd99", "업", "up button 업"),
|
||||
("\ud83c\udd92", "쿨", "cool button 쿨"),
|
||||
("\ud83c\udd95", "새것", "new button 새"),
|
||||
("\ud83c\udd93", "무료", "free button 무료"),
|
||||
("\ud83c\udd98", "SOS", "sos emergency 긴급 구조"),
|
||||
("⚠\ufe0f", "경고", "warning caution 경고 주의"),
|
||||
("\ud83d\udeab", "금지", "prohibited no 금지"),
|
||||
("✅", "체크", "check mark done 완료 확인"),
|
||||
("❌", "엑스", "x cross error 실패 오류"),
|
||||
("❓", "물음표", "question mark 물음표"),
|
||||
("❗", "느낌표", "exclamation mark 느낌표"),
|
||||
("➕", "더하기", "plus add 더하기"),
|
||||
("➖", "빼기", "minus subtract 빼기"),
|
||||
("➗", "나누기", "divide 나누기"),
|
||||
("✖\ufe0f", "곱하기", "multiply times 곱하기"),
|
||||
("♾\ufe0f", "무한대", "infinity 무한"),
|
||||
("\ud83d\udd01", "반복", "repeat loop 반복"),
|
||||
("\ud83d\udd00", "셔플", "shuffle random 랜덤"),
|
||||
("▶\ufe0f", "재생", "play 재생"),
|
||||
("⏸\ufe0f", "일시정지", "pause 일시정지"),
|
||||
("⏹\ufe0f", "정지", "stop 정지"),
|
||||
("⏩", "빨리 감기", "fast forward 빨리감기"),
|
||||
("⏪", "되감기", "rewind 되감기"),
|
||||
("\ud83d\udd14", "알림", "bell notification 알림 벨"),
|
||||
("\ud83d\udd15", "알림 끔", "bell off 알림끔"),
|
||||
("\ud83d\udd0a", "볼륨 크게", "loud speaker volume up 볼륨"),
|
||||
("\ud83d\udd07", "음소거", "muted speaker 음소거"),
|
||||
("\ud83d\udce3", "메가폰", "megaphone loud 확성기"),
|
||||
("\ud83d\udce2", "스피커", "loudspeaker 스피커"),
|
||||
("\ud83d\udcac", "말풍선", "speech bubble chat 대화"),
|
||||
("\ud83d\udcad", "생각 말풍선", "thought bubble thinking 생각"),
|
||||
("\ud83d\udce7", "이메일", "email mail 이메일 메일"),
|
||||
("\ud83d\udce8", "수신 봉투", "incoming envelope 수신"),
|
||||
("\ud83d\udce9", "발신 봉투", "envelope outbox 발신"),
|
||||
("\ud83d\udcec", "우편함", "mailbox 우편함"),
|
||||
("\ud83d\udce6", "택배 박스", "package box parcel 택배 상자"),
|
||||
("\ud83c\udf81", "선물", "gift present 선물"),
|
||||
("\ud83c\udf80", "리본 묶음", "ribbon bow 리본"),
|
||||
("\ud83c\udf8a", "색종이", "confetti 파티 축하"),
|
||||
("\ud83c\udf89", "파티 폭죽", "party popper celebrate 파티 축하"),
|
||||
("\ud83c\udf88", "풍선", "balloon party 풍선"),
|
||||
("\ud83d\udd50", "1시", "one o'clock 1시 시간"),
|
||||
("\ud83d\udd52", "3시", "three o'clock 3시 시간"),
|
||||
("\ud83d\udd54", "4시", "four o'clock 4시 시간"),
|
||||
("⏰", "알람 시계", "alarm clock 알람 시계"),
|
||||
("⏱\ufe0f", "스톱워치", "stopwatch timer 스톱워치 타이머"),
|
||||
("\ud83d\udcc5", "달력", "calendar date 달력 날짜"),
|
||||
("\ud83d\udcc6", "찢는 달력", "tear-off calendar 달력"),
|
||||
("\ud83d\udcb0", "돈 가방", "money bag 돈 부자"),
|
||||
("\ud83d\udcb3", "신용카드", "credit card payment 카드 결제"),
|
||||
("\ud83d\udcb5", "달러", "dollar banknote 달러"),
|
||||
("\ud83d\udcb4", "엔화", "yen banknote 엔"),
|
||||
("\ud83d\udcb6", "유로", "euro banknote 유로"),
|
||||
("\ud83d\udcb7", "파운드", "pound banknote 파운드"),
|
||||
("\ud83d\udcca", "막대 그래프", "bar chart graph 그래프"),
|
||||
("\ud83d\udcc8", "상승 그래프", "chart increasing trend 상승 트렌드"),
|
||||
("\ud83d\udcc9", "하락 그래프", "chart decreasing trend 하락"),
|
||||
("\ud83d\udd0d", "돋보기", "magnifying glass search 검색 돋보기"),
|
||||
("\ud83d\udd0e", "오른쪽 돋보기", "magnifying glass right search 검색"),
|
||||
("\ud83c\udff3\ufe0f", "흰 깃발", "white flag 항복"),
|
||||
("\ud83c\udff4", "검은 깃발", "black flag 해적"),
|
||||
("\ud83d\udea9", "빨간 삼각기", "triangular flag 경고 깃발"),
|
||||
("\ud83c\udfc1", "체크무늬 깃발", "chequered flag finish race 결승"),
|
||||
("\ud83c\udf10", "지구본", "globe internet web 인터넷 웹"),
|
||||
("⚓", "닻", "anchor 닻"),
|
||||
("\ud83c\udfb5", "음표", "music note 음악 음표"),
|
||||
("\ud83c\udfb6", "음표들", "musical notes 음악"),
|
||||
("\ud83c\udfbc", "악보", "musical score 악보"),
|
||||
("\ud83c\udfb9", "피아노", "piano keyboard 피아노"),
|
||||
("\ud83c\udfb8", "기타", "guitar 기타"),
|
||||
("\ud83e\udd41", "드럼", "drum 드럼"),
|
||||
("\ud83e\ude97", "아코디언", "accordion 아코디언"),
|
||||
("\ud83c\udfb7", "색소폰", "saxophone 색소폰"),
|
||||
("\ud83c\udfba", "트럼펫", "trumpet 트럼펫"),
|
||||
("\ud83c\udfbb", "바이올린", "violin 바이올린")
|
||||
};
|
||||
|
||||
public string? Prefix => "emoji";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Emoji", "이모지 피커 — emoji 뒤에 이름 입력", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
IEnumerable<(string, string, string)> source;
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
source = _emojis.Take(30);
|
||||
}
|
||||
else
|
||||
{
|
||||
string q = query.Trim().ToLowerInvariant();
|
||||
source = _emojis.Where(((string Emoji, string Name, string Tags) e) => e.Name.Contains(q, StringComparison.OrdinalIgnoreCase) || e.Tags.Contains(q, StringComparison.OrdinalIgnoreCase) || e.Emoji.Contains(q)).Take(20);
|
||||
}
|
||||
List<LauncherItem> list = source.Select<(string, string, string), LauncherItem>(((string Emoji, string Name, string Tags) e) => new LauncherItem(e.Emoji + " " + e.Name, "Enter로 클립보드에 복사", null, e.Emoji, null, "\ue76e")).ToList();
|
||||
if (!list.Any() && !string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
list.Add(new LauncherItem("검색 결과 없음", "'" + query + "'에 해당하는 이모지가 없습니다", null, null, null, "\ue946"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
199
.decompiledproj/AxCopilot/Handlers/EncodeHandler.cs
Normal file
199
.decompiledproj/AxCopilot/Handlers/EncodeHandler.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using AxCopilot.SDK;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class EncodeHandler : IActionHandler
|
||||
{
|
||||
public string? Prefix => "encode ";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Encoder", "인코딩/해싱 — encode base64/url/hex/md5/sha256 텍스트", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string text = query.Trim();
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return Task.FromResult(HelpItems());
|
||||
}
|
||||
string text2;
|
||||
if (text.StartsWith("decode ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
text2 = text;
|
||||
string rest = text2.Substring(7, text2.Length - 7).Trim();
|
||||
return Task.FromResult(HandleDecode(rest));
|
||||
}
|
||||
int num = text.IndexOf(' ');
|
||||
if (num < 0)
|
||||
{
|
||||
return Task.FromResult(HelpItems());
|
||||
}
|
||||
string type = text.Substring(0, num).Trim().ToLowerInvariant();
|
||||
text2 = text;
|
||||
int num2 = num + 1;
|
||||
string input = text2.Substring(num2, text2.Length - num2);
|
||||
return Task.FromResult(HandleEncode(type, input));
|
||||
}
|
||||
|
||||
private static IEnumerable<LauncherItem> HandleEncode(string type, string input)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
IEnumerable<LauncherItem> result;
|
||||
switch (type)
|
||||
{
|
||||
case "base64":
|
||||
case "b64":
|
||||
result = SingleResult("Base64 인코딩", Convert.ToBase64String(Encoding.UTF8.GetBytes(input)));
|
||||
break;
|
||||
case "url":
|
||||
result = SingleResult("URL 인코딩", Uri.EscapeDataString(input));
|
||||
break;
|
||||
case "hex":
|
||||
result = SingleResult("HEX 인코딩", Convert.ToHexString(Encoding.UTF8.GetBytes(input)).ToLowerInvariant());
|
||||
break;
|
||||
case "md5":
|
||||
result = SingleResult("MD5 해시", MD5Hash(input));
|
||||
break;
|
||||
case "sha1":
|
||||
result = SingleResult("SHA-1 해시", SHA1Hash(input));
|
||||
break;
|
||||
case "sha256":
|
||||
result = SingleResult("SHA-256 해시", SHA256Hash(input));
|
||||
break;
|
||||
case "sha512":
|
||||
result = SingleResult("SHA-512 해시", SHA512Hash(input));
|
||||
break;
|
||||
case "html":
|
||||
result = SingleResult("HTML 엔티티 인코딩", WebUtility.HtmlEncode(input));
|
||||
break;
|
||||
default:
|
||||
result = new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("알 수 없는 타입: " + type, "base64, url, hex, md5, sha1, sha256, sha512, html 중 선택", null, null, null, "\ue7ba"));
|
||||
break;
|
||||
}
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("변환 오류", ex.Message, null, null, null, "\uea39"));
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<LauncherItem> HandleDecode(string rest)
|
||||
{
|
||||
int num = rest.IndexOf(' ');
|
||||
if (num < 0)
|
||||
{
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("사용법: encode decode <타입> <값>", "타입: base64, url, hex, html", null, null, null, "\ue946"));
|
||||
}
|
||||
string text = rest.Substring(0, num).Trim().ToLowerInvariant();
|
||||
int num2 = num + 1;
|
||||
string text2 = rest.Substring(num2, rest.Length - num2);
|
||||
try
|
||||
{
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
IEnumerable<LauncherItem> result;
|
||||
switch (text)
|
||||
{
|
||||
case "base64":
|
||||
case "b64":
|
||||
result = SingleResult("Base64 디코딩", Encoding.UTF8.GetString(Convert.FromBase64String(text2)));
|
||||
break;
|
||||
case "url":
|
||||
result = SingleResult("URL 디코딩", Uri.UnescapeDataString(text2));
|
||||
break;
|
||||
case "hex":
|
||||
result = SingleResult("HEX 디코딩", Encoding.UTF8.GetString(Convert.FromHexString(text2)));
|
||||
break;
|
||||
case "html":
|
||||
result = SingleResult("HTML 엔티티 디코딩", WebUtility.HtmlDecode(text2));
|
||||
break;
|
||||
default:
|
||||
result = new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("디코딩 불가 타입: " + text, "디코딩 지원: base64, url, hex, html", null, null, null, "\ue7ba"));
|
||||
break;
|
||||
}
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("디코딩 오류", ex.Message, null, null, null, "\uea39"));
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<LauncherItem> SingleResult(string title, string result)
|
||||
{
|
||||
string text = ((result.Length > 80) ? (result.Substring(0, 77) + "…") : result);
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem(title, text + " · Enter로 복사", null, result, null, "\ue8cb"));
|
||||
}
|
||||
|
||||
private static IEnumerable<LauncherItem> HelpItems()
|
||||
{
|
||||
return new _003C_003Ez__ReadOnlyArray<LauncherItem>(new LauncherItem[7]
|
||||
{
|
||||
new LauncherItem("encode base64 <텍스트>", "Base64 인코딩/디코딩", null, null, null, "\ue8cb"),
|
||||
new LauncherItem("encode url <텍스트>", "URL 퍼센트 인코딩/디코딩", null, null, null, "\ue8cb"),
|
||||
new LauncherItem("encode hex <텍스트>", "16진수 인코딩/디코딩", null, null, null, "\ue8cb"),
|
||||
new LauncherItem("encode md5 <텍스트>", "MD5 해시 (단방향)", null, null, null, "\ue8cb"),
|
||||
new LauncherItem("encode sha256 <텍스트>", "SHA-256 해시 (단방향)", null, null, null, "\ue8cb"),
|
||||
new LauncherItem("encode html <텍스트>", "HTML 엔티티 인코딩/디코딩", null, null, null, "\ue8cb"),
|
||||
new LauncherItem("encode decode base64 <값>", "Base64 디코딩 예시", null, null, null, "\ue8cb")
|
||||
});
|
||||
}
|
||||
|
||||
private static string MD5Hash(string input)
|
||||
{
|
||||
byte[] inArray = MD5.HashData(Encoding.UTF8.GetBytes(input));
|
||||
return Convert.ToHexString(inArray).ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static string SHA1Hash(string input)
|
||||
{
|
||||
byte[] inArray = SHA1.HashData(Encoding.UTF8.GetBytes(input));
|
||||
return Convert.ToHexString(inArray).ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static string SHA256Hash(string input)
|
||||
{
|
||||
byte[] inArray = SHA256.HashData(Encoding.UTF8.GetBytes(input));
|
||||
return Convert.ToHexString(inArray).ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static string SHA512Hash(string input)
|
||||
{
|
||||
byte[] inArray = SHA512.HashData(Encoding.UTF8.GetBytes(input));
|
||||
return Convert.ToHexString(inArray).ToLowerInvariant();
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
82
.decompiledproj/AxCopilot/Handlers/EnvHandler.cs
Normal file
82
.decompiledproj/AxCopilot/Handlers/EnvHandler.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using AxCopilot.SDK;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class EnvHandler : IActionHandler
|
||||
{
|
||||
private static readonly string[] _priorityKeys = new string[22]
|
||||
{
|
||||
"PATH", "JAVA_HOME", "PYTHON_HOME", "NODE_HOME", "GOPATH", "GOROOT", "USERPROFILE", "APPDATA", "LOCALAPPDATA", "TEMP",
|
||||
"TMP", "COMPUTERNAME", "USERNAME", "USERDOMAIN", "OS", "PROCESSOR_ARCHITECTURE", "SYSTEMROOT", "WINDIR", "PROGRAMFILES", "PROGRAMFILES(X86)",
|
||||
"COMMONPROGRAMFILES", "NUMBER_OF_PROCESSORS"
|
||||
};
|
||||
|
||||
public string? Prefix => "env";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("EnvVars", "환경변수 조회 — env 뒤에 변수명 입력", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string q = query.Trim();
|
||||
List<(string Key, string Value)> allVars = (from DictionaryEntry e in Environment.GetEnvironmentVariables()
|
||||
select (Key: e.Key?.ToString() ?? "", Value: e.Value?.ToString() ?? "") into x
|
||||
where !string.IsNullOrEmpty(x.Key)
|
||||
select x).ToList();
|
||||
IEnumerable<(string, string)> source;
|
||||
if (string.IsNullOrWhiteSpace(q))
|
||||
{
|
||||
HashSet<string> prioritySet = new HashSet<string>(_priorityKeys, StringComparer.OrdinalIgnoreCase);
|
||||
IEnumerable<(string, string)> first = from k in _priorityKeys
|
||||
where allVars.Any(((string Key, string Value) v) => string.Equals(v.Key, k, StringComparison.OrdinalIgnoreCase))
|
||||
select allVars.First(((string Key, string Value) v) => string.Equals(v.Key, k, StringComparison.OrdinalIgnoreCase));
|
||||
IOrderedEnumerable<(string, string)> second = from v in allVars
|
||||
where !prioritySet.Contains(v.Key)
|
||||
orderby v.Key
|
||||
select v;
|
||||
source = first.Concat(second).Take(20);
|
||||
}
|
||||
else
|
||||
{
|
||||
source = (from v in allVars
|
||||
where v.Key.Contains(q, StringComparison.OrdinalIgnoreCase) || v.Value.Contains(q, StringComparison.OrdinalIgnoreCase)
|
||||
orderby (!v.Key.StartsWith(q, StringComparison.OrdinalIgnoreCase)) ? 1 : 0, v.Key
|
||||
select v).Take(20);
|
||||
}
|
||||
List<LauncherItem> list = source.Select<(string, string), LauncherItem>(delegate((string Key, string Value) v)
|
||||
{
|
||||
string text = ((v.Value.Length > 80) ? (v.Value.Substring(0, 77) + "…") : v.Value);
|
||||
if (v.Key.Equals("PATH", StringComparison.OrdinalIgnoreCase) && v.Value.Contains(';'))
|
||||
{
|
||||
text = v.Value.Split(';')[0] + $" (외 {v.Value.Split(';').Length - 1}개)";
|
||||
}
|
||||
return new LauncherItem(v.Key, text + " · Enter로 값 복사", null, v.Value, null, "\ue8d7");
|
||||
}).ToList();
|
||||
if (!list.Any())
|
||||
{
|
||||
list.Add(new LauncherItem("'" + q + "' — 환경변수 없음", "해당 이름의 환경변수를 찾을 수 없습니다", null, null, null, "\ue7ba"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
289
.decompiledproj/AxCopilot/Handlers/EverythingHandler.cs
Normal file
289
.decompiledproj/AxCopilot/Handlers/EverythingHandler.cs
Normal file
@@ -0,0 +1,289 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class EverythingHandler : IActionHandler
|
||||
{
|
||||
private const int EVERYTHING_OK = 0;
|
||||
|
||||
private const int EVERYTHING_REQUEST_FILE_NAME = 1;
|
||||
|
||||
private const int EVERYTHING_REQUEST_PATH = 2;
|
||||
|
||||
private const int EVERYTHING_REQUEST_SIZE = 16;
|
||||
|
||||
private bool? _isAvailable;
|
||||
|
||||
public string? Prefix => "es";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("EverythingSearch", "Everything 초고속 파일 검색 — es [키워드]", "1.0", "AX");
|
||||
|
||||
private bool IsAvailable
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isAvailable.HasValue)
|
||||
{
|
||||
return _isAvailable.Value;
|
||||
}
|
||||
try
|
||||
{
|
||||
Everything_GetMajorVersion();
|
||||
_isAvailable = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_isAvailable = false;
|
||||
}
|
||||
return _isAvailable.Value;
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("Everything64.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern uint Everything_SetSearchW(string lpSearchString);
|
||||
|
||||
[DllImport("Everything64.dll")]
|
||||
private static extern void Everything_SetMax(uint dwMax);
|
||||
|
||||
[DllImport("Everything64.dll")]
|
||||
private static extern void Everything_SetRequestFlags(uint dwRequestFlags);
|
||||
|
||||
[DllImport("Everything64.dll")]
|
||||
private static extern bool Everything_QueryW(bool bWait);
|
||||
|
||||
[DllImport("Everything64.dll")]
|
||||
private static extern uint Everything_GetNumResults();
|
||||
|
||||
[DllImport("Everything64.dll")]
|
||||
private static extern uint Everything_GetLastError();
|
||||
|
||||
[DllImport("Everything64.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern nint Everything_GetResultFullPathNameW(uint nIndex, nint lpString, uint nMaxCount);
|
||||
|
||||
[DllImport("Everything64.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern void Everything_GetResultSize(uint nIndex, out long lpFileSize);
|
||||
|
||||
[DllImport("Everything64.dll")]
|
||||
private static extern uint Everything_GetMajorVersion();
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
if (!IsAvailable)
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new LauncherItem[1]
|
||||
{
|
||||
new LauncherItem("Everything이 설치되어 있지 않습니다", "voidtools.com에서 Everything을 설치하면 초고속 파일 검색을 사용할 수 있습니다", null, null, null, "\ue7ba")
|
||||
});
|
||||
}
|
||||
string text = query.Trim();
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new LauncherItem[1]
|
||||
{
|
||||
new LauncherItem("Everything 파일 검색", "검색어를 입력하세요 — 파일명, 확장자(*.xlsx), 경로 일부 등", null, null, null, "\ue721")
|
||||
});
|
||||
}
|
||||
try
|
||||
{
|
||||
Everything_SetSearchW(text);
|
||||
Everything_SetMax(30u);
|
||||
Everything_SetRequestFlags(19u);
|
||||
if (!Everything_QueryW(bWait: true))
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new LauncherItem[1]
|
||||
{
|
||||
new LauncherItem("Everything 검색 실패", $"오류 코드: {Everything_GetLastError()} — Everything 서비스가 실행 중인지 확인하세요", null, null, null, "\ue7ba")
|
||||
});
|
||||
}
|
||||
uint num = Everything_GetNumResults();
|
||||
if (num == 0)
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new LauncherItem[1]
|
||||
{
|
||||
new LauncherItem("검색 결과 없음: " + text, "다른 키워드나 와일드카드(*.xlsx)를 시도해 보세요", null, null, null, "\ue946")
|
||||
});
|
||||
}
|
||||
List<LauncherItem> list = new List<LauncherItem>();
|
||||
nint num2 = Marshal.AllocHGlobal(1040);
|
||||
try
|
||||
{
|
||||
for (uint num3 = 0u; num3 < num && num3 < 30; num3++)
|
||||
{
|
||||
Everything_GetResultFullPathNameW(num3, num2, 520u);
|
||||
string text2 = Marshal.PtrToStringUni(num2) ?? "";
|
||||
Everything_GetResultSize(num3, out var lpFileSize);
|
||||
string fileName = Path.GetFileName(text2);
|
||||
string text3 = Path.GetDirectoryName(text2) ?? "";
|
||||
string text4 = ((lpFileSize > 0) ? FormatSize(lpFileSize) : "");
|
||||
string subtitle = (string.IsNullOrEmpty(text4) ? text3 : (text4 + " · " + text3));
|
||||
string symbol = (Directory.Exists(text2) ? "\ue8b7" : GetFileSymbol(text2));
|
||||
list.Add(new LauncherItem(fileName, subtitle, null, text2, null, symbol));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(num2);
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("Everything 검색 오류: " + ex.Message);
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new LauncherItem[1]
|
||||
{
|
||||
new LauncherItem("Everything 검색 오류", ex.Message, null, null, null, "\ue7ba")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
string path = data as string;
|
||||
if (path == null || string.IsNullOrEmpty(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = path,
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
else if (File.Exists(path))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = path,
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Exception ex2 = ex;
|
||||
LogService.Warn("Everything 결과 열기 실패: " + ex2.Message);
|
||||
}
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static string FormatSize(long bytes)
|
||||
{
|
||||
if (bytes >= 1024)
|
||||
{
|
||||
if (bytes >= 1048576)
|
||||
{
|
||||
if (bytes >= 1073741824)
|
||||
{
|
||||
return $"{(double)bytes / 1073741824.0:F2} GB";
|
||||
}
|
||||
return $"{(double)bytes / 1048576.0:F1} MB";
|
||||
}
|
||||
return $"{(double)bytes / 1024.0:F1} KB";
|
||||
}
|
||||
return $"{bytes} B";
|
||||
}
|
||||
|
||||
private static string GetFileSymbol(string path)
|
||||
{
|
||||
string text = Path.GetExtension(path).ToLowerInvariant();
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string result;
|
||||
switch (text)
|
||||
{
|
||||
case ".xlsx":
|
||||
case ".xls":
|
||||
case ".csv":
|
||||
result = "\ue9f9";
|
||||
break;
|
||||
case ".docx":
|
||||
case ".doc":
|
||||
result = "\ue8a5";
|
||||
break;
|
||||
case ".pptx":
|
||||
case ".ppt":
|
||||
result = "\uee71";
|
||||
break;
|
||||
case ".pdf":
|
||||
result = "\uea90";
|
||||
break;
|
||||
case ".png":
|
||||
case ".jpg":
|
||||
case ".jpeg":
|
||||
case ".gif":
|
||||
case ".bmp":
|
||||
case ".svg":
|
||||
result = "\ueb9f";
|
||||
break;
|
||||
case ".mp4":
|
||||
case ".avi":
|
||||
case ".mkv":
|
||||
case ".mov":
|
||||
result = "\ue714";
|
||||
break;
|
||||
case ".mp3":
|
||||
case ".wav":
|
||||
case ".flac":
|
||||
result = "\uec4f";
|
||||
break;
|
||||
case ".zip":
|
||||
case ".rar":
|
||||
case ".7z":
|
||||
case ".tar":
|
||||
case ".gz":
|
||||
result = "\ue8c6";
|
||||
break;
|
||||
case ".exe":
|
||||
case ".msi":
|
||||
result = "\uecaa";
|
||||
break;
|
||||
case ".cs":
|
||||
case ".py":
|
||||
case ".js":
|
||||
case ".ts":
|
||||
case ".java":
|
||||
case ".cpp":
|
||||
case ".c":
|
||||
case ".go":
|
||||
result = "\ue943";
|
||||
break;
|
||||
case ".json":
|
||||
case ".xml":
|
||||
case ".yaml":
|
||||
case ".yml":
|
||||
result = "\ue713";
|
||||
break;
|
||||
case ".txt":
|
||||
case ".md":
|
||||
case ".log":
|
||||
result = "\ue8d2";
|
||||
break;
|
||||
case ".html":
|
||||
case ".htm":
|
||||
case ".css":
|
||||
result = "\ue774";
|
||||
break;
|
||||
default:
|
||||
result = "\ue8a5";
|
||||
break;
|
||||
}
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
194
.decompiledproj/AxCopilot/Handlers/FavoriteHandler.cs
Normal file
194
.decompiledproj/AxCopilot/Handlers/FavoriteHandler.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class FavoriteHandler : IActionHandler
|
||||
{
|
||||
private class FavEntry
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; set; } = "";
|
||||
}
|
||||
|
||||
private static readonly string FavFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "favorites.json");
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOpts = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
private List<FavEntry> _cache = new List<FavEntry>();
|
||||
|
||||
private bool _loaded;
|
||||
|
||||
public string? Prefix => "fav";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Favorite", "즐겨찾기 관리 — fav", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
EnsureLoaded();
|
||||
string q = query.Trim();
|
||||
if (q.StartsWith("add ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string text = q;
|
||||
string text2 = text.Substring(4, text.Length - 4).Trim();
|
||||
int num = text2.IndexOf(' ');
|
||||
if (num > 0)
|
||||
{
|
||||
string text3 = text2.Substring(0, num).Trim();
|
||||
text = text2;
|
||||
int num2 = num + 1;
|
||||
string text4 = text.Substring(num2, text.Length - num2).Trim();
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("즐겨찾기 추가: " + text3, "경로: " + text4 + " · Enter로 추가", null, ValueTuple.Create("__ADD__", text3, text4), null, "\ue74e")));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("사용법: fav add [이름] [경로]", "예: fav add 보고서 C:\\work\\report.xlsx", null, null, null, "\ue946")));
|
||||
}
|
||||
if (q.StartsWith("del ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string text = q;
|
||||
string name = text.Substring(4, text.Length - 4).Trim();
|
||||
FavEntry favEntry = _cache.FirstOrDefault((FavEntry b) => b.Name.Contains(name, StringComparison.OrdinalIgnoreCase));
|
||||
if (favEntry != null)
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("즐겨찾기 삭제: " + favEntry.Name, "경로: " + favEntry.Path + " · Enter로 삭제", null, ValueTuple.Create("__DEL__", favEntry.Name), null, "\ue74d")));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("'" + name + "'에 해당하는 즐겨찾기 없음", "fav으로 전체 목록 확인", null, null, null, "\ue7ba")));
|
||||
}
|
||||
List<FavEntry> source = (string.IsNullOrWhiteSpace(q) ? _cache : _cache.Where((FavEntry b) => b.Name.Contains(q, StringComparison.OrdinalIgnoreCase) || b.Path.Contains(q, StringComparison.OrdinalIgnoreCase)).ToList());
|
||||
if (!source.Any())
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem((_cache.Count == 0) ? "즐겨찾기가 없습니다" : ("'" + q + "'에 해당하는 즐겨찾기 없음"), "fav add [이름] [경로]로 추가하세요", null, null, null, "\ue946")));
|
||||
}
|
||||
List<LauncherItem> result = source.Select(delegate(FavEntry b)
|
||||
{
|
||||
bool flag = Directory.Exists(b.Path);
|
||||
bool flag2 = File.Exists(b.Path);
|
||||
string symbol = (flag ? "\ue8b7" : (flag2 ? "\ue8a5" : "\ue7ba"));
|
||||
string text5 = (flag ? "폴더 열기" : (flag2 ? "파일 열기" : "경로를 찾을 수 없음"));
|
||||
return new LauncherItem(b.Name, b.Path + " · " + text5, null, b.Path, null, symbol);
|
||||
}).ToList();
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)result);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is (string, string, string) { Item1: "__ADD__" } tuple)
|
||||
{
|
||||
AddFav(tuple.Item2, tuple.Item3);
|
||||
NotificationService.Notify("AX Copilot", "즐겨찾기 추가: " + tuple.Item2);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (item.Data is (string, string) { Item1: "__DEL__" } tuple2)
|
||||
{
|
||||
RemoveFav(tuple2.Item2);
|
||||
NotificationService.Notify("AX Copilot", "즐겨찾기 삭제: " + tuple2.Item2);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
object data = item.Data;
|
||||
string path = data as string;
|
||||
if (path != null)
|
||||
{
|
||||
if (Directory.Exists(path) || File.Exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(path)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("즐겨찾기 열기 실패: " + ex.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
Application current = Application.Current;
|
||||
if (current != null)
|
||||
{
|
||||
((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
Clipboard.SetText(path);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void EnsureLoaded()
|
||||
{
|
||||
if (_loaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_loaded = true;
|
||||
try
|
||||
{
|
||||
if (File.Exists(FavFile))
|
||||
{
|
||||
_cache = JsonSerializer.Deserialize<List<FavEntry>>(File.ReadAllText(FavFile), JsonOpts) ?? new List<FavEntry>();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("즐겨찾기 로드 실패: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddFav(string name, string path)
|
||||
{
|
||||
EnsureLoaded();
|
||||
_cache.RemoveAll((FavEntry b) => b.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
_cache.Insert(0, new FavEntry
|
||||
{
|
||||
Name = name,
|
||||
Path = path
|
||||
});
|
||||
Save();
|
||||
}
|
||||
|
||||
private void RemoveFav(string name)
|
||||
{
|
||||
EnsureLoaded();
|
||||
_cache.RemoveAll((FavEntry b) => b.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
Save();
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(FavFile));
|
||||
File.WriteAllText(FavFile, JsonSerializer.Serialize(_cache, JsonOpts));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("즐겨찾기 저장 실패: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
.decompiledproj/AxCopilot/Handlers/FolderAliasHandler.cs
Normal file
46
.decompiledproj/AxCopilot/Handlers/FolderAliasHandler.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class FolderAliasHandler : IActionHandler
|
||||
{
|
||||
private readonly SettingsService _settings;
|
||||
|
||||
public string? Prefix => "cd";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("folder-alias", "폴더 별칭", "1.0", "AX");
|
||||
|
||||
public FolderAliasHandler(SettingsService settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
IEnumerable<LauncherItem> result = from a in _settings.Settings.Aliases
|
||||
where a.Type == "folder" && (string.IsNullOrEmpty(query) || a.Key.Contains(query, StringComparison.OrdinalIgnoreCase))
|
||||
select new LauncherItem(a.Key, Environment.ExpandEnvironmentVariables(a.Target), null, a, null, "\ue8b7");
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is AliasEntry aliasEntry)
|
||||
{
|
||||
string arguments = Environment.ExpandEnvironmentVariables(aliasEntry.Target);
|
||||
Process.Start(new ProcessStartInfo("explorer.exe", arguments)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
203
.decompiledproj/AxCopilot/Handlers/HelpHandler.cs
Normal file
203
.decompiledproj/AxCopilot/Handlers/HelpHandler.cs
Normal file
@@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
using AxCopilot.Views;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class HelpHandler : IActionHandler
|
||||
{
|
||||
private record HelpEntry(string Category, string Command, string Title, string Description, string Example, string Symbol, string ColorHex);
|
||||
|
||||
private readonly SettingsService? _settings;
|
||||
|
||||
private static readonly HelpEntry[] _baseEntries = new HelpEntry[47]
|
||||
{
|
||||
new HelpEntry("검색", "", "앱 · 파일 · 북마크 퍼지 검색", "앱 이름, 파일명, 한국어 초성, Chrome·Edge 북마크를 통합 검색", "chrome / 보고서 / ㅅㄷ (설정) / 북마크 제목", "\ue721", "#0078D4"),
|
||||
new HelpEntry("계산", "=", "계산기", "수식 계산 · 단위 변환 · 실시간 환율", "= sqrt(144) / = 100 USD to KRW / = 15km to miles", "\ue8ef", "#4B5EFC"),
|
||||
new HelpEntry("검색", "?", "웹 검색 (10개 엔진)", "?n 네이버 · ?g 구글 · ?y 유튜브 · ?gh 깃허브 · ?d DuckDuckGo · ?w 위키피디아 · ?nw 나무위키 · ?nm 네이버지도 · ?ni 네이버이미지 · ?gi 구글이미지", "? 오늘 날씨 / ?n 뉴스 / ?g python / ?nw 검색어 / ?y 음악 / ?gh axios", "\ue774", "#006EAF"),
|
||||
new HelpEntry("클립보드", "#", "클립보드 히스토리", "복사 이력 검색 & 재사용 · Shift+↑↓로 여러 항목 병합", "# / # 회의록 / (Shift+↑↓ 선택 후 Shift+Enter 병합)", "\ue81c", "#B7791F"),
|
||||
new HelpEntry("클립보드", "$", "클립보드 텍스트 변환 (12종)", "현재 클립보드 내용을 즉시 변환", "$json / $b64e / $upper / $url / $md5 / $trim", "\ue77f", "#8764B8"),
|
||||
new HelpEntry("텍스트", ";", "텍스트 스니펫", "저장된 템플릿 불러오기 · 변수 치환 지원", ";addr / ;sig / ;greet", "\ue70b", "#0F6CBD"),
|
||||
new HelpEntry("단축키", "@", "URL 단축키", "저장해둔 URL을 키워드로 바로 열기", "@gh / @notion / @jira", "\ue774", "#0078D4"),
|
||||
new HelpEntry("단축키", "cd", "폴더 단축키", "저장해둔 폴더를 키워드로 바로 열기", "cd dl / cd work / cd desktop", "\ue8b7", "#107C10"),
|
||||
new HelpEntry("단축키", ">", "터미널 명령 실행", "PowerShell 명령을 AX Commander에서 직접 실행", "> git status / > ipconfig / > cls", "\ue756", "#323130"),
|
||||
new HelpEntry("창관리", "~", "워크스페이스 저장·복원", "현재 창 배치를 스냅샷으로 저장하고 언제든 복원", "~save 업무 / ~restore 업무 / ~list", "\ue8a1", "#C50F1F"),
|
||||
new HelpEntry("창관리", "snap", "창 분할 레이아웃", "창을 화면의 특정 영역에 즉시 스냅", "snap left / snap right / snap tl / snap full", "\ue8a0", "#B45309"),
|
||||
new HelpEntry("창관리", "cap", "스크린샷 캡처 (예약어 변경 가능)", "영역 선택 · 활성 창 · 스크롤 · 전체 화면. Shift+Enter로 지연 캡처(3/5/10초 타이머). 결과는 클립보드에 복사. 글로벌 단축키(PrintScreen 등)로 바로 캡처 가능. 설정 → 캡처 탭에서 예약어·단축키·스크롤 속도 변경", "cap region / cap window / cap scroll / cap screen / Shift+Enter → 지연 캡처", "\ue722", "#BE185D"),
|
||||
new HelpEntry("시스템", "/", "시스템 명령", "잠금·절전·재시작·종료·타이머·알람", "/lock / /sleep / /shutdown / /timer 5m / /alarm 14:30", "\ue7e8", "#4A4A4A"),
|
||||
new HelpEntry("시스템", "info · *", "시스템 정보", "IP · 배터리 · 볼륨 · 가동시간 · CPU · 디스크. `*` 입력으로도 동일하게 사용 가능", "info / info ip / info battery / * / * ip", "\ue7f4", "#5B4E7E"),
|
||||
new HelpEntry("알림", "", "잠금 해제 사용시간 알림", "PC 잠금 해제 시 오늘 누적 사용 시간과 격려 문구·명언 팝업 표시. 설정 → 알림 탭에서 활성화", "설정 → 알림 탭: 활성화 토글 / 표시 위치(4방향) / 표시 간격(30분~4시간) / 자동 닫힘(5초~3분)", "\uea8f", "#EA8F00"),
|
||||
new HelpEntry("시스템", "kill", "프로세스 종료", "프로세스 이름으로 검색 후 강제 종료", "kill chrome / kill node / kill teams", "\uea39", "#CC2222"),
|
||||
new HelpEntry("시스템", "media", "미디어 제어", "재생·일시정지·이전·다음·볼륨 조절", "media play / media next / media vol+ / media mute", "\ue768", "#1A6B3C"),
|
||||
new HelpEntry("개발", "json", "JSON 포맷 · 검증", "클립보드의 JSON을 정렬·압축·유효성 검사", "json → format / minify / validate", "\ue930", "#D97706"),
|
||||
new HelpEntry("개발", "encode", "인코딩 · 해싱", "Base64 · URL · HTML · UTF-8 · MD5 · SHA256", "encode base64 / encode url / encode sha256", "\ue8cb", "#6366F1"),
|
||||
new HelpEntry("개발", "color", "색상 변환", "HEX ↔ RGB ↔ HSL ↔ HSV 변환 및 색상명 지원", "color #FF5733 / color 255,87,51 / color red", "\ue771", "#EC4899"),
|
||||
new HelpEntry("개발", "port", "포트 · 프로세스 조회", "포트 번호로 점유 프로세스 확인", "port 3000 / port 8080 / port 443", "\ue968", "#006699"),
|
||||
new HelpEntry("개발", "env", "환경변수 조회", "시스템 환경변수 검색 및 클립보드 복사", "env / env PATH / env JAVA", "\ue8d7", "#0D9488"),
|
||||
new HelpEntry("앱", "emoji", "이모지 피커", "300개+ 이모지 검색 · Enter로 클립보드 복사", "emoji / emoji 하트 / emoji wave / emoji 불꽃", "\ue76e", "#F59E0B"),
|
||||
new HelpEntry("앱", "recent", "최근 파일", "Windows 최근 파일 목록 검색 & 바로 열기", "recent / recent 보고서 / recent xlsx", "\ue81c", "#059669"),
|
||||
new HelpEntry("앱", "note", "빠른 메모", "간단한 메모를 저장하고 불러오기", "note 내일 회의 10시 / note", "\ue70b", "#7C3AED"),
|
||||
new HelpEntry("앱", "uninstall", "앱 제거", "설치된 앱 검색 후 제거", "uninstall / uninstall kakao / uninstall zoom", "\ue74d", "#DC2626"),
|
||||
new HelpEntry("유틸", "pick", "스포이드 색상 추출", "화면 아무 곳을 클릭하여 HEX 색상 코드 추출 · 돋보기로 실시간 미리보기 · 결과 반투명 창 5초 표시", "pick → 스포이드 모드 진입 → 클릭으로 색상 추출", "\ue771", "#EC4899"),
|
||||
new HelpEntry("유틸", "date", "날짜 계산 · D-day · 타임스탬프", "날짜 가감(+30d), D-day 계산, Unix ↔ 날짜 변환, 요일·ISO 주차 조회", "date / date +30d / date 2026-12-25 / date 1711584000 / date unix", "\ue787", "#0EA5E9"),
|
||||
new HelpEntry("시스템", "svc", "서비스 관리", "Windows 서비스 검색·시작·중지·재시작 + AX 클립보드 서비스 강제 재시작", "svc / svc spooler / svc restart clipboard", "\ue912", "#6366F1"),
|
||||
new HelpEntry("유틸", "pipe", "클립보드 파이프라인", "변환을 > 로 체이닝: 대문자→공백제거→Base64 등 19종 필터 한 번에 적용", "pipe upper > trim > b64e / pipe sort > unique > number", "\ue8c8", "#8B5CF6"),
|
||||
new HelpEntry("유틸", "journal", "업무 일지 자동 생성", "오늘 사용한 앱·명령어·활성 시간을 마크다운 요약으로 자동 생성. 스탠드업/일일 보고에 바로 사용", "journal / journal 2026-03-25", "\ue70b", "#0EA5E9"),
|
||||
new HelpEntry("유틸", "routine", "루틴 자동화", "등록된 루틴(앱·폴더·URL 조합)을 한 번에 순서대로 실행. 출근/퇴근/회의 전 세팅", "routine / routine morning / routine endofday", "\ue82f", "#F59E0B"),
|
||||
new HelpEntry("유틸", "batch", "텍스트 일괄 처리", "클립보드 각 줄에 동시 적용: 접두사·접미사·줄번호·정렬·중복제거·따옴표·치환·CSV 변환", "batch number / batch prefix >> / batch sort / batch replace A B", "\ue8c6", "#10B981"),
|
||||
new HelpEntry("유틸", "diff", "텍스트/파일 비교", "클립보드 최근 2개 텍스트 또는 파일 2개를 줄 단위 비교. 추가·삭제·동일 줄 하이라이트", "diff / diff C:\\a.txt C:\\b.txt", "\ue89a", "#EF4444"),
|
||||
new HelpEntry("유틸", "win", "윈도우 포커스 스위처", "열린 창을 타이틀·프로세스명으로 검색하여 Alt+Tab 없이 즉시 전환", "win / win chrome / win 보고서", "\ue8a7", "#6366F1"),
|
||||
new HelpEntry("시스템", "^", "Windows 실행 명령", "Win+R 실행 창과 동일하게 명령어 실행. notepad, calc, cmd, control, mstsc 등 모든 Windows 실행 명령 지원", "^ notepad / ^ cmd / ^ calc / ^ control / ^ mspaint", "\ue7c4", "#E08850"),
|
||||
new HelpEntry("유틸", "stats", "텍스트 통계 분석", "클립보드 텍스트 글자·단어·줄 수, 키워드 빈도, 읽기 시간 추정", "stats / stats 키워드 (클립보드 텍스트 분석)", "\ue8d2", "#6366F1"),
|
||||
new HelpEntry("유틸", "fav", "즐겨찾기 (파일·폴더)", "자주 쓰는 경로를 등록하고 빠르게 열기", "fav / fav add 보고서 C:\\work\\report.xlsx / fav del 보고서", "\ue728", "#F59E0B"),
|
||||
new HelpEntry("유틸", "rename", "파일 일괄 이름변경", "폴더 내 파일을 패턴·변수로 일괄 이름변경. {n}순번 {date}날짜 {orig}원본명", "rename C:\\work\\*.xlsx 보고서_{n}", "\ue8ac", "#8B5CF6"),
|
||||
new HelpEntry("유틸", "monitor", "시스템 리소스 모니터", "CPU·메모리·디스크·프로세스 실시간 현황 조회", "monitor / monitor cpu / monitor mem / monitor disk", "\ue7f4", "#10B981"),
|
||||
new HelpEntry("유틸", "scaffold", "프로젝트 스캐폴딩", "내장/사용자 템플릿으로 프로젝트 폴더 구조 일괄 생성", "scaffold / scaffold webapi / scaffold C:\\new-project webapi", "\ue8f1", "#0EA5E9"),
|
||||
new HelpEntry("키보드", "Alt+Space", "AX Commander 열기 / 닫기", "어디서든 AX Commander를 토글", "설정 → 핫키에서 변경 가능", "\ue946", "#6B7280"),
|
||||
new HelpEntry("키보드", "Enter", "실행", "선택된 항목 실행", "", "\ue946", "#6B7280"),
|
||||
new HelpEntry("키보드", "Shift+Enter", "Large Type / 병합 실행 / 지연 캡처", "텍스트를 전체화면으로 표시 · 클립보드 항목 병합 · 캡처 모드에서는 지연 캡처(3초/5초/10초) 타이머 선택", "(#모드) Shift+↑↓ 선택 후 Shift+Enter 병합 / (cap모드) Shift+Enter → 타이머 선택", "\ue946", "#6B7280"),
|
||||
new HelpEntry("키보드", "Tab", "자동완성", "선택 항목으로 입력 자동완성", "", "\ue946", "#6B7280"),
|
||||
new HelpEntry("키보드", "→ (커서 끝)", "파일 액션 메뉴", "앱·파일 선택 후 → 키: 경로복사 · 탐색기 · 관리자 · 터미널", "", "\ue946", "#6B7280"),
|
||||
new HelpEntry("키보드", "Ctrl+,", "설정 열기", "AX Copilot 설정 창", "", "\ue946", "#6B7280")
|
||||
};
|
||||
|
||||
public string? Prefix => "help";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Help", "AX Commander 사용 설명서", "1.0", "AX");
|
||||
|
||||
public HelpHandler(SettingsService? settings = null)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
private HelpEntry[] GetEntries()
|
||||
{
|
||||
string text = _settings?.Settings.ScreenCapture.Prefix ?? "cap";
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
text = "cap";
|
||||
}
|
||||
string command = _settings?.Settings.Hotkey ?? "Alt+Space";
|
||||
HelpEntry[] array = (HelpEntry[])_baseEntries.Clone();
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
if (array[i].Title == "스크린샷 캡처 (예약어 변경 가능)")
|
||||
{
|
||||
array[i] = array[i]with
|
||||
{
|
||||
Command = text,
|
||||
Example = $"{text} screen / {text} window / {text} region / {text} scroll / (설정 → 캡처 탭)"
|
||||
};
|
||||
}
|
||||
if (array[i].Title == "AX Commander 열기 / 닫기")
|
||||
{
|
||||
array[i] = array[i]with
|
||||
{
|
||||
Command = command,
|
||||
Example = "설정 → 핫키에서 변경 가능"
|
||||
};
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string q = query.Trim();
|
||||
HelpEntry[] entries = GetEntries();
|
||||
if (string.IsNullOrEmpty(q))
|
||||
{
|
||||
int value = entries.Count((HelpEntry e) => e.Category != "키보드");
|
||||
int value2 = entries.Count((HelpEntry e) => e.Category == "키보드");
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("AX Commander — 전체 기능 목록 보기", $"총 {value}개 명령어 · {value2}개 단축키 · Enter → 기능 설명 창 열기", null, "__HELP_OVERVIEW__", null, "\ue946")));
|
||||
}
|
||||
IEnumerable<HelpEntry> source = entries.Where((HelpEntry e) => e.Category.Contains(q, StringComparison.OrdinalIgnoreCase) || e.Command.Contains(q, StringComparison.OrdinalIgnoreCase) || e.Title.Contains(q, StringComparison.OrdinalIgnoreCase) || e.Description.Contains(q, StringComparison.OrdinalIgnoreCase));
|
||||
List<LauncherItem> list = source.Select((HelpEntry e) => new LauncherItem(FormatTitle(e), FormatSubtitle(e), null, e.Example, null, e.Symbol)).ToList();
|
||||
if (list.Count == 0)
|
||||
{
|
||||
list.Add(new LauncherItem("'" + q + "'에 해당하는 명령어가 없습니다", "help만 입력하면 전체 기능 창을 열 수 있어요", null, null, null, "\ue946"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
string s = data as string;
|
||||
if (s == null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (s == "__HELP_OVERVIEW__")
|
||||
{
|
||||
((DispatcherObject)Application.Current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
HelpEntry[] entries = GetEntries();
|
||||
IEnumerable<HelpItemModel> items = entries.Select((HelpEntry e) => new HelpItemModel
|
||||
{
|
||||
Category = e.Category,
|
||||
Command = (string.IsNullOrEmpty(e.Command) ? "(퍼지 검색)" : e.Command),
|
||||
Title = e.Title,
|
||||
Description = e.Description,
|
||||
Example = e.Example,
|
||||
Symbol = e.Symbol,
|
||||
ColorBrush = ParseColor(e.ColorHex)
|
||||
});
|
||||
string globalHotkey = _settings?.Settings.Hotkey ?? "Alt+Space";
|
||||
new HelpDetailWindow(items, entries.Count((HelpEntry e) => e.Category != "키보드"), globalHotkey).Show();
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(s))
|
||||
{
|
||||
try
|
||||
{
|
||||
((DispatcherObject)Application.Current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
Clipboard.SetText(s);
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static SolidColorBrush ParseColor(string hex)
|
||||
{
|
||||
try
|
||||
{
|
||||
Color color = (Color)ColorConverter.ConvertFromString(hex);
|
||||
return new SolidColorBrush(color);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new SolidColorBrush(Colors.Gray);
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatTitle(HelpEntry e)
|
||||
{
|
||||
string text = (string.IsNullOrEmpty(e.Command) ? ("[" + e.Category + "]") : ("[" + e.Category + "] " + e.Command));
|
||||
return text + " — " + e.Title;
|
||||
}
|
||||
|
||||
private static string FormatSubtitle(HelpEntry e)
|
||||
{
|
||||
List<string> list = new List<string> { e.Description };
|
||||
if (!string.IsNullOrEmpty(e.Example))
|
||||
{
|
||||
list.Add("예) " + e.Example);
|
||||
}
|
||||
return string.Join(" · ", list);
|
||||
}
|
||||
}
|
||||
14
.decompiledproj/AxCopilot/Handlers/InstalledApp.cs
Normal file
14
.decompiledproj/AxCopilot/Handlers/InstalledApp.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
internal sealed class InstalledApp
|
||||
{
|
||||
public string Name { get; init; } = "";
|
||||
|
||||
public string? UninstallString { get; init; }
|
||||
|
||||
public string? Publisher { get; init; }
|
||||
|
||||
public string? Version { get; init; }
|
||||
|
||||
public string? InstallDate { get; init; }
|
||||
}
|
||||
139
.decompiledproj/AxCopilot/Handlers/JournalHandler.cs
Normal file
139
.decompiledproj/AxCopilot/Handlers/JournalHandler.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class JournalHandler : IActionHandler
|
||||
{
|
||||
public string? Prefix => "journal";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Journal", "업무 일지 자동 생성 — journal", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string text = query.Trim();
|
||||
DateTime targetDate;
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
targetDate = DateTime.Today;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!DateTime.TryParse(text, out var result))
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("날짜 형식 오류", "예: journal 2026-03-25 또는 journal (오늘)", null, null, null, "\ue7ba")));
|
||||
}
|
||||
targetDate = result.Date;
|
||||
}
|
||||
int days = (DateTime.Today - targetDate).Days;
|
||||
List<DailyUsageStats> stats = UsageStatisticsService.GetStats(Math.Max(days + 1, 1));
|
||||
DailyUsageStats dailyUsageStats = stats.FirstOrDefault((DailyUsageStats s) => s.Date == targetDate.ToString("yyyy-MM-dd"));
|
||||
List<LauncherItem> list = new List<LauncherItem>();
|
||||
if (dailyUsageStats == null)
|
||||
{
|
||||
list.Add(new LauncherItem($"{targetDate:yyyy-MM-dd} — 기록 없음", "해당 날짜의 사용 기록이 없습니다", null, null, null, "\ue946"));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
double value = (double)dailyUsageStats.ActiveSeconds / 3600.0;
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
StringBuilder stringBuilder2 = stringBuilder;
|
||||
StringBuilder stringBuilder3 = stringBuilder2;
|
||||
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(14, 2, stringBuilder2);
|
||||
handler.AppendLiteral("## 업무 일지 — ");
|
||||
handler.AppendFormatted(targetDate, "yyyy-MM-dd");
|
||||
handler.AppendLiteral(" (");
|
||||
handler.AppendFormatted(targetDate, "dddd");
|
||||
handler.AppendLiteral(")");
|
||||
stringBuilder3.AppendLine(ref handler);
|
||||
stringBuilder.AppendLine();
|
||||
stringBuilder2 = stringBuilder;
|
||||
StringBuilder stringBuilder4 = stringBuilder2;
|
||||
handler = new StringBuilder.AppendInterpolatedStringHandler(18, 1, stringBuilder2);
|
||||
handler.AppendLiteral("- **PC 활성 시간**: ");
|
||||
handler.AppendFormatted(value, "F1");
|
||||
handler.AppendLiteral("시간");
|
||||
stringBuilder4.AppendLine(ref handler);
|
||||
stringBuilder2 = stringBuilder;
|
||||
StringBuilder stringBuilder5 = stringBuilder2;
|
||||
handler = new StringBuilder.AppendInterpolatedStringHandler(14, 1, stringBuilder2);
|
||||
handler.AppendLiteral("- **런처 호출**: ");
|
||||
handler.AppendFormatted(dailyUsageStats.LauncherOpens);
|
||||
handler.AppendLiteral("회");
|
||||
stringBuilder5.AppendLine(ref handler);
|
||||
if (dailyUsageStats.CommandUsage.Count > 0)
|
||||
{
|
||||
stringBuilder.AppendLine();
|
||||
stringBuilder.AppendLine("### 사용한 명령어");
|
||||
foreach (KeyValuePair<string, int> item in dailyUsageStats.CommandUsage.OrderByDescending<KeyValuePair<string, int>, int>((KeyValuePair<string, int> x) => x.Value).Take(10))
|
||||
{
|
||||
stringBuilder2 = stringBuilder;
|
||||
StringBuilder stringBuilder6 = stringBuilder2;
|
||||
handler = new StringBuilder.AppendInterpolatedStringHandler(8, 2, stringBuilder2);
|
||||
handler.AppendLiteral("- `");
|
||||
handler.AppendFormatted(item.Key);
|
||||
handler.AppendLiteral("` — ");
|
||||
handler.AppendFormatted(item.Value);
|
||||
handler.AppendLiteral("회");
|
||||
stringBuilder6.AppendLine(ref handler);
|
||||
}
|
||||
}
|
||||
stringBuilder.AppendLine();
|
||||
stringBuilder.AppendLine("---");
|
||||
stringBuilder2 = stringBuilder;
|
||||
StringBuilder stringBuilder7 = stringBuilder2;
|
||||
handler = new StringBuilder.AppendInterpolatedStringHandler(21, 1, stringBuilder2);
|
||||
handler.AppendLiteral("*AX Copilot 자동 생성 · ");
|
||||
handler.AppendFormatted(DateTime.Now, "HH:mm");
|
||||
handler.AppendLiteral("*");
|
||||
stringBuilder7.AppendLine(ref handler);
|
||||
string data = stringBuilder.ToString();
|
||||
IEnumerable<string> values = from x in dailyUsageStats.CommandUsage.OrderByDescending<KeyValuePair<string, int>, int>((KeyValuePair<string, int> x) => x.Value).Take(3)
|
||||
select x.Key;
|
||||
string value2 = ((dailyUsageStats.CommandUsage.Count > 0) ? ("주요 명령: " + string.Join(", ", values)) : "명령어 사용 기록 없음");
|
||||
list.Add(new LauncherItem($"{targetDate:yyyy-MM-dd} 업무 일지 — 클립보드로 복사", $"활성 {value:F1}h · 런처 {dailyUsageStats.LauncherOpens}회 · {value2}", null, data, null, "\ue70b"));
|
||||
list.Add(new LauncherItem($"PC 활성 시간: {value:F1}시간", "잠금 해제 시간 기준 누적", null, $"PC 활성 시간: {value:F1}시간", null, "\ue823"));
|
||||
list.Add(new LauncherItem($"런처 호출: {dailyUsageStats.LauncherOpens}회", "Alt+Space 또는 트레이 클릭", null, $"런처 호출: {dailyUsageStats.LauncherOpens}회", null, "\ue721"));
|
||||
if (dailyUsageStats.CommandUsage.Count > 0)
|
||||
{
|
||||
foreach (KeyValuePair<string, int> item2 in dailyUsageStats.CommandUsage.OrderByDescending<KeyValuePair<string, int>, int>((KeyValuePair<string, int> x) => x.Value).Take(5))
|
||||
{
|
||||
list.Add(new LauncherItem($"{item2.Key} — {item2.Value}회", "Enter로 복사", null, $"{item2.Key}: {item2.Value}회", null, "\ue756"));
|
||||
}
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
string text = data as string;
|
||||
if (text != null && !string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
try
|
||||
{
|
||||
Application current = Application.Current;
|
||||
if (current != null)
|
||||
{
|
||||
((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
Clipboard.SetText(text);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
NotificationService.Notify("업무 일지", "클립보드에 복사되었습니다");
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
106
.decompiledproj/AxCopilot/Handlers/JsonHandler.cs
Normal file
106
.decompiledproj/AxCopilot/Handlers/JsonHandler.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using AxCopilot.SDK;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class JsonHandler : IActionHandler
|
||||
{
|
||||
private static readonly JsonSerializerOptions _prettyOpts = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
private static readonly JsonSerializerOptions _compactOpts = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
public string? Prefix => "json";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("JsonFormatter", "JSON 검증/포맷 — json 뒤에 내용 또는 명령 입력", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string text = query.Trim();
|
||||
if (string.IsNullOrWhiteSpace(text) || text.Equals("format", StringComparison.OrdinalIgnoreCase) || text.Equals("minify", StringComparison.OrdinalIgnoreCase) || text.Equals("min", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string text2 = "";
|
||||
try
|
||||
{
|
||||
text2 = Clipboard.GetText();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(text2))
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("클립보드가 비어 있습니다", "클립보드에 JSON 텍스트를 복사한 뒤 실행하세요", null, null, null, "\ue77f")));
|
||||
}
|
||||
return Task.FromResult(BuildItems(text2, text.StartsWith("min", StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
return Task.FromResult(BuildItems(text, isMinify: false));
|
||||
}
|
||||
|
||||
private static IEnumerable<LauncherItem> BuildItems(string input, bool isMinify)
|
||||
{
|
||||
try
|
||||
{
|
||||
using JsonDocument jsonDocument = JsonDocument.Parse(input, new JsonDocumentOptions
|
||||
{
|
||||
AllowTrailingCommas = true,
|
||||
CommentHandling = JsonCommentHandling.Skip
|
||||
});
|
||||
string text = JsonSerializer.Serialize(jsonDocument.RootElement, _prettyOpts);
|
||||
string text2 = JsonSerializer.Serialize(jsonDocument.RootElement, _compactOpts);
|
||||
JsonValueKind valueKind = jsonDocument.RootElement.ValueKind;
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string text3 = valueKind switch
|
||||
{
|
||||
JsonValueKind.Object => $"Object ({jsonDocument.RootElement.EnumerateObject().Count()}개 키)",
|
||||
JsonValueKind.Array => $"Array ({jsonDocument.RootElement.GetArrayLength()}개 항목)",
|
||||
_ => jsonDocument.RootElement.ValueKind.ToString(),
|
||||
};
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string text4 = text3;
|
||||
string data = (isMinify ? text2 : text);
|
||||
string text5 = (isMinify ? "미니파이" : "포맷");
|
||||
string text6 = ((text2.Length > 100) ? (text2.Substring(0, 97) + "…") : text2);
|
||||
return new _003C_003Ez__ReadOnlyArray<LauncherItem>(new LauncherItem[3]
|
||||
{
|
||||
new LauncherItem("✅ 유효한 JSON — " + text4, text6 + " · Enter로 " + text5 + " 결과 클립보드 복사", null, data, null, "\ue930"),
|
||||
new LauncherItem("포맷 (Pretty Print) 복사", $"{text.Length}자 · 들여쓰기 2스페이스", null, text, null, "\ue8a4"),
|
||||
new LauncherItem("미니파이 (Minify) 복사", $"{text2.Length}자 · 공백 제거", null, text2, null, "\ue8c6")
|
||||
});
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
string subtitle = ((ex.Message.Length > 100) ? (ex.Message.Substring(0, 97) + "…") : ex.Message);
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("❌ JSON 오류", subtitle, null, null, null, "\uea39"));
|
||||
}
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
6
.decompiledproj/AxCopilot/Handlers/JsonSkillCache.cs
Normal file
6
.decompiledproj/AxCopilot/Handlers/JsonSkillCache.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class JsonSkillCache
|
||||
{
|
||||
public int Ttl { get; set; } = 0;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class JsonSkillCredential
|
||||
{
|
||||
public string Type { get; set; } = "bearer_token";
|
||||
|
||||
public string CredentialKey { get; set; } = "";
|
||||
}
|
||||
20
.decompiledproj/AxCopilot/Handlers/JsonSkillDefinition.cs
Normal file
20
.decompiledproj/AxCopilot/Handlers/JsonSkillDefinition.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class JsonSkillDefinition
|
||||
{
|
||||
public string Id { get; set; } = "";
|
||||
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
public string Version { get; set; } = "1.0";
|
||||
|
||||
public string Prefix { get; set; } = "";
|
||||
|
||||
public JsonSkillCredential? Credential { get; set; }
|
||||
|
||||
public JsonSkillRequest? Request { get; set; }
|
||||
|
||||
public JsonSkillResponse? Response { get; set; }
|
||||
|
||||
public JsonSkillCache? Cache { get; set; }
|
||||
}
|
||||
180
.decompiledproj/AxCopilot/Handlers/JsonSkillHandler.cs
Normal file
180
.decompiledproj/AxCopilot/Handlers/JsonSkillHandler.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class JsonSkillHandler : IActionHandler
|
||||
{
|
||||
private readonly JsonSkillDefinition _def;
|
||||
|
||||
private readonly HttpClient _http = new HttpClient();
|
||||
|
||||
private List<LauncherItem>? _cache;
|
||||
|
||||
private DateTime _cacheExpiry = DateTime.MinValue;
|
||||
|
||||
public string? Prefix => _def.Prefix;
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata(_def.Id, _def.Name, _def.Version, "JSON Skill");
|
||||
|
||||
public JsonSkillHandler(JsonSkillDefinition def)
|
||||
{
|
||||
_def = def;
|
||||
_http.Timeout = TimeSpan.FromSeconds(3.0);
|
||||
if (def.Credential?.Type == "bearer_token")
|
||||
{
|
||||
string token = CredentialManager.GetToken(def.Credential.CredentialKey);
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
_http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
if (_cache != null && DateTime.Now < _cacheExpiry)
|
||||
{
|
||||
return _cache;
|
||||
}
|
||||
if (_def.Request == null || _def.Response == null)
|
||||
{
|
||||
return Enumerable.Empty<LauncherItem>();
|
||||
}
|
||||
try
|
||||
{
|
||||
string url = _def.Request.Url.Replace("{{INPUT}}", Uri.EscapeDataString(query));
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri parsedUrl) || (parsedUrl.Scheme != Uri.UriSchemeHttp && parsedUrl.Scheme != Uri.UriSchemeHttps))
|
||||
{
|
||||
LogService.Error("[" + _def.Name + "] 유효하지 않은 URL: " + url);
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("설정 오류", "스킬 URL이 유효하지 않습니다 (http/https만 허용)", null, null));
|
||||
}
|
||||
string text = _def.Request.Method.ToUpper();
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
HttpResponseMessage httpResponseMessage = ((!(text == "POST")) ? (await _http.GetAsync(url, ct)) : (await _http.PostAsync(url, BuildBody(query), ct)));
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
HttpResponseMessage response = httpResponseMessage;
|
||||
response.EnsureSuccessStatusCode();
|
||||
List<LauncherItem> items = ParseResults(await response.Content.ReadAsStringAsync(ct));
|
||||
JsonSkillCache? cache = _def.Cache;
|
||||
if (cache != null && cache.Ttl > 0)
|
||||
{
|
||||
_cache = items;
|
||||
_cacheExpiry = DateTime.Now.AddSeconds(_def.Cache.Ttl);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
if (_cache != null)
|
||||
{
|
||||
LogService.Warn("[" + _def.Name + "] API 타임아웃, 캐시 반환");
|
||||
return _cache;
|
||||
}
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("네트워크 오류", "연결을 확인하세요", null, null));
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
LogService.Error("[" + _def.Name + "] API 호출 실패: " + ex2.Message);
|
||||
return new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("오류: " + ex2.Message, _def.Name, null, null));
|
||||
}
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.ActionUrl != null && Uri.TryCreate(item.ActionUrl, UriKind.Absolute, out Uri result) && (result.Scheme == Uri.UriSchemeHttp || result.Scheme == Uri.UriSchemeHttps))
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(item.ActionUrl)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private HttpContent BuildBody(string query)
|
||||
{
|
||||
string content = JsonSerializer.Serialize(_def.Request?.Body).Replace("\"{{INPUT}}\"", "\"" + query + "\"");
|
||||
return new StringContent(content, Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
private List<LauncherItem> ParseResults(string json)
|
||||
{
|
||||
List<LauncherItem> list = new List<LauncherItem>();
|
||||
try
|
||||
{
|
||||
JsonNode jsonNode = JsonNode.Parse(json);
|
||||
if (jsonNode == null)
|
||||
{
|
||||
return list;
|
||||
}
|
||||
JsonNode jsonNode2 = NavigatePath(jsonNode, _def.Response.ResultsPath);
|
||||
if (!(jsonNode2 is JsonArray source))
|
||||
{
|
||||
return list;
|
||||
}
|
||||
foreach (JsonNode item in source.Take(10))
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
string title = NavigatePath(item, _def.Response.TitleField)?.ToString() ?? "(제목 없음)";
|
||||
string subtitle = ((_def.Response.SubtitleField == null) ? "" : (NavigatePath(item, _def.Response.SubtitleField)?.ToString() ?? ""));
|
||||
string actionUrl = ((_def.Response.ActionUrl == null) ? null : NavigatePath(item, _def.Response.ActionUrl)?.ToString());
|
||||
list.Add(new LauncherItem(title, subtitle, null, item, actionUrl, "\ue82d"));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Error("[" + _def.Name + "] 응답 파싱 실패: " + ex.Message);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static JsonNode? NavigatePath(JsonNode root, string path)
|
||||
{
|
||||
string[] array = path.Split('.');
|
||||
JsonNode jsonNode = root;
|
||||
string[] array2 = array;
|
||||
foreach (string text in array2)
|
||||
{
|
||||
if (jsonNode == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
int num = text.IndexOf('[');
|
||||
if (num >= 0)
|
||||
{
|
||||
int num2 = text.IndexOf(']');
|
||||
if (num2 < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string propertyName = text.Substring(0, num);
|
||||
int num3 = num + 1;
|
||||
int index = int.Parse(text.Substring(num3, num2 - num3));
|
||||
jsonNode = jsonNode[propertyName]?[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
jsonNode = jsonNode[text];
|
||||
}
|
||||
}
|
||||
return jsonNode;
|
||||
}
|
||||
}
|
||||
22
.decompiledproj/AxCopilot/Handlers/JsonSkillLoader.cs
Normal file
22
.decompiledproj/AxCopilot/Handlers/JsonSkillLoader.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using AxCopilot.SDK;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public static class JsonSkillLoader
|
||||
{
|
||||
public static IActionHandler? Load(string filePath)
|
||||
{
|
||||
string json = File.ReadAllText(filePath);
|
||||
JsonSkillDefinition jsonSkillDefinition = JsonSerializer.Deserialize<JsonSkillDefinition>(json, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
if (jsonSkillDefinition == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new JsonSkillHandler(jsonSkillDefinition);
|
||||
}
|
||||
}
|
||||
14
.decompiledproj/AxCopilot/Handlers/JsonSkillRequest.cs
Normal file
14
.decompiledproj/AxCopilot/Handlers/JsonSkillRequest.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class JsonSkillRequest
|
||||
{
|
||||
public string Method { get; set; } = "GET";
|
||||
|
||||
public string Url { get; set; } = "";
|
||||
|
||||
public Dictionary<string, string>? Headers { get; set; }
|
||||
|
||||
public object? Body { get; set; }
|
||||
}
|
||||
12
.decompiledproj/AxCopilot/Handlers/JsonSkillResponse.cs
Normal file
12
.decompiledproj/AxCopilot/Handlers/JsonSkillResponse.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class JsonSkillResponse
|
||||
{
|
||||
public string ResultsPath { get; set; } = "results";
|
||||
|
||||
public string TitleField { get; set; } = "title";
|
||||
|
||||
public string? SubtitleField { get; set; }
|
||||
|
||||
public string? ActionUrl { get; set; }
|
||||
}
|
||||
256
.decompiledproj/AxCopilot/Handlers/MathEvaluator.cs
Normal file
256
.decompiledproj/AxCopilot/Handlers/MathEvaluator.cs
Normal file
@@ -0,0 +1,256 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
internal static class MathEvaluator
|
||||
{
|
||||
private class Evaluator
|
||||
{
|
||||
private readonly string _s;
|
||||
|
||||
private int _i;
|
||||
|
||||
public Evaluator(string s)
|
||||
{
|
||||
_s = s;
|
||||
_i = 0;
|
||||
}
|
||||
|
||||
public double Parse()
|
||||
{
|
||||
double result = ParseExpr();
|
||||
if (_i < _s.Length)
|
||||
{
|
||||
throw new InvalidOperationException($"예기치 않은 문자: '{_s[_i]}'");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private double ParseExpr()
|
||||
{
|
||||
double num = ParseTerm();
|
||||
while (_i < _s.Length && (_s[_i] == '+' || _s[_i] == '-'))
|
||||
{
|
||||
char c = _s[_i++];
|
||||
double num2 = ParseTerm();
|
||||
num = ((c == '+') ? (num + num2) : (num - num2));
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
private double ParseTerm()
|
||||
{
|
||||
double num = ParsePower();
|
||||
while (_i < _s.Length && (_s[_i] == '*' || _s[_i] == '/' || _s[_i] == '%'))
|
||||
{
|
||||
char c = _s[_i++];
|
||||
double num2 = ParsePower();
|
||||
num = c switch
|
||||
{
|
||||
'/' => num / num2,
|
||||
'*' => num * num2,
|
||||
_ => num % num2,
|
||||
};
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
private double ParsePower()
|
||||
{
|
||||
double num = ParseUnary();
|
||||
if (_i < _s.Length && _s[_i] == '^')
|
||||
{
|
||||
_i++;
|
||||
double y = ParseUnary();
|
||||
return Math.Pow(num, y);
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
private double ParseUnary()
|
||||
{
|
||||
if (_i < _s.Length && _s[_i] == '-')
|
||||
{
|
||||
_i++;
|
||||
return 0.0 - ParsePrimary();
|
||||
}
|
||||
if (_i < _s.Length && _s[_i] == '+')
|
||||
{
|
||||
_i++;
|
||||
return ParsePrimary();
|
||||
}
|
||||
return ParsePrimary();
|
||||
}
|
||||
|
||||
private double ParsePrimary()
|
||||
{
|
||||
if (_i >= _s.Length)
|
||||
{
|
||||
throw new InvalidOperationException("수식이 불완전합니다.");
|
||||
}
|
||||
if (_i + 1 < _s.Length && _s[_i] == '0' && _s[_i + 1] == 'x')
|
||||
{
|
||||
_i += 2;
|
||||
int i = _i;
|
||||
while (_i < _s.Length && "0123456789abcdef".Contains(_s[_i]))
|
||||
{
|
||||
_i++;
|
||||
}
|
||||
string s = _s;
|
||||
int num = i;
|
||||
return Convert.ToInt64(s.Substring(num, _i - num), 16);
|
||||
}
|
||||
if (char.IsDigit(_s[_i]) || _s[_i] == '.')
|
||||
{
|
||||
int i2 = _i;
|
||||
while (_i < _s.Length && (char.IsDigit(_s[_i]) || _s[_i] == '.'))
|
||||
{
|
||||
_i++;
|
||||
}
|
||||
if (_i < _s.Length && _s[_i] == 'e')
|
||||
{
|
||||
_i++;
|
||||
if (_i < _s.Length && (_s[_i] == '+' || _s[_i] == '-'))
|
||||
{
|
||||
_i++;
|
||||
}
|
||||
while (_i < _s.Length && char.IsDigit(_s[_i]))
|
||||
{
|
||||
_i++;
|
||||
}
|
||||
}
|
||||
string s2 = _s;
|
||||
int num = i2;
|
||||
return double.Parse(s2.Substring(num, _i - num), NumberStyles.Float, CultureInfo.InvariantCulture);
|
||||
}
|
||||
if (_s[_i] == '(')
|
||||
{
|
||||
_i++;
|
||||
double result = ParseExpr();
|
||||
if (_i < _s.Length && _s[_i] == ')')
|
||||
{
|
||||
_i++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (char.IsLetter(_s[_i]))
|
||||
{
|
||||
int i3 = _i;
|
||||
while (_i < _s.Length && (char.IsLetterOrDigit(_s[_i]) || _s[_i] == '_'))
|
||||
{
|
||||
_i++;
|
||||
}
|
||||
string s3 = _s;
|
||||
int num = i3;
|
||||
string text = s3.Substring(num, _i - num);
|
||||
switch (text)
|
||||
{
|
||||
case "pi":
|
||||
return Math.PI;
|
||||
case "e":
|
||||
return Math.E;
|
||||
case "inf":
|
||||
return double.PositiveInfinity;
|
||||
default:
|
||||
if (_i < _s.Length && _s[_i] == '(')
|
||||
{
|
||||
_i++;
|
||||
double num2 = ParseExpr();
|
||||
double? num3 = null;
|
||||
if (_i < _s.Length && _s[_i] == ',')
|
||||
{
|
||||
_i++;
|
||||
num3 = ParseExpr();
|
||||
}
|
||||
if (_i < _s.Length && _s[_i] == ')')
|
||||
{
|
||||
_i++;
|
||||
}
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
double result2;
|
||||
switch (text)
|
||||
{
|
||||
case "sqrt":
|
||||
result2 = Math.Sqrt(num2);
|
||||
break;
|
||||
case "abs":
|
||||
result2 = Math.Abs(num2);
|
||||
break;
|
||||
case "ceil":
|
||||
result2 = Math.Ceiling(num2);
|
||||
break;
|
||||
case "floor":
|
||||
result2 = Math.Floor(num2);
|
||||
break;
|
||||
case "round":
|
||||
result2 = (num3.HasValue ? Math.Round(num2, (int)num3.Value) : Math.Round(num2));
|
||||
break;
|
||||
case "sin":
|
||||
result2 = Math.Sin(num2 * Math.PI / 180.0);
|
||||
break;
|
||||
case "cos":
|
||||
result2 = Math.Cos(num2 * Math.PI / 180.0);
|
||||
break;
|
||||
case "tan":
|
||||
result2 = Math.Tan(num2 * Math.PI / 180.0);
|
||||
break;
|
||||
case "asin":
|
||||
result2 = Math.Asin(num2) * 180.0 / Math.PI;
|
||||
break;
|
||||
case "acos":
|
||||
result2 = Math.Acos(num2) * 180.0 / Math.PI;
|
||||
break;
|
||||
case "atan":
|
||||
result2 = Math.Atan(num2) * 180.0 / Math.PI;
|
||||
break;
|
||||
case "log":
|
||||
result2 = (num3.HasValue ? Math.Log(num2, num3.Value) : Math.Log10(num2));
|
||||
break;
|
||||
case "log2":
|
||||
result2 = Math.Log2(num2);
|
||||
break;
|
||||
case "ln":
|
||||
result2 = Math.Log(num2);
|
||||
break;
|
||||
case "exp":
|
||||
result2 = Math.Exp(num2);
|
||||
break;
|
||||
case "pow":
|
||||
if (!num3.HasValue)
|
||||
{
|
||||
throw new InvalidOperationException("pow(x,y) 형식으로 사용하세요.");
|
||||
}
|
||||
result2 = Math.Pow(num2, num3.Value);
|
||||
break;
|
||||
case "min":
|
||||
result2 = (num3.HasValue ? Math.Min(num2, num3.Value) : num2);
|
||||
break;
|
||||
case "max":
|
||||
result2 = (num3.HasValue ? Math.Max(num2, num3.Value) : num2);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("알 수 없는 함수: " + text + "()");
|
||||
}
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
return result2;
|
||||
}
|
||||
throw new InvalidOperationException("알 수 없는 식별자: " + text);
|
||||
}
|
||||
}
|
||||
throw new InvalidOperationException($"예기치 않은 문자: '{_s[_i]}'");
|
||||
}
|
||||
}
|
||||
|
||||
public static double Evaluate(string expr)
|
||||
{
|
||||
Evaluator evaluator = new Evaluator(expr.Replace(" ", "").Replace("×", "*").Replace("÷", "/")
|
||||
.Replace(",", ",")
|
||||
.ToLowerInvariant());
|
||||
return evaluator.Parse();
|
||||
}
|
||||
}
|
||||
86
.decompiledproj/AxCopilot/Handlers/MediaHandler.cs
Normal file
86
.decompiledproj/AxCopilot/Handlers/MediaHandler.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class MediaHandler : IActionHandler
|
||||
{
|
||||
private record MediaKeyData(byte Vk);
|
||||
|
||||
private const byte VK_MEDIA_PLAY_PAUSE = 179;
|
||||
|
||||
private const byte VK_MEDIA_NEXT_TRACK = 176;
|
||||
|
||||
private const byte VK_MEDIA_PREV_TRACK = 177;
|
||||
|
||||
private const byte VK_VOLUME_UP = 175;
|
||||
|
||||
private const byte VK_VOLUME_DOWN = 174;
|
||||
|
||||
private const byte VK_VOLUME_MUTE = 173;
|
||||
|
||||
private const uint KEYEVENTF_EXTENDEDKEY = 1u;
|
||||
|
||||
private const uint KEYEVENTF_KEYUP = 2u;
|
||||
|
||||
private static readonly List<(string[] Keys, string Title, string Subtitle, byte Vk, string Symbol)> _commands;
|
||||
|
||||
public string? Prefix => "media";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("MediaControl", "미디어 컨트롤 — media 뒤에 명령어 입력", "1.0", "AX");
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, nuint dwExtraInfo);
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string q = query.Trim().ToLowerInvariant();
|
||||
IEnumerable<(string[], string, string, byte, string)> source = ((!string.IsNullOrEmpty(q)) ? _commands.Where<(string[], string, string, byte, string)>(((string[] Keys, string Title, string Subtitle, byte Vk, string Symbol) c) => c.Keys.Any((string k) => k.StartsWith(q, StringComparison.OrdinalIgnoreCase)) || c.Title.Contains(q, StringComparison.OrdinalIgnoreCase)) : _commands);
|
||||
List<LauncherItem> list = source.Select<(string[], string, string, byte, string), LauncherItem>(((string[] Keys, string Title, string Subtitle, byte Vk, string Symbol) c) => new LauncherItem(c.Title, c.Subtitle, null, new MediaKeyData(c.Vk), null, c.Symbol)).ToList();
|
||||
if (list.Count == 0)
|
||||
{
|
||||
list.Add(new LauncherItem("알 수 없는 명령어", "play · next · prev · vol+ · vol- · mute 중 하나를 입력하세요", null, null, null, "\ue7ba"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (!(item.Data is MediaKeyData mediaKeyData))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
try
|
||||
{
|
||||
keybd_event(mediaKeyData.Vk, 0, 1u, 0u);
|
||||
keybd_event(mediaKeyData.Vk, 0, 3u, 0u);
|
||||
LogService.Info($"미디어 키 전송: VK=0x{mediaKeyData.Vk:X2}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("미디어 키 전송 실패: " + ex.Message);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
static MediaHandler()
|
||||
{
|
||||
int num = 6;
|
||||
List<(string[], string, string, byte, string)> list = new List<(string[], string, string, byte, string)>(num);
|
||||
CollectionsMarshal.SetCount(list, num);
|
||||
Span<(string[], string, string, byte, string)> span = CollectionsMarshal.AsSpan(list);
|
||||
span[0] = (new string[3] { "play", "pause", "pp" }, "재생 / 일시정지", "현재 미디어 재생 또는 일시정지", 179, "\ue768");
|
||||
span[1] = (new string[2] { "next", ">>" }, "다음 트랙", "다음 곡으로 이동", 176, "\ue893");
|
||||
span[2] = (new string[3] { "prev", "previous", "<<" }, "이전 트랙", "이전 곡으로 이동", 177, "\ue892");
|
||||
span[3] = (new string[3] { "vol+", "volup", "up" }, "볼륨 올리기", "시스템 볼륨 증가", 175, "\ue995");
|
||||
span[4] = (new string[3] { "vol-", "voldown", "down" }, "볼륨 낮추기", "시스템 볼륨 감소", 174, "\ue993");
|
||||
span[5] = (new string[2] { "mute", "음소거" }, "음소거 토글", "볼륨 음소거 / 해제", 173, "\ue74f");
|
||||
_commands = list;
|
||||
}
|
||||
}
|
||||
181
.decompiledproj/AxCopilot/Handlers/MonitorHandler.cs
Normal file
181
.decompiledproj/AxCopilot/Handlers/MonitorHandler.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.SDK;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class MonitorHandler : IActionHandler
|
||||
{
|
||||
private struct MEMORYSTATUSEX
|
||||
{
|
||||
public uint dwLength;
|
||||
|
||||
public uint dwMemoryLoad;
|
||||
|
||||
public ulong ullTotalPhys;
|
||||
|
||||
public ulong ullAvailPhys;
|
||||
|
||||
public ulong ullTotalPageFile;
|
||||
|
||||
public ulong ullAvailPageFile;
|
||||
|
||||
public ulong ullTotalVirtual;
|
||||
|
||||
public ulong ullAvailVirtual;
|
||||
|
||||
public ulong ullAvailExtendedVirtual;
|
||||
}
|
||||
|
||||
public string? Prefix => "monitor";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Monitor", "시스템 리소스 모니터 — monitor", "1.0", "AX");
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string text = query.Trim().ToLowerInvariant();
|
||||
List<LauncherItem> list = new List<LauncherItem>();
|
||||
bool flag = string.IsNullOrWhiteSpace(text);
|
||||
if (flag || text.Contains("cpu") || text.Contains("프로세서"))
|
||||
{
|
||||
int processorCount = Environment.ProcessorCount;
|
||||
int value = Process.GetProcesses().Length;
|
||||
int value2 = 0;
|
||||
try
|
||||
{
|
||||
value2 = Process.GetProcesses().Sum(delegate(Process p)
|
||||
{
|
||||
try
|
||||
{
|
||||
return p.Threads.Count;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
list.Add(new LauncherItem($"CPU: {processorCount}코어 · 프로세스 {value}개 · 스레드 {value2:N0}개", "Enter로 클립보드 복사", null, $"CPU: {processorCount}코어, 프로세스 {value}개, 스레드 {value2:N0}개", null, "\ue950"));
|
||||
}
|
||||
if (flag || text.Contains("mem") || text.Contains("ram") || text.Contains("메모리"))
|
||||
{
|
||||
MEMORYSTATUSEX lpBuffer = new MEMORYSTATUSEX
|
||||
{
|
||||
dwLength = (uint)Marshal.SizeOf<MEMORYSTATUSEX>()
|
||||
};
|
||||
GlobalMemoryStatusEx(ref lpBuffer);
|
||||
double value3 = (double)lpBuffer.ullTotalPhys / 1073741824.0;
|
||||
double value4 = (double)(lpBuffer.ullTotalPhys - lpBuffer.ullAvailPhys) / 1073741824.0;
|
||||
uint dwMemoryLoad = lpBuffer.dwMemoryLoad;
|
||||
list.Add(new LauncherItem($"메모리: {value4:F1}GB / {value3:F1}GB ({dwMemoryLoad}% 사용)", $"사용 가능: {(double)lpBuffer.ullAvailPhys / 1073741824.0:F1}GB · Enter로 복사", null, $"메모리: {value4:F1}GB / {value3:F1}GB ({dwMemoryLoad}% 사용)", null, "\ue950"));
|
||||
}
|
||||
if (flag || text.Contains("disk") || text.Contains("디스크") || text.Contains("저장"))
|
||||
{
|
||||
DriveInfo[] drives = DriveInfo.GetDrives();
|
||||
foreach (DriveInfo driveInfo in drives)
|
||||
{
|
||||
if (driveInfo.IsReady && driveInfo.DriveType == DriveType.Fixed)
|
||||
{
|
||||
double num2 = (double)driveInfo.TotalSize / 1073741824.0;
|
||||
double num3 = (double)driveInfo.AvailableFreeSpace / 1073741824.0;
|
||||
double num4 = num2 - num3;
|
||||
int value5 = (int)(num4 / num2 * 100.0);
|
||||
list.Add(new LauncherItem($"디스크 {driveInfo.Name.TrimEnd('\\')} {num4:F0}GB / {num2:F0}GB ({value5}%)", $"여유: {num3:F1}GB · {driveInfo.DriveFormat} · Enter로 복사", null, $"디스크 {driveInfo.Name}: {num4:F0}GB / {num2:F0}GB ({value5}%), 여유 {num3:F1}GB", null, "\ueda2"));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (flag || text.Contains("uptime") || text.Contains("가동"))
|
||||
{
|
||||
TimeSpan timeSpan = TimeSpan.FromMilliseconds(Environment.TickCount64);
|
||||
string text2 = ((timeSpan.Days > 0) ? $"{timeSpan.Days}일 {timeSpan.Hours}시간 {timeSpan.Minutes}분" : $"{timeSpan.Hours}시간 {timeSpan.Minutes}분");
|
||||
list.Add(new LauncherItem("가동 시간: " + text2, "마지막 재시작 이후 경과 · Enter로 복사", null, "가동 시간: " + text2, null, "\ue823"));
|
||||
}
|
||||
if (flag || text.Contains("top") || text.Contains("프로세스"))
|
||||
{
|
||||
try
|
||||
{
|
||||
IEnumerable<string> values = Process.GetProcesses().Where(delegate(Process p)
|
||||
{
|
||||
try
|
||||
{
|
||||
return p.WorkingSet64 > 0;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}).OrderByDescending(delegate(Process p)
|
||||
{
|
||||
try
|
||||
{
|
||||
return p.WorkingSet64;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0L;
|
||||
}
|
||||
})
|
||||
.Take(5)
|
||||
.Select(delegate(Process p)
|
||||
{
|
||||
try
|
||||
{
|
||||
return $"{p.ProcessName} ({p.WorkingSet64 / 1048576}MB)";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return p.ProcessName;
|
||||
}
|
||||
});
|
||||
list.Add(new LauncherItem("메모리 상위 프로세스", string.Join(", ", values), null, "메모리 상위: " + string.Join(", ", values), null, "\ue7f4"));
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
if (list.Count == 0)
|
||||
{
|
||||
list.Add(new LauncherItem("'" + text + "'에 해당하는 리소스 항목 없음", "cpu / mem / disk / uptime / top", null, null, null, "\ue7ba"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
string text = data as string;
|
||||
if (text != null && !string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
try
|
||||
{
|
||||
Application current = Application.Current;
|
||||
if (current != null)
|
||||
{
|
||||
((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
Clipboard.SetText(text);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
5
.decompiledproj/AxCopilot/Handlers/NoteEntry.cs
Normal file
5
.decompiledproj/AxCopilot/Handlers/NoteEntry.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using System;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
internal record NoteEntry(DateTime SavedAt, string Content);
|
||||
180
.decompiledproj/AxCopilot/Handlers/NoteHandler.cs
Normal file
180
.decompiledproj/AxCopilot/Handlers/NoteHandler.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class NoteHandler : IActionHandler
|
||||
{
|
||||
private static readonly string NotesFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "notes.txt");
|
||||
|
||||
public string? Prefix => "note";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Note", "빠른 메모 — note 뒤에 내용 입력", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string text = query.Trim();
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
List<NoteEntry> list = ReadNotes();
|
||||
if (!list.Any())
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("메모가 없습니다", "note 뒤에 내용을 입력하면 저장됩니다", null, null, null, "\ue70b")));
|
||||
}
|
||||
List<LauncherItem> list2 = (from n in list.Take(10)
|
||||
select new LauncherItem((n.Content.Length > 60) ? (n.Content.Substring(0, 57) + "…") : n.Content, $"{n.SavedAt:yyyy-MM-dd HH:mm} · Enter 복사 · Delete 삭제", null, n.Content, null, "\ue70b")).ToList();
|
||||
list2.Add(new LauncherItem("전체 메모 삭제", $"총 {list.Count}개 메모 모두 삭제 · Enter로 실행", null, "__CLEAR__", null, "\ue74d"));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list2);
|
||||
}
|
||||
if (text.Equals("clear", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
int count = ReadNotes().Count;
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem($"전체 메모 삭제 ({count}개)", "모든 메모를 삭제합니다 · Enter로 실행", null, "__CLEAR__", null, "\ue74d")));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("메모 저장: " + ((text.Length > 60) ? (text.Substring(0, 57) + "…") : text), $"{DateTime.Now:yyyy-MM-dd HH:mm} · Enter로 저장", null, ("__SAVE__", text), null, "\ue70b")));
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
object obj = data;
|
||||
if (!(obj is (string, string) tuple))
|
||||
{
|
||||
if (obj is string text)
|
||||
{
|
||||
if (text == "__CLEAR__")
|
||||
{
|
||||
ClearNotes();
|
||||
}
|
||||
else
|
||||
{
|
||||
string text2 = text;
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(text2);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (tuple.Item1 == "__SAVE__")
|
||||
{
|
||||
SaveNote(tuple.Item2);
|
||||
NotificationService.Notify("AX Copilot", "메모 저장됨: " + ((tuple.Item2.Length > 30) ? (tuple.Item2.Substring(0, 27) + "…") : tuple.Item2));
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static void SaveNote(string content)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(NotesFile));
|
||||
string contents = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {content}{Environment.NewLine}";
|
||||
File.AppendAllText(NotesFile, contents, Encoding.UTF8);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("메모 저장 실패: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<NoteEntry> ReadNotes()
|
||||
{
|
||||
List<NoteEntry> list = new List<NoteEntry>();
|
||||
if (!File.Exists(NotesFile))
|
||||
{
|
||||
return list;
|
||||
}
|
||||
try
|
||||
{
|
||||
string[] source = File.ReadAllLines(NotesFile, Encoding.UTF8);
|
||||
foreach (string item in source.Reverse())
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(item))
|
||||
{
|
||||
if (item.StartsWith('[') && item.Length > 21 && item[20] == ']' && DateTime.TryParse(item.Substring(1, 19), out var result))
|
||||
{
|
||||
DateTime savedAt = result;
|
||||
string text = item;
|
||||
list.Add(new NoteEntry(savedAt, text.Substring(22, text.Length - 22).Trim()));
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(new NoteEntry(DateTime.MinValue, item.Trim()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("메모 읽기 실패: " + ex.Message);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static void ClearNotes()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(NotesFile))
|
||||
{
|
||||
File.Delete(NotesFile);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("메모 삭제 실패: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool DeleteNote(string content)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(NotesFile))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
List<string> list = File.ReadAllLines(NotesFile, Encoding.UTF8).ToList();
|
||||
for (int num = list.Count - 1; num >= 0; num--)
|
||||
{
|
||||
string text = list[num];
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
string text3;
|
||||
if (text.StartsWith('[') && text.Length > 21 && text[20] == ']')
|
||||
{
|
||||
string text2 = text;
|
||||
text3 = text2.Substring(22, text2.Length - 22).Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
text3 = text.Trim();
|
||||
}
|
||||
if (text3 == content)
|
||||
{
|
||||
list.RemoveAt(num);
|
||||
File.WriteAllLines(NotesFile, list, Encoding.UTF8);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("메모 개별 삭제 실패: " + ex.Message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
186
.decompiledproj/AxCopilot/Handlers/PortHandler.cs
Normal file
186
.decompiledproj/AxCopilot/Handlers/PortHandler.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class PortHandler : IActionHandler
|
||||
{
|
||||
private static readonly Dictionary<int, string> _procCache = new Dictionary<int, string>();
|
||||
|
||||
private static readonly Dictionary<int, int> _pidMap = new Dictionary<int, int>();
|
||||
|
||||
private static DateTime _cacheExpiry = DateTime.MinValue;
|
||||
|
||||
public string? Prefix => "port";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("PortChecker", "포트 & 프로세스 점검 — port 뒤에 포트번호 또는 프로세스명", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
RefreshProcessCache();
|
||||
TcpConnectionInformation[] activeTcpConnections;
|
||||
try
|
||||
{
|
||||
IPGlobalProperties iPGlobalProperties = IPGlobalProperties.GetIPGlobalProperties();
|
||||
activeTcpConnections = iPGlobalProperties.GetActiveTcpConnections();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("포트 조회 실패: " + ex.Message);
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("네트워크 정보를 가져올 수 없습니다", ex.Message, null, null, null, "\ue7ba")));
|
||||
}
|
||||
string text = query.Trim();
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
List<LauncherItem> list = (from c in activeTcpConnections
|
||||
where c.State == TcpState.Established || c.State == TcpState.Listen
|
||||
orderby c.LocalEndPoint.Port
|
||||
select c).Take(20).Select(delegate(TcpConnectionInformation c)
|
||||
{
|
||||
string processNameForPort = GetProcessNameForPort(c.LocalEndPoint.Port);
|
||||
string text2 = ((c.State == TcpState.Listen) ? "LISTEN" : "ESTABLISHED");
|
||||
return new LauncherItem($":{c.LocalEndPoint.Port} → {c.RemoteEndPoint}", text2 + " · " + processNameForPort + " · Enter로 포트번호 복사", null, c.LocalEndPoint.Port.ToString(), null, "\ue968");
|
||||
}).ToList();
|
||||
if (!list.Any())
|
||||
{
|
||||
list.Add(new LauncherItem("활성 연결 없음", "TCP 연결이 감지되지 않았습니다", null, null, null, "\ue968"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
if (int.TryParse(text, out var portNum))
|
||||
{
|
||||
List<TcpConnectionInformation> source = activeTcpConnections.Where((TcpConnectionInformation c) => c.LocalEndPoint.Port == portNum || c.RemoteEndPoint.Port == portNum).ToList();
|
||||
if (!source.Any())
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem($"포트 {portNum} — 사용 중 아님", "해당 포트를 사용하는 TCP 연결이 없습니다", null, portNum.ToString(), null, "\ue946")));
|
||||
}
|
||||
List<LauncherItem> result = source.Select(delegate(TcpConnectionInformation c)
|
||||
{
|
||||
string processNameForPort = GetProcessNameForPort(c.LocalEndPoint.Port);
|
||||
int pidForPort = GetPidForPort(c.LocalEndPoint.Port);
|
||||
return new LauncherItem($":{c.LocalEndPoint.Port} ←→ {c.RemoteEndPoint}", $"{c.State} · {processNameForPort} (PID {pidForPort}) · Enter로 PID 복사", null, (pidForPort > 0) ? pidForPort.ToString() : portNum.ToString(), null, "\ue968");
|
||||
}).ToList();
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)result);
|
||||
}
|
||||
string procLower = text.ToLowerInvariant();
|
||||
List<LauncherItem> list2 = activeTcpConnections.Where(delegate(TcpConnectionInformation c)
|
||||
{
|
||||
string text2 = GetProcessNameForPort(c.LocalEndPoint.Port).ToLowerInvariant();
|
||||
return text2.Contains(procLower);
|
||||
}).Take(15).Select(delegate(TcpConnectionInformation c)
|
||||
{
|
||||
string processNameForPort = GetProcessNameForPort(c.LocalEndPoint.Port);
|
||||
return new LauncherItem($"{processNameForPort} : {c.LocalEndPoint.Port} → {c.RemoteEndPoint}", $"{c.State} · Enter로 포트번호 복사", null, c.LocalEndPoint.Port.ToString(), null, "\ue968");
|
||||
})
|
||||
.ToList();
|
||||
if (!list2.Any())
|
||||
{
|
||||
list2.Add(new LauncherItem("'" + text + "' — 연결 없음", "해당 프로세스의 TCP 연결이 없습니다", null, null, null, "\ue7ba"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list2);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
Clipboard.SetText(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static void RefreshProcessCache()
|
||||
{
|
||||
if (DateTime.Now < _cacheExpiry)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_procCache.Clear();
|
||||
_pidMap.Clear();
|
||||
try
|
||||
{
|
||||
Process[] processes = Process.GetProcesses();
|
||||
foreach (Process process in processes)
|
||||
{
|
||||
try
|
||||
{
|
||||
_procCache[process.Id] = process.ProcessName;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("프로세스 목록 갱신 실패: " + ex.Message);
|
||||
}
|
||||
try
|
||||
{
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo("netstat", "-ano")
|
||||
{
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
using Process process2 = Process.Start(startInfo);
|
||||
if (process2 != null)
|
||||
{
|
||||
string text = process2.StandardOutput.ReadToEnd();
|
||||
process2.WaitForExit(2000);
|
||||
string[] array = text.Split('\n');
|
||||
foreach (string text2 in array)
|
||||
{
|
||||
string[] array2 = text2.Trim().Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (array2.Length < 5 || !int.TryParse(array2[^1], out var result))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string text3 = array2[1];
|
||||
int num = text3.LastIndexOf(':');
|
||||
if (num >= 0)
|
||||
{
|
||||
string text4 = text3;
|
||||
int num2 = num + 1;
|
||||
if (int.TryParse(text4.Substring(num2, text4.Length - num2), out var result2))
|
||||
{
|
||||
_pidMap.TryAdd(result2, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
LogService.Warn("netstat 실행 실패: " + ex2.Message);
|
||||
}
|
||||
_cacheExpiry = DateTime.Now.AddSeconds(5.0);
|
||||
}
|
||||
|
||||
private static string GetProcessNameForPort(int port)
|
||||
{
|
||||
int pidForPort = GetPidForPort(port);
|
||||
string value;
|
||||
return (pidForPort > 0 && _procCache.TryGetValue(pidForPort, out value)) ? value : "알 수 없음";
|
||||
}
|
||||
|
||||
private static int GetPidForPort(int port)
|
||||
{
|
||||
int value;
|
||||
return _pidMap.TryGetValue(port, out value) ? value : (-1);
|
||||
}
|
||||
}
|
||||
94
.decompiledproj/AxCopilot/Handlers/ProcessHandler.cs
Normal file
94
.decompiledproj/AxCopilot/Handlers/ProcessHandler.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class ProcessHandler : IActionHandler
|
||||
{
|
||||
private record ProcessKillData(string Name, List<int> Pids);
|
||||
|
||||
private static readonly HashSet<string> ProtectedProcesses = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"system", "smss", "csrss", "wininit", "winlogon", "services", "lsass", "svchost", "explorer", "dwm",
|
||||
"fontdrvhost", "spoolsv", "registry"
|
||||
};
|
||||
|
||||
public string? Prefix => "kill ";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("ProcessKiller", "프로세스 종료 — kill 뒤에 프로세스명 입력", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("종료할 프로세스명을 입력하세요", "예: kill chrome · kill notepad · kill explorer", null, null, null, "\ue7e8")));
|
||||
}
|
||||
string q = query.Trim().ToLowerInvariant();
|
||||
try
|
||||
{
|
||||
List<Process> list = (from p in Process.GetProcesses()
|
||||
where !ProtectedProcesses.Contains(p.ProcessName) && p.ProcessName.ToLowerInvariant().Contains(q)
|
||||
orderby p.ProcessName
|
||||
select p).Take(12).ToList();
|
||||
if (list.Count == 0)
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("'" + query + "' 프로세스를 찾을 수 없습니다", "실행 중인 프로세스가 없거나 이름이 다릅니다", null, null, null, "\ue7ba")));
|
||||
}
|
||||
List<LauncherItem> result = list.GroupBy<Process, string>((Process p) => p.ProcessName, StringComparer.OrdinalIgnoreCase).Select(delegate(IGrouping<string, Process> g)
|
||||
{
|
||||
List<int> list2 = g.Select((Process p) => p.Id).ToList();
|
||||
long value = g.Sum(delegate(Process p)
|
||||
{
|
||||
try
|
||||
{
|
||||
return p.WorkingSet64 / 1024 / 1024;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0L;
|
||||
}
|
||||
});
|
||||
string title = ((g.Count() > 1) ? $"{g.Key} ({g.Count()}개 인스턴스)" : g.Key);
|
||||
string subtitle = $"PID: {string.Join(", ", list2)} · 메모리: {value} MB · Enter로 종료";
|
||||
return new LauncherItem(title, subtitle, null, new ProcessKillData(g.Key, list2), null, "\ue7e8");
|
||||
}).ToList();
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("프로세스 목록 조회 실패: " + ex.Message);
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("프로세스 목록 조회 실패", ex.Message, null, null, null, "\uea39")));
|
||||
}
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (!(item.Data is ProcessKillData processKillData))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
int num = 0;
|
||||
int num2 = 0;
|
||||
foreach (int pid in processKillData.Pids)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process processById = Process.GetProcessById(pid);
|
||||
processById.Kill(entireProcessTree: false);
|
||||
num++;
|
||||
}
|
||||
catch
|
||||
{
|
||||
num2++;
|
||||
}
|
||||
}
|
||||
LogService.Info($"프로세스 종료: {processKillData.Name} — {num}개 성공, {num2}개 실패");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
149
.decompiledproj/AxCopilot/Handlers/RecentFilesHandler.cs
Normal file
149
.decompiledproj/AxCopilot/Handlers/RecentFilesHandler.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class RecentFilesHandler : IActionHandler
|
||||
{
|
||||
private static readonly string RecentFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Microsoft\\Windows\\Recent");
|
||||
|
||||
private static (DateTime At, List<(string Name, string LinkPath, DateTime Modified)> Files)? _cache;
|
||||
|
||||
private static readonly TimeSpan CacheTtl = TimeSpan.FromSeconds(10.0);
|
||||
|
||||
public string? Prefix => "recent";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("RecentFiles", "최근 파일 — recent 뒤에 검색어 입력", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string q = query.Trim();
|
||||
if (string.IsNullOrWhiteSpace(q))
|
||||
{
|
||||
}
|
||||
List<(string, string, DateTime)> recentFiles = GetRecentFiles();
|
||||
IEnumerable<(string, string, DateTime)> source = recentFiles;
|
||||
if (!string.IsNullOrWhiteSpace(q))
|
||||
{
|
||||
source = recentFiles.Where<(string, string, DateTime)>(((string Name, string LinkPath, DateTime Modified) f) => f.Name.Contains(q, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
List<LauncherItem> list = (from f in source.Take(20)
|
||||
select new LauncherItem(f.Name, $"{f.Modified:yyyy-MM-dd HH:mm} · Enter로 열기", null, f.LinkPath, null, GetSymbol(f.Name))).ToList();
|
||||
if (!list.Any())
|
||||
{
|
||||
list.Add(new LauncherItem(string.IsNullOrWhiteSpace(q) ? "최근 파일 없음" : "검색 결과 없음", string.IsNullOrWhiteSpace(q) ? "Windows Recent 폴더가 비어 있습니다" : ("'" + q + "' 파일을 최근 목록에서 찾을 수 없습니다"), null, null, null, "\ue946"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is string text && File.Exists(text))
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(text)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("최근 파일 열기 실패: " + ex.Message);
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static List<(string Name, string LinkPath, DateTime Modified)> GetRecentFiles()
|
||||
{
|
||||
if (_cache.HasValue && DateTime.Now - _cache.Value.At < CacheTtl)
|
||||
{
|
||||
return _cache.Value.Files;
|
||||
}
|
||||
List<(string, string, DateTime)> list = new List<(string, string, DateTime)>();
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(RecentFolder))
|
||||
{
|
||||
return list;
|
||||
}
|
||||
List<(string, FileInfo)> list2 = (from p in Directory.GetFiles(RecentFolder, "*.lnk")
|
||||
select (Path: p, Info: new FileInfo(p)) into f
|
||||
orderby f.Info.LastWriteTime descending
|
||||
select f).Take(100).ToList();
|
||||
foreach (var item3 in list2)
|
||||
{
|
||||
string item = item3.Item1;
|
||||
FileInfo item2 = item3.Item2;
|
||||
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(item2.Name);
|
||||
list.Add((fileNameWithoutExtension, item, item2.LastWriteTime));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("최근 파일 목록 읽기 실패: " + ex.Message);
|
||||
}
|
||||
_cache = (DateTime.Now, list);
|
||||
return list;
|
||||
}
|
||||
|
||||
private static string GetSymbol(string name)
|
||||
{
|
||||
string text = Path.GetExtension(name).ToLowerInvariant();
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string result;
|
||||
switch (text)
|
||||
{
|
||||
case ".exe":
|
||||
case ".msi":
|
||||
result = "\uecaa";
|
||||
break;
|
||||
case ".xlsx":
|
||||
case ".xls":
|
||||
case ".csv":
|
||||
result = "\ue8a5";
|
||||
break;
|
||||
case ".docx":
|
||||
case ".doc":
|
||||
result = "\ue8a5";
|
||||
break;
|
||||
case ".pptx":
|
||||
case ".ppt":
|
||||
result = "\ue8a5";
|
||||
break;
|
||||
case ".pdf":
|
||||
result = "\ue8a5";
|
||||
break;
|
||||
case ".txt":
|
||||
case ".md":
|
||||
case ".log":
|
||||
result = "\ue8d2";
|
||||
break;
|
||||
case ".jpg":
|
||||
case ".jpeg":
|
||||
case ".png":
|
||||
case ".gif":
|
||||
case ".webp":
|
||||
case ".bmp":
|
||||
result = "\ueb9f";
|
||||
break;
|
||||
default:
|
||||
result = "\ue8a5";
|
||||
break;
|
||||
}
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
119
.decompiledproj/AxCopilot/Handlers/RenameHandler.cs
Normal file
119
.decompiledproj/AxCopilot/Handlers/RenameHandler.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class RenameHandler : IActionHandler
|
||||
{
|
||||
public string? Prefix => "rename";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Rename", "파일 일괄 이름변경 — rename", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string text = query.Trim();
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlyArray<LauncherItem>(new LauncherItem[3]
|
||||
{
|
||||
new LauncherItem("파일 일괄 이름변경", "rename [폴더\\패턴] [새이름 템플릿]", null, null, null, "\ue8ac"),
|
||||
new LauncherItem("사용 예시", "rename C:\\work\\*.xlsx 보고서_{n} → 보고서_1.xlsx, 보고서_2.xlsx ...", null, null, null, "\ue946"),
|
||||
new LauncherItem("변수: {n} 순번, {date} 날짜, {orig} 원본명", "rename D:\\photos\\*.jpg {date}_{n} → 2026-03-27_1.jpg ...", null, null, null, "\ue946")
|
||||
}));
|
||||
}
|
||||
string[] array = text.Split(' ', 2, StringSplitOptions.TrimEntries);
|
||||
string path = array[0];
|
||||
string text2 = ((array.Length > 1) ? array[1] : null);
|
||||
string dir = Path.GetDirectoryName(path);
|
||||
string fileName = Path.GetFileName(path);
|
||||
if (string.IsNullOrWhiteSpace(dir) || !Directory.Exists(dir))
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("폴더를 찾을 수 없습니다", "경로: " + (dir ?? "(비어 있음)"), null, null, null, "\ue7ba")));
|
||||
}
|
||||
string[] array2;
|
||||
try
|
||||
{
|
||||
array2 = Directory.GetFiles(dir, fileName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
array2 = Array.Empty<string>();
|
||||
}
|
||||
if (array2.Length == 0)
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("일치하는 파일이 없습니다", "패턴: " + fileName + " · 폴더: " + dir, null, null, null, "\ue7ba")));
|
||||
}
|
||||
Array.Sort(array2);
|
||||
if (string.IsNullOrWhiteSpace(text2))
|
||||
{
|
||||
List<LauncherItem> list = array2.Take(10).Select((string f, int i) => new LauncherItem(Path.GetFileName(f), dir, null, null, null, "\ue8a5")).ToList();
|
||||
list.Insert(0, new LauncherItem($"총 {array2.Length}개 파일 발견", "뒤에 새 이름 템플릿을 추가하세요 (예: 보고서_{n})", null, null, null, "\ue946"));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
string newValue = DateTime.Today.ToString("yyyy-MM-dd");
|
||||
List<(string, string)> list2 = new List<(string, string)>();
|
||||
for (int num = 0; num < array2.Length; num++)
|
||||
{
|
||||
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(array2[num]);
|
||||
string extension = Path.GetExtension(array2[num]);
|
||||
string text3 = text2.Replace("{n}", (num + 1).ToString()).Replace("{date}", newValue).Replace("{orig}", fileNameWithoutExtension);
|
||||
if (!Path.HasExtension(text3))
|
||||
{
|
||||
text3 += extension;
|
||||
}
|
||||
list2.Add((Path.GetFileName(array2[num]), text3));
|
||||
}
|
||||
List<LauncherItem> list3 = new List<LauncherItem>();
|
||||
list3.Add(new LauncherItem($"총 {array2.Length}개 파일 이름변경 실행", $"Enter로 실행 · {list2[0].Item1} → {list2[0].Item2} ...", null, ValueTuple.Create(dir, array2, list2.Select<(string, string), string>(((string From, string To) p) => p.To).ToArray()), null, "\ue8ac"));
|
||||
foreach (var (text4, text5) in list2.Take(8))
|
||||
{
|
||||
list3.Add(new LauncherItem(text4 + " → " + text5, "미리보기", null, null, null, "\ue8a5"));
|
||||
}
|
||||
if (array2.Length > 8)
|
||||
{
|
||||
list3.Add(new LauncherItem($"... 외 {array2.Length - 8}개", "", null, null, null, "\ue946"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list3);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (!(item.Data is (string, string[], string[]) tuple) || 1 == 0)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
(string, string[], string[]) tuple2 = tuple;
|
||||
string item2 = tuple2.Item1;
|
||||
string[] item3 = tuple2.Item2;
|
||||
string[] item4 = tuple2.Item3;
|
||||
int num = 0;
|
||||
int num2 = 0;
|
||||
for (int i = 0; i < item3.Length && i < item4.Length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
string text = Path.Combine(item2, item4[i]);
|
||||
if (File.Exists(text))
|
||||
{
|
||||
num2++;
|
||||
continue;
|
||||
}
|
||||
File.Move(item3[i], text);
|
||||
num++;
|
||||
}
|
||||
catch
|
||||
{
|
||||
num2++;
|
||||
}
|
||||
}
|
||||
string message = ((num2 > 0) ? $"{num}개 이름변경 완료, {num2}개 실패 (이미 존재하거나 접근 불가)" : $"{num}개 파일 이름변경 완료");
|
||||
NotificationService.Notify("AX Copilot", message);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
148
.decompiledproj/AxCopilot/Handlers/RoutineHandler.cs
Normal file
148
.decompiledproj/AxCopilot/Handlers/RoutineHandler.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class RoutineHandler : IActionHandler
|
||||
{
|
||||
internal record RoutineDefinition([property: JsonPropertyName("name")] string Name, [property: JsonPropertyName("description")] string Description, [property: JsonPropertyName("steps")] RoutineStep[] Steps);
|
||||
|
||||
internal record RoutineStep([property: JsonPropertyName("type")] string Type, [property: JsonPropertyName("target")] string Target, [property: JsonPropertyName("label")] string Label);
|
||||
|
||||
private static readonly string RoutineFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "routines.json");
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOpts = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
private static readonly RoutineDefinition[] BuiltInRoutines = new RoutineDefinition[2]
|
||||
{
|
||||
new RoutineDefinition("morning", "출근 루틴", new RoutineStep[2]
|
||||
{
|
||||
new RoutineStep("app", "explorer.exe", "파일 탐색기"),
|
||||
new RoutineStep("info", "info", "시스템 정보 표시")
|
||||
}),
|
||||
new RoutineDefinition("endofday", "퇴근 루틴", new RoutineStep[1]
|
||||
{
|
||||
new RoutineStep("cmd", "journal", "오늘 업무 일지 생성")
|
||||
})
|
||||
};
|
||||
|
||||
public string? Prefix => "routine";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Routine", "루틴 자동화 — routine", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string q = query.Trim();
|
||||
List<RoutineDefinition> list = LoadRoutines();
|
||||
if (string.IsNullOrWhiteSpace(q))
|
||||
{
|
||||
List<LauncherItem> list2 = new List<LauncherItem>
|
||||
{
|
||||
new LauncherItem("루틴 자동화", $"총 {list.Count}개 루틴 · 이름 입력 시 실행 · routines.json에서 편집", null, null, null, "\ue946")
|
||||
};
|
||||
foreach (RoutineDefinition item in list)
|
||||
{
|
||||
string value = string.Join(" → ", item.Steps.Select((RoutineStep s) => s.Label));
|
||||
list2.Add(new LauncherItem("[" + item.Name + "] " + item.Description, $"{item.Steps.Length}단계: {value} · Enter로 실행", null, item, null, "\ue82f"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list2);
|
||||
}
|
||||
RoutineDefinition routineDefinition = list.FirstOrDefault((RoutineDefinition r) => r.Name.Equals(q, StringComparison.OrdinalIgnoreCase));
|
||||
if (routineDefinition != null)
|
||||
{
|
||||
string text = string.Join(" → ", routineDefinition.Steps.Select((RoutineStep s) => s.Label));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("[" + routineDefinition.Name + "] 루틴 실행", routineDefinition.Description + " · " + text, null, routineDefinition, null, "\ue82f")));
|
||||
}
|
||||
IEnumerable<RoutineDefinition> source = list.Where((RoutineDefinition r) => r.Name.Contains(q, StringComparison.OrdinalIgnoreCase) || r.Description.Contains(q, StringComparison.OrdinalIgnoreCase));
|
||||
List<LauncherItem> list3 = source.Select((RoutineDefinition r) => new LauncherItem("[" + r.Name + "] " + r.Description, $"{r.Steps.Length}단계 · Enter로 실행", null, r, null, "\ue82f")).ToList();
|
||||
if (!list3.Any())
|
||||
{
|
||||
list3.Add(new LauncherItem("'" + q + "' 루틴 없음", "routines.json에서 직접 추가하거나 routine으로 목록 확인", null, null, null, "\ue7ba"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list3);
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
if (!(data is RoutineDefinition routine))
|
||||
{
|
||||
return;
|
||||
}
|
||||
int executed = 0;
|
||||
RoutineStep[] steps = routine.Steps;
|
||||
foreach (RoutineStep step in steps)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (step.Type.ToLowerInvariant())
|
||||
{
|
||||
case "app":
|
||||
case "url":
|
||||
case "folder":
|
||||
Process.Start(new ProcessStartInfo(step.Target)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
break;
|
||||
case "cmd":
|
||||
Process.Start(new ProcessStartInfo("powershell.exe", "-Command \"" + step.Target + "\"")
|
||||
{
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
});
|
||||
break;
|
||||
case "info":
|
||||
NotificationService.Notify("루틴", step.Label);
|
||||
break;
|
||||
}
|
||||
executed++;
|
||||
await Task.Delay(300, ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("루틴 단계 실행 실패: " + step.Label + " — " + ex.Message);
|
||||
}
|
||||
}
|
||||
NotificationService.Notify("루틴 완료", $"[{routine.Name}] {executed}/{routine.Steps.Length}단계 실행 완료");
|
||||
}
|
||||
|
||||
private List<RoutineDefinition> LoadRoutines()
|
||||
{
|
||||
List<RoutineDefinition> list = new List<RoutineDefinition>(BuiltInRoutines);
|
||||
try
|
||||
{
|
||||
if (File.Exists(RoutineFile))
|
||||
{
|
||||
string json = File.ReadAllText(RoutineFile);
|
||||
List<RoutineDefinition> list2 = JsonSerializer.Deserialize<List<RoutineDefinition>>(json, JsonOpts);
|
||||
if (list2 != null)
|
||||
{
|
||||
foreach (RoutineDefinition r in list2)
|
||||
{
|
||||
list.RemoveAll((RoutineDefinition x) => x.Name.Equals(r.Name, StringComparison.OrdinalIgnoreCase));
|
||||
list.Add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("루틴 로드 실패: " + ex.Message);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
61
.decompiledproj/AxCopilot/Handlers/RunHandler.cs
Normal file
61
.decompiledproj/AxCopilot/Handlers/RunHandler.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Views;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class RunHandler : IActionHandler
|
||||
{
|
||||
public string? Prefix => "^";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Run", "Windows 실행 명령", "1.0", "AX Copilot");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string text = query.Trim();
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlyArray<LauncherItem>(new LauncherItem[5]
|
||||
{
|
||||
new LauncherItem("Windows 실행 명령", "Win+R 실행 창과 동일 · 명령어 입력 후 Enter", null, null, null, "\ue7c4"),
|
||||
new LauncherItem("^ notepad", "메모장 실행", null, null, null, "\ue946"),
|
||||
new LauncherItem("^ cmd", "명령 프롬프트", null, null, null, "\ue946"),
|
||||
new LauncherItem("^ control", "제어판", null, null, null, "\ue946"),
|
||||
new LauncherItem("^ calc", "계산기", null, null, null, "\ue946")
|
||||
}));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("실행: " + text, "Enter → Windows 실행 명령으로 실행", null, "__RUN__" + text, null, "\ue7c4")));
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (!(item.Data is string text) || !text.StartsWith("__RUN__"))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
string text2 = text;
|
||||
int length = "__RUN__".Length;
|
||||
string text3 = text2.Substring(length, text2.Length - length).Trim();
|
||||
if (string.IsNullOrEmpty(text3))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(text3)
|
||||
{
|
||||
UseShellExecute = true
|
||||
})?.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CustomMessageBox.Show("실행 실패: " + ex.Message, "AX Copilot", MessageBoxButton.OK, MessageBoxImage.Hand);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
164
.decompiledproj/AxCopilot/Handlers/ScaffoldHandler.cs
Normal file
164
.decompiledproj/AxCopilot/Handlers/ScaffoldHandler.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class ScaffoldHandler : IActionHandler
|
||||
{
|
||||
internal record ScaffoldTemplate([property: JsonPropertyName("name")] string Name, [property: JsonPropertyName("description")] string Description, [property: JsonPropertyName("paths")] string[] Paths);
|
||||
|
||||
private static readonly string TemplateDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "templates");
|
||||
|
||||
private static readonly ScaffoldTemplate[] BuiltInTemplates = new ScaffoldTemplate[5]
|
||||
{
|
||||
new ScaffoldTemplate("webapi", "Web API 프로젝트", new string[8] { "src/Controllers/", "src/Models/", "src/Services/", "src/Middleware/", "tests/", "docs/", "README.md", ".gitignore" }),
|
||||
new ScaffoldTemplate("console", "콘솔 애플리케이션", new string[5] { "src/", "src/Core/", "src/Services/", "tests/", "README.md" }),
|
||||
new ScaffoldTemplate("wpf", "WPF 데스크톱 앱", new string[8] { "src/Views/", "src/ViewModels/", "src/Models/", "src/Services/", "src/Themes/", "src/Assets/", "tests/", "docs/" }),
|
||||
new ScaffoldTemplate("data", "데이터 파이프라인", new string[9] { "src/Extractors/", "src/Transformers/", "src/Loaders/", "config/", "scripts/", "tests/", "data/input/", "data/output/", "README.md" }),
|
||||
new ScaffoldTemplate("docs", "문서 프로젝트", new string[5] { "docs/", "images/", "templates/", "README.md", "CHANGELOG.md" })
|
||||
};
|
||||
|
||||
public string? Prefix => "scaffold";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Scaffold", "프로젝트 스캐폴딩 — scaffold", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string q = query.Trim();
|
||||
IEnumerable<ScaffoldTemplate> second = LoadUserTemplates();
|
||||
List<ScaffoldTemplate> list = BuiltInTemplates.Concat(second).ToList();
|
||||
if (string.IsNullOrWhiteSpace(q))
|
||||
{
|
||||
List<LauncherItem> list2 = list.Select((ScaffoldTemplate t) => new LauncherItem("[" + t.Name + "] " + t.Description, $"{t.Paths.Length}개 폴더/파일 · Enter → 대상 경로 입력 후 생성", null, t, null, "\ue8b7")).ToList();
|
||||
list2.Insert(0, new LauncherItem("프로젝트 스캐폴딩", $"총 {list.Count}개 템플릿 · 이름을 입력해 필터링", null, null, null, "\ue946"));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list2);
|
||||
}
|
||||
if (q.Contains('\\') || q.Contains('/'))
|
||||
{
|
||||
int num = q.LastIndexOf(' ');
|
||||
if (num > 0)
|
||||
{
|
||||
string text = q.Substring(0, num).Trim();
|
||||
string text2 = q;
|
||||
int num2 = num + 1;
|
||||
string templateName = text2.Substring(num2, text2.Length - num2).Trim();
|
||||
ScaffoldTemplate scaffoldTemplate = list.FirstOrDefault((ScaffoldTemplate t) => t.Name.Equals(templateName, StringComparison.OrdinalIgnoreCase));
|
||||
if (scaffoldTemplate != null)
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("[" + scaffoldTemplate.Name + "] → " + text, $"{scaffoldTemplate.Paths.Length}개 폴더/파일 생성 · Enter로 실행", null, ValueTuple.Create(text, scaffoldTemplate), null, "\ue74e")));
|
||||
}
|
||||
}
|
||||
}
|
||||
IEnumerable<ScaffoldTemplate> source = list.Where((ScaffoldTemplate t) => t.Name.Contains(q, StringComparison.OrdinalIgnoreCase) || t.Description.Contains(q, StringComparison.OrdinalIgnoreCase));
|
||||
List<LauncherItem> list3 = source.Select(delegate(ScaffoldTemplate t)
|
||||
{
|
||||
string text3 = string.Join(", ", t.Paths.Take(4));
|
||||
if (t.Paths.Length > 4)
|
||||
{
|
||||
text3 += $" ... (+{t.Paths.Length - 4})";
|
||||
}
|
||||
return new LauncherItem("[" + t.Name + "] " + t.Description, text3 + " · 사용법: scaffold [대상경로] " + t.Name, null, t, null, "\ue8b7");
|
||||
}).ToList();
|
||||
if (!list3.Any())
|
||||
{
|
||||
list3.Add(new LauncherItem("'" + q + "'에 해당하는 템플릿 없음", "scaffold 으로 전체 목록 확인", null, null, null, "\ue7ba"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list3);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (!(item.Data is (string, ScaffoldTemplate) tuple))
|
||||
{
|
||||
if (item.Data is ScaffoldTemplate scaffoldTemplate)
|
||||
{
|
||||
string usage = "scaffold [대상경로] " + scaffoldTemplate.Name;
|
||||
try
|
||||
{
|
||||
Application current = Application.Current;
|
||||
if (current != null)
|
||||
{
|
||||
((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
Clipboard.SetText(usage);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
var (basePath, template) = tuple;
|
||||
return CreateStructure(basePath, template);
|
||||
}
|
||||
|
||||
private static Task CreateStructure(string basePath, ScaffoldTemplate template)
|
||||
{
|
||||
try
|
||||
{
|
||||
int num = 0;
|
||||
string[] paths = template.Paths;
|
||||
foreach (string text in paths)
|
||||
{
|
||||
string path = Path.Combine(basePath, text.Replace('/', Path.DirectorySeparatorChar));
|
||||
if (text.EndsWith('/') || text.EndsWith('\\') || !Path.HasExtension(text))
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
File.WriteAllText(path, "");
|
||||
}
|
||||
}
|
||||
num++;
|
||||
}
|
||||
NotificationService.Notify("스캐폴딩 완료", $"[{template.Name}] {num}개 항목 생성 → {basePath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Error("스캐폴딩 실패: " + ex.Message);
|
||||
NotificationService.Notify("AX Copilot", "스캐폴딩 실패: " + ex.Message);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static IEnumerable<ScaffoldTemplate> LoadUserTemplates()
|
||||
{
|
||||
if (!Directory.Exists(TemplateDir))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
string[] files = Directory.GetFiles(TemplateDir, "*.json");
|
||||
foreach (string file in files)
|
||||
{
|
||||
ScaffoldTemplate tmpl = null;
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(file);
|
||||
tmpl = JsonSerializer.Deserialize<ScaffoldTemplate>(json);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
if (tmpl != null)
|
||||
{
|
||||
yield return tmpl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
612
.decompiledproj/AxCopilot/Handlers/ScreenCaptureHandler.cs
Normal file
612
.decompiledproj/AxCopilot/Handlers/ScreenCaptureHandler.cs
Normal file
@@ -0,0 +1,612 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
using AxCopilot.Views;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class ScreenCaptureHandler : IActionHandler
|
||||
{
|
||||
private struct RECT
|
||||
{
|
||||
public int left;
|
||||
|
||||
public int top;
|
||||
|
||||
public int right;
|
||||
|
||||
public int bottom;
|
||||
}
|
||||
|
||||
private readonly SettingsService _settings;
|
||||
|
||||
private const int SW_RESTORE = 9;
|
||||
|
||||
private const byte VK_NEXT = 34;
|
||||
|
||||
private const uint WM_VSCROLL = 277u;
|
||||
|
||||
private const uint WM_KEYDOWN = 256u;
|
||||
|
||||
private const uint WM_KEYUP = 257u;
|
||||
|
||||
private const int SB_PAGEDOWN = 3;
|
||||
|
||||
private static readonly (string Key, string Label, string Desc)[] _options = new(string, string, string)[4]
|
||||
{
|
||||
("region", "영역 선택 캡처", "마우스로 드래그하여 원하는 영역만 캡처 · Shift+Enter: 타이머 캡처"),
|
||||
("window", "활성 창 캡처", "런처 호출 전 활성 창만 캡처 · Shift+Enter: 타이머 캡처"),
|
||||
("scroll", "스크롤 캡처", "활성 창을 끝까지 스크롤하며 페이지 전체 캡처 · Shift+Enter: 타이머 캡처"),
|
||||
("screen", "전체 화면 캡처", "모든 모니터를 포함한 전체 화면 · Shift+Enter: 타이머 캡처")
|
||||
};
|
||||
|
||||
public string? Prefix => string.IsNullOrWhiteSpace(_settings.Settings.ScreenCapture.Prefix) ? "cap" : _settings.Settings.ScreenCapture.Prefix.Trim();
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("ScreenCapture", "화면 캡처 — cap screen/window/scroll/region", "1.0", "AX");
|
||||
|
||||
internal int ScrollDelayMs => Math.Max(50, _settings.Settings.ScreenCapture.ScrollDelayMs);
|
||||
|
||||
public ScreenCaptureHandler(SettingsService settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetWindowRect(nint hWnd, out RECT lpRect);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsWindow(nint hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetForegroundWindow(nint hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool ShowWindow(nint hWnd, int nCmdShow);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool PrintWindow(nint hwnd, nint hdcBlt, uint nFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsWindowVisible(nint hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern nint SendMessage(nint hWnd, uint Msg, nint wParam, nint lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern nint FindWindowEx(nint parent, nint child, string? className, string? windowText);
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string q = query.Trim().ToLowerInvariant();
|
||||
string saveHint = "클립보드에 복사";
|
||||
IEnumerable<(string, string, string)> enumerable;
|
||||
if (!string.IsNullOrWhiteSpace(q))
|
||||
{
|
||||
enumerable = _options.Where(((string Key, string Label, string Desc) o) => o.Key.StartsWith(q) || o.Label.Contains(q));
|
||||
}
|
||||
else
|
||||
{
|
||||
IEnumerable<(string, string, string)> options = _options;
|
||||
enumerable = options;
|
||||
}
|
||||
IEnumerable<(string, string, string)> source = enumerable;
|
||||
List<LauncherItem> list = source.Select<(string, string, string), LauncherItem>(((string Key, string Label, string Desc) o) => new LauncherItem(o.Label, o.Desc + " · " + saveHint, null, o.Key, null, "\ue722")).ToList();
|
||||
if (!list.Any())
|
||||
{
|
||||
list.Add(new LauncherItem("알 수 없는 캡처 모드: " + q, "screen / window / scroll / region", null, null, null, "\ue7ba"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public IEnumerable<LauncherItem> GetDelayItems(string mode)
|
||||
{
|
||||
string text = _options.FirstOrDefault(((string Key, string Label, string Desc) o) => o.Key == mode).Label ?? mode;
|
||||
return new LauncherItem[3]
|
||||
{
|
||||
new LauncherItem("3초 후 " + text, "Shift+Enter로 지연 캡처", null, "delay:" + mode + ":3", null, "\ue916"),
|
||||
new LauncherItem("5초 후 " + text, "Shift+Enter로 지연 캡처", null, "delay:" + mode + ":5", null, "\ue916"),
|
||||
new LauncherItem("10초 후 " + text, "Shift+Enter로 지연 캡처", null, "delay:" + mode + ":10", null, "\ue916")
|
||||
};
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
if (!(data is string data2))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (data2.StartsWith("delay:"))
|
||||
{
|
||||
string[] parts = data2.Split(':');
|
||||
if (parts.Length == 3 && int.TryParse(parts[2], out var delaySec))
|
||||
{
|
||||
await ExecuteDelayedCaptureAsync(parts[1], delaySec, ct);
|
||||
return;
|
||||
}
|
||||
}
|
||||
await Task.Delay(150, ct);
|
||||
await CaptureDirectAsync(data2, ct);
|
||||
}
|
||||
|
||||
private async Task ExecuteDelayedCaptureAsync(string mode, int delaySec, CancellationToken ct)
|
||||
{
|
||||
await Task.Delay(200, ct);
|
||||
for (int i = delaySec; i > 0; i--)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
await Task.Delay(1000, ct);
|
||||
}
|
||||
await CaptureDirectAsync(mode, ct);
|
||||
}
|
||||
|
||||
public async Task CaptureDirectAsync(string mode, CancellationToken ct = default(CancellationToken))
|
||||
{
|
||||
try
|
||||
{
|
||||
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
||||
switch (mode)
|
||||
{
|
||||
case "screen":
|
||||
await CaptureScreenAsync(timestamp);
|
||||
break;
|
||||
case "window":
|
||||
await CaptureWindowAsync(timestamp);
|
||||
break;
|
||||
case "scroll":
|
||||
await CaptureScrollAsync(timestamp, ct);
|
||||
break;
|
||||
case "region":
|
||||
await CaptureRegionAsync(timestamp, ct);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Exception ex2 = ex;
|
||||
LogService.Error("캡처 실패: " + ex2.Message);
|
||||
NotificationService.Notify("AX Copilot", "캡처 실패: " + ex2.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CaptureScreenAsync(string timestamp)
|
||||
{
|
||||
Rectangle bounds = GetAllScreenBounds();
|
||||
using Bitmap bmp = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb);
|
||||
using Graphics g = Graphics.FromImage(bmp);
|
||||
g.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size, CopyPixelOperation.SourceCopy);
|
||||
CopyToClipboard(bmp);
|
||||
await Task.Delay(10);
|
||||
NotificationService.Notify("화면 캡처 완료", "클립보드에 복사되었습니다");
|
||||
}
|
||||
|
||||
private async Task CaptureWindowAsync(string timestamp)
|
||||
{
|
||||
nint hwnd = WindowTracker.PreviousWindow;
|
||||
if (hwnd == IntPtr.Zero || !IsWindow(hwnd))
|
||||
{
|
||||
NotificationService.Notify("AX Copilot", "캡처할 창이 없습니다. 런처 호출 전 창을 확인하세요.");
|
||||
return;
|
||||
}
|
||||
nint launcherHwnd = GetLauncherHwnd();
|
||||
if (launcherHwnd != IntPtr.Zero)
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
if (!IsWindowVisible(launcherHwnd))
|
||||
{
|
||||
break;
|
||||
}
|
||||
await Task.Delay(50);
|
||||
}
|
||||
}
|
||||
ShowWindow(hwnd, 9);
|
||||
SetForegroundWindow(hwnd);
|
||||
await Task.Delay(150);
|
||||
if (!GetWindowRect(hwnd, out var rect))
|
||||
{
|
||||
return;
|
||||
}
|
||||
int w = rect.right - rect.left;
|
||||
int h = rect.bottom - rect.top;
|
||||
if (w <= 0 || h <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
using Bitmap bmp = CaptureWindow(hwnd, w, h, rect);
|
||||
CopyToClipboard(bmp);
|
||||
NotificationService.Notify("창 캡처 완료", "클립보드에 복사되었습니다");
|
||||
}
|
||||
|
||||
private async Task CaptureScrollAsync(string timestamp, CancellationToken ct)
|
||||
{
|
||||
nint hwnd = WindowTracker.PreviousWindow;
|
||||
if (hwnd == IntPtr.Zero || !IsWindow(hwnd))
|
||||
{
|
||||
NotificationService.Notify("AX Copilot", "캡처할 창이 없습니다.");
|
||||
return;
|
||||
}
|
||||
nint launcherHwnd = GetLauncherHwnd();
|
||||
if (launcherHwnd != IntPtr.Zero)
|
||||
{
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
if (!IsWindowVisible(launcherHwnd))
|
||||
{
|
||||
break;
|
||||
}
|
||||
await Task.Delay(50, ct);
|
||||
}
|
||||
}
|
||||
ShowWindow(hwnd, 9);
|
||||
SetForegroundWindow(hwnd);
|
||||
await Task.Delay(200, ct);
|
||||
if (!GetWindowRect(hwnd, out var rect))
|
||||
{
|
||||
return;
|
||||
}
|
||||
int w = rect.right - rect.left;
|
||||
int h = rect.bottom - rect.top;
|
||||
if (w <= 0 || h <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
nint scrollTarget = FindScrollableChild(hwnd);
|
||||
List<Bitmap> frames = new List<Bitmap> { CaptureWindow(hwnd, w, h, rect) };
|
||||
for (int j = 0; j < 14; j++)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
if (scrollTarget != IntPtr.Zero)
|
||||
{
|
||||
SendMessage(scrollTarget, 277u, new IntPtr(3), IntPtr.Zero);
|
||||
}
|
||||
else
|
||||
{
|
||||
SendPageDown(hwnd);
|
||||
}
|
||||
await Task.Delay(ScrollDelayMs, ct);
|
||||
if (!GetWindowRect(hwnd, out var newRect))
|
||||
{
|
||||
break;
|
||||
}
|
||||
Bitmap frame = CaptureWindow(hwnd, w, h, newRect);
|
||||
int num;
|
||||
if (frames.Count > 0)
|
||||
{
|
||||
num = (AreSimilar(frames[frames.Count - 1], frame) ? 1 : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
num = 0;
|
||||
}
|
||||
if (num != 0)
|
||||
{
|
||||
frame.Dispose();
|
||||
break;
|
||||
}
|
||||
frames.Add(frame);
|
||||
}
|
||||
using Bitmap stitched = StitchFrames(frames, h);
|
||||
foreach (Bitmap f in frames)
|
||||
{
|
||||
f.Dispose();
|
||||
}
|
||||
CopyToClipboard(stitched);
|
||||
NotificationService.Notify("스크롤 캡처 완료", $"{stitched.Height}px · 클립보드에 복사되었습니다");
|
||||
}
|
||||
|
||||
private static Bitmap CaptureWindow(nint hwnd, int w, int h, RECT rect)
|
||||
{
|
||||
Bitmap bitmap = new Bitmap(w, h, PixelFormat.Format32bppArgb);
|
||||
using Graphics graphics = Graphics.FromImage(bitmap);
|
||||
nint hdc = graphics.GetHdc();
|
||||
bool flag = PrintWindow(hwnd, hdc, 2u);
|
||||
graphics.ReleaseHdc(hdc);
|
||||
if (!flag)
|
||||
{
|
||||
graphics.CopyFromScreen(rect.left, rect.top, 0, 0, new Size(w, h), CopyPixelOperation.SourceCopy);
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private static Rectangle GetAllScreenBounds()
|
||||
{
|
||||
Screen[] allScreens = Screen.AllScreens;
|
||||
int num = allScreens.Min((Screen s) => s.Bounds.X);
|
||||
int num2 = allScreens.Min((Screen s) => s.Bounds.Y);
|
||||
int num3 = allScreens.Max((Screen s) => s.Bounds.Right);
|
||||
int num4 = allScreens.Max((Screen s) => s.Bounds.Bottom);
|
||||
return new Rectangle(num, num2, num3 - num, num4 - num2);
|
||||
}
|
||||
|
||||
private static nint FindScrollableChild(nint hwnd)
|
||||
{
|
||||
string[] array = new string[7] { "Internet Explorer_Server", "Chrome_RenderWidgetHostHWND", "MozillaWindowClass", "RichEdit20W", "RICHEDIT50W", "TextBox", "EDIT" };
|
||||
foreach (string className in array)
|
||||
{
|
||||
nint num = FindWindowEx(hwnd, IntPtr.Zero, className, null);
|
||||
if (num != IntPtr.Zero)
|
||||
{
|
||||
return num;
|
||||
}
|
||||
}
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
private static void SendPageDown(nint hwnd)
|
||||
{
|
||||
SendMessage(hwnd, 256u, new IntPtr(34), IntPtr.Zero);
|
||||
SendMessage(hwnd, 257u, new IntPtr(34), IntPtr.Zero);
|
||||
}
|
||||
|
||||
private unsafe static bool AreSimilar(Bitmap a, Bitmap b)
|
||||
{
|
||||
if (a.Width != b.Width || a.Height != b.Height)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int num = (int)((double)a.Height * 0.8);
|
||||
int width = a.Width;
|
||||
int height = a.Height;
|
||||
Rectangle rect = new Rectangle(0, num, width, height - num);
|
||||
Rectangle rect2 = new Rectangle(0, num, width, height - num);
|
||||
BitmapData bitmapData = a.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||||
BitmapData bitmapData2 = b.LockBits(rect2, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||||
try
|
||||
{
|
||||
int num2 = 0;
|
||||
int num3 = 0;
|
||||
int stride = bitmapData.Stride;
|
||||
int num4 = width / 16 + 1;
|
||||
int num5 = (height - num) / 8 + 1;
|
||||
byte* ptr = (byte*)((IntPtr)bitmapData.Scan0).ToPointer();
|
||||
byte* ptr2 = (byte*)((IntPtr)bitmapData2.Scan0).ToPointer();
|
||||
for (int i = 0; i < num5; i++)
|
||||
{
|
||||
int num6 = i * 8;
|
||||
if (num6 >= height - num)
|
||||
{
|
||||
break;
|
||||
}
|
||||
for (int j = 0; j < num4; j++)
|
||||
{
|
||||
int num7 = j * 16;
|
||||
if (num7 >= width)
|
||||
{
|
||||
break;
|
||||
}
|
||||
int num8 = num6 * stride + num7 * 4;
|
||||
if (Math.Abs(ptr[num8] - ptr2[num8]) < 5 && Math.Abs(ptr[num8 + 1] - ptr2[num8 + 1]) < 5 && Math.Abs(ptr[num8 + 2] - ptr2[num8 + 2]) < 5)
|
||||
{
|
||||
num2++;
|
||||
}
|
||||
num3++;
|
||||
}
|
||||
}
|
||||
return num3 > 0 && (double)num2 / (double)num3 > 0.97;
|
||||
}
|
||||
finally
|
||||
{
|
||||
a.UnlockBits(bitmapData);
|
||||
b.UnlockBits(bitmapData2);
|
||||
}
|
||||
}
|
||||
|
||||
private static Bitmap StitchFrames(List<Bitmap> frames, int windowHeight)
|
||||
{
|
||||
if (frames.Count == 0)
|
||||
{
|
||||
return new Bitmap(1, 1);
|
||||
}
|
||||
if (frames.Count == 1)
|
||||
{
|
||||
return new Bitmap(frames[0]);
|
||||
}
|
||||
int width = frames[0].Width;
|
||||
List<int> list = new List<int>();
|
||||
List<int> list2 = new List<int>();
|
||||
int num = windowHeight;
|
||||
for (int i = 1; i < frames.Count; i++)
|
||||
{
|
||||
int num2 = FindOverlap(frames[i - 1], frames[i]);
|
||||
int num3 = ((num2 > 0) ? num2 : (windowHeight / 5));
|
||||
int num4 = windowHeight - num3;
|
||||
if (num4 <= 0)
|
||||
{
|
||||
num4 = windowHeight / 4;
|
||||
num3 = windowHeight - num4;
|
||||
}
|
||||
list.Add(num3);
|
||||
list2.Add(num4);
|
||||
num += num4;
|
||||
}
|
||||
Bitmap bitmap = new Bitmap(width, num, PixelFormat.Format32bppArgb);
|
||||
using Graphics graphics = Graphics.FromImage(bitmap);
|
||||
graphics.DrawImage(frames[0], 0, 0, width, windowHeight);
|
||||
int num5 = windowHeight;
|
||||
for (int j = 1; j < frames.Count; j++)
|
||||
{
|
||||
int y = list[j - 1];
|
||||
int num6 = list2[j - 1];
|
||||
graphics.DrawImage(srcRect: new Rectangle(0, y, width, num6), destRect: new Rectangle(0, num5, width, num6), image: frames[j], srcUnit: GraphicsUnit.Pixel);
|
||||
num5 += num6;
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private unsafe static int FindOverlap(Bitmap prev, Bitmap next)
|
||||
{
|
||||
int num = Math.Min(prev.Width, next.Width);
|
||||
int height = prev.Height;
|
||||
if (height < 16 || num < 16)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int num2 = (int)((double)height * 0.7);
|
||||
BitmapData bitmapData = prev.LockBits(new Rectangle(0, 0, prev.Width, prev.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||||
BitmapData bitmapData2 = next.LockBits(new Rectangle(0, 0, next.Width, next.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
|
||||
try
|
||||
{
|
||||
int stride = bitmapData.Stride;
|
||||
int stride2 = bitmapData2.Stride;
|
||||
int result = 0;
|
||||
byte* ptr = (byte*)((IntPtr)bitmapData.Scan0).ToPointer();
|
||||
byte* ptr2 = (byte*)((IntPtr)bitmapData2.Scan0).ToPointer();
|
||||
for (int num3 = num2; num3 > 8; num3 -= 2)
|
||||
{
|
||||
int num4 = height - num3;
|
||||
if (num4 >= 0)
|
||||
{
|
||||
int num5 = 0;
|
||||
int num6 = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
int num7 = i * (num3 / 8);
|
||||
int num8 = num4 + num7;
|
||||
int num9 = num7;
|
||||
if (num8 >= height || num9 >= next.Height)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (int j = 4; j < num - 4; j += 12)
|
||||
{
|
||||
int num10 = num8 * stride + j * 4;
|
||||
int num11 = num9 * stride2 + j * 4;
|
||||
if (num10 + 2 < bitmapData.Height * stride && num11 + 2 < bitmapData2.Height * stride2)
|
||||
{
|
||||
if (Math.Abs(ptr[num10] - ptr2[num11]) < 10 && Math.Abs(ptr[num10 + 1] - ptr2[num11 + 1]) < 10 && Math.Abs(ptr[num10 + 2] - ptr2[num11 + 2]) < 10)
|
||||
{
|
||||
num5++;
|
||||
}
|
||||
num6++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (num6 > 0 && (double)num5 / (double)num6 > 0.8)
|
||||
{
|
||||
result = num3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
prev.UnlockBits(bitmapData);
|
||||
next.UnlockBits(bitmapData2);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CaptureRegionAsync(string timestamp, CancellationToken ct)
|
||||
{
|
||||
Rectangle bounds = GetAllScreenBounds();
|
||||
Bitmap fullBmp = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb);
|
||||
try
|
||||
{
|
||||
using (Graphics g = Graphics.FromImage(fullBmp))
|
||||
{
|
||||
g.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size, CopyPixelOperation.SourceCopy);
|
||||
}
|
||||
Rectangle? selected = null;
|
||||
System.Windows.Application current = System.Windows.Application.Current;
|
||||
if (current != null)
|
||||
{
|
||||
((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
RegionSelectWindow regionSelectWindow = new RegionSelectWindow(fullBmp, bounds);
|
||||
regionSelectWindow.ShowDialog();
|
||||
selected = regionSelectWindow.SelectedRect;
|
||||
});
|
||||
}
|
||||
if (!selected.HasValue || selected.Value.Width < 4 || selected.Value.Height < 4)
|
||||
{
|
||||
NotificationService.Notify("AX Copilot", "영역 선택이 취소되었습니다.");
|
||||
return;
|
||||
}
|
||||
Rectangle r = selected.Value;
|
||||
using Bitmap crop = new Bitmap(r.Width, r.Height, PixelFormat.Format32bppArgb);
|
||||
using (Graphics g2 = Graphics.FromImage(crop))
|
||||
{
|
||||
g2.DrawImage(fullBmp, new Rectangle(0, 0, r.Width, r.Height), r, GraphicsUnit.Pixel);
|
||||
}
|
||||
CopyToClipboard(crop);
|
||||
NotificationService.Notify("영역 캡처 완료", $"{r.Width}×{r.Height} · 클립보드에 복사되었습니다");
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fullBmp != null)
|
||||
{
|
||||
((IDisposable)fullBmp).Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void CopyToClipboard(Bitmap bmp)
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Windows.Application current = System.Windows.Application.Current;
|
||||
if (current == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
using MemoryStream memoryStream = new MemoryStream();
|
||||
bmp.Save(memoryStream, ImageFormat.Bmp);
|
||||
memoryStream.Position = 0L;
|
||||
BitmapImage bitmapImage = new BitmapImage();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.StreamSource = memoryStream;
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.EndInit();
|
||||
((Freezable)bitmapImage).Freeze();
|
||||
System.Windows.Clipboard.SetImage(bitmapImage);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("클립보드 이미지 복사 실패: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static nint GetLauncherHwnd()
|
||||
{
|
||||
try
|
||||
{
|
||||
nint hwnd = IntPtr.Zero;
|
||||
System.Windows.Application current = System.Windows.Application.Current;
|
||||
if (current != null)
|
||||
{
|
||||
((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
Window window = System.Windows.Application.Current.Windows.OfType<Window>().FirstOrDefault((Window w) => ((object)w).GetType().Name == "LauncherWindow");
|
||||
if (window != null)
|
||||
{
|
||||
hwnd = new WindowInteropHelper(window).Handle;
|
||||
}
|
||||
});
|
||||
}
|
||||
return hwnd;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
257
.decompiledproj/AxCopilot/Handlers/ServiceHandler.cs
Normal file
257
.decompiledproj/AxCopilot/Handlers/ServiceHandler.cs
Normal file
@@ -0,0 +1,257 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.ServiceProcess;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class ServiceHandler : IActionHandler
|
||||
{
|
||||
private readonly ClipboardHistoryService? _clipboardService;
|
||||
|
||||
public string? Prefix => "svc";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Service", "Windows 서비스 관리 — svc", "1.0", "AX");
|
||||
|
||||
public ServiceHandler(ClipboardHistoryService? clipboardService = null)
|
||||
{
|
||||
_clipboardService = clipboardService;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string q = query.Trim();
|
||||
List<LauncherItem> list = new List<LauncherItem>();
|
||||
if (q.Equals("restart clipboard", StringComparison.OrdinalIgnoreCase) || q.Equals("클립보드 재시작", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
list.Add(new LauncherItem("AX 클립보드 히스토리 서비스 재시작", "클립보드 감지가 작동하지 않을 때 사용 · Enter로 실행", null, "__RESTART_CLIPBOARD__", null, "\ue777"));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
if (q.StartsWith("start ", StringComparison.OrdinalIgnoreCase) || q.StartsWith("stop ", StringComparison.OrdinalIgnoreCase) || q.StartsWith("restart ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string[] array = q.Split(' ', 2, StringSplitOptions.TrimEntries);
|
||||
string text = array[0].ToLowerInvariant();
|
||||
string svcName = ((array.Length > 1) ? array[1] : "");
|
||||
if (!string.IsNullOrWhiteSpace(svcName))
|
||||
{
|
||||
try
|
||||
{
|
||||
ServiceController serviceController = ServiceController.GetServices().FirstOrDefault((ServiceController s) => s.ServiceName.Contains(svcName, StringComparison.OrdinalIgnoreCase) || s.DisplayName.Contains(svcName, StringComparison.OrdinalIgnoreCase));
|
||||
if (serviceController != null)
|
||||
{
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string text2 = text switch
|
||||
{
|
||||
"start" => "시작",
|
||||
"stop" => "중지",
|
||||
"restart" => "재시작",
|
||||
_ => text,
|
||||
};
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string text3 = text2;
|
||||
list.Add(new LauncherItem("[" + text3 + "] " + serviceController.DisplayName, $"서비스명: {serviceController.ServiceName} · 현재 상태: {StatusText(serviceController.Status)} · Enter로 실행", null, ("__" + text.ToUpperInvariant() + "__", serviceController.ServiceName), null, (text == "stop") ? "\uea39" : "\ue777"));
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(new LauncherItem("'" + svcName + "' 서비스를 찾을 수 없습니다", "서비스 이름 또는 표시 이름으로 검색", null, null, null, "\ue7ba"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
list.Add(new LauncherItem("서비스 조회 실패", ex.Message, null, null, null, "\uea39"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
ServiceController[] services = ServiceController.GetServices();
|
||||
IEnumerable<ServiceController> enumerable = (string.IsNullOrWhiteSpace(q) ? (from s in services
|
||||
where s.Status == ServiceControllerStatus.Running
|
||||
orderby s.DisplayName
|
||||
select s).Take(20) : (from s in services
|
||||
where s.ServiceName.Contains(q, StringComparison.OrdinalIgnoreCase) || s.DisplayName.Contains(q, StringComparison.OrdinalIgnoreCase)
|
||||
orderby s.DisplayName
|
||||
select s).Take(20));
|
||||
list.Add(new LauncherItem("AX 클립보드 히스토리 서비스 재시작", "svc restart clipboard · 클립보드 감지 문제 시 사용", null, "__RESTART_CLIPBOARD__", null, "\ue777"));
|
||||
foreach (ServiceController item in enumerable)
|
||||
{
|
||||
string text4 = StatusText(item.Status);
|
||||
string symbol = ((item.Status == ServiceControllerStatus.Running) ? "\ue73e" : "\ue711");
|
||||
list.Add(new LauncherItem("[" + text4 + "] " + item.DisplayName, item.ServiceName + " · svc start/stop/restart " + item.ServiceName, null, item.ServiceName, null, symbol));
|
||||
}
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
list.Add(new LauncherItem("서비스 조회 실패", ex2.Message, null, null, null, "\uea39"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is string text && text == "__RESTART_CLIPBOARD__")
|
||||
{
|
||||
RestartClipboardService();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (!(item.Data is (string, string) tuple))
|
||||
{
|
||||
object data = item.Data;
|
||||
string name = data as string;
|
||||
if (name != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Application current = Application.Current;
|
||||
if (current != null)
|
||||
{
|
||||
((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
Clipboard.SetText(name);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
var (action, svcName) = tuple;
|
||||
return ExecuteServiceAction(action, svcName);
|
||||
}
|
||||
|
||||
private void RestartClipboardService()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_clipboardService == null)
|
||||
{
|
||||
NotificationService.Notify("AX Copilot", "클립보드 서비스 참조를 찾을 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
_clipboardService.Dispose();
|
||||
Application current = Application.Current;
|
||||
if (current == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
((DispatcherObject)current).Dispatcher.BeginInvoke((Delegate)(Action)delegate
|
||||
{
|
||||
try
|
||||
{
|
||||
_clipboardService.Reinitialize();
|
||||
NotificationService.Notify("AX Copilot", "클립보드 히스토리 서비스가 재시작되었습니다.");
|
||||
LogService.Info("클립보드 히스토리 서비스 강제 재시작 완료");
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
NotificationService.Notify("AX Copilot", "클립보드 재시작 실패: " + ex2.Message);
|
||||
LogService.Error("클립보드 재시작 실패: " + ex2.Message);
|
||||
}
|
||||
}, (DispatcherPriority)4, Array.Empty<object>());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NotificationService.Notify("AX Copilot", "클립보드 재시작 실패: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task ExecuteServiceAction(string action, string svcName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string text = action switch
|
||||
{
|
||||
"__START__" => "start",
|
||||
"__STOP__" => "stop",
|
||||
"__RESTART__" => "stop",
|
||||
_ => null,
|
||||
};
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string verb = text;
|
||||
if (verb != null)
|
||||
{
|
||||
ProcessStartInfo psi = new ProcessStartInfo("sc.exe", verb + " \"" + svcName + "\"")
|
||||
{
|
||||
Verb = "runas",
|
||||
UseShellExecute = true,
|
||||
CreateNoWindow = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden
|
||||
};
|
||||
Process.Start(psi)?.WaitForExit(5000);
|
||||
if (action == "__RESTART__")
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
ProcessStartInfo startPsi = new ProcessStartInfo("sc.exe", "start \"" + svcName + "\"")
|
||||
{
|
||||
Verb = "runas",
|
||||
UseShellExecute = true,
|
||||
CreateNoWindow = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden
|
||||
};
|
||||
Process.Start(startPsi)?.WaitForExit(5000);
|
||||
}
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
text = action switch
|
||||
{
|
||||
"__START__" => "시작",
|
||||
"__STOP__" => "중지",
|
||||
"__RESTART__" => "재시작",
|
||||
_ => action,
|
||||
};
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string label = text;
|
||||
NotificationService.Notify("서비스 관리", svcName + " " + label + " 요청 완료");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Exception ex2 = ex;
|
||||
NotificationService.Notify("AX Copilot", "서비스 제어 실패: " + ex2.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static string StatusText(ServiceControllerStatus status)
|
||||
{
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string result = status switch
|
||||
{
|
||||
ServiceControllerStatus.Running => "실행 중",
|
||||
ServiceControllerStatus.Stopped => "중지됨",
|
||||
ServiceControllerStatus.StartPending => "시작 중",
|
||||
ServiceControllerStatus.StopPending => "중지 중",
|
||||
ServiceControllerStatus.Paused => "일시 중지",
|
||||
ServiceControllerStatus.ContinuePending => "재개 중",
|
||||
ServiceControllerStatus.PausePending => "일시 중지 중",
|
||||
_ => status.ToString(),
|
||||
};
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
194
.decompiledproj/AxCopilot/Handlers/SnapHandler.cs
Normal file
194
.decompiledproj/AxCopilot/Handlers/SnapHandler.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class SnapHandler : IActionHandler
|
||||
{
|
||||
private struct RECT
|
||||
{
|
||||
public int left;
|
||||
|
||||
public int top;
|
||||
|
||||
public int right;
|
||||
|
||||
public int bottom;
|
||||
}
|
||||
|
||||
private struct MONITORINFO
|
||||
{
|
||||
public int cbSize;
|
||||
|
||||
public RECT rcMonitor;
|
||||
|
||||
public RECT rcWork;
|
||||
|
||||
public uint dwFlags;
|
||||
}
|
||||
|
||||
private const uint SWP_SHOWWINDOW = 64u;
|
||||
|
||||
private const uint SWP_NOZORDER = 4u;
|
||||
|
||||
private const uint MONITOR_DEFAULTTONEAREST = 2u;
|
||||
|
||||
private const int SW_RESTORE = 9;
|
||||
|
||||
private const int SW_MAXIMIZE = 3;
|
||||
|
||||
private static readonly (string Key, string Label, string Desc)[] _snapOptions = new(string, string, string)[20]
|
||||
{
|
||||
("left", "왼쪽 절반", "화면 왼쪽 50% 영역에 배치"),
|
||||
("right", "오른쪽 절반", "화면 오른쪽 50% 영역에 배치"),
|
||||
("top", "위쪽 절반", "화면 위쪽 50% 영역에 배치"),
|
||||
("bottom", "아래쪽 절반", "화면 아래쪽 50% 영역에 배치"),
|
||||
("tl", "좌상단 1/4", "화면 좌상단 25% 영역에 배치"),
|
||||
("tr", "우상단 1/4", "화면 우상단 25% 영역에 배치"),
|
||||
("bl", "좌하단 1/4", "화면 좌하단 25% 영역에 배치"),
|
||||
("br", "우하단 1/4", "화면 우하단 25% 영역에 배치"),
|
||||
("l-rt", "좌반 + 우상", "왼쪽 50% + 오른쪽 상단 25% (2창용)"),
|
||||
("l-rb", "좌반 + 우하", "왼쪽 50% + 오른쪽 하단 25% (2창용)"),
|
||||
("r-lt", "우반 + 좌상", "오른쪽 50% + 왼쪽 상단 25% (2창용)"),
|
||||
("r-lb", "우반 + 좌하", "오른쪽 50% + 왼쪽 하단 25% (2창용)"),
|
||||
("third-l", "좌측 1/3", "화면 왼쪽 33% 영역에 배치"),
|
||||
("third-c", "중앙 1/3", "화면 가운데 33% 영역에 배치"),
|
||||
("third-r", "우측 1/3", "화면 오른쪽 33% 영역에 배치"),
|
||||
("two3-l", "좌측 2/3", "화면 왼쪽 66% 영역에 배치"),
|
||||
("two3-r", "우측 2/3", "화면 오른쪽 66% 영역에 배치"),
|
||||
("full", "전체 화면", "최대화"),
|
||||
("center", "화면 중앙", "화면 중앙 80% 크기로 배치"),
|
||||
("restore", "원래 크기 복원", "창을 이전 크기로 복원")
|
||||
};
|
||||
|
||||
public string? Prefix => "snap";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("WindowSnap", "창 배치 — 2/3/4분할, 1/3·2/3, 전체화면, 중앙, 복원", "1.1", "AX");
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetWindowPos(nint hWnd, nint hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool ShowWindow(nint hWnd, int nCmdShow);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern nint MonitorFromWindow(nint hwnd, uint dwFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetMonitorInfo(nint hMonitor, ref MONITORINFO lpmi);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsWindow(nint hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsIconic(nint hWnd);
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string q = query.Trim().ToLowerInvariant();
|
||||
nint previousWindow = WindowTracker.PreviousWindow;
|
||||
bool flag = previousWindow != IntPtr.Zero && IsWindow(previousWindow);
|
||||
string hint = (flag ? "Enter로 현재 활성 창에 적용" : "대상 창 없음 — 런처를 열기 전 창에 적용됩니다");
|
||||
IEnumerable<(string, string, string)> enumerable;
|
||||
if (!string.IsNullOrWhiteSpace(q))
|
||||
{
|
||||
enumerable = _snapOptions.Where(((string Key, string Label, string Desc) o) => o.Key.StartsWith(q) || o.Label.Contains(q));
|
||||
}
|
||||
else
|
||||
{
|
||||
IEnumerable<(string, string, string)> snapOptions = _snapOptions;
|
||||
enumerable = snapOptions;
|
||||
}
|
||||
IEnumerable<(string, string, string)> source = enumerable;
|
||||
List<LauncherItem> list = source.Select<(string, string, string), LauncherItem>(((string Key, string Label, string Desc) o) => new LauncherItem(o.Label, o.Desc + " · " + hint, null, o.Key, null, "\ue8a0")).ToList();
|
||||
if (!list.Any())
|
||||
{
|
||||
list.Add(new LauncherItem("알 수 없는 스냅 방향: " + q, "left / right / tl / tr / bl / br / third-l/c/r / two3-l/r / full / center / restore", null, null, null, "\ue7ba"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
if (!(data is string snapKey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
nint hwnd = WindowTracker.PreviousWindow;
|
||||
if (hwnd != IntPtr.Zero && IsWindow(hwnd))
|
||||
{
|
||||
if (IsIconic(hwnd))
|
||||
{
|
||||
ShowWindow(hwnd, 9);
|
||||
}
|
||||
await Task.Delay(80, ct);
|
||||
ApplySnap(hwnd, snapKey);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplySnap(nint hwnd, string key)
|
||||
{
|
||||
nint hMonitor = MonitorFromWindow(hwnd, 2u);
|
||||
MONITORINFO lpmi = new MONITORINFO
|
||||
{
|
||||
cbSize = Marshal.SizeOf<MONITORINFO>()
|
||||
};
|
||||
if (!GetMonitorInfo(hMonitor, ref lpmi))
|
||||
{
|
||||
return;
|
||||
}
|
||||
RECT rcWork = lpmi.rcWork;
|
||||
int num = rcWork.right - rcWork.left;
|
||||
int num2 = rcWork.bottom - rcWork.top;
|
||||
int left = rcWork.left;
|
||||
int top = rcWork.top;
|
||||
if (key == "full")
|
||||
{
|
||||
ShowWindow(hwnd, 3);
|
||||
return;
|
||||
}
|
||||
if (key == "restore")
|
||||
{
|
||||
ShowWindow(hwnd, 9);
|
||||
return;
|
||||
}
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
(int, int, int, int) tuple = key switch
|
||||
{
|
||||
"left" => (left, top, num / 2, num2),
|
||||
"right" => (left + num / 2, top, num / 2, num2),
|
||||
"top" => (left, top, num, num2 / 2),
|
||||
"bottom" => (left, top + num2 / 2, num, num2 / 2),
|
||||
"tl" => (left, top, num / 2, num2 / 2),
|
||||
"tr" => (left + num / 2, top, num / 2, num2 / 2),
|
||||
"bl" => (left, top + num2 / 2, num / 2, num2 / 2),
|
||||
"br" => (left + num / 2, top + num2 / 2, num / 2, num2 / 2),
|
||||
"l-rt" => (left + num / 2, top, num / 2, num2 / 2),
|
||||
"l-rb" => (left + num / 2, top + num2 / 2, num / 2, num2 / 2),
|
||||
"r-lt" => (left, top, num / 2, num2 / 2),
|
||||
"r-lb" => (left, top + num2 / 2, num / 2, num2 / 2),
|
||||
"third-l" => (left, top, num / 3, num2),
|
||||
"third-c" => (left + num / 3, top, num / 3, num2),
|
||||
"third-r" => (left + num * 2 / 3, top, num / 3, num2),
|
||||
"two3-l" => (left, top, num * 2 / 3, num2),
|
||||
"two3-r" => (left + num / 3, top, num * 2 / 3, num2),
|
||||
"center" => (left + num / 10, top + num2 / 10, num * 8 / 10, num2 * 8 / 10),
|
||||
_ => (left, top, num, num2),
|
||||
};
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
var (x, y, cx, cy) = tuple;
|
||||
ShowWindow(hwnd, 9);
|
||||
SetWindowPos(hwnd, IntPtr.Zero, x, y, cx, cy, 68u);
|
||||
}
|
||||
}
|
||||
164
.decompiledproj/AxCopilot/Handlers/SnippetHandler.cs
Normal file
164
.decompiledproj/AxCopilot/Handlers/SnippetHandler.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class SnippetHandler : IActionHandler
|
||||
{
|
||||
private struct INPUT
|
||||
{
|
||||
public uint type;
|
||||
|
||||
public InputUnion u;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private struct InputUnion
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public KEYBDINPUT ki;
|
||||
}
|
||||
|
||||
private struct KEYBDINPUT
|
||||
{
|
||||
public ushort wVk;
|
||||
|
||||
public ushort wScan;
|
||||
|
||||
public uint dwFlags;
|
||||
|
||||
public uint time;
|
||||
|
||||
public nint dwExtraInfo;
|
||||
}
|
||||
|
||||
private readonly SettingsService _settings;
|
||||
|
||||
public string? Prefix => ";";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Snippets", "텍스트 스니펫 — ; 뒤에 키워드 입력", "1.0", "AX");
|
||||
|
||||
public SnippetHandler(SettingsService settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
List<SnippetEntry> snippets = _settings.Settings.Snippets;
|
||||
if (!snippets.Any())
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("등록된 스니펫이 없습니다", "설정 → 스니펫 탭에서 추가하세요", null, null, null, "\ue70b")));
|
||||
}
|
||||
string q = query.Trim().ToLowerInvariant();
|
||||
List<LauncherItem> list = (from s in snippets
|
||||
where string.IsNullOrEmpty(q) || s.Key.ToLowerInvariant().Contains(q) || s.Name.ToLowerInvariant().Contains(q)
|
||||
select new LauncherItem((s.Name.Length > 0) ? s.Name : s.Key, TruncatePreview(s.Content), null, s, null, "\ue70b")).ToList();
|
||||
if (list.Count == 0)
|
||||
{
|
||||
list.Add(new LauncherItem("'" + query + "'에 해당하는 스니펫 없음", "설정에서 새 스니펫을 추가하세요", null, null, null, "\ue70b"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (!(item.Data is SnippetEntry snippetEntry))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
try
|
||||
{
|
||||
string text = ExpandVariables(snippetEntry.Content);
|
||||
Clipboard.SetText(text);
|
||||
Task.Delay(100, ct).ContinueWith(delegate
|
||||
{
|
||||
((DispatcherObject)Application.Current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
INPUT[] array = new INPUT[4]
|
||||
{
|
||||
new INPUT
|
||||
{
|
||||
type = 1u,
|
||||
u = new InputUnion
|
||||
{
|
||||
ki = new KEYBDINPUT
|
||||
{
|
||||
wVk = 17
|
||||
}
|
||||
}
|
||||
},
|
||||
new INPUT
|
||||
{
|
||||
type = 1u,
|
||||
u = new InputUnion
|
||||
{
|
||||
ki = new KEYBDINPUT
|
||||
{
|
||||
wVk = 86
|
||||
}
|
||||
}
|
||||
},
|
||||
new INPUT
|
||||
{
|
||||
type = 1u,
|
||||
u = new InputUnion
|
||||
{
|
||||
ki = new KEYBDINPUT
|
||||
{
|
||||
wVk = 86,
|
||||
dwFlags = 2u
|
||||
}
|
||||
}
|
||||
},
|
||||
new INPUT
|
||||
{
|
||||
type = 1u,
|
||||
u = new InputUnion
|
||||
{
|
||||
ki = new KEYBDINPUT
|
||||
{
|
||||
wVk = 17,
|
||||
dwFlags = 2u
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
SendInput((uint)array.Length, array, Marshal.SizeOf<INPUT>());
|
||||
});
|
||||
}, ct, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("스니펫 실행 실패: " + ex.Message);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static string ExpandVariables(string content)
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
return content.Replace("{date}", now.ToString("yyyy-MM-dd")).Replace("{time}", now.ToString("HH:mm:ss")).Replace("{datetime}", now.ToString("yyyy-MM-dd HH:mm:ss"))
|
||||
.Replace("{year}", now.Year.ToString())
|
||||
.Replace("{month}", now.Month.ToString("D2"))
|
||||
.Replace("{day}", now.Day.ToString("D2"));
|
||||
}
|
||||
|
||||
private static string TruncatePreview(string content)
|
||||
{
|
||||
string text = content.Replace("\r\n", " ").Replace("\n", " ").Replace("\r", " ");
|
||||
return (text.Length > 60) ? (text.Substring(0, 57) + "…") : text;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
|
||||
}
|
||||
25
.decompiledproj/AxCopilot/Handlers/StarInfoHandler.cs
Normal file
25
.decompiledproj/AxCopilot/Handlers/StarInfoHandler.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.SDK;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class StarInfoHandler : IActionHandler
|
||||
{
|
||||
private readonly SystemInfoHandler _inner = new SystemInfoHandler();
|
||||
|
||||
public string? Prefix => "*";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("StarInfo", "시스템 정보 빠른 조회 — * 단축키 (info와 동일)", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
return _inner.GetItemsAsync(query, ct);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
return _inner.ExecuteAsync(item, ct);
|
||||
}
|
||||
}
|
||||
270
.decompiledproj/AxCopilot/Handlers/SystemCommandHandler.cs
Normal file
270
.decompiledproj/AxCopilot/Handlers/SystemCommandHandler.cs
Normal file
@@ -0,0 +1,270 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
using AxCopilot.Views;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class SystemCommandHandler : IActionHandler
|
||||
{
|
||||
private readonly SettingsService _settings;
|
||||
|
||||
private static readonly Regex _timerRe = new Regex("^(?:(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?)$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex _alarmRe = new Regex("^(\\d{1,2}):(\\d{2})$", RegexOptions.Compiled);
|
||||
|
||||
public string? Prefix => "/";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("SystemCommands", "시스템 명령 — / 뒤에 명령 입력", "1.0", "AX");
|
||||
|
||||
public SystemCommandHandler(SettingsService settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
SystemCommandSettings cfg = _settings.Settings.SystemCommands;
|
||||
string q = query.Trim().ToLowerInvariant();
|
||||
if (q.StartsWith("timer"))
|
||||
{
|
||||
string text = q;
|
||||
string text2 = text.Substring(5, text.Length - 5).Trim();
|
||||
List<LauncherItem> list = new List<LauncherItem>();
|
||||
if (string.IsNullOrEmpty(text2))
|
||||
{
|
||||
list.Add(new LauncherItem("타이머 — 시간을 입력하세요", "예: /timer 5m · /timer 1h30m · /timer 30s 회의", null, null, null, "\ue916"));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
string[] array = text2.Split(' ', 2);
|
||||
string s = array[0];
|
||||
string label = ((array.Length > 1) ? array[1].Trim() : "");
|
||||
if (TryParseTimer(s, out var seconds) && seconds > 0)
|
||||
{
|
||||
string display = FormatDuration(seconds);
|
||||
string title = (string.IsNullOrEmpty(label) ? ("타이머 " + display) : ("타이머 " + display + " — " + label));
|
||||
list.Add(new LauncherItem(title, display + " 후 알림 · Enter로 시작", null, (Func<Task>)(() => StartTimerAsync(seconds, (label.Length > 0) ? label : display)), null, "\ue916"));
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(new LauncherItem("형식 오류", "예: /timer 5m · /timer 1h30m · /timer 90s", null, null, null, "\uea39"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
if (q.StartsWith("alarm"))
|
||||
{
|
||||
string text = q;
|
||||
string text3 = text.Substring(5, text.Length - 5).Trim();
|
||||
List<LauncherItem> list2 = new List<LauncherItem>();
|
||||
if (string.IsNullOrEmpty(text3))
|
||||
{
|
||||
list2.Add(new LauncherItem("알람 — 시각을 입력하세요", "예: /alarm 14:30 · /alarm 9:00", null, null, null, "\ue916"));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list2);
|
||||
}
|
||||
Match match = _alarmRe.Match(text3.Split(' ')[0]);
|
||||
if (match.Success)
|
||||
{
|
||||
int num = int.Parse(match.Groups[1].Value);
|
||||
int num2 = int.Parse(match.Groups[2].Value);
|
||||
object obj;
|
||||
if (!text3.Contains(' '))
|
||||
{
|
||||
obj = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
text = text3;
|
||||
int num3 = text3.IndexOf(' ') + 1;
|
||||
obj = text.Substring(num3, text.Length - num3);
|
||||
}
|
||||
string label2 = (string)obj;
|
||||
if (num >= 0 && num <= 23 && num2 >= 0 && num2 <= 59)
|
||||
{
|
||||
DateTime dateTime = DateTime.Today.AddHours(num).AddMinutes(num2);
|
||||
if (dateTime <= DateTime.Now)
|
||||
{
|
||||
dateTime = dateTime.AddDays(1.0);
|
||||
}
|
||||
int diff = (int)(dateTime - DateTime.Now).TotalSeconds;
|
||||
string timeStr = dateTime.ToString("HH:mm");
|
||||
list2.Add(new LauncherItem("알람 " + timeStr + " " + ((dateTime.Date > DateTime.Today) ? "(내일)" : ""), (FormatDuration(diff) + " 후 알림 · Enter로 시작 " + ((label2.Length > 0) ? ("— " + label2) : "")).Trim(), null, (Func<Task>)(() => StartTimerAsync(diff, (label2.Length > 0) ? label2 : (timeStr + " 알람"))), null, "\ue916"));
|
||||
}
|
||||
else
|
||||
{
|
||||
list2.Add(new LauncherItem("시각 범위 오류", "00:00 ~ 23:59 범위로 입력하세요", null, null, null, "\uea39"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
list2.Add(new LauncherItem("형식 오류", "예: /alarm 14:30 · /alarm 09:00", null, null, null, "\uea39"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list2);
|
||||
}
|
||||
List<(string, string, string, string, bool, Func<Task>)> list3 = new List<(string, string, string, string, bool, Func<Task>)>();
|
||||
list3.Add(("lock", "화면 잠금", "현재 세션을 잠급니다", "\ue72e", cfg.ShowLock, LockAsync));
|
||||
list3.Add(("sleep", "절전 모드", "시스템을 절전 상태로 전환합니다", "\uec46", cfg.ShowSleep, SleepAsync));
|
||||
list3.Add(("restart", "재시작", "컴퓨터를 재시작합니다", "\ue777", cfg.ShowRestart, RestartAsync));
|
||||
list3.Add(("shutdown", "시스템 종료", "컴퓨터를 종료합니다", "\ue7e8", cfg.ShowShutdown, ShutdownAsync));
|
||||
list3.Add(("hibernate", "최대 절전", "최대 절전 모드로 전환합니다", "\uec46", cfg.ShowHibernate, HibernateAsync));
|
||||
list3.Add(("logout", "로그아웃", "현재 사용자 세션에서 로그아웃합니다", "\uf3b1", cfg.ShowLogout, LogoutAsync));
|
||||
list3.Add(("recycle", "휴지통 비우기", "휴지통의 모든 파일을 영구 삭제합니다", "\ue74d", cfg.ShowRecycleBin, EmptyRecycleBinAsync));
|
||||
list3.Add(("dock", "독 바 표시/숨기기", "화면 하단 독 바를 표시하거나 숨깁니다", "\ue8a0", true, ToggleDockAsync));
|
||||
List<(string, string, string, string, bool, Func<Task>)> source = list3;
|
||||
List<string> value;
|
||||
IEnumerable<LauncherItem> source2 = from c in source
|
||||
where c.Enabled
|
||||
where string.IsNullOrEmpty(q) || c.Key.StartsWith(q) || c.Name.Contains(q) || (cfg.CommandAliases.TryGetValue(c.Key, out value) && value.Any((string a) => a.StartsWith(q, StringComparison.OrdinalIgnoreCase)))
|
||||
select new LauncherItem(c.Name, c.Hint, null, c.Action, null, c.Symbol);
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)source2.ToList());
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
Func<Task> action = data as Func<Task>;
|
||||
if (action != null)
|
||||
{
|
||||
await action();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task StartTimerAsync(int totalSeconds, string label)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(totalSeconds));
|
||||
NotificationService.Notify("⏰ 타이머 완료", label);
|
||||
}
|
||||
|
||||
private static bool TryParseTimer(string s, out int totalSeconds)
|
||||
{
|
||||
totalSeconds = 0;
|
||||
Match match = _timerRe.Match(s.ToLowerInvariant());
|
||||
if (!match.Success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int num = (match.Groups[1].Success ? int.Parse(match.Groups[1].Value) : 0);
|
||||
int num2 = (match.Groups[2].Success ? int.Parse(match.Groups[2].Value) : 0);
|
||||
int num3 = (match.Groups[3].Success ? int.Parse(match.Groups[3].Value) : 0);
|
||||
totalSeconds = num * 3600 + num2 * 60 + num3;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string FormatDuration(int totalSeconds)
|
||||
{
|
||||
int num = totalSeconds / 3600;
|
||||
int num2 = totalSeconds % 3600 / 60;
|
||||
int num3 = totalSeconds % 60;
|
||||
if (num > 0 && num2 > 0)
|
||||
{
|
||||
return $"{num}시간 {num2}분";
|
||||
}
|
||||
if (num <= 0)
|
||||
{
|
||||
if (num2 > 0 && num3 > 0)
|
||||
{
|
||||
return $"{num2}분 {num3}초";
|
||||
}
|
||||
if (num2 <= 0)
|
||||
{
|
||||
return $"{num3}초";
|
||||
}
|
||||
return $"{num2}분";
|
||||
}
|
||||
return $"{num}시간";
|
||||
}
|
||||
|
||||
private static Task LockAsync()
|
||||
{
|
||||
LockWorkStation();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static Task SleepAsync()
|
||||
{
|
||||
System.Windows.Forms.Application.SetSuspendState(PowerState.Suspend, force: false, disableWakeEvent: false);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static Task HibernateAsync()
|
||||
{
|
||||
System.Windows.Forms.Application.SetSuspendState(PowerState.Hibernate, force: false, disableWakeEvent: false);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static Task RestartAsync()
|
||||
{
|
||||
MessageBoxResult messageBoxResult = CustomMessageBox.Show("컴퓨터를 재시작하시겠습니까?\n저장되지 않은 작업이 있으면 먼저 저장하세요.", "재시작 확인", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
if (messageBoxResult == MessageBoxResult.Yes)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("shutdown", "/r /t 5 /c \"AX Copilot에 의해 재시작됩니다.\"")
|
||||
{
|
||||
UseShellExecute = true,
|
||||
CreateNoWindow = true
|
||||
});
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static Task ShutdownAsync()
|
||||
{
|
||||
MessageBoxResult messageBoxResult = CustomMessageBox.Show("컴퓨터를 종료하시겠습니까?\n저장되지 않은 작업이 있으면 먼저 저장하세요.", "종료 확인", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
if (messageBoxResult == MessageBoxResult.Yes)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("shutdown", "/s /t 5 /c \"AX Copilot에 의해 종료됩니다.\"")
|
||||
{
|
||||
UseShellExecute = true,
|
||||
CreateNoWindow = true
|
||||
});
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static Task LogoutAsync()
|
||||
{
|
||||
MessageBoxResult messageBoxResult = CustomMessageBox.Show("로그아웃하시겠습니까?", "로그아웃 확인", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
if (messageBoxResult == MessageBoxResult.Yes)
|
||||
{
|
||||
ExitWindowsEx(0u, 0u);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static Task EmptyRecycleBinAsync()
|
||||
{
|
||||
MessageBoxResult messageBoxResult = CustomMessageBox.Show("휴지통을 비우시겠습니까?\n삭제된 파일은 복구할 수 없습니다.", "휴지통 비우기", MessageBoxButton.YesNo, MessageBoxImage.Exclamation);
|
||||
if (messageBoxResult == MessageBoxResult.Yes)
|
||||
{
|
||||
SHEmptyRecycleBin(IntPtr.Zero, null, 7u);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static Task ToggleDockAsync()
|
||||
{
|
||||
((DispatcherObject)System.Windows.Application.Current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
(System.Windows.Application.Current as App)?.ToggleDockBar();
|
||||
});
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern bool LockWorkStation();
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern bool ExitWindowsEx(uint uFlags, uint dwReason);
|
||||
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern uint SHEmptyRecycleBin(nint hwnd, string? pszRootPath, uint dwFlags);
|
||||
}
|
||||
462
.decompiledproj/AxCopilot/Handlers/SystemInfoHandler.cs
Normal file
462
.decompiledproj/AxCopilot/Handlers/SystemInfoHandler.cs
Normal file
@@ -0,0 +1,462 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
using AxCopilot.Views;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class SystemInfoHandler : IActionHandler
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
private struct MEMORYSTATUSEX
|
||||
{
|
||||
public uint dwLength;
|
||||
|
||||
public uint dwMemoryLoad;
|
||||
|
||||
public ulong ullTotalPhys;
|
||||
|
||||
public ulong ullAvailPhys;
|
||||
|
||||
public ulong ullTotalPageFile;
|
||||
|
||||
public ulong ullAvailPageFile;
|
||||
|
||||
public ulong ullTotalVirtual;
|
||||
|
||||
public ulong ullAvailVirtual;
|
||||
|
||||
public ulong ullAvailExtendedVirtual;
|
||||
}
|
||||
|
||||
internal sealed record InfoAction(string Type, string? Payload = null);
|
||||
|
||||
private static volatile PerformanceCounter? _cpuCounter;
|
||||
|
||||
private static float _cpuCached;
|
||||
|
||||
private static DateTime _cpuUpdated;
|
||||
|
||||
private static readonly object _cpuLock;
|
||||
|
||||
public string? Prefix => "info";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("SystemInfo", "시스템 정보 — IP, 배터리, 볼륨, 가동시간 등", "1.0", "AX");
|
||||
|
||||
[DllImport("winmm.dll")]
|
||||
private static extern int waveOutGetVolume(nint hwo, out uint dwVolume);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
|
||||
|
||||
static SystemInfoHandler()
|
||||
{
|
||||
_cpuUpdated = DateTime.MinValue;
|
||||
_cpuLock = new object();
|
||||
Task.Run(delegate
|
||||
{
|
||||
try
|
||||
{
|
||||
PerformanceCounter performanceCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
|
||||
performanceCounter.NextValue();
|
||||
_cpuCounter = performanceCounter;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static float GetCpuUsage()
|
||||
{
|
||||
try
|
||||
{
|
||||
PerformanceCounter cpuCounter = _cpuCounter;
|
||||
if (cpuCounter == null)
|
||||
{
|
||||
return -1f;
|
||||
}
|
||||
lock (_cpuLock)
|
||||
{
|
||||
if ((DateTime.Now - _cpuUpdated).TotalMilliseconds > 800.0)
|
||||
{
|
||||
_cpuCached = cpuCounter.NextValue();
|
||||
_cpuUpdated = DateTime.Now;
|
||||
}
|
||||
return _cpuCached;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return -1f;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string text = query.Trim().ToLowerInvariant();
|
||||
List<LauncherItem> list = new List<LauncherItem>();
|
||||
try
|
||||
{
|
||||
bool flag = string.IsNullOrEmpty(text);
|
||||
bool flag2 = flag || text.Contains("ip") || text.Contains("네트워크") || text.Contains("network");
|
||||
bool flag3 = flag || text.Contains("bat") || text.Contains("배터리") || text.Contains("battery");
|
||||
bool flag4 = flag || text.Contains("vol") || text.Contains("볼륨") || text.Contains("volume");
|
||||
bool flag5 = flag || text.Contains("up") || text.Contains("가동") || text.Contains("uptime");
|
||||
bool flag6 = flag || text.Contains("sys") || text.Contains("시스템") || text.Contains("system") || text.Contains("host") || text.Contains("호스트") || text.Contains("os");
|
||||
bool flag7 = flag || text.Contains("user") || text.Contains("사용자");
|
||||
bool flag8 = flag || text.Contains("cpu") || text.Contains("프로세서") || text.Contains("processor");
|
||||
bool flag9 = flag || text.Contains("ram") || text.Contains("메모리") || text.Contains("memory");
|
||||
bool flag10 = flag || text.Contains("disk") || text.Contains("디스크") || text.Contains("storage") || text.Contains("저장");
|
||||
bool flag11 = flag || text.Contains("screen") || text.Contains("화면") || text.Contains("resolution") || text.Contains("display");
|
||||
if (flag6)
|
||||
{
|
||||
list.Add(new LauncherItem("컴퓨터: " + Environment.MachineName, "OS: " + GetOsVersion() + " · Enter로 시스템 정보 열기", null, new InfoAction("shell", "msinfo32"), null, "\ue7f4"));
|
||||
}
|
||||
if (flag7)
|
||||
{
|
||||
string text2 = Environment.UserDomainName + "\\" + Environment.UserName;
|
||||
list.Add(new LauncherItem("사용자: " + Environment.UserName, text2 + " · Enter로 사용자 계정 설정 열기", null, new InfoAction("shell", "netplwiz"), null, "\ue77b"));
|
||||
}
|
||||
if (flag2)
|
||||
{
|
||||
string localIpAddress = GetLocalIpAddress();
|
||||
if (localIpAddress != null)
|
||||
{
|
||||
list.Add(new LauncherItem("로컬 IP: " + localIpAddress, "LAN / Wi-Fi 주소 · Enter로 네트워크 설정 열기", null, new InfoAction("ms_settings", "ms-settings:network"), null, "\ue968"));
|
||||
}
|
||||
string defaultGateway = GetDefaultGateway();
|
||||
if (defaultGateway != null)
|
||||
{
|
||||
list.Add(new LauncherItem("게이트웨이: " + defaultGateway, "기본 게이트웨이 · Enter로 네트워크 설정 열기", null, new InfoAction("ms_settings", "ms-settings:network"), null, "\ue968"));
|
||||
}
|
||||
}
|
||||
if (flag3)
|
||||
{
|
||||
LauncherItem batteryItem = GetBatteryItem();
|
||||
if (batteryItem != null)
|
||||
{
|
||||
list.Add(batteryItem);
|
||||
}
|
||||
}
|
||||
if (flag4)
|
||||
{
|
||||
LauncherItem volumeItem = GetVolumeItem();
|
||||
if (volumeItem != null)
|
||||
{
|
||||
list.Add(volumeItem);
|
||||
}
|
||||
}
|
||||
if (flag5)
|
||||
{
|
||||
TimeSpan timeSpan = TimeSpan.FromMilliseconds(Environment.TickCount64);
|
||||
string text3 = FormatUptime(timeSpan);
|
||||
DateTime value = DateTime.Now - timeSpan;
|
||||
list.Add(MakeItem("가동 시간: " + text3, $"마지막 재시작: {value:MM/dd HH:mm}", text3, "\ue823"));
|
||||
}
|
||||
if (flag8)
|
||||
{
|
||||
float cpuUsage = GetCpuUsage();
|
||||
string title = ((cpuUsage < 0f) ? "CPU: 측정 중…" : $"CPU: {(int)cpuUsage}%");
|
||||
string processorName = GetProcessorName();
|
||||
list.Add(new LauncherItem(title, (string.IsNullOrEmpty(processorName) ? "전체 CPU 사용률" : processorName) + " · Enter로 리소스 모니터 열기", null, new InfoAction("resource_mon"), null, "\ue950"));
|
||||
}
|
||||
if (flag9)
|
||||
{
|
||||
MEMORYSTATUSEX lpBuffer = new MEMORYSTATUSEX
|
||||
{
|
||||
dwLength = (uint)Marshal.SizeOf<MEMORYSTATUSEX>()
|
||||
};
|
||||
if (GlobalMemoryStatusEx(ref lpBuffer))
|
||||
{
|
||||
double value2 = (double)lpBuffer.ullTotalPhys / 1024.0 / 1024.0 / 1024.0;
|
||||
double value3 = (double)(lpBuffer.ullTotalPhys - lpBuffer.ullAvailPhys) / 1024.0 / 1024.0 / 1024.0;
|
||||
uint dwMemoryLoad = lpBuffer.dwMemoryLoad;
|
||||
list.Add(new LauncherItem($"RAM: {value3:F1} / {value2:F1} GB ({dwMemoryLoad}%)", $"사용 중: {value3:F1} GB · 여유: {(double)lpBuffer.ullAvailPhys / 1024.0 / 1024.0 / 1024.0:F1} GB · Enter로 리소스 모니터 열기", null, new InfoAction("resource_mon"), null, "\ue950"));
|
||||
}
|
||||
}
|
||||
if (flag10)
|
||||
{
|
||||
foreach (DriveInfo item in from d in DriveInfo.GetDrives()
|
||||
where d.IsReady && d.DriveType == DriveType.Fixed
|
||||
select d)
|
||||
{
|
||||
double num = (double)item.TotalSize / 1024.0 / 1024.0 / 1024.0;
|
||||
double num2 = (double)item.AvailableFreeSpace / 1024.0 / 1024.0 / 1024.0;
|
||||
double num3 = num - num2;
|
||||
int value4 = (int)(num3 / num * 100.0);
|
||||
string value5 = (string.IsNullOrWhiteSpace(item.VolumeLabel) ? item.Name.TrimEnd('\\') : item.VolumeLabel);
|
||||
list.Add(new LauncherItem($"드라이브 {item.Name.TrimEnd('\\')} ({value5}) — {num3:F0} / {num:F0} GB ({value4}%)", $"여유 공간: {num2:F1} GB · Enter로 탐색기 열기", null, new InfoAction("open_drive", item.RootDirectory.FullName), null, "\ueda2"));
|
||||
}
|
||||
}
|
||||
if (flag11)
|
||||
{
|
||||
double primaryScreenWidth = SystemParameters.PrimaryScreenWidth;
|
||||
double primaryScreenHeight = SystemParameters.PrimaryScreenHeight;
|
||||
list.Add(new LauncherItem($"화면: {(int)primaryScreenWidth} × {(int)primaryScreenHeight}", "기본 모니터 해상도 · Enter로 디스플레이 설정 열기", null, new InfoAction("ms_settings", "ms-settings:display"), null, "\ue7f4"));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("시스템 정보 조회 오류: " + ex.Message);
|
||||
list.Add(new LauncherItem("시스템 정보 조회 실패", ex.Message, null, null, null, "\uea39"));
|
||||
}
|
||||
if (list.Count == 0)
|
||||
{
|
||||
list.Add(new LauncherItem("시스템 정보 없음", "ip · battery · volume · uptime · system 키워드로 검색하세요", null, null, null, "\ue946"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
InfoAction action = data as InfoAction;
|
||||
if ((object)action == null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
switch (action.Type)
|
||||
{
|
||||
case "copy":
|
||||
if (string.IsNullOrWhiteSpace(action.Payload))
|
||||
{
|
||||
break;
|
||||
}
|
||||
try
|
||||
{
|
||||
((DispatcherObject)System.Windows.Application.Current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
System.Windows.Clipboard.SetText(action.Payload);
|
||||
});
|
||||
LogService.Info("클립보드 복사: " + action.Payload);
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
LogService.Warn("클립보드 복사 실패: " + ex2.Message);
|
||||
}
|
||||
break;
|
||||
case "open_drive":
|
||||
if (!string.IsNullOrWhiteSpace(action.Payload))
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo("explorer.exe", action.Payload)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex4)
|
||||
{
|
||||
LogService.Warn("드라이브 열기 실패: " + ex4.Message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "resource_mon":
|
||||
((DispatcherObject)System.Windows.Application.Current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
ResourceMonitorWindow resourceMonitorWindow = System.Windows.Application.Current.Windows.OfType<ResourceMonitorWindow>().FirstOrDefault();
|
||||
if (resourceMonitorWindow != null)
|
||||
{
|
||||
resourceMonitorWindow.Activate();
|
||||
}
|
||||
else
|
||||
{
|
||||
new ResourceMonitorWindow().Show();
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "shell":
|
||||
if (!string.IsNullOrWhiteSpace(action.Payload))
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(action.Payload)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex3)
|
||||
{
|
||||
LogService.Warn("셸 명령 실행 실패: " + ex3.Message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "ms_settings":
|
||||
if (!string.IsNullOrWhiteSpace(action.Payload))
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(action.Payload)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("설정 열기 실패: " + ex.Message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static LauncherItem MakeItem(string title, string subtitle, string? copyValue, string symbol)
|
||||
{
|
||||
return new LauncherItem(title, subtitle, null, (copyValue != null) ? new InfoAction("copy", copyValue) : null, null, symbol);
|
||||
}
|
||||
|
||||
private static string? GetLocalIpAddress()
|
||||
{
|
||||
try
|
||||
{
|
||||
NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
|
||||
foreach (NetworkInterface networkInterface in allNetworkInterfaces)
|
||||
{
|
||||
if (networkInterface.OperationalStatus != OperationalStatus.Up)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
NetworkInterfaceType networkInterfaceType = networkInterface.NetworkInterfaceType;
|
||||
if ((networkInterfaceType == NetworkInterfaceType.Loopback || networkInterfaceType == NetworkInterfaceType.Tunnel) ? true : false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
foreach (UnicastIPAddressInformation unicastAddress in networkInterface.GetIPProperties().UnicastAddresses)
|
||||
{
|
||||
if (unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
return unicastAddress.Address.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string? GetDefaultGateway()
|
||||
{
|
||||
try
|
||||
{
|
||||
NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
|
||||
foreach (NetworkInterface networkInterface in allNetworkInterfaces)
|
||||
{
|
||||
if (networkInterface.OperationalStatus == OperationalStatus.Up)
|
||||
{
|
||||
GatewayIPAddressInformation gatewayIPAddressInformation = networkInterface.GetIPProperties().GatewayAddresses.FirstOrDefault((GatewayIPAddressInformation g) => g.Address.AddressFamily == AddressFamily.InterNetwork);
|
||||
if (gatewayIPAddressInformation != null)
|
||||
{
|
||||
return gatewayIPAddressInformation.Address.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static LauncherItem? GetBatteryItem()
|
||||
{
|
||||
try
|
||||
{
|
||||
PowerStatus powerStatus = SystemInformation.PowerStatus;
|
||||
float batteryLifePercent = powerStatus.BatteryLifePercent;
|
||||
if (batteryLifePercent < 0f)
|
||||
{
|
||||
return new LauncherItem("배터리: 해당 없음", "데스크톱 PC 또는 항상 연결됨 · Enter로 전원 설정 열기", null, new InfoAction("ms_settings", "ms-settings:powersleep"), null, "\ue83f");
|
||||
}
|
||||
int num = (int)(batteryLifePercent * 100f);
|
||||
bool flag = powerStatus.PowerLineStatus == System.Windows.Forms.PowerLineStatus.Online;
|
||||
string text = ((powerStatus.BatteryLifeRemaining >= 0) ? (" · 잔여: " + FormatUptime(TimeSpan.FromSeconds(powerStatus.BatteryLifeRemaining))) : "");
|
||||
string symbol = (flag ? "\ue83e" : ((num > 50) ? "\ue83f" : "\ueba0"));
|
||||
return new LauncherItem($"배터리: {num}%{(flag ? " ⚡ 충전 중" : "")}", "전원: " + (flag ? "AC 연결됨" : "배터리 사용 중") + text + " · Enter로 전원 설정 열기", null, new InfoAction("ms_settings", "ms-settings:powersleep"), null, symbol);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static LauncherItem? GetVolumeItem()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (waveOutGetVolume(IntPtr.Zero, out var dwVolume) == 0)
|
||||
{
|
||||
int num = (int)((double)(dwVolume & 0xFFFF) / 655.35);
|
||||
int num2 = (int)((double)((dwVolume >> 16) & 0xFFFF) / 655.35);
|
||||
int num3 = (num + num2) / 2;
|
||||
return new LauncherItem($"볼륨: {num3}%", $"L: {num}% · R: {num2}% · Enter로 사운드 설정 열기", null, new InfoAction("ms_settings", "ms-settings:sound"), null, (num3 == 0) ? "\ue74f" : "\ue995");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string GetOsVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
using RegistryKey registryKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion");
|
||||
if (registryKey != null)
|
||||
{
|
||||
string text = (registryKey.GetValue("ProductName") as string) ?? "Windows";
|
||||
string text2 = (registryKey.GetValue("CurrentBuildNumber") as string) ?? "";
|
||||
object value = registryKey.GetValue("UBR");
|
||||
return (value != null) ? $"{text} (빌드 {text2}.{value})" : (text + " (빌드 " + text2 + ")");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return Environment.OSVersion.ToString();
|
||||
}
|
||||
|
||||
private static string GetProcessorName()
|
||||
{
|
||||
try
|
||||
{
|
||||
using RegistryKey registryKey = Registry.LocalMachine.OpenSubKey("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0");
|
||||
return (registryKey?.GetValue("ProcessorNameString") as string) ?? "";
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatUptime(TimeSpan t)
|
||||
{
|
||||
if (!(t.TotalDays >= 1.0))
|
||||
{
|
||||
if (!(t.TotalHours >= 1.0))
|
||||
{
|
||||
return $"{t.Minutes}분 {t.Seconds}초";
|
||||
}
|
||||
return $"{t.Hours}시간 {t.Minutes}분";
|
||||
}
|
||||
return $"{(int)t.TotalDays}일 {t.Hours}시간 {t.Minutes}분";
|
||||
}
|
||||
}
|
||||
112
.decompiledproj/AxCopilot/Handlers/TextStatsHandler.cs
Normal file
112
.decompiledproj/AxCopilot/Handlers/TextStatsHandler.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using AxCopilot.SDK;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class TextStatsHandler : IActionHandler
|
||||
{
|
||||
public string? Prefix => "stats";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("TextStats", "텍스트 통계 분석", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string text = null;
|
||||
try
|
||||
{
|
||||
Application current = Application.Current;
|
||||
if (current != null && ((DispatcherObject)current).Dispatcher.Invoke<bool>((Func<bool>)(() => Clipboard.ContainsText())))
|
||||
{
|
||||
text = ((DispatcherObject)Application.Current).Dispatcher.Invoke<string>((Func<string>)(() => Clipboard.GetText()));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("클립보드에 텍스트가 없습니다", "텍스트를 복사한 후 다시 시도하세요", null, null, null, "\ue946")));
|
||||
}
|
||||
string text2 = query.Trim();
|
||||
List<LauncherItem> list = new List<LauncherItem>();
|
||||
int length = text.Length;
|
||||
int length2 = text.Replace(" ", "").Replace("\t", "").Replace("\r", "")
|
||||
.Replace("\n", "")
|
||||
.Length;
|
||||
string[] array = (from w in Regex.Split(text, "\\s+", RegexOptions.None, TimeSpan.FromSeconds(1.0))
|
||||
where !string.IsNullOrWhiteSpace(w)
|
||||
select w).ToArray();
|
||||
int num = array.Length;
|
||||
string[] array2 = text.Split('\n');
|
||||
int value = array2.Length;
|
||||
int value2 = array2.Count((string l) => !string.IsNullOrWhiteSpace(l));
|
||||
int byteCount = Encoding.UTF8.GetByteCount(text);
|
||||
string text3 = $"글자 {length:N0} · 공백 제외 {length2:N0} · 단어 {num:N0} · 줄 {value:N0}";
|
||||
list.Add(new LauncherItem($"글자 수: {length:N0} ({length2:N0} 공백 제외)", $"UTF-8: {byteCount:N0} bytes · Enter로 결과 복사", null, $"글자 수: {length:N0} (공백 제외: {length2:N0})", null, "\ue8d2"));
|
||||
list.Add(new LauncherItem($"단어 수: {num:N0}", "공백·줄바꿈 기준 분리 · Enter로 결과 복사", null, $"단어 수: {num:N0}", null, "\ue8d2"));
|
||||
list.Add(new LauncherItem($"줄 수: {value:N0} (비어있지 않은 줄: {value2:N0})", "Enter로 결과 복사", null, $"줄 수: {value:N0} (비어있지 않은 줄: {value2:N0})", null, "\ue8d2"));
|
||||
int num2 = text.Count((char c) => c >= '가' && c <= '\ud7af');
|
||||
double num3 = ((num2 > length / 2) ? ((double)length2 / 500.0) : ((double)num / 200.0));
|
||||
string text4 = ((num3 < 1.0) ? "1분 미만" : $"약 {Math.Ceiling(num3)}분");
|
||||
list.Add(new LauncherItem("예상 읽기 시간: " + text4, (num2 > length / 2) ? "한국어 기준 (500자/분)" : "영어 기준 (200단어/분)", null, "예상 읽기 시간: " + text4, null, "\ue823"));
|
||||
if (string.IsNullOrWhiteSpace(text2))
|
||||
{
|
||||
IEnumerable<string> values = from g in (from w in array
|
||||
where w.Length >= 2
|
||||
group w by w.ToLowerInvariant() into g
|
||||
orderby g.Count() descending
|
||||
select g).Take(5)
|
||||
select $"{g.Key}({g.Count()})";
|
||||
string text5 = string.Join(", ", values);
|
||||
if (!string.IsNullOrEmpty(text5))
|
||||
{
|
||||
list.Add(new LauncherItem("상위 키워드", text5, null, "상위 키워드: " + text5, null, "\ue721"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
int count = Regex.Matches(text, Regex.Escape(text2), RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1.0)).Count;
|
||||
list.Insert(0, new LauncherItem($"'{text2}' 출현 횟수: {count}회", "대소문자 무시 검색 · Enter로 결과 복사", null, $"'{text2}' 출현 횟수: {count}회", null, "\ue721"));
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
list.Add(new LauncherItem("전체 요약 복사", text3, null, $"[텍스트 통계]\n{text3}\nUTF-8: {byteCount:N0} bytes\n읽기 시간: {text4}", null, "\ue77f"));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
string text = data as string;
|
||||
if (text != null && !string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
try
|
||||
{
|
||||
Application current = Application.Current;
|
||||
if (current != null)
|
||||
{
|
||||
((DispatcherObject)current).Dispatcher.Invoke((Action)delegate
|
||||
{
|
||||
Clipboard.SetText(text);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
208
.decompiledproj/AxCopilot/Handlers/UninstallHandler.cs
Normal file
208
.decompiledproj/AxCopilot/Handlers/UninstallHandler.cs
Normal file
@@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class UninstallHandler : IActionHandler
|
||||
{
|
||||
private static readonly string[] _regPaths = new string[2] { "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall" };
|
||||
|
||||
private static (DateTime At, List<InstalledApp> Apps)? _cache;
|
||||
|
||||
private static readonly TimeSpan CacheTtl = TimeSpan.FromSeconds(30.0);
|
||||
|
||||
public string? Prefix => "uninstall";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("Uninstall", "앱 제거 — uninstall 뒤에 앱 이름 입력", "1.0", "AX");
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string q = query.Trim();
|
||||
if (string.IsNullOrWhiteSpace(q))
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("제거할 앱 이름을 입력하세요", "예: uninstall chrome · uninstall office", null, null, null, "\ue74d")));
|
||||
}
|
||||
List<InstalledApp> installedApps = GetInstalledApps();
|
||||
List<InstalledApp> source = (from a in installedApps
|
||||
where a.Name.Contains(q, StringComparison.OrdinalIgnoreCase) || (a.Publisher?.Contains(q, StringComparison.OrdinalIgnoreCase) ?? false)
|
||||
orderby a.Name
|
||||
select a).Take(12).ToList();
|
||||
if (!source.Any())
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("검색 결과 없음", "'" + q + "'에 해당하는 설치된 앱이 없습니다", null, null, null, "\ue946")));
|
||||
}
|
||||
List<LauncherItem> result = source.Select((InstalledApp a) => new LauncherItem(a.Name, BuildSubtitle(a), null, a, null, "\ue74d")).ToList();
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)result);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (!(item.Data is InstalledApp installedApp))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(installedApp.UninstallString))
|
||||
{
|
||||
LogService.Warn("제거 문자열 없음: " + installedApp.Name);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
try
|
||||
{
|
||||
string text = installedApp.UninstallString.Trim();
|
||||
if (text.StartsWith("msiexec", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string arguments = text.Substring("msiexec".Length).Trim();
|
||||
Process.Start(new ProcessStartInfo("msiexec.exe", arguments)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
string fileName;
|
||||
string arguments2;
|
||||
if (text.StartsWith('"'))
|
||||
{
|
||||
int num = text.IndexOf('"', 1);
|
||||
fileName = ((num > 0) ? text.Substring(1, num - 1) : text);
|
||||
object obj;
|
||||
if (num <= 0)
|
||||
{
|
||||
obj = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
string text2 = text;
|
||||
int num2 = num + 1;
|
||||
obj = text2.Substring(num2, text2.Length - num2).Trim();
|
||||
}
|
||||
arguments2 = (string)obj;
|
||||
}
|
||||
else
|
||||
{
|
||||
int num3 = text.IndexOf(' ');
|
||||
if (num3 > 0)
|
||||
{
|
||||
fileName = text.Substring(0, num3);
|
||||
string text2 = text;
|
||||
int num2 = num3 + 1;
|
||||
arguments2 = text2.Substring(num2, text2.Length - num2);
|
||||
}
|
||||
else
|
||||
{
|
||||
fileName = text;
|
||||
arguments2 = "";
|
||||
}
|
||||
}
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo(fileName, arguments2)
|
||||
{
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(startInfo);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("제거 실행 실패 [" + installedApp.Name + "]: " + ex.Message);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static string BuildSubtitle(InstalledApp a)
|
||||
{
|
||||
List<string> list = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(a.Publisher))
|
||||
{
|
||||
list.Add(a.Publisher);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(a.Version))
|
||||
{
|
||||
list.Add("v" + a.Version);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(a.InstallDate))
|
||||
{
|
||||
list.Add(a.InstallDate);
|
||||
}
|
||||
list.Add("Enter로 제거");
|
||||
return string.Join(" · ", list);
|
||||
}
|
||||
|
||||
private static List<InstalledApp> GetInstalledApps()
|
||||
{
|
||||
if (_cache.HasValue && DateTime.Now - _cache.Value.At < CacheTtl)
|
||||
{
|
||||
return _cache.Value.Apps;
|
||||
}
|
||||
List<InstalledApp> list = new List<InstalledApp>();
|
||||
HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
RegistryKey[] array = new RegistryKey[2]
|
||||
{
|
||||
Registry.LocalMachine,
|
||||
Registry.CurrentUser
|
||||
};
|
||||
foreach (RegistryKey registryKey in array)
|
||||
{
|
||||
string[] regPaths = _regPaths;
|
||||
foreach (string name in regPaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
using RegistryKey registryKey2 = registryKey.OpenSubKey(name);
|
||||
if (registryKey2 == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string[] subKeyNames = registryKey2.GetSubKeyNames();
|
||||
foreach (string name2 in subKeyNames)
|
||||
{
|
||||
try
|
||||
{
|
||||
using RegistryKey registryKey3 = registryKey2.OpenSubKey(name2);
|
||||
if (registryKey3 != null)
|
||||
{
|
||||
string text = registryKey3.GetValue("DisplayName") as string;
|
||||
string text2 = registryKey3.GetValue("UninstallString") as string;
|
||||
if (!string.IsNullOrWhiteSpace(text) && !string.IsNullOrWhiteSpace(text2) && (!text.StartsWith("KB", StringComparison.OrdinalIgnoreCase) || text.Length >= 12) && hashSet.Add(text) && (!(registryKey3.GetValue("SystemComponent") is int num) || num != 1))
|
||||
{
|
||||
list.Add(new InstalledApp
|
||||
{
|
||||
Name = text,
|
||||
UninstallString = text2,
|
||||
Publisher = (registryKey3.GetValue("Publisher") as string),
|
||||
Version = (registryKey3.GetValue("DisplayVersion") as string),
|
||||
InstallDate = FormatDate(registryKey3.GetValue("InstallDate") as string)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
list.Sort((InstalledApp a, InstalledApp b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase));
|
||||
_cache = (DateTime.Now, list);
|
||||
return list;
|
||||
}
|
||||
|
||||
private static string? FormatDate(string? raw)
|
||||
{
|
||||
if (raw != null && raw.Length == 8 && int.TryParse(raw, out var _))
|
||||
{
|
||||
return $"{raw.Substring(0, 4)}-{raw.Substring(4, 2)}-{raw.Substring(6, 2)}";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
212
.decompiledproj/AxCopilot/Handlers/UnitConverter.cs
Normal file
212
.decompiledproj/AxCopilot/Handlers/UnitConverter.cs
Normal file
@@ -0,0 +1,212 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
internal static class UnitConverter
|
||||
{
|
||||
private static readonly Regex Pattern = new Regex("^(-?\\d+(?:\\.\\d+)?)\\s*([a-z°/²³µ]+)\\s+(?:in|to)\\s+([a-z°/²³µ]+)$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Dictionary<string, double> _length = new Dictionary<string, double>
|
||||
{
|
||||
["km"] = 1000.0,
|
||||
["m"] = 1.0,
|
||||
["cm"] = 0.01,
|
||||
["mm"] = 0.001,
|
||||
["mi"] = 1609.344,
|
||||
["mile"] = 1609.344,
|
||||
["miles"] = 1609.344,
|
||||
["ft"] = 0.3048,
|
||||
["feet"] = 0.3048,
|
||||
["foot"] = 0.3048,
|
||||
["in"] = 0.0254,
|
||||
["inch"] = 0.0254,
|
||||
["inches"] = 0.0254,
|
||||
["yd"] = 0.9144,
|
||||
["yard"] = 0.9144,
|
||||
["yards"] = 0.9144,
|
||||
["nm"] = 1E-09
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, double> _weight = new Dictionary<string, double>
|
||||
{
|
||||
["t"] = 1000.0,
|
||||
["ton"] = 1000.0,
|
||||
["tonnes"] = 1000.0,
|
||||
["kg"] = 1.0,
|
||||
["g"] = 0.001,
|
||||
["mg"] = 1E-06,
|
||||
["lb"] = 0.453592,
|
||||
["lbs"] = 0.453592,
|
||||
["pound"] = 0.453592,
|
||||
["pounds"] = 0.453592,
|
||||
["oz"] = 0.0283495,
|
||||
["ounce"] = 0.0283495,
|
||||
["ounces"] = 0.0283495
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, double> _speed = new Dictionary<string, double>
|
||||
{
|
||||
["m/s"] = 1.0,
|
||||
["mps"] = 1.0,
|
||||
["km/h"] = 5.0 / 18.0,
|
||||
["kmh"] = 5.0 / 18.0,
|
||||
["kph"] = 5.0 / 18.0,
|
||||
["mph"] = 0.44704,
|
||||
["kn"] = 0.514444,
|
||||
["knot"] = 0.514444,
|
||||
["knots"] = 0.514444
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, double> _data = new Dictionary<string, double>
|
||||
{
|
||||
["b"] = 1.0,
|
||||
["byte"] = 1.0,
|
||||
["bytes"] = 1.0,
|
||||
["kb"] = 1024.0,
|
||||
["kib"] = 1024.0,
|
||||
["mb"] = 1048576.0,
|
||||
["mib"] = 1048576.0,
|
||||
["gb"] = 1073741824.0,
|
||||
["gib"] = 1073741824.0,
|
||||
["tb"] = 1099511627776.0,
|
||||
["tib"] = 1099511627776.0,
|
||||
["pb"] = 1125899906842624.0
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, double> _area = new Dictionary<string, double>
|
||||
{
|
||||
["m²"] = 1.0,
|
||||
["m2"] = 1.0,
|
||||
["km²"] = 1000000.0,
|
||||
["km2"] = 1000000.0,
|
||||
["cm²"] = 0.0001,
|
||||
["cm2"] = 0.0001,
|
||||
["ha"] = 10000.0,
|
||||
["acre"] = 4046.86,
|
||||
["acres"] = 4046.86,
|
||||
["ft²"] = 0.092903,
|
||||
["ft2"] = 0.092903
|
||||
};
|
||||
|
||||
private static readonly List<Dictionary<string, double>> _tables = new List<Dictionary<string, double>> { _length, _weight, _speed, _data, _area };
|
||||
|
||||
public static bool TryConvert(string input, out string? result)
|
||||
{
|
||||
result = null;
|
||||
Match match = Pattern.Match(input.Trim());
|
||||
if (!match.Success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!double.TryParse(match.Groups[1].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
string text = match.Groups[2].Value.ToLowerInvariant();
|
||||
string text2 = match.Groups[3].Value.ToLowerInvariant();
|
||||
if (TryConvertTemperature(result2, text, text2, out var r))
|
||||
{
|
||||
result = FormatNum(r) + " " + TemperatureLabel(text2);
|
||||
return true;
|
||||
}
|
||||
foreach (Dictionary<string, double> table in _tables)
|
||||
{
|
||||
if (table.TryGetValue(text, out var value) && table.TryGetValue(text2, out var value2))
|
||||
{
|
||||
double v = result2 * value / value2;
|
||||
result = FormatNum(v) + " " + text2;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryConvertTemperature(double v, string from, string to, out double r)
|
||||
{
|
||||
r = 0.0;
|
||||
double num;
|
||||
switch (from)
|
||||
{
|
||||
case "c":
|
||||
case "°c":
|
||||
case "celsius":
|
||||
num = v;
|
||||
break;
|
||||
case "f":
|
||||
case "°f":
|
||||
case "fahrenheit":
|
||||
num = (v - 32.0) * 5.0 / 9.0;
|
||||
break;
|
||||
case "k":
|
||||
case "kelvin":
|
||||
num = v - 273.15;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
switch (to)
|
||||
{
|
||||
case "c":
|
||||
case "°c":
|
||||
case "celsius":
|
||||
r = num;
|
||||
break;
|
||||
case "f":
|
||||
case "°f":
|
||||
case "fahrenheit":
|
||||
r = num * 9.0 / 5.0 + 32.0;
|
||||
break;
|
||||
case "k":
|
||||
case "kelvin":
|
||||
r = num + 273.15;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string TemperatureLabel(string unit)
|
||||
{
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
string result;
|
||||
switch (unit)
|
||||
{
|
||||
case "c":
|
||||
case "°c":
|
||||
case "celsius":
|
||||
result = "°C";
|
||||
break;
|
||||
case "f":
|
||||
case "°f":
|
||||
case "fahrenheit":
|
||||
result = "°F";
|
||||
break;
|
||||
case "k":
|
||||
case "kelvin":
|
||||
result = "K";
|
||||
break;
|
||||
default:
|
||||
result = unit;
|
||||
break;
|
||||
}
|
||||
if (1 == 0)
|
||||
{
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string FormatNum(double v)
|
||||
{
|
||||
if (v == Math.Floor(v) && Math.Abs(v) < 1000000000000.0)
|
||||
{
|
||||
return ((long)v).ToString("N0", CultureInfo.CurrentCulture);
|
||||
}
|
||||
return v.ToString("G6", CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
88
.decompiledproj/AxCopilot/Handlers/UrlAliasHandler.cs
Normal file
88
.decompiledproj/AxCopilot/Handlers/UrlAliasHandler.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class UrlAliasHandler : IActionHandler
|
||||
{
|
||||
private readonly SettingsService _settings;
|
||||
|
||||
private static readonly HashSet<string> AllowedSchemes = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "http", "https", "ftp", "ftps", "ms-settings", "mailto", "file" };
|
||||
|
||||
public string? Prefix => "@";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("url-alias", "URL 별칭", "1.0", "AX");
|
||||
|
||||
public UrlAliasHandler(SettingsService settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
IEnumerable<LauncherItem> result = _settings.Settings.Aliases.Where((AliasEntry a) => a.Type == "url" && (string.IsNullOrEmpty(query) || a.Key.Contains(query, StringComparison.OrdinalIgnoreCase))).Select(delegate(AliasEntry a)
|
||||
{
|
||||
string faviconPath = GetFaviconPath(a.Target);
|
||||
return new LauncherItem(a.Key, a.Description ?? a.Target, faviconPath, a, a.Target, "\ue774");
|
||||
});
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is AliasEntry aliasEntry)
|
||||
{
|
||||
string text = Environment.ExpandEnvironmentVariables(aliasEntry.Target);
|
||||
if (!IsAllowedUrl(text))
|
||||
{
|
||||
LogService.Warn("허용되지 않는 URL 스킴: " + text);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
Process.Start(new ProcessStartInfo(text)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static bool IsAllowedUrl(string url)
|
||||
{
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return AllowedSchemes.Contains(result.Scheme);
|
||||
}
|
||||
|
||||
private static string? GetFaviconPath(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
url = "https://" + url;
|
||||
}
|
||||
string text = new Uri(url).Host.ToLowerInvariant();
|
||||
string text2 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "favicons", text + ".png");
|
||||
if (File.Exists(text2))
|
||||
{
|
||||
return text2;
|
||||
}
|
||||
FaviconService.GetFavicon(url);
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
163
.decompiledproj/AxCopilot/Handlers/WebSearchHandler.cs
Normal file
163
.decompiledproj/AxCopilot/Handlers/WebSearchHandler.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class WebSearchHandler : IActionHandler
|
||||
{
|
||||
private readonly SettingsService _settings;
|
||||
|
||||
private static readonly string _iconDir = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? ".", "Assets", "SearchEngines");
|
||||
|
||||
private static readonly Dictionary<string, (string Name, string UrlTemplate, string Icon)> _engines = new Dictionary<string, (string, string, string)>
|
||||
{
|
||||
["g"] = ("Google", "https://www.google.com/search?q={0}", "google"),
|
||||
["n"] = ("Naver", "https://search.naver.com/search.naver?query={0}", "naver"),
|
||||
["y"] = ("YouTube", "https://www.youtube.com/results?search_query={0}", "youtube"),
|
||||
["gh"] = ("GitHub", "https://github.com/search?q={0}", "github"),
|
||||
["d"] = ("DuckDuckGo", "https://duckduckgo.com/?q={0}", "duckduckgo"),
|
||||
["w"] = ("Wikipedia", "https://ko.wikipedia.org/w/index.php?search={0}", "wikipedia"),
|
||||
["nm"] = ("Naver Map", "https://map.naver.com/p/search/{0}", "navermap"),
|
||||
["nw"] = ("나무위키", "https://namu.wiki/Special:Search?query={0}", "namuwiki"),
|
||||
["ni"] = ("Naver 이미지", "https://search.naver.com/search.naver?where=image&query={0}", "naver"),
|
||||
["gi"] = ("Google 이미지", "https://www.google.com/search?tbm=isch&q={0}", "google")
|
||||
};
|
||||
|
||||
private const string SecurityWarn = "⚠ 검색어가 외부로 전송됩니다 — 기밀 데이터 입력 금지";
|
||||
|
||||
public string? Prefix => "?";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("WebSearch", "웹 검색 — ? 뒤에 검색어 또는 URL 입력", "1.0", "AX");
|
||||
|
||||
private string DefaultEngineKey => _settings.Settings.Launcher.WebSearchEngine ?? "g";
|
||||
|
||||
private (string Name, string UrlTemplate, string Icon) DefaultEngine
|
||||
{
|
||||
get
|
||||
{
|
||||
(string, string, string) value;
|
||||
return _engines.TryGetValue(DefaultEngineKey, out value) ? value : _engines["g"];
|
||||
}
|
||||
}
|
||||
|
||||
public WebSearchHandler(SettingsService settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
private static string? GetIconPath(string engineKey)
|
||||
{
|
||||
if (!_engines.TryGetValue(engineKey, out (string, string, string) value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string text = Path.Combine(_iconDir, value.Item3 + ".png");
|
||||
return File.Exists(text) ? text : null;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
string iconPath = GetIconPath(DefaultEngineKey);
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem("검색어를 입력하세요", "예: ? 오늘 날씨 · ?g python · ?y 음악 · ?n 뉴스 | ⚠ 검색어가 외부로 전송됩니다 — 기밀 데이터 입력 금지", iconPath, null, null, "\ue774")));
|
||||
}
|
||||
List<LauncherItem> list = new List<LauncherItem>();
|
||||
string text = query.Trim();
|
||||
if (IsUrl(text))
|
||||
{
|
||||
string text2 = (text.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? text : ("https://" + text));
|
||||
string faviconPathForUrl = GetFaviconPathForUrl(text2);
|
||||
list.Add(new LauncherItem("열기: " + text, text2 + " | ⚠ 검색어가 외부로 전송됩니다 — 기밀 데이터 입력 금지", faviconPathForUrl, text2, null, "\ue774"));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
string[] array = text.Split(' ', 2);
|
||||
string text3 = array[0].ToLowerInvariant();
|
||||
if (array.Length == 2 && _engines.TryGetValue(text3, out (string, string, string) value))
|
||||
{
|
||||
string text4 = array[1].Trim();
|
||||
if (!string.IsNullOrWhiteSpace(text4))
|
||||
{
|
||||
string data = string.Format(value.Item2, Uri.EscapeDataString(text4));
|
||||
list.Add(new LauncherItem(value.Item1 + "에서 '" + text4 + "' 검색", "⚠ 검색어가 외부로 전송됩니다 — 기밀 데이터 입력 금지", GetIconPath(text3), data, null, "\ue774"));
|
||||
}
|
||||
}
|
||||
string defaultEngineKey = DefaultEngineKey;
|
||||
(string, string, string) defaultEngine = DefaultEngine;
|
||||
string data2 = string.Format(defaultEngine.Item2, Uri.EscapeDataString(text));
|
||||
list.Add(new LauncherItem(defaultEngine.Item1 + "에서 '" + text + "' 검색", "⚠ 검색어가 외부로 전송됩니다 — 기밀 데이터 입력 금지", GetIconPath(defaultEngineKey), data2, null, "\ue774"));
|
||||
if (text.Length <= 2)
|
||||
{
|
||||
foreach (var (text6, tuple2) in _engines)
|
||||
{
|
||||
list.Add(new LauncherItem("?" + text6 + " — " + tuple2.Item1, $"예: ?{text6} {text} | {"⚠ 검색어가 외부로 전송됩니다 — 기밀 데이터 입력 금지"}", GetIconPath(text6), null, null, "\ue774"));
|
||||
}
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is string text && !string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(text)
|
||||
{
|
||||
UseShellExecute = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn("웹 검색 열기 실패: " + ex.Message);
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static bool IsUrl(string input)
|
||||
{
|
||||
if (input.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || input.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!input.Contains(' ') && input.Contains('.') && input.Length > 3)
|
||||
{
|
||||
string text = input.Split('/')[0];
|
||||
string[] array = text.Split('.');
|
||||
return array.Length >= 2 && array[^1].Length >= 2 && array[^1].Length <= 6 && array[^1].All(char.IsLetter);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string? GetFaviconPathForUrl(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
url = "https://" + url;
|
||||
}
|
||||
string text = new Uri(url).Host.ToLowerInvariant();
|
||||
string text2 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "favicons", text + ".png");
|
||||
if (File.Exists(text2))
|
||||
{
|
||||
return text2;
|
||||
}
|
||||
FaviconService.GetFavicon(url);
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
142
.decompiledproj/AxCopilot/Handlers/WindowSwitchHandler.cs
Normal file
142
.decompiledproj/AxCopilot/Handlers/WindowSwitchHandler.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.SDK;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class WindowSwitchHandler : IActionHandler
|
||||
{
|
||||
private delegate bool EnumWindowsProc(nint hWnd, nint lParam);
|
||||
|
||||
private record WindowInfo(nint Handle, string Title, string ProcessName, bool IsMinimized);
|
||||
|
||||
private const int SW_RESTORE = 9;
|
||||
|
||||
public string? Prefix => "win";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("WindowSwitch", "윈도우 포커스 스위처 — win", "1.0", "AX");
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, nint lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsWindowVisible(nint hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetWindowTextLength(nint hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetWindowText(nint hWnd, StringBuilder lpString, int nMaxCount);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint GetWindowThreadProcessId(nint hWnd, out uint lpdwProcessId);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetForegroundWindow(nint hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool ShowWindow(nint hWnd, int nCmdShow);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsIconic(nint hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern nint GetShellWindow();
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string q = query.Trim();
|
||||
List<WindowInfo> list = GetOpenWindows();
|
||||
if (!string.IsNullOrWhiteSpace(q))
|
||||
{
|
||||
list = list.Where((WindowInfo w) => w.Title.Contains(q, StringComparison.OrdinalIgnoreCase) || w.ProcessName.Contains(q, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
}
|
||||
if (!list.Any())
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new _003C_003Ez__ReadOnlySingleElementList<LauncherItem>(new LauncherItem(string.IsNullOrWhiteSpace(q) ? "열린 창이 없습니다" : ("'" + q + "'에 해당하는 창 없음"), "win 뒤에 프로세스명 또는 창 제목 입력", null, null, null, "\ue946")));
|
||||
}
|
||||
List<LauncherItem> list2 = (from w in list.Take(15)
|
||||
select new LauncherItem((w.Title.Length > 60) ? (w.Title.Substring(0, 57) + "…") : w.Title, w.ProcessName + " · " + (w.IsMinimized ? "[최소화됨] · " : "") + "Enter → 전환", null, w.Handle, null, "\ue7f4")).ToList();
|
||||
list2.Insert(0, new LauncherItem($"열린 창 {list.Count}개", "Enter → 해당 창으로 즉시 전환", null, null, null, "\ue946"));
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list2);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is nint num && num != IntPtr.Zero)
|
||||
{
|
||||
if (IsIconic(num))
|
||||
{
|
||||
ShowWindow(num, 9);
|
||||
}
|
||||
SetForegroundWindow(num);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static List<WindowInfo> GetOpenWindows()
|
||||
{
|
||||
List<WindowInfo> list = new List<WindowInfo>();
|
||||
nint shellWnd = GetShellWindow();
|
||||
string currentProcessName = Process.GetCurrentProcess().ProcessName;
|
||||
EnumWindows(delegate(nint hWnd, nint _)
|
||||
{
|
||||
if (hWnd == shellWnd)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!IsWindowVisible(hWnd))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
int windowTextLength = GetWindowTextLength(hWnd);
|
||||
if (windowTextLength == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
StringBuilder stringBuilder = new StringBuilder(windowTextLength + 1);
|
||||
GetWindowText(hWnd, stringBuilder, stringBuilder.Capacity);
|
||||
string title = stringBuilder.ToString();
|
||||
string text = "";
|
||||
try
|
||||
{
|
||||
GetWindowThreadProcessId(hWnd, out var lpdwProcessId);
|
||||
text = Process.GetProcessById((int)lpdwProcessId).ProcessName;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
if (text == currentProcessName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool flag;
|
||||
switch (text)
|
||||
{
|
||||
case "ApplicationFrameHost":
|
||||
case "ShellExperienceHost":
|
||||
case "SearchHost":
|
||||
case "StartMenuExperienceHost":
|
||||
case "TextInputHost":
|
||||
flag = true;
|
||||
break;
|
||||
default:
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
if (flag)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
list.Add(new WindowInfo(hWnd, title, text, IsIconic(hWnd)));
|
||||
return true;
|
||||
}, IntPtr.Zero);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
3
.decompiledproj/AxCopilot/Handlers/WorkspaceAction.cs
Normal file
3
.decompiledproj/AxCopilot/Handlers/WorkspaceAction.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public record WorkspaceAction(WorkspaceActionType Type, string Name, string? NewName = null);
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public enum WorkspaceActionType
|
||||
{
|
||||
Restore,
|
||||
Save,
|
||||
Delete,
|
||||
Rename
|
||||
}
|
||||
96
.decompiledproj/AxCopilot/Handlers/WorkspaceHandler.cs
Normal file
96
.decompiledproj/AxCopilot/Handlers/WorkspaceHandler.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AxCopilot.Core;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
public class WorkspaceHandler : IActionHandler
|
||||
{
|
||||
private readonly ContextManager _context;
|
||||
|
||||
private readonly SettingsService _settings;
|
||||
|
||||
public string? Prefix => "~";
|
||||
|
||||
public PluginMetadata Metadata => new PluginMetadata("workspace", "워크스페이스", "1.0", "AX");
|
||||
|
||||
public WorkspaceHandler(ContextManager context, SettingsService settings)
|
||||
{
|
||||
_context = context;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
string[] array = query.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (array.Length == 0 || string.IsNullOrEmpty(query))
|
||||
{
|
||||
List<LauncherItem> list = _settings.Settings.Profiles.Select((WorkspaceProfile p) => new LauncherItem("~" + p.Name, $"{p.Windows.Count}개 창 | {p.CreatedAt:MM/dd HH:mm}", null, p, null, "\ue8a1")).ToList();
|
||||
if (list.Count == 0)
|
||||
{
|
||||
list.Add(new LauncherItem("저장된 프로필 없음", "~save <이름> 으로 현재 배치를 저장하세요", null, null, null, "\ue946"));
|
||||
}
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)list);
|
||||
}
|
||||
string text = array[0].ToLowerInvariant();
|
||||
if (text == "save")
|
||||
{
|
||||
string text2 = ((array.Length > 1) ? array[1] : "default");
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new LauncherItem[1]
|
||||
{
|
||||
new LauncherItem("현재 창 배치를 '" + text2 + "'으로 저장", "Enter로 확인", null, new WorkspaceAction(WorkspaceActionType.Save, text2), null, "\ue74e")
|
||||
});
|
||||
}
|
||||
if (text == "delete" && array.Length > 1)
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new LauncherItem[1]
|
||||
{
|
||||
new LauncherItem("프로필 '" + array[1] + "' 삭제", "Enter로 확인 (되돌릴 수 없습니다)", null, new WorkspaceAction(WorkspaceActionType.Delete, array[1]), null, "\ue74d")
|
||||
});
|
||||
}
|
||||
if (text == "rename" && array.Length > 2)
|
||||
{
|
||||
return Task.FromResult((IEnumerable<LauncherItem>)new LauncherItem[1]
|
||||
{
|
||||
new LauncherItem($"프로필 '{array[1]}' → '{array[2]}'로 이름 변경", "Enter로 확인", null, new WorkspaceAction(WorkspaceActionType.Rename, array[1], array[2]), null, "\ue8ac")
|
||||
});
|
||||
}
|
||||
IEnumerable<LauncherItem> result = from p in _settings.Settings.Profiles
|
||||
where p.Name.Contains(query, StringComparison.OrdinalIgnoreCase)
|
||||
select new LauncherItem("~" + p.Name + " 복원", $"{p.Windows.Count}개 창 | {p.CreatedAt:MM/dd HH:mm}", null, new WorkspaceAction(WorkspaceActionType.Restore, p.Name), null, "\ue72c");
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
object data = item.Data;
|
||||
if (!(data is WorkspaceAction action))
|
||||
{
|
||||
return;
|
||||
}
|
||||
switch (action.Type)
|
||||
{
|
||||
case WorkspaceActionType.Restore:
|
||||
LogService.Info("복원 결과: " + (await _context.RestoreProfileAsync(action.Name, ct)).Message);
|
||||
break;
|
||||
case WorkspaceActionType.Save:
|
||||
_context.CaptureProfile(action.Name);
|
||||
break;
|
||||
case WorkspaceActionType.Delete:
|
||||
_context.DeleteProfile(action.Name);
|
||||
break;
|
||||
case WorkspaceActionType.Rename:
|
||||
if (action.NewName != null)
|
||||
{
|
||||
_context.RenameProfile(action.Name, action.NewName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
.decompiledproj/AxCopilot/Models/AgentHookEntry.cs
Normal file
24
.decompiledproj/AxCopilot/Models/AgentHookEntry.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AxCopilot.Models;
|
||||
|
||||
public class AgentHookEntry
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("toolName")]
|
||||
public string ToolName { get; set; } = "*";
|
||||
|
||||
[JsonPropertyName("timing")]
|
||||
public string Timing { get; set; } = "post";
|
||||
|
||||
[JsonPropertyName("scriptPath")]
|
||||
public string ScriptPath { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("arguments")]
|
||||
public string Arguments { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("enabled")]
|
||||
public bool Enabled { get; set; } = true;
|
||||
}
|
||||
27
.decompiledproj/AxCopilot/Models/AliasEntry.cs
Normal file
27
.decompiledproj/AxCopilot/Models/AliasEntry.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AxCopilot.Models;
|
||||
|
||||
public class AliasEntry
|
||||
{
|
||||
[JsonPropertyName("key")]
|
||||
public string Key { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } = "url";
|
||||
|
||||
[JsonPropertyName("target")]
|
||||
public string Target { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string? Description { get; set; }
|
||||
|
||||
[JsonPropertyName("showWindow")]
|
||||
public bool ShowWindow { get; set; } = false;
|
||||
|
||||
[JsonPropertyName("adapter")]
|
||||
public string? Adapter { get; set; }
|
||||
|
||||
[JsonPropertyName("query")]
|
||||
public string? Query { get; set; }
|
||||
}
|
||||
15
.decompiledproj/AxCopilot/Models/ApiAdapter.cs
Normal file
15
.decompiledproj/AxCopilot/Models/ApiAdapter.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AxCopilot.Models;
|
||||
|
||||
public class ApiAdapter
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("baseUrl")]
|
||||
public string BaseUrl { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("credentialKey")]
|
||||
public string CredentialKey { get; set; } = "";
|
||||
}
|
||||
70
.decompiledproj/AxCopilot/Models/AppSettings.cs
Normal file
70
.decompiledproj/AxCopilot/Models/AppSettings.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AxCopilot.Models;
|
||||
|
||||
public class AppSettings
|
||||
{
|
||||
[JsonPropertyName("version")]
|
||||
public string Version { get; set; } = "1.0";
|
||||
|
||||
[JsonPropertyName("ai_enabled")]
|
||||
public bool AiEnabled { get; set; } = false;
|
||||
|
||||
[JsonPropertyName("hotkey")]
|
||||
public string Hotkey { get; set; } = "Alt+Space";
|
||||
|
||||
[JsonPropertyName("launcher")]
|
||||
public LauncherSettings Launcher { get; set; } = new LauncherSettings();
|
||||
|
||||
[JsonPropertyName("indexPaths")]
|
||||
public List<string> IndexPaths { get; set; } = new List<string> { "%USERPROFILE%\\Desktop", "%APPDATA%\\Microsoft\\Windows\\Start Menu" };
|
||||
|
||||
[JsonPropertyName("indexExtensions")]
|
||||
public List<string> IndexExtensions { get; set; } = new List<string>
|
||||
{
|
||||
".exe", ".lnk", ".bat", ".ps1", ".url", ".cmd", ".msi", ".pdf", ".doc", ".docx",
|
||||
".xls", ".xlsx", ".ppt", ".pptx", ".hwp", ".hwpx", ".txt", ".md", ".csv", ".json",
|
||||
".xml", ".yaml", ".yml", ".log", ".ini", ".cfg", ".conf", ".png", ".jpg", ".jpeg",
|
||||
".gif", ".bmp", ".svg", ".webp", ".ico", ".tiff", ".zip", ".7z", ".rar"
|
||||
};
|
||||
|
||||
[JsonPropertyName("indexSpeed")]
|
||||
public string IndexSpeed { get; set; } = "normal";
|
||||
|
||||
[JsonPropertyName("monitorMismatch")]
|
||||
public string MonitorMismatch { get; set; } = "warn";
|
||||
|
||||
[JsonPropertyName("profiles")]
|
||||
public List<WorkspaceProfile> Profiles { get; set; } = new List<WorkspaceProfile>();
|
||||
|
||||
[JsonPropertyName("aliases")]
|
||||
public List<AliasEntry> Aliases { get; set; } = new List<AliasEntry>();
|
||||
|
||||
[JsonPropertyName("clipboardTransformers")]
|
||||
public List<ClipboardTransformer> ClipboardTransformers { get; set; } = new List<ClipboardTransformer>();
|
||||
|
||||
[JsonPropertyName("apiAdapters")]
|
||||
public List<ApiAdapter> ApiAdapters { get; set; } = new List<ApiAdapter>();
|
||||
|
||||
[JsonPropertyName("plugins")]
|
||||
public List<PluginEntry> Plugins { get; set; } = new List<PluginEntry>();
|
||||
|
||||
[JsonPropertyName("snippets")]
|
||||
public List<SnippetEntry> Snippets { get; set; } = new List<SnippetEntry>();
|
||||
|
||||
[JsonPropertyName("clipboardHistory")]
|
||||
public ClipboardHistorySettings ClipboardHistory { get; set; } = new ClipboardHistorySettings();
|
||||
|
||||
[JsonPropertyName("systemCommands")]
|
||||
public SystemCommandSettings SystemCommands { get; set; } = new SystemCommandSettings();
|
||||
|
||||
[JsonPropertyName("screenCapture")]
|
||||
public ScreenCaptureSettings ScreenCapture { get; set; } = new ScreenCaptureSettings();
|
||||
|
||||
[JsonPropertyName("reminder")]
|
||||
public ReminderSettings Reminder { get; set; } = new ReminderSettings();
|
||||
|
||||
[JsonPropertyName("llm")]
|
||||
public LlmSettings Llm { get; set; } = new LlmSettings();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user