unit DBlueMonMain; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.ComCtrls, VirtualTrees, Tocsg.Bluetooth, EM.jwabluetoothapis, Vcl.Menus, Vcl.Buttons; type PBlueEnt = ^TBlueEnt; TBlueEnt = record sName, sMajor, sMinor, sAddress: String; nClassofDevice: Integer; bAuth, bConnected: Boolean; dtSeen, dtRecent: TDateTime; Address: BLUETOOTH_ADDRESS; end; TPreventBlue = record bPreventAll, bPvIncName, bPvMajor, bPvMinor: Boolean; // sExcepDev, // sPvIncName, // sPvMajor, // sPvMinor: String; end; TDlgBlueMon = class(TForm) pcMain: TPageControl; tabBlueList: TTabSheet; pnTop: TPanel; btnRefresh: TButton; tabBlueMon: TTabSheet; vtList: TVirtualStringTree; popFun: TPopupMenu; miPreventBlue: TMenuItem; Panel1: TPanel; btnBlueMon: TButton; N1: TMenuItem; miCopyCB: TMenuItem; chPreventBtAll: TCheckBox; Label1: TLabel; mmExcept: TMemo; chPB_Name: TCheckBox; edPB_Name: TEdit; cbPB_MJ: TComboBox; btnPB_MJAdd: TSpeedButton; chPB_MJ: TCheckBox; cbPB_MN: TComboBox; btnPB_MNAdd: TSpeedButton; chPB_MN: TCheckBox; lxPB_MJ: TListBox; btnPB_MJDel: TSpeedButton; btnPB_MNDel: TSpeedButton; lxPB_MN: TListBox; procedure btnRefreshClick(Sender: TObject); procedure vtListGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer); procedure vtListFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode); procedure vtListHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo); procedure vtListGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); procedure miPreventBlueClick(Sender: TObject); procedure vtListContextPopup(Sender: TObject; MousePos: TPoint; var Handled: Boolean); procedure miCopyCBClick(Sender: TObject); procedure btnBlueMonClick(Sender: TObject); procedure btnPB_MJAddClick(Sender: TObject); procedure btnPB_MNAddClick(Sender: TObject); procedure btnPB_MJDelClick(Sender: TObject); procedure btnPB_MNDelClick(Sender: TObject); private { Private declarations } ThdBlueMon_: TThdBtDevNotify; PreventBlue_: TPreventBlue; PvBtNameList_, PvMajorList_, PvMinorList_, ExcepBtDevList_: TStringList; procedure RefreshBtList; function ProcessPreventBT(pEnt: PBtDevEnt): Boolean; procedure OnBtDevEntNotify(pEnt: PBtDevEnt; csBT: TBTChangeStates; var bPrevent: Boolean); public { Public declarations } Constructor Create(aOwner: TComponent); override; Destructor Destroy; override; end; var DlgBlueMon: TDlgBlueMon; implementation uses Tocsg.Safe, VirtualTrees.Types, Tocsg.Convert, Tocsg.VTUtil, Tocsg.Strings, Define, GlobalDefine, superobject, Tocsg.Driver, Tocsg.Exception, DNoticeBT; {$R *.dfm} Constructor TDlgBlueMon.Create(aOwner: TComponent); begin Inherited Create(aOwner); Caption := APP_TITLE; ThdBlueMon_ := nil; PvBtNameList_ := TStringList.Create; PvMajorList_ := TStringList.Create; PvMinorList_ := TStringList.Create; ExcepBtDevList_ := TStringList.Create; RefreshBtList; pcMain.ActivePageIndex := 0; end; Destructor TDlgBlueMon.Destroy; begin if ThdBlueMon_ <> nil then FreeAndNil(ThdBlueMon_); FreeAndNil(ExcepBtDevList_); FreeAndNil(PvMinorList_); FreeAndNil(PvMajorList_); FreeAndNil(PvBtNameList_); Inherited; end; procedure TDlgBlueMon.RefreshBtList; var BTDevice: TBluetoothDevice; i: Integer; pData: PBlueEnt; sMajor, sMinor: String; begin Guard(BTDevice, TBluetoothDevice.Create); BTDevice.RefreshBTDevice; vtList.BeginUpdate; try vtList.Clear; for i := 0 to BTDevice.Count - 1 do begin BtDevTypeToStr(BTDevice[i].dInfo.ulClassofDevice, sMajor, sMinor); pData := VT_AddChildData(vtList); pData.sName := BTDevice[i].dInfo.szName; pData.sMajor := sMajor; pData.sMinor := sMinor; pData.nClassofDevice := BTDevice[i].dInfo.ulClassofDevice; pData.sAddress := BTDevice[i].sAddress; pData.bConnected := BTDevice[i].dInfo.fConnected; pData.bAuth := BTDevice[i].dInfo.fAuthenticated; pData.dtSeen := BTDevice[i].dtLastSeen; pData.dtRecent := BTDevice[i].dtLastUsed; pData.Address := BTDevice[i].dInfo.Address; end; finally vtList.EndUpdate; end; end; procedure PopMsg(pEnt: PBtDevEnt; bPrevent: Boolean); var O: ISuperObject; sMajor, sMinor, sData: String; begin BtDevTypeToStr(pEnt.dInfo.ulClassofDevice, sMajor, sMinor); O := SO; O.I['T'] := TYPE_MSG_PREVENT_BLUETOOTH; sData := String(pEnt.dInfo.szName) + '|' + Format('%s (%s)', [sMajor, sMinor]) + '|' + pEnt.sAddress; if bPrevent then sData := sData + '|PV'; O.S['D'] := sData; TDlgNoticeBT.Create(nil).PopupMessage(O.AsString); end; function TDlgBlueMon.ProcessPreventBT(pEnt: PBtDevEnt): Boolean; var sTemp1, sTemp2: String; i: Integer; begin Result := false; if not pEnt.dInfo.fConnected then exit; if ExcepBtDevList_.IndexOf(pEnt.sAddress) <> -1 then exit; if PreventBlue_.bPreventAll then begin Result := BluetoothRemoveDevice(pEnt.dInfo.Address) = 0; Result := true; // 차단 대상이면 위 작업과 별개로 장치 차단을 하도록 함 22_0630 09:15:10 kku exit; end; if PreventBlue_.bPvIncName then begin sTemp1 := UpperCase(pEnt.dInfo.szName); for i := 0 to PvBtNameList_.Count - 1 do begin if sTemp1.Contains(PvBtNameList_[i]) then begin Result := BluetoothRemoveDevice(pEnt.dInfo.Address) = 0; Result := true; // 차단 대상이면 위 작업과 별개로 장치 차단을 하도록 함 22_0630 09:15:10 kku exit; end; end; end; if PreventBlue_.bPvMajor or PreventBlue_.bPvMinor then begin BtDevTypeToStr(pEnt.dInfo.ulClassofDevice, sTemp1, sTemp2); if PreventBlue_.bPvMajor and (PvMajorList_.IndexOf(sTemp1) <> -1) then begin Result := BluetoothRemoveDevice(pEnt.dInfo.Address) = 0; Result := true; // 차단 대상이면 위 작업과 별개로 장치 차단을 하도록 함 22_0630 09:15:10 kku exit; end; if PreventBlue_.bPvMinor and (PvMinorList_.IndexOf(sTemp2) <> -1) then begin Result := BluetoothRemoveDevice(pEnt.dInfo.Address) = 0; Result := true; // 차단 대상이면 위 작업과 별개로 장치 차단을 하도록 함 22_0630 09:15:10 kku end; end; end; procedure TDlgBlueMon.OnBtDevEntNotify(pEnt: PBtDevEnt; csBT: TBTChangeStates; var bPrevent: Boolean); var bPreventBtEnt: Boolean; begin if (csConnected in csBT) or (csAuthenticated in csBT) then begin bPreventBtEnt := ProcessPreventBT(pEnt); bPrevent := bPrevent or bPreventBtEnt; PopMsg(pEnt, bPreventBtEnt); end; end; procedure TDlgBlueMon.btnBlueMonClick(Sender: TObject); var BTDevice: TBluetoothDevice; i: Integer; bPvreventCurBT: Boolean; begin if ThdBlueMon_ = nil then begin mmExcept.Text := Trim(mmExcept.Text); edPB_Name.Text := Trim(edPB_Name.Text); if chPB_Name.Checked and (edPB_Name.Text = '') then begin MessageBox(Handle, PChar('차단 문구를 입력해 주십시오.'), PChar(Caption), MB_ICONWARNING or MB_OK); edPB_Name.SetFocus; exit; end; if chPB_MJ.Checked and (lxPB_MJ.Count = 0) then begin MessageBox(Handle, PChar('차단 Major 정보를 하나이상 추가해 주십시오.'), PChar(Caption), MB_ICONWARNING or MB_OK); exit; end; if chPB_MN.Checked and (lxPB_MN.Count = 0) then begin MessageBox(Handle, PChar('차단 Minor 정보를 하나이상 추가해 주십시오.'), PChar(Caption), MB_ICONWARNING or MB_OK); exit; end; ZeroMemory(@PreventBlue_, SizeOf(PreventBlue_)); PreventBlue_.bPreventAll := chPreventBtAll.Checked; PreventBlue_.bPvIncName := chPB_Name.Checked; PreventBlue_.bPvMajor := chPB_MJ.Checked; PreventBlue_.bPvMinor := chPB_MN.Checked; SplitString(mmExcept.Text, ';', ExcepBtDevList_); SplitString(UpperCase(edPB_Name.Text), ';', PvBtNameList_); SplitString(lxPB_MJ.Items.CommaText, ',', PvMajorList_); SplitString(lxPB_MN.Items.CommaText, ',', PvMinorList_); bPvreventCurBT := false; Guard(BTDevice, TBluetoothDevice.Create); BTDevice.RefreshBTDevice; for i := 0 to BTDevice.Count - 1 do bPvreventCurBT := bPvreventCurBT or ProcessPreventBT(BTDevice[i]); ThdBlueMon_ := TThdBtDevNotify.Create(true); ThdBlueMon_.PreventBtDevs := bPvreventCurBT; ThdBlueMon_.OnChangeBTDevice := OnBtDevEntNotify; ThdBlueMon_.StartThread; btnBlueMon.Caption := '감시/차단 중지'; end else begin FreeAndNil(ThdBlueMon_); btnBlueMon.Caption := '감시/차단 시작'; end; chPreventBtAll.Enabled := ThdBlueMon_ = nil; tabBlueList.TabVisible := chPreventBtAll.Enabled; Label1.Enabled := chPreventBtAll.Enabled; mmExcept.Enabled := chPreventBtAll.Enabled; chPB_Name.Enabled := chPreventBtAll.Enabled; edPB_Name.Enabled := chPreventBtAll.Enabled; chPB_MJ.Enabled := chPreventBtAll.Enabled; cbPB_MJ.Enabled := chPreventBtAll.Enabled; btnPB_MJAdd.Enabled := chPreventBtAll.Enabled; btnPB_MJDel.Enabled := chPreventBtAll.Enabled; lxPB_MJ.Enabled := chPreventBtAll.Enabled; chPB_MN.Enabled := chPreventBtAll.Enabled; cbPB_MN.Enabled := chPreventBtAll.Enabled; btnPB_MNAdd.Enabled := chPreventBtAll.Enabled; btnPB_MNDel.Enabled := chPreventBtAll.Enabled; lxPB_MN.Enabled := chPreventBtAll.Enabled; if tabBlueList.TabVisible then RefreshBtList; Application.ProcessMessages; end; procedure TDlgBlueMon.btnPB_MJAddClick(Sender: TObject); var sAdd: String; begin sAdd := cbPB_MJ.Text; if lxPB_MJ.Items.IndexOf(sAdd) = -1 then lxPB_MJ.Items.Add(sAdd); end; procedure TDlgBlueMon.btnPB_MJDelClick(Sender: TObject); begin if lxPB_MJ.SelCount = 0 then begin MessageBox(Handle, PChar('삭제할 항목을 선택해 주십시오.'), PChar(Caption), MB_ICONWARNING or MB_OK); exit; end; lxPB_MJ.DeleteSelected; end; procedure TDlgBlueMon.btnPB_MNAddClick(Sender: TObject); var sAdd: String; begin sAdd := cbPB_MN.Text; if lxPB_MN.Items.IndexOf(sAdd) = -1 then lxPB_MN.Items.Add(sAdd); end; procedure TDlgBlueMon.btnPB_MNDelClick(Sender: TObject); begin if lxPB_MN.SelCount = 0 then begin MessageBox(Handle, PChar('삭제할 항목을 선택해 주십시오.'), PChar(Caption), MB_ICONWARNING or MB_OK); exit; end; lxPB_MN.DeleteSelected; end; procedure TDlgBlueMon.btnRefreshClick(Sender: TObject); begin RefreshBtList; end; function FindFirstBluetoothDevicePath(out ADevicePath: string): Boolean; const GUID_BLUETOOTH_DEVICE_INTERFACE: TGUID = '{E0CBF06C-CD8B-4647-BB8A-263B43F0F974}'; // 클래식 GUID_BLUETOOTHLE_DEVICE_INTERFACE: TGUID = '{781AEE18-7733-4CE4-ADD0-91F41C67B592}'; // BLE type TGuidArray = array[0..1] of TGUID; var Guids: TGuidArray; FlagsArr: array[0..1] of DWORD; g, f: Integer; hDev: HDEVINFO; DevIfData: SP_DEVICE_INTERFACE_DATA; Required: DWORD; Detail: PSPDeviceInterfaceDetailData; Index: DWORD; begin Result := False; ADevicePath := ''; Guids[0] := GUID_BLUETOOTH_DEVICE_INTERFACE; // 클래식 Guids[1] := GUID_BLUETOOTHLE_DEVICE_INTERFACE; // BLE // PRESENT 유무 두 번 시도: (1) PRESENT 포함, (2) PRESENT 제외 FlagsArr[0] := DIGCF_DEVICEINTERFACE or DIGCF_PRESENT; FlagsArr[1] := DIGCF_DEVICEINTERFACE; for g := Low(Guids) to High(Guids) do begin for f := Low(FlagsArr) to High(FlagsArr) do begin hDev := SetupDiGetClassDevs(@Guids[g], nil, 0, FlagsArr[f]); if hDev = INVALID_HANDLE_VALUE then Continue; try Index := 0; while True do begin ZeroMemory(@DevIfData, SizeOf(DevIfData)); DevIfData.cbSize := SizeOf(DevIfData); if not SetupDiEnumDeviceInterfaces(hDev, nil, Guids[g], Index, DevIfData) then begin if GetLastError = ERROR_NO_MORE_ITEMS then Break else Break; // 다른 오류라면 필요시 로그 end; // 1차 호출: 필요한 버퍼 크기 얻기 (여기서는 실패 + ERROR_INSUFFICIENT_BUFFER가 정상) Required := 0; SetupDiGetDeviceInterfaceDetail(hDev, @DevIfData, nil, 0, Required, nil); if (Required = 0) then begin Inc(Index); Continue; end; Detail := PSPDeviceInterfaceDetailData(AllocMem(Required)); try Detail.cbSize := SizeOf(TSPDeviceInterfaceDetailData); if SetupDiGetDeviceInterfaceDetail(hDev, @DevIfData, Detail, Required, Required, nil) then begin {$IFDEF UNICODE} ADevicePath := Detail.DevicePath; {$ELSE} ADevicePath := string(Detail.DevicePath); // 필요시 변환 {$ENDIF} Result := True; Exit; // 첫 번째 것만 반환. 모두 수집하려면 리스트에 Add하고 계속. end; finally FreeMem(Detail); end; Inc(Index); end; finally SetupDiDestroyDeviceInfoList(hDev); end; end; end; end; function GetBluetoothDevicePath: string; const GUID_BLUETOOTH_DEVICE_INTERFACE: TGUID = '{E0CBF06C-CD8B-4647-BB8A-263B43F0F974}'; GUID_BLUETOOTHLE_DEVICE_INTERFACE: TGUID = '{781AEE18-7733-4CE4-ADD0-91F41C67B592}'; // BLE GUID_BTHPORT_DEVICE_INTERFACE: TGUID = '{0850302A-B344-4FDA-9BE9-90576B8D46F0}'; var hDInfo: HDEVINFO; DevInfData: TSPDeviceInterfaceData; pBuf: TBytes; dwReqSize: DWORD; begin Result := ''; // FindFirstBluetoothDevicePath(Result); // exit; // DeviceInfoSet := SetupDiGetClassDevs(nil, 'BTHENUM', 0, DIGCF_PRESENT or DIGCF_ALLCLASSES); // DeviceInfoSet := SetupDiGetClassDevs(@GUID_DEVCLASS_BLUETOOTH, nil, 0, DIGCF_PRESENT or DIGCF_ALLCLASSES); // DeviceInfoSet := SetupDiGetClassDevs(@GUID_BLUETOOTH_DEVICE_INTERFACE, nil, 0, DIGCF_PRESENT or DIGCF_DEVICEINTERFACE); hDInfo := SetupDiGetClassDevs(@GUID_BTHPORT_DEVICE_INTERFACE, nil, 0, DIGCF_PRESENT or DIGCF_DEVICEINTERFACE); if hDInfo = INVALID_HANDLE_VALUE then exit; try ZeroMemory(@DevInfData, SizeOf(DevInfData)); DevInfData.cbSize := SizeOf(SP_DEVICE_INTERFACE_DATA); if SetupDiEnumDeviceInterfaces(hDInfo, nil, GUID_BTHPORT_DEVICE_INTERFACE, 0, DevInfData) then begin SetupDiGetDeviceInterfaceDetail(hDInfo, @DevInfData, nil, 0, dwReqSize, nil); // DeviceInterfaceDetailData := AllocMem(RequiredSize); SetLength(pBuf, dwReqSize); ZeroMemory(pBuf, dwReqSize); PSPDeviceInterfaceDetailData(@pBuf[0]).cbSize := SizeOf(TSPDeviceInterfaceDetailData); try if SetupDiGetDeviceInterfaceDetail(hDInfo, @DevInfData, PSPDeviceInterfaceDetailData(@pBuf[0]), dwReqSize, dwReqSize, nil) then Result := PChar(@pBuf[4]); finally // end; end; finally SetupDiDestroyDeviceInfoList(hDInfo); end; end; function BluetoothDisconnectDevice(aBtAddr: BLUETOOTH_ADDRESS): Boolean; const IOCTL_BTH_DISCONNECT_DEVICE = ( (FILE_DEVICE_BLUETOOTH shl 16) or (METHOD_BUFFERED shl 14) or ($0301 shl 2) or FILE_ANY_ACCESS); // CTL_CODE(FILE_DEVICE_BLUETOOTH, 6, METHOD_BUFFERED, FILE_ANY_ACCESS); var BtFindParams: BLUETOOTH_FIND_RADIO_PARAMS; hRadio: THandle; hDevice: THandle; sDevPath: String; ulllBuf: ULONGLONG; begin Result := false; // 블루투스 라디오 핸들 가져오기 // hRadio := 0; hDevice := 0; // ZeroMemory(@BtFindParams, SizeOf(BtFindParams)); // BtFindParams.dwSize := SizeOf(BtFindParams); // if BluetoothFindFirstRadio(@BtFindParams, hRadio) <> 0 then // begin // try // Bluetooth 장치 핸들 열기 sDevPath := GetBluetoothDevicePath; if sDevPath = '' then exit; // hDevice := CreateFile(PChar('\\.\BTHENUM#' + IntToStr(aBtAddr.ullLong)), // hDevice := CreateFile(PChar('\\.\BTHLE\DEV_BC87FA05FBF7\7&17003C4D&0&BC87FA05FBF7'), hDevice := CreateFile(PChar(sDevPath), GENERIC_WRITE or GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0); if hDevice <> INVALID_HANDLE_VALUE then begin try ulllBuf := aBtAddr.ullLong; // DeviceIoControl을 사용하여 연결 해제 Result := DeviceIoControl(hDevice, IOCTL_BTH_DISCONNECT_DEVICE, @ulllBuf, SizeOf(ulllBuf), nil, 0, DWORD(nil^), nil); finally CloseHandle(hDevice); end; end; // finally // CloseHandle(hRadio); // end; // end; end; function BluetoothDisconnectDevice2(aBtAddr: BLUETOOTH_ADDRESS): Boolean; const // 표준 Bluetooth 서비스 GUID BLUETOOTH_SERVICE_GUID: TGUID = '{00001124-0000-1000-8000-00805F9B34FB}'; // 서비스 GUID 목록 SERVICE_GUID_A2DP: TGUID = '{0000110D-0000-1000-8000-00805F9B34FB}'; // Advanced Audio Distribution SERVICE_GUID_AVRCP: TGUID = '{0000110E-0000-1000-8000-00805F9B34FB}'; // Audio/Video Remote Control SERVICE_GUID_HFP: TGUID = '{0000111E-0000-1000-8000-00805F9B34FB}'; // Hands-Free Profile SERVICE_GUID_HID: TGUID = '{00001124-0000-1000-8000-00805F9B34FB}'; // Human Interface Device SERVICE_GUID_PANU: TGUID = '{00001115-0000-1000-8000-00805F9B34FB}'; // Personal Area Networking SERVICE_GUID_SPP: TGUID = '{00001101-0000-1000-8000-00805F9B34FB}'; // Serial Port Profile var hFindRadio: HBLUETOOTH_RADIO_FIND; hFindDev: HBLUETOOTH_DEVICE_FIND; hRadio: THandle; BtRadiFindParam: BLUETOOTH_FIND_RADIO_PARAMS; // BtRadiInfo: BLUETOOTH_RADIO_INFO; BtDevInfo: BLUETOOTH_DEVICE_INFO; searchParams: BLUETOOTH_DEVICE_SEARCH_PARAMS; pInfo: PBtRdiEnt; begin Result := false; ZeroMemory(@BtRadiFindParam, SizeOf(BtRadiFindParam)); BtRadiFindParam.dwSize := SizeOf(BtRadiFindParam); // ZeroMemory(@BtRadiInfo, SizeOf(BtRadiInfo)); // BtRadiInfo.dwSize := SizeOf(BtRadiInfo); hFindRadio := 0; hFindDev := 0; hRadio := 0; hFindRadio := BluetoothFindFirstRadio(@BtRadiFindParam, hRadio); if hFindRadio = 0 then exit; try ZeroMemory(@searchParams, SizeOf(searchParams)); searchParams.dwSize := SizeOf(searchParams); searchParams.fReturnAuthenticated := True; searchParams.fReturnRemembered := True; searchParams.fReturnConnected := True; searchParams.fReturnUnknown := False; searchParams.hRadio := hRadio; ZeroMemory(@BtDevInfo, SizeOf(BtDevInfo)); BtDevInfo.dwSize := SizeOf(BtDevInfo); hFindDev := BluetoothFindFirstDevice(searchParams, BtDevInfo); if hFindDev = 0 then exit; repeat // 주소 비교 if CompareMem(@BtDevInfo.Address, @aBtAddr, SizeOf(aBtAddr)) then begin // 장치 연결 해제 시도 Result := BluetoothSetServiceState(hRadio, BtDevInfo, SERVICE_GUID_A2DP, BLUETOOTH_SERVICE_DISABLE) = ERROR_SUCCESS; if not Result then Result := BluetoothSetServiceState(hRadio, BtDevInfo, SERVICE_GUID_AVRCP, BLUETOOTH_SERVICE_DISABLE) = ERROR_SUCCESS; if not Result then Result := BluetoothSetServiceState(hRadio, BtDevInfo, SERVICE_GUID_HFP, BLUETOOTH_SERVICE_DISABLE) = ERROR_SUCCESS; if not Result then Result := BluetoothSetServiceState(hRadio, BtDevInfo, SERVICE_GUID_HID, BLUETOOTH_SERVICE_DISABLE) = ERROR_SUCCESS; if not Result then Result := BluetoothSetServiceState(hRadio, BtDevInfo, SERVICE_GUID_PANU, BLUETOOTH_SERVICE_DISABLE) = ERROR_SUCCESS; if not Result then Result := BluetoothSetServiceState(hRadio, BtDevInfo, SERVICE_GUID_SPP, BLUETOOTH_SERVICE_DISABLE) = ERROR_SUCCESS; exit; end; until not BluetoothFindNextDevice(hFindDev, BtDevInfo); finally if hFindDev <> 0 then BluetoothFindDeviceClose(hFindDev); if hFindRadio <> 0 then BluetoothFindRadioClose(hFindRadio); end; end; procedure TDlgBlueMon.miPreventBlueClick(Sender: TObject); var pNode: PVirtualNode; pData: PBlueEnt; dwResult: DWORD; begin pNode := vtList.GetFirstSelected; if pNode = nil then exit; if MessageBox(Handle, PChar('연결을 해제 하시겠습니까?'), PChar(Caption), MB_ICONQUESTION or MB_YESNO) = IDNO then exit; pData := vtList.GetNodeData(pNode); // dwResult := BluetoothRemoveDevice(pData.Address); // if dwResult = 0 then if BluetoothDisconnectDevice2(pData.Address) then vtList.DeleteNode(pNode) else ShowMessage(Format('Fail .. %d', [dwResult])); end; procedure TDlgBlueMon.miCopyCBClick(Sender: TObject); begin VT_CopyToClipboardSelectedInfo(vtList); MessageBox(Handle, PChar('클립보드로 복사되었습니다.'), PChar(Caption), MB_ICONINFORMATION or MB_OK); end; procedure TDlgBlueMon.vtListContextPopup(Sender: TObject; MousePos: TPoint; var Handled: Boolean); begin Handled := vtList.GetNodeAt(MousePos) = nil; end; procedure TDlgBlueMon.vtListFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode); var pData: PBlueEnt; begin pData := Sender.GetNodeData(Node); Finalize(pData^); end; procedure TDlgBlueMon.vtListGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer); begin NodeDataSize := SizeOf(TBlueEnt); end; procedure TDlgBlueMon.vtListGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); var pData: PBlueEnt; begin pData := Sender.GetNodeData(Node); case Column of 0 : CellText := IntToStr(Node.Index + 1); 1 : CellText := BooleanToStr(pData.bConnected, '연결됨', '연결안됨'); 2 : CellText := pData.sName; 3 : CellText := Format('0x%.6x', [pData.nClassofDevice]); 4 : CellText := pData.sMajor; 5 : CellText := pData.sMinor; 6 : CellText := BooleanToStr(pData.bAuth, 'O', 'X'); 7 : CellText := pData.sAddress; 8 : if pData.dtRecent <> 0 then CellText := DateTimeToStr(pData.dtRecent); 9 : if pData.dtSeen <> 0 then CellText := DateTimeToStr(pData.dtSeen); end; end; procedure TDlgBlueMon.vtListHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo); begin if HitInfo.Button = mbLeft then begin if HitInfo.Column < 0 then exit; with Sender, Treeview do begin if SortColumn > NoColumn then Columns[SortColumn].Options := Columns[SortColumn].Options + [coParentColor]; if HitInfo.Column = 0 then SortColumn := NoColumn else begin if (SortColumn = NoColumn) or (SortColumn <> HitInfo.Column) then begin SortColumn := HitInfo.Column; SortDirection := sdAscending; end else if SortDirection = sdAscending then SortDirection := sdDescending else SortDirection := sdAscending; Columns[SortColumn].Color := $00EFEFEF; vtList.BeginUpdate; try vtList.SortTree(SortColumn, SortDirection, False); finally vtList.EndUpdate; end; end; end; end; end; end.