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.
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.
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[ veya \x1b[ şeklinde olur, burada basılan tuşu, ve ise fare imlecinin konumunu temsil eder. Tekerlek kaydırma olayları da benzer şekilde özel VT sekanslarına çevrilebilir.
Bu dönüşüm fonksiyonlarının kod örnekleri, makalenin hacmini aşacağı için burada genel prensipleri açıklamakla yetindik. Ancak temel mantık, gelen Win32 INPUT_RECORD verilerini analiz edip, terminal uygulamalarının evrensel dili olan VT sekanslarına çevirmektir. Bu sayede, Go ile yazdığımız ConPTY uygulamaları, Windows Terminal'in özel win32-input-mode'u karşısında bile tam işlevsellik sunabilen, interaktif ve modern CLI araçları geliştirmemize olanak tanır.
Gerçek Dünya Senaryosu: Interaktif Bir CLI Uygulamasını ConPTY Üzerinde Çalıştırma
Şimdiye kadar, Windows Terminal'in win32-input-mode'unun doğasını, Go ile temel ConPTY uygulamalarını nasıl oluşturacağımızı ve INPUT_RECORD yapılarını nasıl ayrıştıracağımızı teorik ve temel kod parçacıklarıyla inceledik. Artık bilgimizi gerçek dünya bir senaryoya uygulayarak pekiştirelim: Interaktif bir CLI uygulamasının ConPTY üzerinde nasıl sorunsuz çalıştığını ve win32-input-mode'dan gelen girdileri nasıl işlediğini göreceğiz. Bu vaka analizi, özellikle görsel olarak zengin, fare etkileşimli veya karmaşık tuş kombinasyonları gerektiren uygulamalar geliştirenler için kritik öneme sahiptir.
Birçok modern CLI uygulaması, htop, lazygit, fzf gibi araçlar, kullanıcı arayüzleri için ncurses veya tcell gibi kütüphaneleri kullanır. Bu kütüphaneler genellikle standart VT sekansları beklerler. Dolayısıyla, Go ConPTY uygulamamızın ana görevi, win32-input-mode'dan gelen INPUT_RECORD'ları bu kütüphanelerin anlayabileceği VT sekanslarına dönüştürmek ve ana terminalin (veya bizim Go uygulamamızın) çıktı pipe'ına yazmaktır. Alt süreç genellikle bu dönüştürülmüş VT sekanslarını okuyarak etkileşimi sağlar.
Vaka Analizi: Basit Bir Fare Destekli Metin Görüntüleyici
Hayali bir senaryo oluşturalım: Basit bir metin dosyasını görüntüleyen, fare tekerleği ile kaydırma ve fare tıklaması ile metin seçme özelliklerine sahip bir CLI uygulaması geliştirmek istiyoruz. Bu uygulama, kullanıcıdan gelen fare ve klavye girdilerine duyarlı olmalıdır. Uygulamanın çekirdeği Go ile yazılmış ve ConPTY aracılığıyla Windows Terminal üzerinde çalıştırılmaktadır. İşte adımlarımız ve karşılaşacağımız zorluklar:
- ConPTY Oturumunun Başlatılması: Go uygulamamız, daha önce anlattığımız gibi bir ConPTY oturumu oluşturur. Bu oturumda metin görüntüleyici alt sürecini çalıştırırız. Metin görüntüleyici, standart Girdi (
stdin), Çıktı (stdout) ve Hata (stderr) akışlarını kullanır. - Girdilerin Dinlenmesi: Go uygulamamız, ConPTY'nin "input" pipe'ından (yani Windows Terminal'den gelen)
INPUT_RECORD'ları sürekli olarak okur. Bu, fare hareketleri, tıklamalar, tekerlek kaydırmaları ve klavye basmalarını içerir. INPUT_RECORD'ların Ayrıştırılması: HerINPUT_RECORDokunduğunda,EventTypekontrol edilir.- Eğer bir
KEY_EVENT_RECORDise, Go uygulamamız bunu uygun bir VT klavye sekansına dönüştürür (örneğin, "a" tuşu için "a", yukarı ok tuşu için\x1b[A). - Eğer bir
MOUSE_EVENT_RECORDise, Go uygulamamız fare konumunu, basılan tuşu ve varsa tekerlek kaydırma bilgisini alır. Bunu SGR fare raporlama formatına (\x1b[) veya tekerlek olayları için özel sekanslara dönüştürür. WINDOW_BUFFER_SIZE_EVENT_RECORDiçin, pencere boyutunu günceller ve ConPTY'nin boyutunuResizePseudoConsoleile ayarlarız. Ayrıca alt sürece de bir yeniden boyutlandırma VT sekansı gönderebiliriz (\x1b[8;), böylece metin görüntüleyici arayüzünü kendini yeniden düzenleyebilir.; t - Dönüştürülmüş Girdilerin Yönlendirilmesi: Ayrıştırılıp VT sekanslarına dönüştürülen bu girdiler, alt sürecin
stdin'ine (yani ConPTY'nin "output" pipe'ına) yazılır. Bu sayede, metin görüntüleyici uygulamamız, sanki doğrudan standart bir terminalden VT sekansları alıyormuş gibi davranır. - Çıktıların Yönlendirilmesi: Metin görüntüleyicinin
stdout'a yazdığı VT sekansları (metin, renklendirme, imleç hareketleri vb.), ConPTY'nin "output" pipe'ından Go uygulamamız tarafından okunur ve doğrudanos.Stdout'a (yani kullanıcının Windows Terminal penceresine) yazılır.
Bu senaryo, Go uygulamasının bir "ara katman" (proxy) görevi gördüğünü açıkça göstermektedir. Windows Terminal'in gönderdiği platforma özgü INPUT_RECORD'ları yakalar, bunları evrensel VT sekanslarına çevirir ve alt süreç olan metin görüntüleyiciye iletir. Böylece metin görüntüleyici, Windows Terminal'in karmaşık girdi mekanizmalarından haberdar olmadan, sadece standart VT protokolüne uygun çalışabilir.
// processInputRecords içinde KEY_EVENT_RECORD'u VT'ye çevirme örneği
func convertKeyEventToVT(event *KEY_EVENT_RECORD) string {
// Örnek: Basit ASCII karakterler
if event.U_Char >= 32 && event.U_Char <= 126 { // Basılabilir ASCII karakterler
return string(rune(event.U_Char))
}
// Özel tuşlar için örnek dönüşümler
switch event.wVirtualKeyCode {
case 0x25: // VK_LEFT
return "\x1b[D"
case 0x26: // VK_UP
return "\x1b[A"
case 0x27: // VK_RIGHT
return "\x1b[C"
case 0x28: // VK_DOWN
return "\x1b[B"
case 0x0D: // VK_RETURN
return "\r" // Enter tuşu
case 0x08: // VK_BACK
return "\x7f" // Backspace
// Diğer özel tuşlar (Home, End, PageUp, PageDown, F1-F12 vb.) için eşlemeler buraya eklenecek
// Ctrl, Alt, Shift gibi modifikatör tuş durumları da dwControlKeyState ile kontrol edilmelidir.
// Örneğin, Ctrl+C için farklı bir sekans olabilir.
}
return "" // Eşleşmeyen tuşlar için boş dönüş
}
// processInputRecords içinde MOUSE_EVENT_RECORD'u VT'ye çevirme örneği (SGR formatı)
func convertMouseEventToVT(event *MOUSE_EVENT_RECORD) string {
buttonState := 0
// Fare tuşları ve kaydırma için basılan tuşları belirle
if (event.dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED) != 0 {
buttonState = 0 // Sol tıklama
} else if (event.dwButtonState & RIGHTMOST_BUTTON_PRESSED) != 0 {
buttonState = 2 // Sağ tıklama
} else if (event.dwButtonState & FROM_LEFT_2ND_BUTTON_PRESSED) != 0 {
buttonState = 1 // Orta tıklama
} else if (event.dwEventFlags == 0x0004) { // MOUSE_WHEELED (tekerlek kaydırma)
// Tekerlek kaydırma durumları
wheelDelta := int16(event.dwButtonState >> 16) // Fare tekerleği delta değeri
if wheelDelta > 0 {
buttonState = 64 // Yukarı kaydırma
} else {
buttonState = 65 // Aşağı kaydırma
}
return fmt.Sprintf("\x1b[<%d;%d;%dM", buttonState, event.dwMousePosition.X+1, event.dwMousePosition.Y+1)
}
// Fare hareketi olayları için
if event.dwEventFlags == 0x0001 { // MOUSE_MOVED
// Eğer herhangi bir tuş basılı değilse ve sadece hareketse, farklı bir sekans olabilir
// Standart SGR raporlama hareket için de kullanılır.
// Genellikle, sadece tuş basılıyken hareket raporlanır.
}
// Eğer bir tuşa basıldıysa veya bırakıldıysa
if event.dwEventFlags == 0 { // Tuş basma/bırakma
// ASCII koda eklemek için 32 kullanılır, SGR formatında.
// Basılı tuşlar için M, bırakılan tuşlar için m.
return fmt.Sprintf("\x1b[<%d;%d;%dM", buttonState, event.dwMousePosition.X+1, event.dwMousePosition.Y+1)
}
return ""
}
Bu vaka analizi, win32-input-mode'un nasıl bir köprü görevi gördüğümüzü ve Go'nun bu köprüyü inşa etmek için ne kadar uygun bir dil olduğunu göstermektedir. Geliştiriciler, bu teknikleri kullanarak Windows Terminal'de de Linux/macOS benzeri zengin ve interaktif CLI deneyimleri sunabilirler. Bu, terminal uygulamalarının platformlar arası uyumluluğunu artırmak ve kullanıcı deneyimini iyileştirmek açısından hayati öneme sahiptir. Özellikle, karmaşık arayüzlere sahip araçlar geliştirirken, bu ayrıştırma ve normalleştirme adımı vazgeçilmezdir.
İleri Düzey Teknikler ve Optimizasyonlar
Temel ConPTY kurulumunu ve win32-input-mode girdilerini ayrıştırma yöntemlerini öğrendik. Şimdi ise, daha deneyimli kullanıcılar için ConPTY uygulamalarını daha sağlam, performanslı ve uyumlu hale getirecek ileri düzey tekniklere ve optimizasyonlara değineceğiz. Bu ipuçları, özellikle büyük ölçekli veya yüksek performans gerektiren terminal uygulamaları geliştirirken işinize yarayacaktır.
1. EnableVirtualTerminalInput ve DisableVirtualTerminalInput API'leri ile Geçişler
Windows konsol API'leri, SetConsoleMode fonksiyonu ve ENABLE_VIRTUAL_TERMINAL_INPUT bayrağı aracılığıyla terminal girdi modlarını dinamik olarak değiştirmenize olanak tanır. ConPTY, varsayılan olarak win32-input-mode ile INPUT_RECORD yapıları gönderse de, bazı durumlarda alt süreç doğrudan VT sekanslarını almayı tercih edebilir. EnableVirtualTerminalInput (veya SetConsoleMode ile ENABLE_VIRTUAL_TERMINAL_INPUT'u etkinleştirmek), ConPTY'nin bu modda da VT sekansları üretmesini sağlayabilir. Ancak, bu durumun davranışları ve uyumluluğu her zaman beklenen gibi olmayabilir ve terminal emülatörüne göre değişiklik gösterebilir. Bu bayrağı dikkatli kullanmak ve uygulamanızı farklı senaryolarda test etmek önemlidir. Özellikle eski terminal emülatörleri veya belirli console host sürümleriyle uyumluluk sorunları yaşanabilir. Bu, Go'daki syscall paketi ile kernel32.dll çağrılarak gerçekleştirilebilir.
2. Buffer Yönetimi ve Eşzamanlılık (Goroutines) Kullanımı
Girdi ve çıktı akışlarını yönetirken performans kritik öneme sahiptir. Özellikle yoğun girdi alan interaktif uygulamalarda, her INPUT_RECORD'u tek tek okumak ve işlemek gecikmelere yol açabilir. Bu noktada Go'nun eşzamanlılık yetenekleri (goroutines ve kanallar) devreye girer.
- Girdi Okuma Goroutinesi: ConPTY'nin "output" pipe'ından
INPUT_RECORD'ları okumak için ayrı bir goroutine başlatın. Bu goroutine, okuduğu herINPUT_RECORD'u ayrıştırır ve dönüştürülmüş VT sekanslarını bir kanala gönderir. - Girdi İşleme Goroutinesi: Başka bir goroutine, bu kanaldan gelen VT sekanslarını alır ve alt sürecin
stdin'ine yazar. Bu, okuma ve yazma işlemlerini ayırarak bloklama sorunlarını azaltır ve tepkiselliği artırır. - Çıktı Okuma ve Yazma Goroutineleri: Benzer şekilde, alt süreçten gelen çıktıları okumak ve
os.Stdout'a yazmak için de ayrı goroutineler kullanın. Bu sayede, çıktı işlemleri girdi işlemlerini engellemez.
Verimli buffer yönetimi, özellikle büyük verilerin okunup yazıldığı durumlarda hayati önem taşır. io.Reader ve io.Writer arayüzlerini kullanan Go'nun standart kütüphanesi, buffer'lı okuma/yazma için bufio paketi gibi araçlar sunar. Bu, sistem çağrısı sayısını azaltarak performansı artırabilir.
3. Hata Yönetimi ve Beklenmeyen Girdilerle Başa Çıkma
Sistem programlamasında hatalar kaçınılmazdır. Pipe'ların beklenmedik şekilde kapanması, okuma/yazma hataları veya bozuk INPUT_RECORD verileri gibi durumlar ortaya çıkabilir. Sağlam bir ConPTY uygulaması, bu tür senaryoları doğru bir şekilde ele almalıdır.
syscall.ReadFileveyasyscall.WriteFilegibi fonksiyonlardan dönen hataları dikkatlice kontrol edin. Özelliklesyscall.ERROR_BROKEN_PIPEgibi hatalar, pipe'ın kapandığını ve alt sürecin sonlandığını gösterir.INPUT_RECORDayrıştırma mantığınızı esnek tutun. Eğer beklenmeyen birEventTypeveya bozuk bir yapı gelirse, uygulamanızın çökmemesi, bunun yerine uygun bir şekilde günlüğe kaydetmesi ve hatayı atlaması gerekir.- ConPTY handle'ları ve pipe'ları için
deferkullanarak düzgün kaynak temizliği yapmak, hafıza sızıntılarını ve handle sızıntılarını önler.
4. Çeşitli Terminal Emülatörleri ve Uyumluluk
Windows Terminal yaygın olarak kullanılsa da, kullanıcılar hala cmd.exe, PowerShell ISE veya üçüncü taraf terminal emülatörleri (örneğin Git Bash, Cmder) kullanabilirler. ConPTY bu emülatörlerle de çalışabilir, ancak win32-input-mode'un davranışı her zaman aynı olmayabilir.
- Uygulamanızı farklı terminal ortamlarında test edin.
win32-input-mode'un ne zaman devreye girdiğini ve her ortamda nasıl davrandığını anlayın. - Bazı eski terminal emülatörleri veya hatta belirli
console hostyapılandırmaları, modern VT sekanslarını tam olarak desteklemeyebilir. Bu durumlarda, geri dönüş mekanizmaları veya daha temel girdi işleme yöntemleri düşünebilirsiniz. - Go uygulamanızın, terminalin yeteneklerini sorgulayarak (örneğin
TERMortam değişkenine bakarak veya belirli VT sekansları gönderip cevaplarını okuyarak) davranışını dinamik olarak ayarlaması faydalı olabilir.
Bu ileri düzey teknikler, Go ile ConPTY uygulamaları geliştirirken sadece işlevsellik değil, aynı zamanda güvenilirlik, performans ve geniş uyumluluk sağlamanıza yardımcı olacaktır. Terminal uygulamalarının karmaşık dünyasında, bu detaylara dikkat etmek, kullanıcılarınız için üstün bir deneyim yaratmanın anahtarıdır.
Sonuç: Daha İyi Bir Terminal Deneyimi İçin Adımlar
Bu kapsamlı makale boyunca, Windows Terminal'in modern yapısını, Go ile ConPTY uygulamaları geliştirmenin temellerini ve özellikle win32-input-mode'un getirdiği girdi yönetimi zorluklarını ayrıntılarıyla inceledik. Geleneksel VT sekanslarından farklı olarak, INPUT_RECORD yapıları şeklinde gelen girdilerin nasıl ayrıştırılacağını ve interaktif CLI uygulamaları için nasıl normalleştirileceğini adım adım öğrendik. Go'nun syscall paketi aracılığıyla Windows API'leriyle etkileşim kurarak, bu karmaşık mekanizmaları kendi lehimize çevirmenin yollarını keşfettik. Metin görüntüleyici vaka analizi ile teorik bilgiyi pratik bir senaryoya dönüştürdük ve ileri düzey optimizasyon teknikleriyle uygulamalarımızı daha sağlam ve performanslı hale getirmenin yollarını paylaştık.
win32-input-mode, başlangıçta kafa karıştırıcı gibi görünse de, aslında Windows'un terminal deneyimini zenginleştirme çabasının bir sonucudur. Go geliştiricileri olarak bizlere düşen görev, bu modun dinamiklerini anlayarak, uygulamalarımızı her platformda sorunsuz çalışacak şekilde tasarlamaktır. Go'nun eşzamanlılık yetenekleri ve düşük seviyeli sistem çağrılarına erişim imkanı, bu tür zorlukların üstesinden gelmek için bize güçlü bir araç seti sunmaktadır. Doğru ayrıştırma, buffer yönetimi ve hata kontrolü ile, kullanıcıların Windows Terminal'de de Linux veya macOS'teki gibi akıcı ve zengin interaktif CLI deneyimleri yaşamasını sağlayabiliriz. Bu sayede, terminal uygulamaları ekosisteminde Go'nun potansiyelini tam olarak kullanmış oluruz.
Gelecekte, ConPTY ve terminal ekosisteminde daha fazla gelişme yaşanması beklenmektedir. Microsoft'un bu alandaki sürekli yatırımları, geliştiriciler için daha standartlaştırılmış ve daha kolay kullanılabilir API'lere yol açabilir. Bu gelişmeler, win32-input-mode gibi özel durumlarla başa çıkma ihtiyacını azaltabilir veya daha basit soyutlamalar sunabilir. Ancak şimdilik, Go ile bu derinlemesine anlayışa sahip olmak, terminal uygulamalarımızın geleceğe hazır olmasını sağlamanın en iyi yoludur. Geliştiricilerin bu makalede sunulan bilgileri kullanarak, hem kendi projelerinde hem de açık kaynak topluluğuna katkıda bulunarak daha iyi terminal araçları inşa etmelerini umuyoruz. Unutmayın, terminal uygulamalarının kalitesi, sadece çıktıları değil, aynı zamanda girdileri ne kadar iyi işleyebildiğiyle de ölçülür.
Sıkça Sorulan Sorular (SSS)
1. win32-input-mode her zaman aktif midir?
Hayır, win32-input-mode her zaman aktif değildir. Genellikle Windows Terminal gibi modern bir terminal emülatörü tarafından barındırılan ConPTY oturumlarında devreye girer. Diğer konsol pencereleri (örneğin, doğrudan cmd.exe veya PowerShell) veya eski terminal emülatörleri, varsayılan olarak geleneksel VT sekanslarını veya Windows'un kendi eski konsol girdi API'lerini kullanabilir.
2. ConPTY'nin Linux/macOS'teki PTY'den farkı nedir?
ConPTY (Console Pseudo-terminal) ve PTY (Pseudo-terminal), benzer amaçlara hizmet etse de, temel farkları vardır. PTY, Unix benzeri sistemlerde bir terminal arayüzünü taklit eder ve alt süreçlerin doğrudan terminal aygıtlarıyla etkileşime girmesini sağlar. ConPTY ise Windows'a özgüdür ve alt sürecin Windows Konsol API'lerini kullanmaya devam etmesini sağlar. Yani, PTY çekirdek düzeyinde bir aygıtken, ConPTY bir API katmanıdır. Bu fark, girdi/çıktı yönetiminde ve özellikle win32-input-mode gibi Windows'a özgü davranışlarda kendini gösterir.
3. Sadece Go mu bu sorunları çözebilir? Başka dillerde durum nedir?
Hayır, sadece Go bu sorunları çözemez. ConPTY ve win32-input-mode, Windows'un genel bir API'si ve mekanizmasıdır. C#, Python, Rust gibi Windows API'leriyle etkileşime geçebilen diğer programlama dilleri de benzer yöntemlerle (kendi syscall veya FFI (Foreign Function Interface) mekanizmalarını kullanarak) bu sorunları çözebilir. Go'nun burada öne çıkması, sistem programlama yetenekleri, eşzamanlılık desteği ve nispeten basit sözdizimi ile bu tür düşük seviyeli işler için oldukça uygun olmasıdır.
4. Fare kaydırma olaylarını nasıl yakalarım ve VT sekansına çeviririm?
Fare kaydırma olayları, MOUSE_EVENT_RECORD yapısında dwEventFlags alanının MOUSE_WHEELED (0x0004) olarak ayarlanmasıyla tespit edilir. Tekerleğin ne kadar kaydırıldığı bilgisi ise dwButtonState alanının yüksek baytlarında (>> 16 ile elde edilir) bulunur. Bu değeri pozitif veya negatif olarak yorumlayarak yukarı veya aşağı kaydırmayı belirleyebilirsiniz. Ardından, bu bilgiyi SGR formatında fare raporlamasına uygun özel VT sekanslarına (örneğin, yukarı kaydırma için \x1b[<64;X;YM, aşağı kaydırma için \x1b[<65;X;YM) dönüştürebilirsiniz.
5. ConPTY kullanmak performans sorunlarına yol açar mı?
Her ek soyutlama katmanı gibi, ConPTY de minimal bir performans yükü getirebilir. INPUT_RECORD'ları okuma, ayrıştırma ve VT sekanslarına dönüştürme işlemleri de ek CPU döngüleri gerektirir. Ancak, modern donanımlarda ve Go'nun verimli eşzamanlılık modelleriyle (goroutines), çoğu interaktif CLI uygulaması için bu performans maliyeti genellikle göz ardı edilebilir düzeydedir. Yüksek frekanslı, saniyede binlerce girdi alan çok özel durumlarda optimizasyonlara (örneğin, daha büyük buffer'lar, doğrudan bellek erişimi) ihtiyaç duyulabilir, ancak genel kullanım için performansı yeterince iyidir.