<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ko">
	<id>https://devcafe.co.kr/w/index.php?action=history&amp;feed=atom&amp;title=%EC%B9%B4%EB%A0%9B_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</id>
	<title>카렛 프로그래밍 - 편집 역사</title>
	<link rel="self" type="application/atom+xml" href="https://devcafe.co.kr/w/index.php?action=history&amp;feed=atom&amp;title=%EC%B9%B4%EB%A0%9B_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D"/>
	<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EC%B9%B4%EB%A0%9B_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D&amp;action=history"/>
	<updated>2026-11-04T12:08:05Z</updated>
	<subtitle>이 문서의 편집 역사</subtitle>
	<generator>MediaWiki 1.42.1</generator>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%EC%B9%B4%EB%A0%9B_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D&amp;diff=2697&amp;oldid=prev</id>
		<title>Devcafe: 새 문서: &lt;source lang=python&gt;   import os import sqlite3 import threading import time import tkinter as tk from tkinter import ttk import keyboard import pywinauto from pywinauto.keyboard import send_keys  DB_FILE = &quot;caret_data.db&quot;  class CaretReaderApp:     def __init__(self, root):         self.root = root         self.root.title(&quot;Fast Caret DB v1.3&quot;)         self.root.geometry(&quot;360x160&quot;)         self.root.attributes(&quot;-topmost&quot;, True)         self.root.resizable(False, False)...</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%EC%B9%B4%EB%A0%9B_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D&amp;diff=2697&amp;oldid=prev"/>
		<updated>2026-11-02T00:29:49Z</updated>

		<summary type="html">&lt;p&gt;새 문서: &amp;lt;source lang=python&amp;gt;   import os import sqlite3 import threading import time import tkinter as tk from tkinter import ttk import keyboard import pywinauto from pywinauto.keyboard import send_keys  DB_FILE = &amp;quot;caret_data.db&amp;quot;  class CaretReaderApp:     def __init__(self, root):         self.root = root         self.root.title(&amp;quot;Fast Caret DB v1.3&amp;quot;)         self.root.geometry(&amp;quot;360x160&amp;quot;)         self.root.attributes(&amp;quot;-topmost&amp;quot;, True)         self.root.resizable(False, False)...&lt;/p&gt;
&lt;p&gt;&lt;b&gt;새 문서&lt;/b&gt;&lt;/p&gt;&lt;div&gt;&amp;lt;source lang=python&amp;gt; &lt;br /&gt;
&lt;br /&gt;
import os&lt;br /&gt;
import sqlite3&lt;br /&gt;
import threading&lt;br /&gt;
import time&lt;br /&gt;
import tkinter as tk&lt;br /&gt;
from tkinter import ttk&lt;br /&gt;
import keyboard&lt;br /&gt;
import pywinauto&lt;br /&gt;
from pywinauto.keyboard import send_keys&lt;br /&gt;
&lt;br /&gt;
DB_FILE = &amp;quot;caret_data.db&amp;quot;&lt;br /&gt;
&lt;br /&gt;
class CaretReaderApp:&lt;br /&gt;
    def __init__(self, root):&lt;br /&gt;
        self.root = root&lt;br /&gt;
        self.root.title(&amp;quot;Fast Caret DB v1.3&amp;quot;)&lt;br /&gt;
        self.root.geometry(&amp;quot;360x160&amp;quot;)&lt;br /&gt;
        self.root.attributes(&amp;quot;-topmost&amp;quot;, True)&lt;br /&gt;
        self.root.resizable(False, False)&lt;br /&gt;
&lt;br /&gt;
        # 1. 성능 최적화: 메모리 캐시 딕셔너리 초기화&lt;br /&gt;
        self.memory_cache = {}&lt;br /&gt;
&lt;br /&gt;
        # 2. 성능 최적화: 고성능 DB 초기화 및 커넥션 풀링 유지&lt;br /&gt;
        self.db_conn = None&lt;br /&gt;
        self.init_database()&lt;br /&gt;
&lt;br /&gt;
        # UI 요소 배치&lt;br /&gt;
        style = ttk.Style()&lt;br /&gt;
        style.theme_use(&amp;quot;clam&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
        self.label_info = ttk.Label(root, text=&amp;quot;[Ctrl + Shift + F12]를 누르면\n커서 위치 문자를 고속 조회합니다.&amp;quot;, justify=&amp;quot;center&amp;quot;, font=(&amp;quot;Malgun Gothic&amp;quot;, 10))&lt;br /&gt;
        self.label_info.pack(pady=10)&lt;br /&gt;
&lt;br /&gt;
        self.label_result = ttk.Label(root, text=&amp;quot;읽은 문자: 없음&amp;quot;, font=(&amp;quot;Malgun Gothic&amp;quot;, 11, &amp;quot;bold&amp;quot;), foreground=&amp;quot;#555555&amp;quot;)&lt;br /&gt;
        self.label_result.pack(pady=2)&lt;br /&gt;
&lt;br /&gt;
        self.label_db_content = ttk.Label(root, text=&amp;quot;DB 내용: 매칭 데이터 없음&amp;quot;, font=(&amp;quot;Malgun Gothic&amp;quot;, 12, &amp;quot;bold&amp;quot;), foreground=&amp;quot;#0078D7&amp;quot;, wraplength=340, justify=&amp;quot;center&amp;quot;)&lt;br /&gt;
        self.label_db_content.pack(pady=8)&lt;br /&gt;
&lt;br /&gt;
        # 단축키 리스너 등록 (데몬 스레드)&lt;br /&gt;
        self.hotkey_thread = threading.Thread(target=self.setup_hotkey, daemon=True)&lt;br /&gt;
        self.hotkey_thread.start()&lt;br /&gt;
&lt;br /&gt;
    def init_database(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;SQLite 고속 읽기 튜닝 및 인덱스 설정&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        is_new = not os.path.exists(DB_FILE)&lt;br /&gt;
        &lt;br /&gt;
        # 상시 연결 유지 (매 요청마다 open/close를 하지 않음)&lt;br /&gt;
        self.db_conn = sqlite3.connect(DB_FILE, check_same_thread=False)&lt;br /&gt;
        cursor = self.db_conn.cursor()&lt;br /&gt;
        &lt;br /&gt;
        # 성능 향상을 위한 SQLite PRAGMA 튜닝 설정&lt;br /&gt;
        cursor.execute(&amp;quot;PRAGMA journal_mode=WAL;&amp;quot;)  # Write-Ahead Logging 모드로 읽기 성능 향상&lt;br /&gt;
        cursor.execute(&amp;quot;PRAGMA synchronous=NORMAL;&amp;quot;) # 디스크 동기화 오버헤드 감소&lt;br /&gt;
        cursor.execute(&amp;quot;PRAGMA cache_size=-2000;&amp;quot;)  # 2MB 캐시 지정&lt;br /&gt;
        &lt;br /&gt;
        # 테이블 생성&lt;br /&gt;
        cursor.execute(&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
            CREATE TABLE IF NOT EXISTS dict_data (&lt;br /&gt;
                character TEXT PRIMARY KEY,&lt;br /&gt;
                content TEXT&lt;br /&gt;
            )&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        # 고속 조회를 위한 인덱스 생성&lt;br /&gt;
        cursor.execute(&amp;quot;CREATE INDEX IF NOT EXISTS idx_char ON dict_data(character);&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        if is_new:&lt;br /&gt;
            samples = [&lt;br /&gt;
                (&amp;#039;A&amp;#039;, &amp;#039;알파벳의 첫 번째 글자 (Apple, Ace)&amp;#039;),&lt;br /&gt;
                (&amp;#039;B&amp;#039;, &amp;#039;알파벳의 두 번째 글자 (Banana, Best)&amp;#039;),&lt;br /&gt;
                (&amp;#039;C&amp;#039;, &amp;#039;알파벳의 세 번째 글자 (Cherry, Cool)&amp;#039;),&lt;br /&gt;
                (&amp;#039;한&amp;#039;, &amp;#039;대한민국의 국호이자 한글의 첫 글자&amp;#039;),&lt;br /&gt;
                (&amp;#039;1&amp;#039;, &amp;#039;숫자의 시작, 최초 혹은 최고를 의미&amp;#039;),&lt;br /&gt;
                (&amp;#039;?&amp;#039;, &amp;#039;질문이나 의문을 나타내는 문장 부호&amp;#039;)&lt;br /&gt;
            ]&lt;br /&gt;
            cursor.executemany(&amp;quot;INSERT OR IGNORE INTO dict_data (character, content) VALUES (?, ?)&amp;quot;, samples)&lt;br /&gt;
            self.db_conn.commit()&lt;br /&gt;
            &lt;br /&gt;
        print(&amp;quot;[성공] SQLite 고속 데이터베이스 인프라 튜닝 및 캐시 활성화 완료.&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    def setup_hotkey(self):&lt;br /&gt;
        keyboard.add_hotkey(&amp;quot;ctrl+shift+f12&amp;quot;, self.trigger_caret_reading)&lt;br /&gt;
        keyboard.wait()&lt;br /&gt;
&lt;br /&gt;
    def trigger_caret_reading(self):&lt;br /&gt;
        # 무거운 작업 분리&lt;br /&gt;
        threading.Thread(target=self.read_caret_pipeline, daemon=True).start()&lt;br /&gt;
&lt;br /&gt;
    def read_caret_pipeline(self):&lt;br /&gt;
        # 1단계: 고성능 UI Automation 백엔드 시도&lt;br /&gt;
        character = self.try_uia_backend()&lt;br /&gt;
        &lt;br /&gt;
        # 2단계: 실패 시 고속 폴백 매크로 시도&lt;br /&gt;
        if not character:&lt;br /&gt;
            character = self.try_cursor_right_fast_fallback()&lt;br /&gt;
&lt;br /&gt;
        if character and character.strip() != &amp;quot;&amp;quot;:&lt;br /&gt;
            cleaned_char = character.strip()&lt;br /&gt;
            &lt;br /&gt;
            # 3단계: 고속 조회 패턴 (메모리 캐시 확인 -&amp;gt; DB 파일 확인)&lt;br /&gt;
            db_content = self.query_database_fast(cleaned_char)&lt;br /&gt;
            &lt;br /&gt;
            display_char = cleaned_char.replace(&amp;quot;\n&amp;quot;, &amp;quot;\\n&amp;quot;).replace(&amp;quot;\r&amp;quot;, &amp;quot;\\r&amp;quot;)&lt;br /&gt;
            self.root.after(0, lambda: self.update_ui(f&amp;quot;읽은 문자: &amp;#039;{display_char}&amp;#039;&amp;quot;, db_content))&lt;br /&gt;
        else:&lt;br /&gt;
            self.root.after(0, lambda: self.update_ui(&amp;quot;읽은 문자: 추출 실패&amp;quot;, &amp;quot;매칭 데이터 없음&amp;quot;))&lt;br /&gt;
&lt;br /&gt;
    def query_database_fast(self, char):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;메모리 캐시 ➔ 상시 개방된 DB 인덱스 스캔을 통한 Ultra-fast 조회&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        # 1단계: 가장 빠른 메모리 딕셔너리 캐시 탐색&lt;br /&gt;
        if char in self.memory_cache:&lt;br /&gt;
            return self.memory_cache[char]&lt;br /&gt;
            &lt;br /&gt;
        # 2단계: 캐시 미스 시, 상시 열려있는 고성능 DB 인덱스 조회&lt;br /&gt;
        try:&lt;br /&gt;
            cursor = self.db_conn.cursor()&lt;br /&gt;
            cursor.execute(&amp;quot;SELECT content FROM dict_data WHERE character = ?&amp;quot;, (char,))&lt;br /&gt;
            row = cursor.fetchone()&lt;br /&gt;
            &lt;br /&gt;
            if row:&lt;br /&gt;
                res = f&amp;quot;DB 내용: {row[0]}&amp;quot;&lt;br /&gt;
            else:&lt;br /&gt;
                res = &amp;quot;DB 내용: 등록되지 않은 문자입니다.&amp;quot;&lt;br /&gt;
                &lt;br /&gt;
            # 결과를 다음 조회를 위해 메모리 캐시에 적재&lt;br /&gt;
            self.memory_cache[char] = res&lt;br /&gt;
            return res&lt;br /&gt;
        except Exception as e:&lt;br /&gt;
            return f&amp;quot;DB 조회 에러: {e}&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    def update_ui(self, result_text, db_text):&lt;br /&gt;
        self.label_result.config(text=result_text)&lt;br /&gt;
        self.label_db_content.config(text=db_text)&lt;br /&gt;
        if &amp;quot;등록되지 않은&amp;quot; in db_text or &amp;quot;없음&amp;quot; in db_text:&lt;br /&gt;
            self.label_db_content.config(foreground=&amp;quot;#E81123&amp;quot;)&lt;br /&gt;
        else:&lt;br /&gt;
            self.label_db_content.config(foreground=&amp;quot;#0078D7&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    def try_uia_backend(self):&lt;br /&gt;
        try:&lt;br /&gt;
            app = pywinauto.Application(backend=&amp;quot;uia&amp;quot;).connect(active_only=True)&lt;br /&gt;
            focused_control = app.top_window().set_focus()&lt;br /&gt;
            text_pattern = focused_control.get_text_block()&lt;br /&gt;
            selection = text_pattern.get_selection()&lt;br /&gt;
            &lt;br /&gt;
            if selection:&lt;br /&gt;
                caret_range = selection&lt;br /&gt;
                caret_range.expand_to_enclosing_unit(&amp;quot;Character&amp;quot;)&lt;br /&gt;
                return caret_range.get_text()&lt;br /&gt;
        except Exception:&lt;br /&gt;
            pass&lt;br /&gt;
        return None&lt;br /&gt;
&lt;br /&gt;
    def try_cursor_right_fast_fallback(self):&lt;br /&gt;
        &amp;quot;&amp;quot;&amp;quot;딜레이 오버헤드를 극한으로 줄인 고속 매크로&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
        try:&lt;br /&gt;
            import win32clipboard&lt;br /&gt;
            original_text = &amp;quot;&amp;quot;&lt;br /&gt;
            try:&lt;br /&gt;
                win32clipboard.OpenClipboard()&lt;br /&gt;
                if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_UNICODETEXT):&lt;br /&gt;
                    original_text = win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT)&lt;br /&gt;
                win32clipboard.CloseClipboard()&lt;br /&gt;
            except Exception:&lt;br /&gt;
                pass&lt;br /&gt;
&lt;br /&gt;
            win32clipboard.OpenClipboard()&lt;br /&gt;
            win32clipboard.EmptyClipboard()&lt;br /&gt;
            win32clipboard.CloseClipboard()&lt;br /&gt;
&lt;br /&gt;
            # 성능 최적화: OS 타겟 윈도우 부하 타이밍 최소화 (기존 0.06초 -&amp;gt; 0.02초)&lt;br /&gt;
            send_keys(&amp;quot;+{RIGHT}&amp;quot;)&lt;br /&gt;
            time.sleep(0.02)&lt;br /&gt;
            send_keys(&amp;quot;^c&amp;quot;)&lt;br /&gt;
            time.sleep(0.02)&lt;br /&gt;
&lt;br /&gt;
            target_char = None&lt;br /&gt;
            win32clipboard.OpenClipboard()&lt;br /&gt;
            try:&lt;br /&gt;
                if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_UNICODETEXT):&lt;br /&gt;
                    target_char = win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT)&lt;br /&gt;
            except Exception:&lt;br /&gt;
                pass&lt;br /&gt;
            finally:&lt;br /&gt;
                win32clipboard.CloseClipboard()&lt;br /&gt;
&lt;br /&gt;
            send_keys(&amp;quot;{LEFT}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
            if original_text:&lt;br /&gt;
                try:&lt;br /&gt;
                    win32clipboard.OpenClipboard()&lt;br /&gt;
                    win32clipboard.EmptyClipboard()&lt;br /&gt;
                    win32clipboard.SetClipboardText(original_text, win32clipboard.CF_UNICODETEXT)&lt;br /&gt;
                    win32clipboard.CloseClipboard()&lt;br /&gt;
                except Exception:&lt;br /&gt;
                    pass&lt;br /&gt;
&lt;br /&gt;
            return target_char&lt;br /&gt;
        except Exception:&lt;br /&gt;
            pass&lt;br /&gt;
        return None&lt;br /&gt;
&lt;br /&gt;
    def __del__(self):&lt;br /&gt;
        # 프로그램 종료 시 안전하게 DB 세션 닫기&lt;br /&gt;
        if self.db_conn:&lt;br /&gt;
            self.db_conn.close()&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    root = tk.Tk()&lt;br /&gt;
    app = CaretReaderApp(root)&lt;br /&gt;
    root.mainloop()&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
</feed>