diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 7ac24c7..2996d53 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -3,14 +3,11 @@ diff --git a/.idea/misc.xml b/.idea/misc.xml index 99202cc..af0bbdd 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,29 +1,9 @@ - - - + + + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..35eb1dd 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index a82120a..c7a5b5e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,7 +4,7 @@ android { compileSdkVersion 27 defaultConfig { applicationId "me.xor_hydrogen.i3control" - minSdkVersion 22 + minSdkVersion 27 targetSdkVersion 27 versionCode 1 versionName "1.0" @@ -21,6 +21,6 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:27.1.1' - implementation 'com.android.support.constraint:constraint-layout:1.1.1' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'com.android.volley:volley:1.1.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b52e250..3809651 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ + package="com.theedgeofrage.i3control"> @@ -11,14 +11,14 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> - + diff --git a/app/src/main/java/com/theedgeofrage/i3control/MainActivity.java b/app/src/main/java/com/theedgeofrage/i3control/MainActivity.java new file mode 100644 index 0000000..6eef936 --- /dev/null +++ b/app/src/main/java/com/theedgeofrage/i3control/MainActivity.java @@ -0,0 +1,153 @@ +package com.theedgeofrage.i3control; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Handler; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; + +import com.android.volley.Request; +import com.android.volley.RequestQueue; +import com.android.volley.Response; +import com.android.volley.VolleyError; +import com.android.volley.toolbox.JsonObjectRequest; +import com.android.volley.toolbox.Volley; + +import org.json.JSONException; +import org.json.JSONObject; + +public class MainActivity extends AppCompatActivity { + public void sendRequest(String endpoint, final Context context) { + RequestQueue queue = Volley.newRequestQueue(context); + + SharedPreferences settings = context.getSharedPreferences( + context.getApplicationContext().getPackageName() + "_preferences", 0); + String host = settings.getString("pref_host", ""); + if (host.equals("")) { + Toast.makeText(context, "No host set", Toast.LENGTH_SHORT).show(); + return; + } + + String url ="http://" + host + "/" + endpoint; + JsonObjectRequest stringRequest = new JsonObjectRequest(Request.Method.GET, url, new JSONObject(), new Response.Listener() { + @Override + public void onResponse(JSONObject response) { + if (response.length() > 0) { + try { + String current = response.getString("artist") + " - " + response.getString("title"); + ((TextView)findViewById(R.id.currentlyPlayingText)).setText(current); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + }, new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + } + }); + + queue.add(stringRequest); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.actionbar_menu, menu); + return super.onCreateOptionsMenu(menu); + + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_settings: + startActivity(new Intent(MainActivity.this, SettingsActivity.class)); + break; + } + return true; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + final Context context = this; + + View.OnClickListener buttonClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.muteButton: + ((MainActivity)context).sendRequest("mute", context); + break; + case R.id.volDownButton: + ((MainActivity)context).sendRequest("vol_down", context); + break; + case R.id.volUpButton: + ((MainActivity)context).sendRequest("vol_up", context); + break; + case R.id.prevButton: + ((MainActivity)context).sendRequest("prev", context); + break; + case R.id.playButton: + ((MainActivity)context).sendRequest("play", context); + break; + case R.id.nextButton: + ((MainActivity)context).sendRequest("next", context); + break; + case R.id.shuffleButton: + ((MainActivity)context).sendRequest("shuffle", context); + break; + } + } + }; + + findViewById(R.id.muteButton).setOnClickListener(buttonClickListener); + findViewById(R.id.volDownButton).setOnClickListener(buttonClickListener); + findViewById(R.id.volUpButton).setOnClickListener(buttonClickListener); + findViewById(R.id.prevButton).setOnClickListener(buttonClickListener); + findViewById(R.id.playButton).setOnClickListener(buttonClickListener); + findViewById(R.id.nextButton).setOnClickListener(buttonClickListener); + findViewById(R.id.shuffleButton).setOnClickListener(buttonClickListener); + + class RunnableQuery implements Runnable { + private Handler mHandler; + private boolean mRun = false; + + RunnableQuery() { + mHandler = new Handler(getMainLooper()); + } + + public void start() { + mRun = true; + mHandler.postDelayed(this, 1000); + } + + void stop() { + mRun = false; + mHandler.removeCallbacks(this); + } + + @Override + public void run() { + if (!mRun) { + return; + } + ((MainActivity) context).sendRequest("query", context); + mHandler.postDelayed(this, 1000); + } + } + + RunnableQuery runnableQuery = new RunnableQuery(); + runnableQuery.start(); + } +} diff --git a/app/src/main/java/me/xor_hydrogen/i3control/SettingsActivity.java b/app/src/main/java/com/theedgeofrage/i3control/SettingsActivity.java similarity index 94% rename from app/src/main/java/me/xor_hydrogen/i3control/SettingsActivity.java rename to app/src/main/java/com/theedgeofrage/i3control/SettingsActivity.java index 71d5ce7..9f9538a 100644 --- a/app/src/main/java/me/xor_hydrogen/i3control/SettingsActivity.java +++ b/app/src/main/java/com/theedgeofrage/i3control/SettingsActivity.java @@ -1,4 +1,4 @@ -package me.xor_hydrogen.i3control; +package com.theedgeofrage.i3control; import android.os.Bundle; import android.preference.PreferenceActivity; diff --git a/app/src/main/java/me/xor_hydrogen/i3control/ApiService.java b/app/src/main/java/me/xor_hydrogen/i3control/ApiService.java deleted file mode 100644 index 9c04a6f..0000000 --- a/app/src/main/java/me/xor_hydrogen/i3control/ApiService.java +++ /dev/null @@ -1,42 +0,0 @@ -package me.xor_hydrogen.i3control; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; -import android.widget.Toast; - -import com.android.volley.Request; -import com.android.volley.RequestQueue; -import com.android.volley.Response; -import com.android.volley.VolleyError; -import com.android.volley.toolbox.StringRequest; -import com.android.volley.toolbox.Volley; - -class ApiService { - static void sendRequest(String endpoint, Context context) { - RequestQueue queue = Volley.newRequestQueue(context); - - SharedPreferences settings = context.getSharedPreferences( - context.getApplicationContext().getPackageName() + "_preferences", 0); - String host = settings.getString("pref_host", ""); - if (host.equals("")) { - Toast.makeText(context, "No host set", Toast.LENGTH_SHORT).show(); - return; - } - - String url ="http://" + host + "/" + endpoint; - Log.i(context.getClass().getName(), "Sending request to: " + url); - - StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener() { - @Override - public void onResponse(String response) { - } - }, new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - } - }); - - queue.add(stringRequest); - } -} diff --git a/app/src/main/java/me/xor_hydrogen/i3control/MainActivity.java b/app/src/main/java/me/xor_hydrogen/i3control/MainActivity.java deleted file mode 100644 index 8503358..0000000 --- a/app/src/main/java/me/xor_hydrogen/i3control/MainActivity.java +++ /dev/null @@ -1,82 +0,0 @@ -package me.xor_hydrogen.i3control; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.support.v7.app.AppCompatActivity; -import android.os.Bundle; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.ImageButton; -import android.widget.Toast; - -public class MainActivity extends AppCompatActivity { - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.actionbar_menu, menu); - return super.onCreateOptionsMenu(menu); - - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_settings: - startActivity(new Intent(MainActivity.this, SettingsActivity.class)); - break; - } - return true; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - final Context context = this; - ImageButton muteButton = findViewById(R.id.muteButton); - ImageButton volDownButton = findViewById(R.id.volDownButton); - ImageButton volUpButton = findViewById(R.id.volUpButton); - ImageButton prevButton = findViewById(R.id.prevButton); - ImageButton playButton = findViewById(R.id.playButton); - ImageButton nextButton = findViewById(R.id.nextButton); - - View.OnClickListener buttonClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.muteButton: - ApiService.sendRequest("mute", context); - break; - case R.id.volDownButton: - ApiService.sendRequest("vol_down", context); - break; - case R.id.volUpButton: - ApiService.sendRequest("vol_up", context); - break; - case R.id.prevButton: - ApiService.sendRequest("prev", context); - break; - case R.id.playButton: - ApiService.sendRequest("play", context); - break; - case R.id.nextButton: - ApiService.sendRequest("next", context); - break; - } - } - }; - - muteButton.setOnClickListener(buttonClickListener); - volDownButton.setOnClickListener(buttonClickListener); - volUpButton.setOnClickListener(buttonClickListener); - prevButton.setOnClickListener(buttonClickListener); - playButton.setOnClickListener(buttonClickListener); - nextButton.setOnClickListener(buttonClickListener); - } -} diff --git a/app/src/main/res/drawable/ic_shuffle_black_24dp.xml b/app/src/main/res/drawable/ic_shuffle_black_24dp.xml new file mode 100644 index 0000000..ec80046 --- /dev/null +++ b/app/src/main/res/drawable/ic_shuffle_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 5f9b88a..f9e2397 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -4,114 +4,130 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context="me.xor_hydrogen.i3control.MainActivity"> + tools:context="com.theedgeofrage.i3control.MainActivity"> + app:srcCompat="@drawable/ic_volume_mute_black_24dp" /> + app:srcCompat="@drawable/ic_volume_down_black_24dp" /> - - + app:srcCompat="@drawable/ic_volume_up_black_24dp" /> + + + app:srcCompat="@drawable/ic_skip_previous_black_24dp" /> + app:srcCompat="@drawable/ic_skip_next_black_24dp" /> + + + + diff --git a/backend/run.py b/backend/run.py index 8c59d5e..999b61e 100755 --- a/backend/run.py +++ b/backend/run.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from flask import Flask -from subprocess import call +from flask import Flask, jsonify +from subprocess import call, Popen, PIPE app = Flask(__name__) @@ -8,40 +8,70 @@ app = Flask(__name__) @app.route('/mute') def mute(): - call(['pactl', 'set-sink-mute', '0', 'toggle']) - return '' + call(['pactl', 'set-sink-mute', '0', 'toggle']) + return '' @app.route('/vol_down') def vol_down(): - call(['pactl', 'set-sink-volume', '0', '-5%']) - return '' + call(['pactl', 'set-sink-volume', '0', '-2%']) + return 'vol down' @app.route('/vol_up') def vol_up(): - call(['pactl', 'set-sink-volume', '0', '+5%']) - return '' + call(['pactl', 'set-sink-volume', '0', '+2%']) + return 'vol up' @app.route('/prev') def prev(): - call(['cmus-remote', '-r']) - return '' + call(['cmus-remote', '-r']) + return '' @app.route('/play') def play(): - call(['cmus-remote', '-u']) - return '' + call(['cmus-remote', '-u']) + return '' @app.route('/next') def next(): - call(['cmus-remote', '-n']) - return '' + call(['cmus-remote', '-n']) + return '' + + +@app.route('/shuffle') +def shuffle(): + call(['cmus-remote', '-S']) + return '' + + +@app.route('/query') +def query(): + p = Popen(['cmus-remote', '-Q'], stdout=PIPE, stderr=PIPE) + output, err = p.communicate() + output = output.decode() + status = {} + + for line in output.split('\n'): + if line == 'status playing': + status['playing'] = True + elif line == 'status paused': + status['playing'] = False + elif line.startswith('tag artist '): + status['artist'] = line[11:] + elif line.startswith('tag title '): + status['title'] = line[10:] + elif line == 'set shuffle true': + status['shuffle'] = True + elif line == 'set shuffle false': + status['shuffle'] = False + + return jsonify(status) if __name__ == '__main__': - app.run() + app.run(host='0.0.0.0') diff --git a/build.gradle b/build.gradle index 88560b4..2c1be2c 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.3' + classpath 'com.android.tools.build:gradle:3.3.0' // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cab8395..af1cf20 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Apr 02 19:56:32 CEST 2018 +#Wed Jan 16 16:47:13 CET 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip