Giter Site home page Giter Site logo

shizuku-api's People

Contributors

fengyuecanzhu avatar hamza417 avatar haruue avatar pranavpurwar avatar rikkaw avatar ryuunoakaihitomi avatar vvb2060 avatar yorick-ryu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

shizuku-api's Issues

Question: are there limitations of using Shizuku?

I've noticed that the demo shows only about getting the instance via SystemServiceHelper.getSystemService .

But what about other types of classes? For example Singleton classes like Runtime , which you need to reach its instance via a static function (Runtime.getRuntime() ) ?

image

And what about classes that need to be instantiated by the library, including those that have multiple arguments for the CTOR (such as ProcessBuilder, which is created via Runtime.getRuntime().exec )?

Are both of these possible to reach?

Hello, Can I use shizuku api Send System Broadcast?

I want send system broadcast

adb shell settings put global airplane_mode_on 0/1

# and send broadcast android.intent.action.AIRPLANE_MODE

Can it complete?

Thanks!

I'm try

<uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL"
        tools:ignore="ProtectedPermissions" />
SystemServiceHelper.getSystemService(Context.CONNECTIVITY_SERVICE);

but error...

`UnsatisfiedLinkError: No implementation found` when attempting to use native code loaded via a DexClassLoader

I'm attempting to load an APK using DexClassLoader within the Shizuku service. It all seems to work well and I can call methods (and have also got LSPlant working via Aliucord's Hook library too), but when trying to call JNI from within the APK, it seems to stop working.

My intentions are to try to run the recognition code for Ambient Music within Shell, which in theory would work - I've already got a PoC working as a system app (no Xposed or model loading needed as LSPlant can stop the framework calls and instead trigger it manually), but the ultimate goal is to get it working with just Shizuku. Shell has access to record audio using the hotword mic, but simply recording the audio and sending it back to a client isn't sufficient as there's also something in the recognition (not this crash) that requires being a system app.

Minimal setup:

val context = Class.forName("android.app.ActivityThread")
	.getMethod("currentApplication")
	.invoke(null) as Context

val apkPath = File("/data/local/tmp", "aiai.apk").absolutePath
val sourceDir = context.packageManager.getApplicationInfo(BuildConfig.APPLICATION_ID, 0).sourceDir
var librarySearchPath: String = sourceDir.toString() + "!/lib/" + Build.SUPPORTED_ABIS[0]
val systemLibrarySearchPath = System.getProperty("java.library.path")
if (!TextUtils.isEmpty(systemLibrarySearchPath)) {
	librarySearchPath += File.pathSeparatorChar.toString() + systemLibrarySearchPath
}
val classLoader = DexClassLoader(apkPath, ".", librarySearchPath, ClassLoader.getSystemClassLoader())

System.loadLibrary("sense")

val lfr = classLoader.loadClass("lfr").getDeclaredConstructor().apply {
	isAccessible = true
}.newInstance()

val nnfpRecognizer = classLoader.loadClass("com.google.audio.ambientmusic.NnfpRecognizer")
	.getConstructor(lfr::class.java, Array<String>::class.java, Array<String>::class.java)
	.newInstance(lfr, emptyArray<String>(), emptyArray<String>())
  • Run the project, you will get an exception:
04-22 01:46:26.851 22051 22074 E com.kieronquinn.app.shizukutest:shizuku: No implementation found for long com.google.audio.ambientmusic.NnfpRecognizer.init(java.lang.String[], java.lang.String[], byte[]) (tried Java_com_google_audio_ambientmusic_NnfpRecognizer_init and Java_com_google_audio_ambientmusic_NnfpRecognizer_init___3Ljava_lang_String_2_3Ljava_lang_String_2_3B)
04-22 01:46:26.851 22051 22074 E JavaBinder: *** Uncaught remote exception!  (Exceptions are not yet supported across processes.)
04-22 01:46:26.851 22051 22074 E JavaBinder: java.lang.reflect.InvocationTargetException
04-22 01:46:26.851 22051 22074 E JavaBinder:    at java.lang.reflect.Constructor.newInstance0(Native Method)
04-22 01:46:26.851 22051 22074 E JavaBinder:    at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
04-22 01:46:26.851 22051 22074 E JavaBinder:    at com.kieronquinn.app.shizukutest.ShizukuService.start(ShizukuService.kt:77)
04-22 01:46:26.851 22051 22074 E JavaBinder:    at com.kieronquinn.app.shizukutest.IShizukuService$Stub.onTransact(IShizukuService.java:59)
04-22 01:46:26.851 22051 22074 E JavaBinder:    at android.os.Binder.execTransactInternal(Binder.java:1179)
04-22 01:46:26.851 22051 22074 E JavaBinder:    at android.os.Binder.execTransact(Binder.java:1143)
04-22 01:46:26.851 22051 22074 E JavaBinder: Caused by: java.lang.UnsatisfiedLinkError: No implementation found for long com.google.audio.ambientmusic.NnfpRecognizer.init(java.lang.String[], java.lang.String[], byte[]) (tried Java_com_google_audio_ambientmusic_NnfpRecognizer_init and Java_com_google_audio_ambientmusic_NnfpRecognizer_init___3Ljava_lang_String_2_3Ljava_lang_String_2_3B)
04-22 01:46:26.851 22051 22074 E JavaBinder:    at com.google.audio.ambientmusic.NnfpRecognizer.init(Native Method)
04-22 01:46:26.851 22051 22074 E JavaBinder:    at com.google.audio.ambientmusic.NnfpRecognizer.<init>(PG:2)
04-22 01:46:26.851 22051 22074 E JavaBinder:    ... 6 more

I've made the code identical to how Shizuku starts the Java code from a shell service, other than loading the source directory using PackageManager.

I've also tried:

  • Using PathClassLoader
  • Loading the library from /data/local/tmp
  • Using a different version of the APK
  • Loading the library in static {}
  • Loading the Application fully (ie. creating an instance of the Application class where it loads libsense normally)
  • Disabling selinux, although that kind of defeats the whole point of using Shizuku!

None of these make any difference, the exception is identical.

I've also verified that the function in the library is exported:

image

There's no other relevant logcat output either.

What's really strange is that using Hook does work, even though it also uses native libraries in exactly the same way. Those are loaded fine, and methods are hooked without a problem.

Obviously this is quite a specific edge case, so if there's something that prevents this working in the system that simply isn't surfacing in the logcat, I'll happily move on and make the mod require a Magisk module to make it a system app.

Thanks!

Request service to run as shell (when Shizuku has been started by root)

This is a massive edge case, but I've found a scenario that it's needed. Not sure if it's a "bug" in the Manager or an API feature request, so I've put it here, but feel free to move it.

Basically, if you start Shizuku on a rooted device, subsequent calls to getCallingUid() (when not handling a transaction from the app) will return 0, root. If the Shizuku service has been started by ADB, it will return 2000 (com.android.shell). This is expected, as the service has been started by those UIDs originally.

However, there are some cases (eg. the launcherapps and shortcut system services), where root is worse than shell, and the system service will actually reject calls from root, but take them from shell. Here's an example:

ILauncherApps.aidl (you'll also need to create the stubs for ParceledListSlice + its parent and ShortcutQueryWrapper, sorry - I can provide more if needed)

package android.content.pm;

import android.os.UserHandle;
import android.content.pm.ParceledListSlice;
import android.content.pm.ShortcutQueryWrapper;

interface ILauncherApps {
    ParceledListSlice getShortcuts(String callingPackage, in ShortcutQueryWrapper query, in UserHandle user) = 13;
}

In a Shizuku service:

private val launcherApps by lazy {
    val launcherAppsProxy = SystemServiceHelper.getSystemService("launcherapps")
    ILauncherApps.Stub.asInterface(launcherAppsProxy)
}

//context is the system context, get it however you prefer
private fun getUserHandle(): UserHandle {
	return Context::class.java.getMethod("getUser").invoke(context) as UserHandle
}

override fun getShortcuts(query: ShortcutQueryWrapper) {
	val token = clearCallingIdentity()
	Log.d("TTSS", "getShortcuts ${getCallingUid()}")
	val shortcuts = launcherApps.getShortcuts("com.android.shell", query, getUserHandle()) as ParceledListSlice<ShortcutInfo>
	Log.d("TTSS", "Got shortcuts! Size ${shortcuts.list.size}")
	restoreCallingIdentity(token)
}

For the query you can use the generic:

val query = ShortcutQueryWrapper(LauncherApps.ShortcutQuery().apply {
	setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC or LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST)
})

When running from Shizuku started with root, the call will result in a crash:

11-09 00:14:12.617 30021 30021 E AndroidRuntime: Process: com.kieronquinn.app.taptap, PID: 30021
11-09 00:14:12.617 30021 30021 E AndroidRuntime: java.lang.SecurityException: Calling package name mismatch
11-09 00:14:12.617 30021 30021 E AndroidRuntime:        at android.os.Parcel.createExceptionOrNull(Parcel.java:2425)
11-09 00:14:12.617 30021 30021 E AndroidRuntime:        at android.os.Parcel.createException(Parcel.java:2409)
11-09 00:14:12.617 30021 30021 E AndroidRuntime:        at android.os.Parcel.readException(Parcel.java:2392)
11-09 00:14:12.617 30021 30021 E AndroidRuntime:        at android.os.Parcel.readException(Parcel.java:2334)
11-09 00:14:12.617 30021 30021 E AndroidRuntime:        at com.kieronquinn.app.taptap.shizuku.ITapTapShizukuService$Stub$Proxy.getShortcuts(ITapTapShizukuService.java:151)

When running from Shizuku started with ADB, the call will work fine and a list of shortcuts will be returned.

We can trace the crash back to here:

void verifyCallingPackage(String callingPackage) {
	int packageUid = -1;
	try {
		packageUid = AppGlobals.getPackageManager().getPackageUid(callingPackage,
				PackageManager.MATCH_DIRECT_BOOT_AWARE
						| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
						| PackageManager.MATCH_UNINSTALLED_PACKAGES,
				UserHandle.getUserId(getCallingUid()));
	} catch (RemoteException ignore) {
	}
	if (packageUid < 0) {
		Log.e(TAG, "Package not found: " + callingPackage);
	}
	if (packageUid != injectBinderCallingUid()) {
		throw new SecurityException("Calling package name mismatch");
	}
}

//...

int injectBinderCallingUid() {
	return getCallingUid();
}

Which as you can see is checking the calling UID against the one found for the package name. It's worth noting that passing "android" as the package name does not work here either, and there's no package name for root, so it will always fail with that.

Just for the sake of it you can also prove this in the shell:

When running as shell:

raven:/ $ service call launcherapps 13 s16 com.android.shell
Result: Parcel(
  0x00000000: fffffffc 00000067 00740041 00650074 '....g...A.t.t.e.'
  0x00000010: 0070006d 00200074 006f0074 00690020 'm.p.t. .t.o. .i.'
  0x00000020: 0076006e 006b006f 00200065 00690076 'n.v.o.k.e. .v.i.'
  0x00000030: 00740072 00610075 0020006c 0065006d 'r.t.u.a.l. .m.e.'
  0x00000040: 00680074 0064006f 00270020 006e0069 't.h.o.d. .'.i.n.'
  0x00000050: 00200074 006e0061 00720064 0069006f 't. .a.n.d.r.o.i.'
  0x00000060: 002e0064 0073006f 0055002e 00650073 'd...o.s...U.s.e.'
  0x00000070: 00480072 006e0061 006c0064 002e0065 'r.H.a.n.d.l.e...'
  0x00000080: 00650067 00490074 00650064 0074006e 'g.e.t.I.d.e.n.t.'
  0x00000090: 00660069 00650069 00280072 00270029 'i.f.i.e.r.(.).'.'

(etc., it's passed the package name check that's what matters)

When running as root:

raven:/ # service call launcherapps 13 s16 com.android.shell
Result: Parcel(
  0x00000000: ffffffff 0000001d 00610043 006c006c '........C.a.l.l.'
  0x00000010: 006e0069 00200067 00610070 006b0063 'i.n.g. .p.a.c.k.'
  0x00000020: 00670061 00200065 0061006e 0065006d 'a.g.e. .n.a.m.e.'
  0x00000030: 006d0020 00730069 0061006d 00630074 ' .m.i.s.m.a.t.c.'
  0x00000040: 00000068 00000000                   'h.......        ')

My suggestion for getting around this would be to simply run the start command from the manager as shell, with su -l 2000 - if that is possible with how Shizuku works (admittedly I've not checked). If possible, to maintain compatibility, it could also be an API option (hence making the issue here).

If you need any more code, I can send a WIP version of Tap, Tap that has this issue.

Cheers

Add `removeAllListeners` if possible

While using lambda to add listener in Kotlin, it seems that the listener cannot be removed in this case.

For example,

Shizuku.addRequestPermissionResultListener { _, _ -> doSomething() }

There is no way to remove this particular listener. Since I only added this one listener, it would be better to remove them all at once.

Maybe we can add a function named removeAllListeners in a easy case.

public static void removeAllListeners() {
    RECEIVED_LISTENERS.clear();
    DEAD_LISTENERS.clear();
    PERMISSION_LISTENERS.clear();
}

or divide this function into 3 cases

Demo project needs to be updated

I got a compilation error:

Manifest merger failed : uses-sdk:minSdkVersion 23 cannot be smaller than version 24 declared in library [:api] C:\StudioProjects\Shizuku-API\api\build\intermediates\merged_manifest\debug\AndroidManifest.xml as the library might be using APIs not available in 23
	Suggestion: use a compatible library with a minSdk of at most 23,
		or increase this project's minSdk version to at least 24,
		or use tools:overrideLibrary="rikka.shizuku.api" to force usage (may lead to runtime failures)

rish or sh rish

I am confused about why i dont get rish use without preceeding it with sh.
Sh wouldnt work termix previously. Now it worka without really working and rish doesnt work on its own.

对于没有在公共SDK中导出AIDL的类,如何调用比较方便

image

如图,希望调用ActivityManager的getRunningAppProcesses方法,但是ActivityManager的AIDL没有在公共sdk中导出,因此好像不好通过IActivityManager.Stub.asInterface来构造Singleton。这种情况有没有什么比较方便的解决办法?
(倒是好像可以靠写UserService解决,但是那里面在api13以下没有context,又不好拿ActivityManager)

rish的stderr问题

rish/src/main/java/rikka/rish/RishTerminal.java
这行写错了吧:
ttyFd = start(tty, getFd(stdin, 1), getFd(stdout, 0), getFd(stdout, 0));

關於rish調用方法

Shizuku API 拿到權限后,需要怎麽樣才能調用 adb 的指令?
請求各位大佬指點。

Kill all running instances of Shizuku service

I'm working on moving DarQ [I think you suggested I move it a long time ago and it was rejected due to libsuperuser being more suited to the task, which with the advent of local ADB is no longer the case] to use a hybrid of Shizuku for ADB and libsu's bound services for root, and it's working well - except that I need the service to outlast the Application.

libsu handles this gracefully by allowing services to be daemons (see here), which seems to be similarly possible with Shizuku by simply not unbinding the service.

However, when the app needs to bind the service again (for example when the user launches the settings for config), Shizuku doesn't have a way of cleaning up the running daemon service to start a new one for binding. I can do it easily with libsu using RootService.stop(...), which removes any remote services with that intent, but the Shizuku.unbindUserService() method expects a service connection which I no longer have - and passing a dummy one doesn't remove the remote service. This means that when I call RootService.bindUserService it starts a new one and I end up with two running.

Could a similar stop() method to libsu's be added please?

How to call the system settings api in UserService?

Settings.Secure.putString(resolver,
                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                String.format("%s/%s",packageName,serviceName));
        Settings.Secure.putInt(resolver,
                Settings.Secure.ACCESSIBILITY_ENABLED, 1);

The "resolver" object here cannot be obtained from UserService.

没看懂demo,求大佬指点一下

我想在授权shizuku后执行shell命令,但是在demo没有翻到此类的操作,看了其他issue有提到,是需在service里执行命令吗?然后在bindservice后调用?

初始化时无法获得binder

Shizuku.OnBinderReceivedListener 事件接收不到,请求权限就会抛出异常,问题出现在rikka.sui.SuiIBinder received = reply.readStrongBinder();receivednull
步骤:

  1. 添加依赖,添加provider,添加FreeReflection
  2. 添加Application,调用Sui.init(BuildConfig.APPLICATION_ID);
  3. Activity 绑定事件

How to add fallback in case the calling to the reflected API fails?

I hope I'm asking this in the correct place. Couldn't find a forum for this...

About my question, suppose you reach the PackageManager class function to disable some app.
If for some reason this API changes in some Android version, can I use Shizuku to call more official commands like on adb?

Meaning in adb it's :

adb shell pm disable PACKAGE_NAME

Is the same thing possible via Shizuku?

API Compatibility question

Hi

I can't seem to find any info about APP - API version compatibility other than the "preV11" check

Will the current version of the API (12.1.0) continue to work correctly with newer versions of Shizuku e.g. when shizuku 14 and shizuku-api 14 come out? Is compatibility guaranteed until a specific version?

Does rish work when the system in Doze(sleeping)?

This is a great tool for non-root systems. Everything is working well when in screen on state. however after screen off with 5 minutes, we get no response when executing the shell scripts. So may know if it supports? Thanks!

n

adb shell pm grant com.arumcomm.crashlogviewer android.permission.PACKAGE_USAGE_STATS
adb shell pm grant com.arumcomm.crashlogviewer android.permission.READ_LOGS

一些第三方工具(测试了Tasker和MacroDroid)无法调用Shizuku的rish

没打完字就提交了,按错了,在编辑 已编辑完成

测试的Shizuku版本为v12.6.2

测试过程中使用了TaskerMacroDroid,均已经修改了RISH_APPLICATION_ID(MacroDroid需要使用MD-Helper,因为高版本Android受到Google Play政策限制MD无法申请到存储空间权限)。

将rish与dex文件释放在/storage/emulated/0/shizuku文件夹,根据文档提示,在两者的shell脚本任务中尝试执行命令。

sh /storage/emulated/0/shizuku/rish -c "ls"

但没有任何的输出,具体表现为命令卡在了这里,没有任何的输出,设定的超时时间之后执行中断。

我不太确定是否是我对这个工具的一些使用理解上出了一些问题,能否提示一些调试这个功能有关的细节以便我分析问题?谢谢

请问如何调用非SDK系统API

我尝试以以下方式调用非SDK系统API,但是不起作用。

public static final Singleton<IPowerManager> POWER_MANAGER = new Singleton<IPowerManager>() {
        @Override
        protected IPowerManager create() {
            return IPowerManager.Stub.asInterface(new ShizukuBinderWrapper(SystemServiceHelper.getSystemService(Context.POWER_SERVICE)));
        }
    };
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {

  //这个不是隐藏api,可以访问到值
    HiddenApiBypass.invoke(IPowerManager::class.java, ShizukuSystemServerApi.POWER_MANAGER.get(), "isPowerSaveMode", true);
  //这个是隐藏api,调用后没有反应
    HiddenApiBypass.invoke(IPowerManager::class.java, ShizukuSystemServerApi.POWER_MANAGER.get(), "setPowerSaveModeEnabled", true);
}

请问如果我要调用PowerManager里的隐藏方法我应该如何操作呢?

如何通过Shizuku调用Shell

因为项目需要,故要调用以下Shell:
/system/xbin/bstk/su
mount /system -o remount,rw
但看了半天开发文档还是摸不着头脑,故发个issue求解答

coreLibraryDesugaring导致的问题

启用coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.0.2'对高版本API进行脱糖后,List的removeIf方法会被替换成调用iterator遍历,然后调用remove删除。
巧的是CopyOnWriteArrayList.COWIterator不支持remove方法,这导致Shizuku类里面几个调用removeIf的方法都会报错。
修改建议是把字段类型均改成CopyOnWriteArrayListCompat而不是List:

private static final CopyOnWriteArrayListCompat<ListenerHolder<OnBinderReceivedListener>> RECEIVED_LISTENERS = new CopyOnWriteArrayListCompat<>();
private static final CopyOnWriteArrayListCompat<ListenerHolder<OnBinderDeadListener>> DEAD_LISTENERS = new CopyOnWriteArrayListCompat<>();
private static final CopyOnWriteArrayListCompat<ListenerHolder<OnRequestPermissionResultListener>> PERMISSION_LISTENERS = new CopyOnWriteArrayListCompat<>();

How to turn request permission callback into coroutine

I want to try some other strategies if shizuku setup fail, so I try to make this into coroutine. The shizuku permision dialog appeared and i clicked yes, but onRequestPermissionResult never called. It works well without coroutine. Any one know why.

suspend fun tryShizuku() = runCatching {
    suspendCoroutine { cont ->
        Log.d(TAG, "try shizuki")                             // <-- this called

        Shizuku.addRequestPermissionResultListener(
            object : OnRequestPermissionResultListener {
                override fun onRequestPermissionResult(requestCode: Int, grantResult: Int) {

                    Log.d(TAG, "onRequestPermissionResult")   // <--- this not called

                    Shizuku.removeRequestPermissionResultListener(this)

                    if (grantResult != PERMISSION_GRANTED) {
                        cont.resumeWithException(Exception())
                        return
                    }
                    val userServiceArgs = UserServiceArgs(
                        ComponentName(
                            BuildConfig.LIBRARY_PACKAGE_NAME.replace(".task", ".host"),
                            shizukiServiceClass.getName()
                        )
                    )
                        .daemon(false)
                        .processNameSuffix("service")
                        .debuggable(BuildConfig.DEBUG)
                        .version(1)
                    Shizuku.bindUserService(userServiceArgs, RootServiceConnection())
                    cont.resume(Unit)
                }
            })
        Shizuku.requestPermission(0)
    }
}

[bug] One of RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED should be specified when a receiver isn't being registered exclusively for system broadcasts

context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
BinderContainer container = intent.getParcelableExtra(EXTRA_BINDER);
if (container != null && container.binder != null) {
Log.i(TAG, "binder received from broadcast");
Shizuku.onBinderReceived(container.binder, context.getPackageName());
}
}
}, new IntentFilter(ACTION_BINDER_RECEIVED));

https://developer.android.com/about/versions/14/behavior-changes-14?hl=zh-cn#runtime-receivers-exported

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.