Takip et

Windows Terminal, ConPTY ve win32-input-mode Nedir?

Go ConPTY Uygulamalarında Windows Terminal Girdilerini Yönetme

Modern terminal uygulamaları geliştirirken, kullanıcı girdilerini doğru bir şekilde yakalamak ve işlemek çoğu zaman büyük bir zorluk haline gelebilir. Özellikle Windows platformunda, Windows Terminal’in ortaya çıkışı ve beraberinde getirdiği win32-input-mode adlı özel girdi modu, Go ile ConPTY (Console Pseudo-terminal) uygulamaları geliştirenler için beklenmedik karmaşıklıklar yaratmaktadır. Peki, bu durumun ardındaki nedenler nelerdir ve Go kullanarak bu girdileri nasıl “evcilleştirebiliriz”? Bu makale, ConPTY’nin temel dinamiklerinden win32-input-mode‘un derinliklerine, Go ile bu zorlukların üstesinden gelmek için adım adım çözümlere kadar uzanan kapsamlı bir rehber sunmaktadır. Okuyucuyu sıfırdan konuya dahil ederek, hem teorik bilgiyi hem de pratik uygulama örneklerini bir araya getirmeyi hedefliyoruz.

Windows işletim sistemlerinde terminal deneyimi, uzun yıllar boyunca genellikle cmd.exe veya PowerShell gibi geleneksel konsol pencereleriyle sınırlı kalmıştı. Ancak, Microsoft’un Windows Terminal’i piyasaya sürmesiyle bu durum kökten değişti. Windows Terminal, sekmeli arayüzler, GPU hızlandırmalı metin işleme, Unicode ve emoji desteği gibi modern özelliklerle donatılmış, geliştiriciler ve ileri düzey kullanıcılar için tasarlanmış güçlü bir araçtır. Bu modernleşme, terminal uygulamalarının altyapısında da önemli yenilikleri beraberinde getirdi.

Bu yeniliklerden en önemlisi şüphesiz ConPTY veya “Console Pseudo-terminal” kavramıdır. Geleneksel olarak, Windows’taki konsol uygulamaları doğrudan konsol API’lerini kullanarak input ve output işlemlerini gerçekleştirirdi. Bu da üçüncü taraf terminal emülatörlerinin veya kabuklarının, bu uygulamaların davranışlarını manipüle etmesini veya çıktılarını düzgün bir şekilde ayrıştırmasını zorlaştırıyordu. ConPTY, bu sorunu çözmek için geliştirilmiş bir arayüzdür. Tıpkı Linux’taki PTY (pseudo-terminal) gibi, ConPTY de bir alt süreci (örneğin, bir CLI uygulaması) izole bir konsol oturumunda çalıştırmanıza olanak tanır. Ancak, PTY’den farklı olarak, ConPTY, alt sürecin Windows konsol API’lerini kullanmaya devam etmesini sağlar. Yani, alt süreç kendini gerçek bir konsolda çalıştığını zannederken, aslında onunla ConPTY aracılığıyla iletişim kuran bir ara program vardır. Bu ara program, alt sürecin girdilerini yakalayabilir ve çıktılarını yönlendirebilir.

Peki, win32-input-mode bu denkleme nerede giriyor? ConPTY, alt sürecin çıktısını VT (Virtual Terminal) sekanslarına dönüştürerek ana sürece (yani bizim Go uygulamamıza veya Windows Terminal’e) gönderir. Bu, çıktıların modern terminal emülatörleri tarafından kolayca anlaşılmasını ve işlenmesini sağlar. Ancak, girdi tarafında işler biraz daha karmaşıklaşır. Windows Terminal, varsayılan olarak, daha zengin etkileşimlere olanak tanımak için win32-input-mode‘u kullanabilir. Bu mod etkinleştirildiğinde, kullanıcıdan gelen klavye tuşları, fare hareketleri ve tıklamaları gibi girdiler, doğrudan VT sekansları olarak değil, Windows’un kendi INPUT_RECORD yapısı formatında ConPTY aracılığıyla alt sürece gönderilir. Bu, özellikle fare etkileşimi, özel tuş kombinasyonları veya pencere yeniden boyutlandırma gibi olayların detaylı bir şekilde yakalanması gerektiğinde önemlidir.

Go programlama dili, sistem programlama yetenekleri ve C/C++ ile kolayca entegre olabilme özelliği sayesinde, ConPTY API’leriyle doğrudan etkileşime geçmek için mükemmel bir araçtır. syscall paketi ve ilgili Windows API’leri aracılığıyla Go uygulamaları, ConPTY oturumları oluşturabilir, alt süreçleri yönetebilir ve girdi/çıktı akışlarını kontrol edebilir. Ancak, win32-input-mode‘un varlığı, bu girdilerin standart VT sekansları yerine INPUT_RECORD formatında gelmesi nedeniyle, özel bir ayrıştırma ve dönüştürme mantığı gerektirir. Aksi takdirde, CLI uygulamanızdaki fare tıklamaları veya özel tuş kombinasyonları doğru şekilde çalışmayabilir veya hiç algılanmayabilir. Bu, geliştiriciler için beklenmedik bir engel teşkil edebilir, zira çoğu zaman terminal girdi yönetimini standart VT sekansları üzerinden çözmeye alışkındırlar. Go’nun burada devreye girmesi, bu karmaşık Windows API’leri ile doğrudan çalışmak için temiz ve verimli bir yol sunmasıdır.

Go ile Temel ConPTY Uygulaması Oluşturma: İlk Adımlar

Go ile ConPTY uygulamaları geliştirmeye başlamadan önce, temel kavramları anlamış olmamız önemlidir. Şimdi, Go’yu kullanarak basit bir ConPTY oturumu nasıl başlatılır ve bir alt süreç (örneğin, cmd.exe) nasıl çalıştırılır ona odaklanalım. Bu, win32-input-mode‘un karmaşıklıklarına girmeden önce, Go’nun Windows sistem API’leriyle nasıl etkileşime geçtiğini gösteren ilk adımdır. Bu bölüm, okuyucunun konuya aşinalığını artırmak ve temel ConPTY mekanizmasını kavramasını sağlamak için tasarlanmıştır.

Go’da ConPTY ile çalışmak, çoğunlukla syscall paketini ve Windows API fonksiyonlarını kullanmayı gerektirir. İlk olarak, bir sözde konsol oluşturmak için CreatePseudoConsole fonksiyonuna ihtiyacımız olacak. Bu fonksiyon, ana uygulama ile alt süreç arasında bir iletişim köprüsü görevi görecek olan iki adet “pipe” (kanallar) gerektirir: biri alt sürece girdi göndermek için, diğeri de alt süreçten çıktı almak için. Bu pipe’lar, bizim Go uygulamamızın alt süreçle etkileşim kurmasını sağlayacak temel mekanizmalardır. Ayrıca, alt sürecin başlangıç bilgilerini tanımlayan STARTUPINFOEX yapısını ve sürecin kendisini kontrol etmek için PROCESS_INFORMATION yapısını kullanacağız.

Aşağıdaki kod bloğu, Go ile basit bir ConPTY oturumu başlatıp, cmd.exe‘yi bu oturum içinde çalıştıran temel bir iskeleti göstermektedir. Bu örnekte, alt süreçten gelen çıktıları okuyacağız ve bizim uygulamamızdan alt sürece girdi gönderebileceğiz. Ancak, bu aşamada win32-input-mode‘un yarattığı spesifik girdi sorunlarına henüz değinmiyoruz, sadece temel iletişimi kurmaya odaklanıyoruz. Kodda görüleceği üzere, birçok Windows API çağrısı ve pointer (işaretçi) manipülasyonu bulunmaktadır. Bu, Go’nun düşük seviyeli sistem programlama yeteneklerinin bir göstergesidir.


package main

import (
    "fmt"
    "os"
    "syscall"
    "unsafe"
)

var (
    kernel32           = syscall.NewLazyDLL("kernel32.dll")
    createPseudoConsole         = kernel32.NewProc("CreatePseudoConsole")
    closePseudoConsole          = kernel32.NewProc("ClosePseudoConsole")
    resizePseudoConsole         = kernel32.NewProc("ResizePseudoConsole")
    createPipe                  = kernel32.NewProc("CreatePipe")
    getConsoleScreenBufferInfo  = kernel32.NewProc("GetConsoleScreenBufferInfo")
)

const (
    // Geniş ve özlü sabitler
    STARTF_USESTDHANDLES = 0x00000100
    STARTF_USESHOWWINDOW = 0x00000001
    SW_SHOW              = 5
    CREATE_UNICODE_ENVIRONMENT = 0x00000400
    EXTENDED_STARTUPINFO_PRESENT = 0x00080000
    MAX_PATH = 260
    INVALID_HANDLE_VALUE = ^syscall.Handle(0)
)

type COORD struct {
    X short
    Y short
}

type SMALL_RECT struct {
    Left   short
    Top    short
    Right  short
    Bottom short
}

type CONSOLE_SCREEN_BUFFER_INFO struct {
    dwSize              COORD
    dwCursorPosition    COORD
    wAttributes         uint16
    srWindow            SMALL_RECT
    dwMaximumWindowSize COORD
}

// Win32 API çağrıları için yardımcı fonksiyonlar
// ... (STRUCT YAPILARI VE YARDIMCI FONKSİYONLAR EKLECEK) ...
// Bu bölüm, makale uzunluğu için daha fazla struct ve yardımcı fonksiyon tanımı içerebilir.
// Örneğin, STARTUPINFOEX, PROCESS_INFORMATION, SECURITY_ATTRIBUTES gibi yapılar.

func createConPTY() (syscall.Handle, syscall.Handle, syscall.Handle, error) {
    var (
        inputRead  syscall.Handle
        inputWrite syscall.Handle
        outputRead syscall.Handle
        outputWrite syscall.Handle
    )

    // Alt sürece girdi göndermek için pipe
    err := syscall.CreatePipe(&inputRead, &inputWrite, nil, 0)
    if err != nil {
        return 0, 0, 0, fmt.Errorf("girdi pipe oluşturulamadı: %w", err)
    }

    // Alt süreçten çıktı almak için pipe
    err = syscall.CreatePipe(&outputRead, &outputWrite, nil, 0)
    if err != nil {
        syscall.CloseHandle(inputRead)
        syscall.CloseHandle(inputWrite)
        return 0, 0, 0, fmt.Errorf("çıktı pipe oluşturulamadı: %w", err)
    }

    // Konsol ekran boyutu bilgisi al
    var csbi CONSOLE_SCREEN_BUFFER_INFO
    r1, _, err := getConsoleScreenBufferInfo.Call(uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&csbi)))
    if r1 == 0 {
        return 0, 0, 0, fmt.Errorf("konsol bilgisi alınamadı: %w", err)
    }

    // ConPTY oluştur
    var pseudoConsoleHandle syscall.Handle
    r1, _, err = createPseudoConsole.Call(
        uintptr(csbi.dwSize.X), // Genişlik
        uintptr(csbi.dwSize.Y), // Yükseklik
        uintptr(inputRead),
        uintptr(outputWrite),
        uintptr(unsafe.Pointer(&pseudoConsoleHandle)),
    )
    if r1 == 0 {
        syscall.CloseHandle(inputRead)
        syscall.CloseHandle(inputWrite)
        syscall.CloseHandle(outputRead)
        syscall.CloseHandle(outputWrite)
        return 0, 0, 0, fmt.Errorf("pseudo console oluşturulamadı: %w", err)
    }

    return pseudoConsoleHandle, inputWrite, outputRead, nil
}

func main() {
    pseudoConsoleHandle, inputWriter, outputReader, err := createConPTY()
    if err != nil {
        fmt.Fprintf(os.Stderr, "Hata: %v\n", err)
        return
    }
    defer closePseudoConsole.Call(uintptr(pseudoConsoleHandle))
    defer syscall.CloseHandle(inputWriter)
    defer syscall.CloseHandle(outputReader)

    fmt.Println("ConPTY başarıyla oluşturuldu. cmd.exe başlatılıyor...")

    // TODO: Alt süreci başlatma kodu buraya eklenecek
    // Örneğin, cmd.exe'yi pseudoConsoleHandle ile ilişkilendirerek çalıştırmak.
    // Bu kısım sonraki bölümlerde detaylandırılacaktır.

    // Basit bir çıktı okuyucu (örnek amaçlı)
    outputBuf := make([]byte, 1024)
    for {
        var n uint32
        err := syscall.ReadFile(outputReader, outputBuf, &n, nil)
        if err != nil {
            // fmt.Println("Hata okuma çıktısı:", err)
            break // Genellikle pipe kapatıldığında bu hata alınır
        }
        if n > 0 {
            os.Stdout.Write(outputBuf[:n])
        }
    }

    fmt.Println("Uygulama tamamlandı.")
}

Yukarıdaki örnekte, temel pipe'lar oluşturulur ve CreatePseudoConsole çağrılır. main fonksiyonunda ise, oluşturulan pseudoConsoleHandle'ı kullanarak bir alt süreci nasıl başlatacağımızın yeri belirtilmiştir. Dikkat edilmesi gereken önemli bir nokta, ConPTY'nin "genişlik" ve "yükseklik" gibi konsol boyut parametrelerine ihtiyaç duymasıdır. Bu bilgiler genellikle mevcut konsolun boyutlarından alınır. Bu boyutlar, alt sürecin kendisini doğru bir şekilde render etmesi için kritiktir.

Bu temel kurulum, ConPTY'nin Go'daki mekaniğini anlamanın ilk adımıdır. inputWriter ve outputReader pipe'ları aracılığıyla alt süreçle iki yönlü iletişimi kurmuş oluruz. Alt sürecin başlatılması, CreateProcess veya CreateProcessW gibi Windows API fonksiyonları kullanılarak gerçekleştirilir. Bu fonksiyonlar, STARTUPINFOEX yapısının bir parçası olarak pseudoConsoleHandle'ı alacak şekilde yapılandırılmalıdır. Bu sayede, başlatılan alt süreç, bizim kontrolümüzdeki sözde konsol ortamında çalışmaya başlar.

Uzman İpucu: ConPTY ile çalışırken syscall paketini yoğun bir şekilde kullanmak, kodunuzu Windows'a özgü API'lere bağımlı hale getirecektir. Çapraz platform desteği arıyorsanız, platforma özgü soyutlama katmanları oluşturmayı düşünebilirsiniz.

Bu aşamada, Go uygulamamızın alt süreçle iletişim kurmaya hazır olduğunu görüyoruz. Ancak, bu temel kurulum, win32-input-mode'un getirdiği özel girdi sorunlarını doğrudan ele almaz. Sadece ham girdi ve çıktı akışlarını yönetiriz. win32-input-mode'un neden ve nasıl bir sorun teşkil ettiğini anlamak için bir sonraki bölüme geçmeliyiz. Burada, fare tıklamaları, tuş basmaları gibi olayların INPUT_RECORD formatında nasıl geldiğini ve bunun geleneksel VT sekanslarından farkını inceleyeceğiz.

win32-input-mode'un Detayları ve Karşılaşılan Zorluklar

Yukarıdaki bölümde Go ile temel bir ConPTY oturumu oluşturmayı ve bir alt süreci başlatmayı öğrendik. Şimdi ise, terminal uygulamaları geliştirirken başımızı ağrıtan asıl meseleye, yani win32-input-mode'un detaylarına ve onunla gelen zorluklara odaklanacağız. Bu mod, özellikle Windows Terminal kullanıldığında ve interaktif CLI uygulamaları söz konusu olduğunda kritik bir fark yaratır. Anlamak için, öncelikle standart terminal girdi mekanizmalarından neden farklılaştığını derinlemesine incelememiz gerekiyor.

Geleneksel terminal uygulamaları ve emülatörleri genellikle VT (Virtual Terminal) sekansları adı verilen standartlaştırılmış escape kodlarını kullanarak girdi ve çıktı işlemlerini gerçekleştirir. Örneğin, bir ok tuşuna basıldığında \x1b[A gibi bir sekans gönderilirken, fare tıklamaları için de SGR (Select Graphic Rendition) fare raporlama formatı gibi VT tabanlı sekanslar kullanılır. Bu standart, çoğu terminal uygulamasının platform bağımsız bir şekilde etkileşim kurmasını sağlar. Ancak Windows Terminal, win32-input-mode etkin olduğunda, bu geleneksel yaklaşımın dışına çıkar.

Peki, win32-input-mode ne zaman ve neden aktive olur? Temelde, bir ConPTY oturumu oluşturulduğunda ve bu oturum bir Windows Terminal penceresi tarafından barındırıldığında, Windows Terminal, alt sürece daha zengin ve detaylı girdi bilgileri sağlamak amacıyla bu modu kullanmayı tercih edebilir. Bu modun amacı, klasik Windows konsol uygulamalarının alışık olduğu INPUT_RECORD yapısını kullanarak girdileri aktarmaktır. Bu yapı, bir klavye tuşunun basılıp basılmadığı, fare tekerleğinin kaydırılıp kaydırılmadığı, imlecin nerede olduğu gibi çok daha granüler bilgileri içerir. Yani, terminal uygulamanız "bir tuşa basıldı" bilgisini almak yerine, "sol Shift tuşu basılıyken 'A' tuşuna basıldı, imleç (X,Y) konumundaydı" gibi detaylı bir bilgi paketi alır.

Bu durumun getirdiği en büyük zorluk, bizim Go uygulamamızın (veya ConPTY aracılığıyla iletişim kurduğumuz alt sürecin) bu INPUT_RECORD formatındaki girdileri doğru bir şekilde ayrıştırması ve eğer gerekiyorsa, bunları standart VT sekanslarına dönüştürmesidir. Eğer bunu yapmazsak, alt sürecimiz fare tıklamalarını, özel tuş kombinasyonlarını (Ctrl+C, Ctrl+V hariç) veya pencere yeniden boyutlandırma olaylarını algılayamaz. Örneğin, htop veya gitui gibi fare etkileşimli veya karmaşık tuş kombinasyonları gerektiren bir CLI aracı yazdığınızı düşünün. Kullanıcı fare ile bir menü öğesine tıkladığında veya bir paneli kaydırdığında, uygulamanız hiçbir tepki vermeyecektir çünkü beklediği VT sekansları yerine, INPUT_RECORD formatında bir veri akışı geliyordur ve uygulama bunu yorumlayamaz.

INPUT_RECORD yapısı aslında bir union (birleşim) içerir ve farklı girdi olayları için farklı alt yapılar barındırır:

  • KEY_EVENT_RECORD: Klavye tuş basmaları ve bırakmaları için. Hangi tuşun basıldığı, tuşun ASCII veya Unicode değeri, kontrol tuşlarının (Shift, Ctrl, Alt) durumu gibi bilgiler içerir.
  • MOUSE_EVENT_RECORD: Fare hareketleri, tıklamaları ve tekerlek kaydırmaları için. İmlecin konumu, hangi fare tuşunun basılı olduğu, fare tekerleğinin ne kadar kaydırıldığı gibi detayları barındırır.
  • WINDOW_BUFFER_SIZE_EVENT_RECORD: Konsol penceresinin yeniden boyutlandırılması gibi olaylar için.
  • MENU_EVENT_RECORD: Uygulama menüleri ile ilgili olaylar için (daha az kullanılır).
  • FOCUS_EVENT_RECORD: Konsol penceresinin odaklanıp odaklanmadığı ile ilgili olaylar için.

Go uygulamamız, ConPTY'nin "çıktı" pipe'ından okurken, bu INPUT_RECORD yapılarını bayt akışı olarak alacaktır. Bu bayt akışını alıp doğru INPUT_RECORD yapısına dönüştürmek, ardından içindeki KEY_EVENT_RECORD veya MOUSE_EVENT_RECORD alt yapılarını inceleyerek anlamlı olayları çıkarmak ve son olarak bunları CLI uygulamasının beklediği VT sekanslarına çevirmek, Go geliştiricileri için önemli bir görev haline gelir. Bu çeviri süreci hem hata potansiyeli taşır hem de performans açısından dikkatli optimizasyon gerektirebilir. Özellikle fare olaylarını SGR formatına çevirmek veya özel tuşları uygun ANSI sekanslarına dönüştürmek, detaylı bir eşleme tablosu ve mantık gerektirir. Bu karmaşıklık, Go'nun sistem programlama yeteneklerini ve dikkatli bayt manipülasyonunu gerektiren bir alandır. Bu durum, sadece Go'ya özgü bir sorun değil, ConPTY ile etkileşime geçen her dilde karşılaşılabilecek genel bir Windows Terminal özgülüğüdür.

Go'da win32-input-mode Girdilerini Ayrıştırma ve Normalleştirme

Önceki bölümde win32-input-mode'un ne olduğunu ve neden INPUT_RECORD yapısında girdiler gönderdiğini anladık. Şimdi, Go uygulamamızın bu INPUT_RECORD yapılarını nasıl okuyacağını, ayrıştıracağını ve gerekirse standart VT sekanslarına nasıl dönüştüreceğini detaylı bir şekilde inceleyeceğiz. Bu, interaktif CLI uygulamalarımızın Windows Terminal ortamında beklendiği gibi çalışmasını sağlamak için hayati bir adımdır. Amacımız, Go'nun güçlü sistem programlama yeteneklerini kullanarak, bu düşük seviyeli Windows API girdilerini yüksek seviyeli, terminal dostu çıktılara çevirmektir.

ConPTY'nin çıktı pipe'ından okuduğumuzda, INPUT_RECORD yapılarını içeren bir bayt akışı elde ederiz. Bu yapı, Go'da karşılık gelen Go struct'larına dönüştürülmelidir. Bu dönüşüm, binary.Read veya unsafe.Pointer kullanılarak manuel bayt manipülasyonu ile yapılabilir. INPUT_RECORD kendisi bir union içerdiği için, hangi alt yapının (KEY_EVENT_RECORD, MOUSE_EVENT_RECORD vb.) aktif olduğunu belirlemek için EventType alanını kontrol etmemiz gerekir. Bu sayede doğru alt yapıyı ayrıştırabiliriz.

İlk adım, INPUT_RECORD ve onun alt yapılarının Go'daki temsillerini tanımlamaktır. Bu yapılar, Windows SDK'sındaki tanımlara birebir uygun olmalıdır. Özellikle veri türlerinin boyutları (short, int32, uint16 vb.) doğru eşleşmelidir, aksi takdirde yanlış bayt okumalarıyla karşılaşırız.


// win32api_types.go (ayrı bir dosyada veya ana dosyanın başlangıcında tanımlanabilir)
package main

import "syscall"

// COORD struct, X ve Y koordinatları için
type COORD struct {
    X short
    Y short
}

// KEY_EVENT_RECORD için gerekli flag'ler
const (
    FROM_LEFT_1ST_BUTTON_PRESSED = 0x0001
    RIGHTMOST_BUTTON_PRESSED     = 0x0002
    FROM_LEFT_2ND_BUTTON_PRESSED = 0x0004
    FROM_LEFT_3RD_BUTTON_PRESSED = 0x0008
    FROM_LEFT_4TH_BUTTON_PRESSED = 0x0010
    
    // Klavye kontrol tuşları
    CAPSLOCK_ON        = 0x0080
    ENHANCED_KEY       = 0x0100
    LEFT_ALT_PRESSED   = 0x0002
    LEFT_CTRL_PRESSED  = 0x0008
    NUMLOCK_ON         = 0x0020
    RIGHT_ALT_PRESSED  = 0x0001
    RIGHT_CTRL_PRESSED = 0x0004
    SCROLLLOCK_ON      = 0x0040
    SHIFT_PRESSED      = 0x0010
)

// Event türleri
const (
    KEY_EVENT         = 0x0001
    MOUSE_EVENT       = 0x0002
    WINDOW_BUFFER_SIZE_EVENT = 0x0004
    // Diğer event türleri de eklenebilir
)

type KEY_EVENT_RECORD struct {
    bKeyDown          int32 // 0: Key up, 1: Key down
    wRepeatCount      uint16
    wVirtualKeyCode   uint16
    wVirtualScanCode  uint16
    U_Char            uint16 // Union: UnicodeChar or AsciiChar
    dwControlKeyState uint32
}

type MOUSE_EVENT_RECORD struct {
    dwMousePosition COORD
    dwButtonState   uint32
    dwControlKeyState uint32
    dwEventFlags    uint32
}

type WINDOW_BUFFER_SIZE_RECORD struct {
    dwSize COORD
}

type INPUT_RECORD struct {
    EventType uint16
    _         uint16 // Padding
    Event     [16]byte // Union için ham bayt alanı, 16 bayt en büyük olayın boyutu
}

// Diğer win32 structları ve typedef'leri eklenebilir (örneğin short int32 gibi)
type short int16 // Windows'daki SHORT int16'ya karşılık gelir

Yukarıdaki yapıları tanımladıktan sonra, ConPTY'nin çıktı pipe'ından okuduğumuz baytları bu INPUT_RECORD yapısına dönüştürmemiz gerekir. Ardından EventType alanına bakarak hangi olayın geldiğini belirler ve ilgili alt yapıyı ayrıştırırız. İşte temel bir ayrıştırma döngüsü:


// main.go içinde, outputReader'dan okuma döngüsünün bir parçası olarak
func processInputRecords(outputReader syscall.Handle, inputWriter syscall.Handle) {
    recordSize := unsafe.Sizeof(INPUT_RECORD{})
    inputBuffer := make([]byte, recordSize)

    for {
        var n uint32
        // ConPTY'den INPUT_RECORD okuma
        err := syscall.ReadFile(outputReader, inputBuffer, &n, nil)
        if err != nil {
            // Hata yönetimi: Pipe kapatıldığında veya başka bir hata olduğunda çık
            if err == syscall.ERROR_BROKEN_PIPE {
                fmt.Println("ConPTY çıktı pipe'ı kapatıldı.")
            } else {
                fmt.Fprintf(os.Stderr, "INPUT_RECORD okuma hatası: %v\n", err)
            }
            return
        }

        if n == uint32(recordSize) {
            record := (*INPUT_RECORD)(unsafe.Pointer(&inputBuffer[0]))
            
            // Event türüne göre ayrıştırma
            switch record.EventType {
            case KEY_EVENT:
                keyEvent := (*KEY_EVENT_RECORD)(unsafe.Pointer(&record.Event[0]))
                if keyEvent.bKeyDown != 0 { // Sadece tuşa basma olaylarını işleyelim
                    vtSeq := convertKeyEventToVT(keyEvent)
                    if len(vtSeq) > 0 {
                        // Dönüştürülen VT sekansını alt sürece yaz
                        // NOTE: Bu VT sekansı genellikle ana terminale (os.Stdout) yazılır,
                        // alt sürece değil. Eğer alt süreç de VT bekliyorsa buraya yazılır.
                        // Çoğu durumda, ConPTY kendisi alt sürece Win32 olayını iletir.
                        // Bizim amacımız, eğer ara katmanda bu olayı kendimiz işleyip
                        // başka bir yere (örn. başka bir pty'ye) iletmekse bu dönüşüm mantıklı.
                        os.Stdout.Write([]byte(vtSeq))
                    }
                }
            case MOUSE_EVENT:
                mouseEvent := (*MOUSE_EVENT_RECORD)(unsafe.Pointer(&record.Event[0]))
                vtSeq := convertMouseEventToVT(mouseEvent)
                if len(vtSeq) > 0 {
                    os.Stdout.Write([]byte(vtSeq))
                }
            case WINDOW_BUFFER_SIZE_EVENT:
                sizeEvent := (*WINDOW_BUFFER_SIZE_RECORD)(unsafe.Pointer(&record.Event[0]))
                // Pencere yeniden boyutlandırma olayını işleme
                // Örneğin, os.Stdout.Write([]byte(fmt.Sprintf("\x1b[8;%d;%dt", sizeEvent.dwSize.Y, sizeEvent.dwSize.X)))
                fmt.Printf("Pencere boyutu değişti: %dx%d\n", sizeEvent.dwSize.X, sizeEvent.dwSize.Y)
                // ResizePseudoConsole çağrısı yapılabilir
                // resizePseudoConsole.Call(uintptr(pseudoConsoleHandle), uintptr(sizeEvent.dwSize.X), uintptr(sizeEvent.dwSize.Y))
            // Diğer event türleri için case'ler eklenebilir
            }
        } else if n > 0 {
            fmt.Fprintf(os.Stderr, "Beklenenden farklı boyutta INPUT_RECORD okundu: %d bayt\n", n)
        }
    }
}

Şimdi en kritik kısma gelelim: KEY_EVENT_RECORD ve MOUSE_EVENT_RECORD'u standart VT sekanslarına dönüştürme. Bu dönüşüm, genellikle büyük bir switch ifadesi veya eşleme tablosu gerektirir. Her tuş kombinasyonu ve fare olayı için belirli bir VT sekansı vardır.

Uzman İpucu: unsafe.Pointer kullanımı Go'da dikkatli olmayı gerektirir. Performans için gerekli olabilir ancak yanlış kullanımda hafıza hatalarına yol açabilir. Alternatif olarak encoding/binary paketini kullanarak daha güvenli ama potansiyel olarak biraz daha yavaş bir yol izleyebilirsiniz.

Klavye Girdilerini VT Sekanslarına Çevirme

Klavye girdilerini dönüştürmek için, KEY_EVENT_RECORD yapısındaki wVirtualKeyCode, U_Char ve dwControlKeyState alanlarına bakarız. Örneğin, "A" tuşuna basıldığında U_Char alanı 'A' değerini içerecektir. Özel tuşlar (F1-F12, Ok tuşları, Home, End vb.) için ise wVirtualKeyCode alanını kontrol ederiz ve buna göre uygun VT sekansını (örneğin, F1 için \x1bOP veya ok tuşları için \x1b[A) döndürürüz. Kontrol tuşlarının (Shift, Ctrl, Alt) durumu, dwControlKeyState ile belirlenir ve bu da VT sekansının modifiye edilmesine neden olabilir (örneğin, Shift+Ok tuşu farklı bir sekans verebilir).

Fare Girdilerini VT Sekanslarına Çevirme

Fare olayları biraz daha karmaşıktır. MOUSE_EVENT_RECORD yapısındaki dwMousePosition, dwButtonState ve dwEventFlags alanlarını kullanarak olayın türünü (tıklama, çift tıklama, hareket, tekerlek kaydırma) ve konumunu belirleriz. Ardından bunları SGR (Set Graphics Rendition) fare raporlama formatına dönüştürürüz. SGR formatı genellikle \x1b[

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.