Windows 10 - 어플 실행 단축키 추가

|

어플 실행 단축키 추가

Windows 10에서 단축키를 등록하는 방법입니다. 저는 화면 캡쳐 기능을 상당히 많이 사용하는 편인데 마음에 윈도우 기본 캡쳐 기능은 아쉬운 부분이 많습니다. 그래서 저는 무료 프로그램인 반디카메라를 애용하고 있었는데, 최근에 Picpick로 변경했습니다. 하지만, 아래 예제는 예전에 작성했던 글이기 때문에 반디카메라의 단축키를 설정하는 이미지로 대신하도록 하겠습니다.

먼저, 반디카메라 프로그램을 시작메뉴에서 찾습니다.

image

그리고, 반디카메라 실행 아이콘을 마우스 오른 버튼을 눌러 ‘속성’ 항목을 눌러줍니다.

image

속성창에서 ‘바로 가기 키’를 등록할 수 있습니다.

Windows 10 - 터치패드의 3손가락 제스처 변경

|

터치패드의 3손가락 제스처 변경

맥북 트랙패드를 쓰면서 가장 편리했던 것 중 하나가 3 손가락을 이용해서 데스크탑을 전환하는 기능이었습니다. Windows 10에서 거의 비슷하게 설정할 수 있습니다. 물론 멀티 터치가 지원되는 터치 패드만 가능합니다. MS 서피스나 삼성 갤럭시 북 등은 적용할 수 있습니다.

먼저

image

‘마우스 및 터치 패드 설정’ 창을 엽니다.

그리고, 설정 창 안에서

image

3 손가락과 4 손가락 제스처의 설정값을 서로 바꾸면 됩니다.

이제 3 손가락 제스처로 가상 데스크탑 전환을 할 수 있습니다. 개인적으로 가상 데스크탑 자체는 맥북보다 Windows가 더 편리하게 제공하는 것 같습니다.

Python 2.7.x vs Python 3.x

|

Python 2.7.x 버전과 Python 3.x 버전의 차이점을 간단하게 적어봅니다. 생각보다 많은 것들이 바뀌어서 혼란을 가져올 수 있는 부분들이 꽤 있습니다. ‘두 버전 중에 어느 버전이 더 좋은건가’라고 하면 아무래도 좀 더 많은 부분이 개선된 3.x 버전을 사용하는 것이 더 좋습니다. 하지만, 2.7.x 버전도 많이 사용하고 있고 시중에 나와 있는 많은 교재들도 2.7.x 버전 위주로 설명하고 있는 부분이 많아서 버전별 차이점은 조금 알고 있어야 할 것 같습니다.

Python Wiki에 따르면 다음과 같은 경우에는 2.7.x 버전을 사용하는 것이 더 좋다고 설명하고 있습니다.

  • 개발한 내용을 어딘가에 배포할 때, 그 환경을 제어할 수 없는 경우(예를 들어 2.7.x 버전만 사용가능한 환경인 경우)
  • 특정 라이브러리나 유틸리티 등을 사용할 때, 해당 라이브러리가 3.x 버전을 지원하지 않는 경우

즉, 대부분은 그냥 3.x 버전을 사용하는 편이 더 나을 것 같습니다.

자, 그럼 Python 2.7.x 버전과 Python 3.x 버전의 차이점을 살펴보도록 하겠습니다. 더 많은 정보는 여기를 참조하시길 바랍니다.


Python 2.7.x과 Python 3.x의 차이점

print

Python 3.x 버전은 print 문에 대해 괄호를 필요로 합니다.

예를 들어, 2.7.x 버전에서는 다음과 같은 코드를 사용할 수 있었습니다.

print 'Hello, Python!'
print('Hello, Python!')
print "Hello",;
print 'snowdeer'

하지만, 3.x 버전에는 무조건 괄호가 있어야 하며, 다음과 같이 작성해야 합니다.

print('Hello, Python!')
print("Hello ", end="")
print 'snowdeer'


자동 형 변환

Python 2.7.x 버전에서는 자동으로 형 변환을 하지 않지만, 3.x 버전에서는 자동 형 변환을 지원합니다.

예를 들어, 다음 코드의 실행 결과는

print ('3 / 2 =', 3 / 2)

2.7.x 버전에서는 ‘1’ 이 출력되지만, 3.x 버전에서는 ‘1.5’가 출력됩니다.


인코딩

Python 3.x 버전 부터는 소스 코드의 인코딩이 기본적으로 ‘utf-8’이기 때문에 소스 코드 첫 줄에 다음과 같은 라인이 생략되어져도 됩니다.

# -*- coding: utf-8 -*-


input

Python 2.7.x에서 사용자 입력은 다음과 같은 함수를 사용했었습니다.

name = raw_input("input name:")

하지만 3.x 버전에서는 다음 함수를 사용하면 됩니다.

name = input("input name:")


Exception Handling

Python 2.7.x 버전에서는 예외 처리 코드를 다음과 같이 사용했다면,

print 'Exception Handling'
try:
    let_us_cause_a_NameError
except NameError, err:
    print err, '--> our error message'

 

3.x 버전에는 다음과 같은 코드로 사용해야 합니다.

print 'Exception Handling'
try:
    let_us_cause_a_NameError
except NameError as err:
    print(err, '--> our error message')

블루투스 기반 소켓 통신 예제

|

안드로이드에서 블루투스(Bluetooth)를 이용하여 간단하게 단말간 통신할 수 있는 코드를 포스팅해봅니다.

단말 2개가 필요하며, 한 쪽 단말은 서버, 한 쪽 단말은 클라이언트가 됩니다. 또한, 두 단말간의 페어링(Pairing) 단계는 이미 되었다고 가정하고 건너뛰도록 하겠습니다.


권한(Permission) 추가

블루투스 통신을 사용하기 위해서는 아래 두 권한이 필요합니다. AndroidManifest.xml 파일에 추가해주시면 됩니다.

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />


상수값 설정

public class SnowConstant {
  public static final UUID BLUETOOTH_UUID_SECURE = UUID.fromString( "fa87c0d0-afac-11de-8a39-0800200c9a66" );
  public static final UUID BLUETOOTH_UUID_INSECURE = UUID.fromString( "8ce255c0-200a-11e0-ac64-0800200c9a66" );
}


서버

fragment_btserver.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical">

  <Button
    android:id="@+id/start_server"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Start Server" />

  <Button
    android:id="@+id/stop_server"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Stop Server" />

  <Button
    android:id="@+id/send_welcome"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Send Welcome Message" />

  <TextView
    android:id="@+id/logview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

</LinearLayout>


BTServerFragment.java

public class BTServerFragment extends Fragment {

  Handler handler = new Handler();
  TextView logView;
  StringBuilder sbLog = new StringBuilder();

  BluetoothServer btServer = new BluetoothServer();

  public BTServerFragment() {
    // Required empty public constructor
  }


  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    View view = inflater.inflate(R.layout.fragment_btserver, container, false);

    logView = (TextView) view.findViewById(R.id.logview);
    btServer.setOnEventLogListener(new OnEventLogListener() {
      @Override
      public void onLog(String message) {
        log(message);
      }
    });

    view.findViewById(R.id.start_server).setOnClickListener(mOnClickListener);
    view.findViewById(R.id.stop_server).setOnClickListener(mOnClickListener);
    view.findViewById(R.id.send_welcome).setOnClickListener(mOnClickListener);

    return view;
  }

  private View.OnClickListener mOnClickListener = new View.OnClickListener() {

    @Override
    public void onClick(View v) {
      switch (v.getId()) {
        case R.id.start_server:
          btServer.startServer();
          break;
        case R.id.stop_server:
          btServer.stopServer();
          break;
        case R.id.send_welcome:
          btServer.sendWelcome();
          break;
      }
    }
  };

  void log(String message) {
    sbLog.append(message + "\n");

    if (logView != null) {
      handler.post(new Runnable() {
        @Override
        public void run() {
          logView.setText(sbLog.toString());
        }
      });
    }

    Log.i("", "[snowdeer] " + message);
  }
}


BluetoothServer.java

public class BluetoothServer {

  BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
  BluetoothServerSocket acceptSocket;

  AcceptThread acceptThread;

  DataInputStream is = null;
  DataOutputStream os = null;

  public interface OnEventLogListener {

    void onLog(String message);
  }

  OnEventLogListener mOnEventLogListener = null;

  public void setOnEventLogListener(OnEventLogListener listener) {
    mOnEventLogListener = listener;
  }

  void printLog(String message) {
    if (mOnEventLogListener != null) {
      mOnEventLogListener.onLog(message);
    }
  }

  public void startServer() {
    stopServer();

    printLog("Start Server.");
    acceptThread = new AcceptThread();
    acceptThread.start();
  }

  public void stopServer() {
    if (acceptThread == null) {
      return;
    }

    try {
      acceptThread.stopThread();
      acceptThread.join(1000);
      acceptThread.interrupt();
    } catch (Exception e) {
      e.printStackTrace();
    }

    printLog("Stop Server.");
  }

  class AcceptThread extends Thread {

    boolean isRunning = false;

    AcceptThread() {
      try {
        acceptSocket = null;
        acceptSocket = btAdapter.listenUsingRfcommWithServiceRecord("SnowDeerBluetoothSample",
            SnowConstant.BLUETOOTH_UUID_INSECURE);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

    public void run() {
      BluetoothSocket socket;

      isRunning = true;

      while (isRunning) {
        try {
          printLog("Waiting clients...");
          socket = acceptSocket.accept();
        } catch (Exception e) {
          e.printStackTrace();
          break;
        }

        if (socket != null) {
          printLog("Client is connected.");

          CommunicationThread commThread = new CommunicationThread(socket);
          commThread.start();
        }
      }
    }

    public void stopThread() {
      isRunning = false;
      try {
        acceptSocket.close();
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  class CommunicationThread extends Thread {

    final BluetoothSocket socket;
    boolean isRunning = false;

    CommunicationThread(BluetoothSocket socket) {
      this.socket = socket;
    }

    public void run() {
      isRunning = true;

      try {
        is = new DataInputStream(socket.getInputStream());
        os = new DataOutputStream(socket.getOutputStream());
      } catch (Exception e) {
        e.printStackTrace();
      }

      while (isRunning) {
        try {
          String message = is.readUTF();
          printLog(message);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }

    public void stopThread() {
      isRunning = false;
      try {
        is.close();
        os.close();
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  public void sendWelcome() {
    if (os == null) {
      return;
    }

    try {
      os.writeUTF("Welcome");
      os.flush();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}


클라이언트

fragment_btclient.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical">

  <Button
    android:id="@+id/search_devices"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Search Devices" />

  <Button
    android:id="@+id/connect_to_server"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Connect to Server" />

  <Button
    android:id="@+id/disconnect_from_server"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Disconnect from Server" />

  <Button
    android:id="@+id/send_hello"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Send Hello Message" />

  <TextView
    android:id="@+id/logview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

</LinearLayout>


BTClientFragment.java

public class BTClientFragment extends Fragment {

  Handler handler = new Handler();
  TextView logView;
  StringBuilder sbLog = new StringBuilder();

  BluetoothClient btClient = new BluetoothClient();

  public BTClientFragment() {
    // Required empty public constructor
  }


  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    View view =  inflater.inflate(R.layout.fragment_btclient, container, false);

    logView = (TextView) view.findViewById(R.id.logview);
    btClient.setOnEventLogListener(new OnEventLogListener() {
      @Override
      public void onLog(String message) {
        log(message);
      }
    });

    view.findViewById(R.id.search_devices).setOnClickListener(mOnClickListener);
    view.findViewById(R.id.connect_to_server).setOnClickListener(mOnClickListener);
    view.findViewById(R.id.disconnect_from_server).setOnClickListener(mOnClickListener);
    view.findViewById(R.id.send_hello).setOnClickListener(mOnClickListener);

    return view;
  }

  private View.OnClickListener mOnClickListener = new View.OnClickListener() {

    @Override
    public void onClick(View v) {
      switch (v.getId()) {
        case R.id.search_devices:
          btClient.searchPairedDevice();
          break;
        case R.id.connect_to_server:
          btClient.connectToServer();
          break;
        case R.id.disconnect_from_server:
          btClient.disconnectFromServer();
          break;
        case R.id.send_hello:
          btClient.sendHello();
          break;
      }
    }
  };

  void log(String message) {
    sbLog.append(message + "\n");

    if (logView != null) {
      handler.post(new Runnable() {
        @Override
        public void run() {
          logView.setText(sbLog.toString());
        }
      });
    }

    Log.i("", "[snowdeer] " + message);
  }
}


BluetoothClient.java

public class BluetoothClient {

  BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
  BluetoothDevice mTargetDevice = null;

  DataInputStream is = null;
  DataOutputStream os = null;

  ClientThread clientThread;

  public interface OnEventLogListener {

    void onLog(String message);
  }

  OnEventLogListener mOnEventLogListener = null;

  public void setOnEventLogListener(OnEventLogListener listener) {
    mOnEventLogListener = listener;
  }

  void printLog(String message) {
    if (mOnEventLogListener != null) {
      mOnEventLogListener.onLog(message);
    }
  }

  public void searchPairedDevice() {
    Set<BluetoothDevice> pairedDevices = btAdapter.getBondedDevices();
    printLog("paired Devices count : " + pairedDevices.size());

    if (pairedDevices.size() > 0) {
      for (BluetoothDevice deivce : pairedDevices) {
        printLog(deivce.getName() + " / " + deivce.getAddress());

        mTargetDevice = deivce;

        printLog(deivce.getName() + "(" + deivce.getAddress() + ") is found !!!");
      }
    }
  }

  public void connectToServer() {
    disconnectFromServer();

    if (mTargetDevice == null) {
      return;
    }

    printLog("Connect to Server.");
    clientThread = new ClientThread(mTargetDevice);
    clientThread.start();
  }

  public void disconnectFromServer() {
    if (clientThread == null) {
      return;
    }

    try {
      clientThread.stopThread();
      clientThread.join(1000);
      clientThread.interrupt();
    } catch (Exception e) {
      e.printStackTrace();
    }

    printLog("Disconnect from Server.");
  }


  class ClientThread extends Thread {

    BluetoothSocket socket;
    BluetoothDevice mDevice;

    ClientThread(BluetoothDevice device) {
      try {
        mDevice = device;

        //socket = null;

        socket = device.createRfcommSocketToServiceRecord(SnowConstant.BLUETOOTH_UUID_INSECURE);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

    public void run() {
      btAdapter.cancelDiscovery();

      while (true) {
        try {
          printLog("try to connect to Server...");
          socket.connect();
        } catch (Exception e) {
          e.printStackTrace();
          try {
            socket.close();
          } catch (Exception ex) {
            ex.printStackTrace();
          }
          break;
        }

        if (socket != null) {
          printLog("Connected !!!");

          try {
            is = new DataInputStream(socket.getInputStream());
            os = new DataOutputStream(socket.getOutputStream());

          } catch (Exception e) {
            e.printStackTrace();
          }

          while (true) {
            try {
              String message = is.readUTF();
              printLog(message);
            } catch (Exception e) {
              e.printStackTrace();
            }
          }
        }

        try {
          sleep(1000);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }

    public void stopThread() {
      try {
        socket.close();
        is.close();
        os.close();
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  public void sendHello() {
    if (os == null) {
      return;
    }

    try {
      os.writeUTF("Hello");
      os.flush();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

안드로이드 Navigation Drawer 사용하기

|

요즘 나오는 App들은 Navigation Drawer를 사용한 UX가 많습니다.

image


Google Developer 사이트에서 훌륭한 예제 코드를 다루고 있는데, 여기서는 Android Studio에서 자동으로 생성해준 템플릿 코드 기반으로 간단하게 구현을 해보았습니다.

먼저 메뉴 항목들을 나열할 xml 파일을 만들어줍니다. (res 폴더 아래 menu 폴더 아래 만들어 주시면 됩니다.)


activity_main_drawer.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

  <group android:checkableBehavior="single">
    <item
      android:id="@+id/menu_bt_server"
      android:icon="@drawable/ic_menu_camera"
      android:title="Bluetooth Server" />
    <item
      android:id="@+id/menu_bt_client"
      android:icon="@drawable/ic_menu_gallery"
      android:title="Bluetooth Client" />

  </group>

</menu>


그리고, 액티비티에서 사용할 레이아웃은 다음과 같이 작성합니다.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/drawer_layout"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:fitsSystemWindows="true"
  tools:openDrawer="start">

  <include
    layout="@layout/app_bar_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

  <android.support.design.widget.NavigationView
    android:id="@+id/nav_view"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    android:fitsSystemWindows="true"
    app:headerLayout="@layout/nav_header_main"
    app:menu="@menu/activity_main_drawer" />

</android.support.v4.widget.DrawerLayout>


app_bar_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="snowdeer.bluetooth.sample.MainActivity">

  <android.support.design.widget.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:theme="@style/AppTheme.AppBarOverlay">

    <android.support.v7.widget.Toolbar
      android:id="@+id/toolbar"
      android:layout_width="match_parent"
      android:layout_height="?attr/actionBarSize"
      android:background="?attr/colorPrimary"
      app:popupTheme="@style/AppTheme.PopupOverlay" />

  </android.support.design.widget.AppBarLayout>

  <RelativeLayout
    android:id="@+id/content_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</android.support.design.widget.CoordinatorLayout>


nav_header_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="@dimen/nav_header_height"
  android:paddingTop="@dimen/activity_vertical_margin"
  android:paddingBottom="@dimen/activity_vertical_margin"
  android:paddingLeft="@dimen/activity_horizontal_margin"
  android:paddingRight="@dimen/activity_horizontal_margin"
  android:background="@drawable/side_nav_bar"
  android:gravity="bottom"
  android:orientation="vertical"
  android:theme="@style/ThemeOverlay.AppCompat.Dark">

  <ImageView
    android:id="@+id/imageView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:paddingTop="@dimen/nav_header_vertical_spacing"
    app:srcCompat="@android:drawable/sym_def_app_icon" />

  <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="@dimen/nav_header_vertical_spacing"
    android:text="Android Studio"
    android:textAppearance="@style/TextAppearance.AppCompat.Body1" />

  <TextView
    android:id="@+id/textView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="android.studio@android.com" />

</LinearLayout>


그리고 실제 Java 코드는 다음과 같이 작성하면 됩니다.

MainActivity.java

public class MainActivity extends AppCompatActivity
    implements NavigationView.OnNavigationItemSelectedListener {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
        this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
    drawer.setDrawerListener(toggle);
    toggle.syncState();

    NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
    navigationView.setNavigationItemSelectedListener(this);

    replaceServerFragment();
  }

  @Override
  public void onBackPressed() {
    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    if (drawer.isDrawerOpen(GravityCompat.START)) {
      drawer.closeDrawer(GravityCompat.START);
    } else {
      super.onBackPressed();
    }
  }

  @SuppressWarnings("StatementWithEmptyBody")
  @Override
  public boolean onNavigationItemSelected(MenuItem item) {
    int id = item.getItemId();

    if (id == R.id.menu_bt_server) {
      replaceServerFragment();
    } else if (id == R.id.menu_bt_client) {
      replaceClientFragment();
    }

    DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
    drawer.closeDrawer(GravityCompat.START);
    return true;
  }

  void replaceServerFragment() {
    setTitle("Bluetooth Server");
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    Fragment fragment = new BTServerFragment();
    ft.replace(R.id.content_main, fragment);
    ft.commit();
  }

  void replaceClientFragment() {
    setTitle("Bluetooth Client");
    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    Fragment fragment = new BTClientFragment();
    ft.replace(R.id.content_main, fragment);
    ft.commit();
  }
}