golang.zx2c4.com/wireguard/windows@v0.5.4-0.20230123132234-dcc0eb72a04b/ui/syntax/syntaxedit.go (about)

     1  /* SPDX-License-Identifier: MIT
     2   *
     3   * Copyright (C) 2019-2022 WireGuard LLC. All Rights Reserved.
     4   */
     5  
     6  package syntax
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"strings"
    12  	"sync/atomic"
    13  	"syscall"
    14  	"unsafe"
    15  
    16  	"github.com/lxn/walk"
    17  	"github.com/lxn/win"
    18  	"golang.org/x/sys/windows"
    19  )
    20  
    21  type SyntaxEdit struct {
    22  	walk.WidgetBase
    23  	irich                           *win.IRichEditOle
    24  	idoc                            *win.ITextDocument
    25  	lastBlockState                  BlockState
    26  	yheight                         int
    27  	highlightGuard                  uint32
    28  	textChangedPublisher            walk.EventPublisher
    29  	privateKeyPublisher             walk.StringEventPublisher
    30  	blockUntunneledTrafficPublisher walk.IntEventPublisher
    31  }
    32  
    33  type BlockState int
    34  
    35  const (
    36  	InevaluableBlockingUntunneledTraffic BlockState = iota
    37  	BlockingUntunneledTraffic
    38  	NotBlockingUntunneledTraffic
    39  )
    40  
    41  func (se *SyntaxEdit) LayoutFlags() walk.LayoutFlags {
    42  	return walk.GrowableHorz | walk.GrowableVert | walk.GreedyHorz | walk.GreedyVert
    43  }
    44  
    45  func (se *SyntaxEdit) MinSizeHint() walk.Size {
    46  	return walk.Size{20, 12}
    47  }
    48  
    49  func (se *SyntaxEdit) SizeHint() walk.Size {
    50  	return walk.Size{200, 100}
    51  }
    52  
    53  func (*SyntaxEdit) CreateLayoutItem(ctx *walk.LayoutContext) walk.LayoutItem {
    54  	return walk.NewGreedyLayoutItem()
    55  }
    56  
    57  func (se *SyntaxEdit) Text() string {
    58  	textLength := se.SendMessage(win.WM_GETTEXTLENGTH, 0, 0)
    59  	buf := make([]uint16, textLength+1)
    60  	se.SendMessage(win.WM_GETTEXT, uintptr(textLength+1), uintptr(unsafe.Pointer(&buf[0])))
    61  	return strings.Replace(syscall.UTF16ToString(buf), "\r\n", "\n", -1)
    62  }
    63  
    64  func (se *SyntaxEdit) SetText(text string) (err error) {
    65  	if text == se.Text() {
    66  		return nil
    67  	}
    68  	text = strings.Replace(text, "\n", "\r\n", -1)
    69  	if win.TRUE != se.SendMessage(win.WM_SETTEXT, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text)))) {
    70  		err = errors.New("WM_SETTEXT failed")
    71  	}
    72  	return
    73  }
    74  
    75  func (se *SyntaxEdit) TextChanged() *walk.Event {
    76  	return se.textChangedPublisher.Event()
    77  }
    78  
    79  func (se *SyntaxEdit) PrivateKeyChanged() *walk.StringEvent {
    80  	return se.privateKeyPublisher.Event()
    81  }
    82  
    83  func (se *SyntaxEdit) BlockUntunneledTrafficStateChanged() *walk.IntEvent {
    84  	return se.blockUntunneledTrafficPublisher.Event()
    85  }
    86  
    87  type spanStyle struct {
    88  	color   win.COLORREF
    89  	effects uint32
    90  }
    91  
    92  var stylemap = map[highlight]spanStyle{
    93  	highlightSection:      {color: win.RGB(0x32, 0x6D, 0x74), effects: win.CFE_BOLD},
    94  	highlightField:        {color: win.RGB(0x9B, 0x23, 0x93), effects: win.CFE_BOLD},
    95  	highlightPrivateKey:   {color: win.RGB(0x64, 0x38, 0x20)},
    96  	highlightPublicKey:    {color: win.RGB(0x64, 0x38, 0x20)},
    97  	highlightPresharedKey: {color: win.RGB(0x64, 0x38, 0x20)},
    98  	highlightIP:           {color: win.RGB(0x0E, 0x0E, 0xFF)},
    99  	highlightCidr:         {color: win.RGB(0x81, 0x5F, 0x03)},
   100  	highlightHost:         {color: win.RGB(0x0E, 0x0E, 0xFF)},
   101  	highlightPort:         {color: win.RGB(0x81, 0x5F, 0x03)},
   102  	highlightMTU:          {color: win.RGB(0x1C, 0x00, 0xCF)},
   103  	highlightTable:        {color: win.RGB(0x1C, 0x00, 0xCF)},
   104  	highlightKeepalive:    {color: win.RGB(0x1C, 0x00, 0xCF)},
   105  	highlightComment:      {color: win.RGB(0x53, 0x65, 0x79), effects: win.CFE_ITALIC},
   106  	highlightDelimiter:    {color: win.RGB(0x00, 0x00, 0x00)},
   107  	highlightCmd:          {color: win.RGB(0x63, 0x75, 0x89)},
   108  	highlightError:        {color: win.RGB(0xC4, 0x1A, 0x16), effects: win.CFE_UNDERLINE},
   109  }
   110  
   111  func (se *SyntaxEdit) evaluateUntunneledBlocking(cfg string, spans []highlightSpan) {
   112  	state := InevaluableBlockingUntunneledTraffic
   113  	var onAllowedIPs,
   114  		onTable,
   115  		tableOff,
   116  		seenPeer,
   117  		seen00v6,
   118  		seen00v4,
   119  		seen01v6,
   120  		seen80001v6,
   121  		seen01v4,
   122  		seen1281v4 bool
   123  
   124  	for i := range spans {
   125  		span := &spans[i]
   126  		switch span.t {
   127  		case highlightError:
   128  			goto done
   129  		case highlightSection:
   130  			if !strings.EqualFold(cfg[span.s:span.s+span.len], "[Peer]") {
   131  				break
   132  			}
   133  			if !seenPeer {
   134  				seenPeer = true
   135  			} else {
   136  				goto done
   137  			}
   138  		case highlightField:
   139  			onAllowedIPs = strings.EqualFold(cfg[span.s:span.s+span.len], "AllowedIPs")
   140  			onTable = strings.EqualFold(cfg[span.s:span.s+span.len], "Table")
   141  		case highlightTable:
   142  			if onTable {
   143  				tableOff = cfg[span.s:span.s+span.len] == "off"
   144  			}
   145  		case highlightIP:
   146  			if !onAllowedIPs || !seenPeer {
   147  				break
   148  			}
   149  			if i+2 >= len(spans) || spans[i+1].t != highlightDelimiter || spans[i+2].t != highlightCidr {
   150  				break
   151  			}
   152  			if spans[i+2].len != 1 {
   153  				break
   154  			}
   155  			switch cfg[spans[i+2].s] {
   156  			case '0':
   157  				switch cfg[span.s : span.s+span.len] {
   158  				case "0.0.0.0":
   159  					seen00v4 = true
   160  				case "::":
   161  					seen00v6 = true
   162  				}
   163  			case '1':
   164  				switch cfg[span.s : span.s+span.len] {
   165  				case "0.0.0.0":
   166  					seen01v4 = true
   167  				case "128.0.0.0":
   168  					seen1281v4 = true
   169  				case "::":
   170  					seen01v6 = true
   171  				case "8000::":
   172  					seen80001v6 = true
   173  				}
   174  			}
   175  		}
   176  	}
   177  	if tableOff {
   178  		return
   179  	}
   180  
   181  	if seen00v4 || seen00v6 {
   182  		state = BlockingUntunneledTraffic
   183  	} else if (seen01v4 && seen1281v4) || (seen01v6 && seen80001v6) {
   184  		state = NotBlockingUntunneledTraffic
   185  	}
   186  
   187  done:
   188  	if state != se.lastBlockState {
   189  		se.blockUntunneledTrafficPublisher.Publish(int(state))
   190  		se.lastBlockState = state
   191  	}
   192  }
   193  
   194  func (se *SyntaxEdit) highlightText() error {
   195  	if !atomic.CompareAndSwapUint32(&se.highlightGuard, 0, 1) {
   196  		return nil
   197  	}
   198  	defer atomic.StoreUint32(&se.highlightGuard, 0)
   199  
   200  	hWnd := se.Handle()
   201  	gettextlengthex := win.GETTEXTLENGTHEX{
   202  		Flags:    win.GTL_NUMBYTES,
   203  		Codepage: win.CP_ACP, // Probably CP_UTF8 would be better, but (wine at least) returns utf32 sizes.
   204  	}
   205  	msgSize := uint32(win.SendMessage(hWnd, win.EM_GETTEXTLENGTHEX, uintptr(unsafe.Pointer(&gettextlengthex)), 0))
   206  	if msgSize == win.E_INVALIDARG {
   207  		return errors.New("Failed to get text length")
   208  	}
   209  
   210  	gettextex := win.GETTEXTEX{
   211  		Flags:    win.GT_NOHIDDENTEXT,
   212  		Codepage: gettextlengthex.Codepage,
   213  		Cb:       msgSize + 1,
   214  	}
   215  	msg := make([]byte, msgSize+1)
   216  	msgCount := win.SendMessage(hWnd, win.EM_GETTEXTEX, uintptr(unsafe.Pointer(&gettextex)), uintptr(unsafe.Pointer(&msg[0])))
   217  	if msgCount < 0 {
   218  		return errors.New("Failed to get text")
   219  	}
   220  	cfg := strings.Replace(string(msg[:msgCount]), "\r", "\n", -1)
   221  
   222  	spans := highlightConfig(cfg)
   223  	se.evaluateUntunneledBlocking(cfg, spans)
   224  
   225  	se.idoc.Undo(win.TomSuspend, nil)
   226  	win.SendMessage(hWnd, win.EM_SETEVENTMASK, 0, 0)
   227  	win.SendMessage(hWnd, win.WM_SETREDRAW, win.FALSE, 0)
   228  	var origSelection win.CHARRANGE
   229  	win.SendMessage(hWnd, win.EM_EXGETSEL, 0, uintptr(unsafe.Pointer(&origSelection)))
   230  	var origScroll win.POINT
   231  	win.SendMessage(hWnd, win.EM_GETSCROLLPOS, 0, uintptr(unsafe.Pointer(&origScroll)))
   232  	win.SendMessage(hWnd, win.EM_HIDESELECTION, win.TRUE, 0)
   233  	format := win.CHARFORMAT2{
   234  		CHARFORMAT: win.CHARFORMAT{
   235  			CbSize:    uint32(unsafe.Sizeof(win.CHARFORMAT2{})),
   236  			DwMask:    win.CFM_COLOR | win.CFM_CHARSET | win.CFM_SIZE | win.CFM_BOLD | win.CFM_ITALIC | win.CFM_UNDERLINE,
   237  			DwEffects: win.CFE_AUTOCOLOR,
   238  			BCharSet:  win.ANSI_CHARSET,
   239  		},
   240  	}
   241  	if se.yheight != 0 {
   242  		format.YHeight = 20 * 10
   243  	}
   244  	win.SendMessage(hWnd, win.EM_SETCHARFORMAT, win.SCF_ALL, uintptr(unsafe.Pointer(&format)))
   245  	bgColor := win.COLORREF(win.GetSysColor(win.COLOR_WINDOW))
   246  	bgInversion := (bgColor & win.RGB(0xFF, 0xFF, 0xFF)) ^ win.RGB(0xFF, 0xFF, 0xFF)
   247  	win.SendMessage(hWnd, win.EM_SETBKGNDCOLOR, 0, uintptr(bgColor))
   248  	numSpans := len(spans)
   249  	foundPrivateKey := false
   250  	for i := range spans {
   251  		span := &spans[i]
   252  		if numSpans <= 2048 {
   253  			selection := win.CHARRANGE{int32(span.s), int32(span.s + span.len)}
   254  			win.SendMessage(hWnd, win.EM_EXSETSEL, 0, uintptr(unsafe.Pointer(&selection)))
   255  			format.CrTextColor = stylemap[span.t].color ^ bgInversion
   256  			format.DwEffects = stylemap[span.t].effects
   257  			win.SendMessage(hWnd, win.EM_SETCHARFORMAT, win.SCF_SELECTION, uintptr(unsafe.Pointer(&format)))
   258  		}
   259  		if span.t == highlightPrivateKey && !foundPrivateKey {
   260  			privateKey := cfg[span.s : span.s+span.len]
   261  			se.privateKeyPublisher.Publish(privateKey)
   262  			foundPrivateKey = true
   263  		}
   264  	}
   265  	win.SendMessage(hWnd, win.EM_SETSCROLLPOS, 0, uintptr(unsafe.Pointer(&origScroll)))
   266  	win.SendMessage(hWnd, win.EM_EXSETSEL, 0, uintptr(unsafe.Pointer(&origSelection)))
   267  	win.SendMessage(hWnd, win.EM_HIDESELECTION, win.FALSE, 0)
   268  	win.SendMessage(hWnd, win.WM_SETREDRAW, win.TRUE, 0)
   269  	win.RedrawWindow(hWnd, nil, 0, win.RDW_ERASE|win.RDW_FRAME|win.RDW_INVALIDATE|win.RDW_ALLCHILDREN)
   270  	win.SendMessage(hWnd, win.EM_SETEVENTMASK, 0, win.ENM_CHANGE)
   271  	se.idoc.Undo(win.TomResume, nil)
   272  	if !foundPrivateKey {
   273  		se.privateKeyPublisher.Publish("")
   274  	}
   275  	return nil
   276  }
   277  
   278  func (se *SyntaxEdit) contextMenu(x, y int32) error {
   279  	/* This disturbing hack grabs the system edit menu normally used for the EDIT control. */
   280  	comctl32UTF16, err := windows.UTF16PtrFromString("comctl32.dll")
   281  	if err != nil {
   282  		return err
   283  	}
   284  	comctl32Handle := win.GetModuleHandle(comctl32UTF16)
   285  	if comctl32Handle == 0 {
   286  		return errors.New("Failed to get comctl32.dll handle")
   287  	}
   288  	menu := win.LoadMenu(comctl32Handle, win.MAKEINTRESOURCE(1))
   289  	if menu == 0 {
   290  		return errors.New("Failed to load menu")
   291  	}
   292  	defer win.DestroyMenu(menu)
   293  
   294  	hWnd := se.Handle()
   295  	enableWhenSelected := uint32(win.MF_GRAYED)
   296  	var selection win.CHARRANGE
   297  	win.SendMessage(hWnd, win.EM_EXGETSEL, 0, uintptr(unsafe.Pointer(&selection)))
   298  	if selection.CpMin < selection.CpMax {
   299  		enableWhenSelected = win.MF_ENABLED
   300  	}
   301  	enableSelectAll := uint32(win.MF_GRAYED)
   302  	gettextlengthex := win.GETTEXTLENGTHEX{
   303  		Flags:    win.GTL_DEFAULT,
   304  		Codepage: win.CP_ACP,
   305  	}
   306  	if selection.CpMin != 0 || (selection.CpMax < int32(win.SendMessage(hWnd, win.EM_GETTEXTLENGTHEX, uintptr(unsafe.Pointer(&gettextlengthex)), 0))) {
   307  		enableSelectAll = win.MF_ENABLED
   308  	}
   309  	enableUndo := uint32(win.MF_GRAYED)
   310  	if win.SendMessage(hWnd, win.EM_CANUNDO, 0, 0) != 0 {
   311  		enableUndo = win.MF_ENABLED
   312  	}
   313  	enablePaste := uint32(win.MF_GRAYED)
   314  	if win.SendMessage(hWnd, win.EM_CANPASTE, win.CF_TEXT, 0) != 0 {
   315  		enablePaste = win.MF_ENABLED
   316  	}
   317  
   318  	popup := win.GetSubMenu(menu, 0)
   319  	win.EnableMenuItem(popup, win.WM_UNDO, win.MF_BYCOMMAND|enableUndo)
   320  	win.EnableMenuItem(popup, win.WM_CUT, win.MF_BYCOMMAND|enableWhenSelected)
   321  	win.EnableMenuItem(popup, win.WM_COPY, win.MF_BYCOMMAND|enableWhenSelected)
   322  	win.EnableMenuItem(popup, win.WM_PASTE, win.MF_BYCOMMAND|enablePaste)
   323  	win.EnableMenuItem(popup, win.WM_CLEAR, win.MF_BYCOMMAND|enableWhenSelected)
   324  	win.EnableMenuItem(popup, win.EM_SETSEL, win.MF_BYCOMMAND|enableSelectAll)
   325  
   326  	// Delete items that we don't handle.
   327  	for ctl := win.GetMenuItemCount(popup) - 1; ctl >= 0; ctl-- {
   328  		menuItem := win.MENUITEMINFO{
   329  			CbSize: uint32(unsafe.Sizeof(win.MENUITEMINFO{})),
   330  			FMask:  win.MIIM_FTYPE | win.MIIM_ID,
   331  		}
   332  		if !win.GetMenuItemInfo(popup, uint32(ctl), win.MF_BYPOSITION, &menuItem) {
   333  			continue
   334  		}
   335  		if (menuItem.FType & win.MFT_SEPARATOR) != 0 {
   336  			continue
   337  		}
   338  		switch menuItem.WID {
   339  		case win.WM_UNDO, win.WM_CUT, win.WM_COPY, win.WM_PASTE, win.WM_CLEAR, win.EM_SETSEL:
   340  			continue
   341  		}
   342  		win.DeleteMenu(popup, uint32(ctl), win.MF_BYPOSITION)
   343  	}
   344  	// Delete trailing and adjacent separators.
   345  	end := true
   346  	for ctl := win.GetMenuItemCount(popup) - 1; ctl >= 0; ctl-- {
   347  		menuItem := win.MENUITEMINFO{
   348  			CbSize: uint32(unsafe.Sizeof(win.MENUITEMINFO{})),
   349  			FMask:  win.MIIM_FTYPE,
   350  		}
   351  		if !win.GetMenuItemInfo(popup, uint32(ctl), win.MF_BYPOSITION, &menuItem) {
   352  			continue
   353  		}
   354  		if (menuItem.FType & win.MFT_SEPARATOR) == 0 {
   355  			end = false
   356  			continue
   357  		}
   358  		if !end && ctl > 0 {
   359  			if !win.GetMenuItemInfo(popup, uint32(ctl-1), win.MF_BYPOSITION, &menuItem) {
   360  				continue
   361  			}
   362  			if (menuItem.FType & win.MFT_SEPARATOR) == 0 {
   363  				continue
   364  			}
   365  		}
   366  		win.DeleteMenu(popup, uint32(ctl), win.MF_BYPOSITION)
   367  	}
   368  
   369  	if x == -1 && y == -1 {
   370  		var rect win.RECT
   371  		win.GetWindowRect(hWnd, &rect)
   372  		x = (rect.Left + rect.Right) / 2
   373  		y = (rect.Top + rect.Bottom) / 2
   374  	}
   375  
   376  	if win.GetFocus() != hWnd {
   377  		win.SetFocus(hWnd)
   378  	}
   379  
   380  	cmd := win.TrackPopupMenu(popup, win.TPM_LEFTALIGN|win.TPM_RIGHTBUTTON|win.TPM_RETURNCMD|win.TPM_NONOTIFY, x, y, 0, hWnd, nil)
   381  	if cmd != 0 {
   382  		lParam := uintptr(0)
   383  		if cmd == win.EM_SETSEL {
   384  			lParam = ^uintptr(0)
   385  		}
   386  		win.SendMessage(hWnd, cmd, 0, lParam)
   387  	}
   388  
   389  	return nil
   390  }
   391  
   392  func (*SyntaxEdit) NeedsWmSize() bool {
   393  	return true
   394  }
   395  
   396  func (se *SyntaxEdit) WndProc(hWnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
   397  	switch msg {
   398  	case win.WM_DESTROY:
   399  		if se.idoc != nil {
   400  			se.idoc.Release()
   401  		}
   402  		if se.irich != nil {
   403  			se.irich.Release()
   404  		}
   405  
   406  	case win.WM_SETTEXT:
   407  		ret := se.WidgetBase.WndProc(hWnd, msg, wParam, lParam)
   408  		se.highlightText()
   409  		win.SendMessage(hWnd, win.EM_EMPTYUNDOBUFFER, 0, 0)
   410  		se.textChangedPublisher.Publish()
   411  		return ret
   412  
   413  	case win.WM_COMMAND, win.WM_NOTIFY:
   414  		switch win.HIWORD(uint32(wParam)) {
   415  		case win.EN_CHANGE:
   416  			se.highlightText()
   417  			se.textChangedPublisher.Publish()
   418  		}
   419  
   420  	case win.WM_PASTE:
   421  		win.SendMessage(hWnd, win.EM_PASTESPECIAL, win.CF_TEXT, 0)
   422  		return 0
   423  
   424  	case win.WM_KEYDOWN:
   425  		key := win.LOWORD(uint32(wParam))
   426  		if key == 'V' && win.GetKeyState(win.VK_CONTROL) < 0 ||
   427  			key == win.VK_INSERT && win.GetKeyState(win.VK_SHIFT) < 0 {
   428  			win.SendMessage(hWnd, win.EM_PASTESPECIAL, win.CF_TEXT, 0)
   429  			return 0
   430  		}
   431  
   432  	case win.WM_CONTEXTMENU:
   433  		se.contextMenu(win.GET_X_LPARAM(lParam), win.GET_Y_LPARAM(lParam))
   434  		return 0
   435  
   436  	case win.WM_THEMECHANGED:
   437  		se.highlightText()
   438  
   439  	case win.WM_GETDLGCODE:
   440  		m := (*win.MSG)(unsafe.Pointer(lParam))
   441  		ret := se.WidgetBase.WndProc(hWnd, msg, wParam, lParam)
   442  		ret &^= win.DLGC_WANTTAB
   443  		if m != nil && m.Message == win.WM_KEYDOWN && m.WParam == win.VK_TAB && win.GetKeyState(win.VK_CONTROL) >= 0 {
   444  			ret &^= win.DLGC_WANTMESSAGE
   445  		}
   446  		return ret
   447  	}
   448  
   449  	return se.WidgetBase.WndProc(hWnd, msg, wParam, lParam)
   450  }
   451  
   452  func NewSyntaxEdit(parent walk.Container) (*SyntaxEdit, error) {
   453  	const LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
   454  	_, err := windows.LoadLibraryEx("msftedit.dll", 0, LOAD_LIBRARY_SEARCH_SYSTEM32)
   455  	if err != nil {
   456  		return nil, fmt.Errorf("Failed to load msftedit.dll: %w", err)
   457  	}
   458  
   459  	se := &SyntaxEdit{}
   460  	if err := walk.InitWidget(
   461  		se,
   462  		parent,
   463  		win.MSFTEDIT_CLASS,
   464  		win.WS_CHILD|win.ES_MULTILINE|win.WS_VISIBLE|win.WS_VSCROLL|win.WS_BORDER|win.WS_HSCROLL|win.WS_TABSTOP|win.ES_WANTRETURN|win.ES_NOOLEDRAGDROP,
   465  		0); err != nil {
   466  		return nil, err
   467  	}
   468  	hWnd := se.Handle()
   469  	win.SetWindowLong(hWnd, win.GWL_EXSTYLE, win.GetWindowLong(hWnd, win.GWL_EXSTYLE)&^win.WS_EX_CLIENTEDGE)
   470  	win.SendMessage(hWnd, win.EM_GETOLEINTERFACE, 0, uintptr(unsafe.Pointer(&se.irich)))
   471  	var idoc unsafe.Pointer
   472  	se.irich.QueryInterface(&win.IID_ITextDocument, &idoc)
   473  	se.idoc = (*win.ITextDocument)(idoc)
   474  	win.SendMessage(hWnd, win.EM_SETEVENTMASK, 0, win.ENM_CHANGE)
   475  	win.SendMessage(hWnd, win.EM_SETTEXTMODE, win.TM_SINGLECODEPAGE, 0)
   476  	se.ApplyDPI(parent.DPI())
   477  	se.GraphicsEffects().Add(walk.InteractionEffect)
   478  	se.GraphicsEffects().Add(walk.FocusEffect)
   479  	se.MustRegisterProperty("Text", walk.NewProperty(
   480  		func() any {
   481  			return se.Text()
   482  		},
   483  		func(v any) error {
   484  			if s, ok := v.(string); ok {
   485  				return se.SetText(s)
   486  			}
   487  			return se.SetText("")
   488  		},
   489  		se.textChangedPublisher.Event()))
   490  	return se, nil
   491  }
   492  
   493  func (se *SyntaxEdit) ApplyDPI(dpi int) {
   494  	hWnd := se.Handle()
   495  	hdc := win.GetDC(hWnd)
   496  	logPixels := win.GetDeviceCaps(hdc, win.LOGPIXELSY)
   497  	if se.yheight != 0 {
   498  		win.SendMessage(hWnd, win.EM_SETZOOM, uintptr(logPixels), uintptr(dpi))
   499  	}
   500  	se.yheight = 20 * 10 * dpi / int(logPixels)
   501  	win.ReleaseDC(hWnd, hdc)
   502  	se.highlightText()
   503  }