757 lines
23 KiB
Plaintext
757 lines
23 KiB
Plaintext
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.
|